Merge branch 'feat/ffmpeg-conv' into 'main'
Feat/ffmpeg conv See merge request jscampucci/libu-notube!1
This commit is contained in:
commit
86142c0028
|
|
@ -10,6 +10,9 @@
|
|||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
#VsCode
|
||||
.vscode/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
|
|
@ -42,3 +45,5 @@ app.*.map.json
|
|||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
samples/
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "notube",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "notube (profile mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "notube (release mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -8,8 +8,14 @@ const colorDarkRed = Color.fromRGBO(153, 0, 0, 1);
|
|||
const colorMainBlue = Color(0xFF1f74ad);
|
||||
|
||||
enum Format {
|
||||
mp3(format: 'MP3'),
|
||||
mp3HD(format: 'MP3 HD'),
|
||||
mp3(
|
||||
format: 'MP3',
|
||||
ffmpegCmd: '-f mp3 -loglevel quiet -ab 192k -vn',
|
||||
extension: 'mp3'),
|
||||
mp3HD(
|
||||
format: 'MP3 HD',
|
||||
ffmpegCmd: '-f mp3 -loglevel quiet -ab 320k -vn',
|
||||
extension: 'mp3'),
|
||||
mp4(format: 'MP4'),
|
||||
mp4HD(
|
||||
ytCmd:
|
||||
|
|
@ -19,16 +25,36 @@ enum Format {
|
|||
ytCmd:
|
||||
"bestvideo[height=1440][ext=mp4]+bestaudio[ext=m4a]/bestvideo[height<=1080][ext=mp4][vcodec~='^(avc|h264)']+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
|
||||
format: 'MP4 2K'),
|
||||
tGP(format: '3GP'),
|
||||
flv(format: 'FLV'),
|
||||
m4a;
|
||||
tGP(
|
||||
format: '3GP',
|
||||
ffmpegCmd:
|
||||
'-movflags frag_keyframe+empty_moov -r 20 -s 352x288 -vb 400k -acodec aac -strict experimental -ac 1 -ar 8000 -ab 24k -f 3gp',
|
||||
extension: '3gp'),
|
||||
flv(
|
||||
format: 'FLV',
|
||||
ffmpegCmd:
|
||||
'-vcodec libx264 -preset slower -b 512k -bt 512k -threads 0 -s 640x360 -aspect 16:9 -acodec libmp3lame -ar 44100 -ab 32 -progress pipe:1',
|
||||
extension: 'flv'),
|
||||
m4a(format: 'M4A'),
|
||||
;
|
||||
|
||||
final String ytCmd;
|
||||
final String format;
|
||||
final String ffmpegCmd;
|
||||
final String extension;
|
||||
|
||||
const Format({
|
||||
this.ytCmd =
|
||||
'"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"',
|
||||
this.format = 'MP4',
|
||||
this.ffmpegCmd = '',
|
||||
this.extension = '',
|
||||
});
|
||||
}
|
||||
|
||||
const convertedFormats = [
|
||||
Format.mp3,
|
||||
Format.mp3HD,
|
||||
Format.tGP,
|
||||
Format.flv,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import 'dart:convert';
|
|||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
import 'package:notube/models/video.dart';
|
||||
import 'package:notube/services/download.dart';
|
||||
|
||||
import 'dart:developer';
|
||||
|
||||
part 'dl_form_state.dart';
|
||||
|
||||
class DlFormCubit extends Cubit<DlFormState> {
|
||||
|
|
@ -14,7 +14,7 @@ class DlFormCubit extends Cubit<DlFormState> {
|
|||
|
||||
late DLServices dlService;
|
||||
|
||||
void setFormat(String newFormat) {
|
||||
void setFormat(Format newFormat) {
|
||||
emit(state.copyWith(
|
||||
format: newFormat,
|
||||
));
|
||||
|
|
@ -29,7 +29,7 @@ class DlFormCubit extends Cubit<DlFormState> {
|
|||
|
||||
void parseUrl() async {
|
||||
dlService = await DLServices.init();
|
||||
var shellStream = await dlService.analyseUrl(state.url!);
|
||||
var shellStream = await dlService.analyseUrl(state.url);
|
||||
|
||||
shellStream.listen((line) {
|
||||
if (line[0] == '{') {
|
||||
|
|
@ -41,13 +41,15 @@ class DlFormCubit extends Cubit<DlFormState> {
|
|||
var playlistTitle = dataInfos['title'];
|
||||
for (var videoTmp in dataInfos['entries']) {
|
||||
var video = Video.fromJson(videoTmp);
|
||||
video.format = state.format;
|
||||
videos.add(video);
|
||||
videos.add(video.copyWith(
|
||||
format: state.format,
|
||||
filename: '${video.id}_${state.format.format}'));
|
||||
}
|
||||
} else {
|
||||
var video = Video.fromJson(dataInfos);
|
||||
video.format = state.format;
|
||||
videos.add(video);
|
||||
videos.add(video.copyWith(
|
||||
format: state.format,
|
||||
filename: '${video.id}_${state.format.format}'));
|
||||
}
|
||||
emit(state.copyWith(
|
||||
isParsed: true,
|
||||
|
|
@ -58,7 +60,7 @@ class DlFormCubit extends Cubit<DlFormState> {
|
|||
});
|
||||
}
|
||||
|
||||
void clearVideos() {
|
||||
void clearForm() {
|
||||
emit(state.copyWith(
|
||||
videos: [],
|
||||
isParsed: false,
|
||||
|
|
|
|||
|
|
@ -3,21 +3,21 @@ part of 'dl_form_cubit.dart';
|
|||
class DlFormState extends Equatable {
|
||||
const DlFormState({
|
||||
this.url = '',
|
||||
this.format = 'MP3',
|
||||
this.format = Format.mp4,
|
||||
this.videos = const [],
|
||||
this.isParsed = false,
|
||||
this.extractor = '',
|
||||
});
|
||||
|
||||
final String url;
|
||||
final String format;
|
||||
final Format format;
|
||||
final bool isParsed;
|
||||
final List<Video> videos;
|
||||
final String extractor;
|
||||
|
||||
DlFormState copyWith({
|
||||
String? url,
|
||||
String? format,
|
||||
Format? format,
|
||||
List<Video>? videos,
|
||||
bool? isParsed,
|
||||
String? extractor,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:notube/dlForm/cubit/dl_form_cubit.dart';
|
||||
import 'package:notube/dlForm/formComponents/formatDropdown.dart';
|
||||
import 'package:notube/dlForm/formComponents/submitButton.dart';
|
||||
import 'package:notube/dlForm/formComponents/urlTextField.dart';
|
||||
import 'package:notube/dlForm/formComponents/format_dropdown.dart';
|
||||
import 'package:notube/dlForm/formComponents/submit_button.dart';
|
||||
import 'package:notube/dlForm/formComponents/url_text_field.dart';
|
||||
|
||||
class DlForm extends StatelessWidget {
|
||||
const DlForm({super.key});
|
||||
|
|
@ -69,8 +69,8 @@ class DebugDlFormState extends StatelessWidget {
|
|||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Text('Url: ${url}'),
|
||||
Text('Format: ${format}'),
|
||||
Text('Url: $url'),
|
||||
Text('Format: ${format.format}'),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
|
@ -3,17 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
import 'package:notube/constants.dart';
|
||||
import 'package:notube/dlForm/cubit/dl_form_cubit.dart';
|
||||
|
||||
const List<String> formatList = <String>[
|
||||
"MP3",
|
||||
"MP3 HD",
|
||||
"MP4",
|
||||
"MP4 HD",
|
||||
"MP4 2K",
|
||||
"3GP",
|
||||
"FLV",
|
||||
"M4A",
|
||||
];
|
||||
|
||||
class DropdownFormat extends StatelessWidget {
|
||||
const DropdownFormat({super.key});
|
||||
|
||||
|
|
@ -29,18 +18,20 @@ class DropdownFormat extends StatelessWidget {
|
|||
|
||||
// dropdown below..
|
||||
child: DropdownButton<String>(
|
||||
value: dlFormState.format,
|
||||
value: dlFormState.format.format,
|
||||
elevation: 16,
|
||||
style: const TextStyle(color: colorMainWhite),
|
||||
dropdownColor: colorMainBlue,
|
||||
onChanged: (String? value) {
|
||||
context.read<DlFormCubit>().setFormat(value!);
|
||||
var format =
|
||||
Format.values.firstWhere((element) => element.format == value);
|
||||
context.read<DlFormCubit>().setFormat(format!);
|
||||
},
|
||||
underline: Container(),
|
||||
items: formatList.map<DropdownMenuItem<String>>((String value) {
|
||||
items: Format.values.map<DropdownMenuItem<String>>((Format format) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
value: format.format,
|
||||
child: Text(format.format),
|
||||
);
|
||||
}).toList(),
|
||||
));
|
||||
|
|
@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
import 'package:notube/models/video.dart';
|
||||
import 'package:notube/videoList/cubit/videos_cubit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:notube/dlForm/cubit/dl_form_cubit.dart';
|
||||
|
||||
class SubmitButton extends StatefulWidget {
|
||||
|
|
@ -23,7 +22,7 @@ class _SubmitButtonState extends State<SubmitButton> {
|
|||
debugPrint('Adding video: $video');
|
||||
context.read<VideosCubit>().addVideo(video);
|
||||
}
|
||||
context.read<DlFormCubit>().clearVideos();
|
||||
context.read<DlFormCubit>().clearForm();
|
||||
context.read<VideosCubit>().startDownloader();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import 'package:window_manager/window_manager.dart';
|
|||
import 'package:upgrader/upgrader.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:notube/videoList/cubit/videos_cubit.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
|
||||
@immutable
|
||||
class Video extends Equatable {
|
||||
Video({
|
||||
this.id = '',
|
||||
|
|
@ -7,7 +10,7 @@ class Video extends Equatable {
|
|||
this.extractor = '',
|
||||
this.isParsed = false,
|
||||
this.status = 'pending',
|
||||
this.format = 'MP4 HD',
|
||||
this.format = Format.mp4,
|
||||
this.title = '',
|
||||
this.thumbnail = '',
|
||||
this.description = '',
|
||||
|
|
@ -17,19 +20,19 @@ class Video extends Equatable {
|
|||
this.filename = '',
|
||||
});
|
||||
|
||||
String id;
|
||||
String url;
|
||||
String extractor;
|
||||
bool isParsed;
|
||||
String status;
|
||||
String format;
|
||||
String title;
|
||||
String thumbnail;
|
||||
String description;
|
||||
String duration;
|
||||
String uploader;
|
||||
String uploadDate;
|
||||
String filename;
|
||||
final String id;
|
||||
final String url;
|
||||
final String extractor;
|
||||
final bool isParsed;
|
||||
final String status;
|
||||
final Format format;
|
||||
final String title;
|
||||
final String thumbnail;
|
||||
final String description;
|
||||
final String duration;
|
||||
final String uploader;
|
||||
final String uploadDate;
|
||||
final String filename;
|
||||
|
||||
factory Video.fromJson(Map<String, dynamic> json) {
|
||||
return Video(
|
||||
|
|
@ -45,6 +48,39 @@ class Video extends Equatable {
|
|||
uploadDate: json['upload_date'] ?? '');
|
||||
}
|
||||
|
||||
//set Status
|
||||
Video copyWith({
|
||||
String? id,
|
||||
String? url,
|
||||
String? extractor,
|
||||
bool? isParsed,
|
||||
String? status,
|
||||
Format? format,
|
||||
String? title,
|
||||
String? thumbnail,
|
||||
String? description,
|
||||
String? duration,
|
||||
String? uploader,
|
||||
String? uploadDate,
|
||||
String? filename,
|
||||
}) {
|
||||
return Video(
|
||||
id: id ?? this.id,
|
||||
url: url ?? this.url,
|
||||
extractor: extractor ?? this.extractor,
|
||||
isParsed: isParsed ?? this.isParsed,
|
||||
status: status ?? this.status,
|
||||
format: format ?? this.format,
|
||||
title: title ?? this.title,
|
||||
thumbnail: thumbnail ?? this.thumbnail,
|
||||
description: description ?? this.description,
|
||||
duration: duration ?? this.duration,
|
||||
uploader: uploader ?? this.uploader,
|
||||
uploadDate: uploadDate ?? this.uploadDate,
|
||||
filename: filename ?? this.filename,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
import 'package:notube/dlForm/dlForm.dart';
|
||||
import 'package:notube/dlForm/dl_form.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:notube/videoList/cubit/videos_cubit.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
Home({super.key});
|
||||
|
|
@ -52,7 +50,9 @@ class Home extends StatelessWidget {
|
|||
fit: BoxFit.cover),
|
||||
),
|
||||
title: Text(currentVideo.title),
|
||||
subtitle: Text(currentVideo.status));
|
||||
subtitle: Text(
|
||||
'${currentVideo.status} - ${currentVideo.format.format}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:process_run/process_run.dart';
|
||||
import 'package:notube/models/video.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ConverterService {
|
||||
late Directory tempDir;
|
||||
|
|
@ -39,18 +42,47 @@ class ConverterService {
|
|||
ffmpegPath = tempFile.path;
|
||||
}
|
||||
|
||||
Future<Stream> convertFile(String inputPath, String outputPath) async {
|
||||
debugPrint('Converting $inputPath to $outputPath');
|
||||
Future<File?> getTmpFile(String filename) async {
|
||||
final Directory directory = Directory('temp');
|
||||
final List<FileSystemEntity> files = directory.listSync();
|
||||
for (FileSystemEntity file in files) {
|
||||
final String dirFilename = p.basenameWithoutExtension(file.path);
|
||||
debugPrint('dirFilename $dirFilename');
|
||||
debugPrint('filename $filename');
|
||||
if (dirFilename == '${filename}_tmp') {
|
||||
return File(file.path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var command = ' -q --flat-playlist -J';
|
||||
Future<Stream> convertFile(Video video) async {
|
||||
debugPrint(
|
||||
'____Converting ${video.title} to ${video.format.format} format_____');
|
||||
|
||||
File tmpFile = File('temp/${video.filename}_tmp.mp4');
|
||||
if (!tmpFile.existsSync()) {
|
||||
tmpFile = await getTmpFile(video.filename) ??
|
||||
File('temp/${video.filename}_tmp.mp4');
|
||||
}
|
||||
|
||||
File doneFile =
|
||||
File('temp/${video.filename}_done.${video.format.extension}');
|
||||
if (doneFile.existsSync()) {
|
||||
debugPrint('File already converted');
|
||||
return Stream.fromIterable(['progress=end']);
|
||||
}
|
||||
|
||||
var command =
|
||||
'-i "${tmpFile.path}" ${video.format.ffmpegCmd} "temp/${video.filename}_done.${video.format.extension}"';
|
||||
|
||||
var shellLinesController = ShellLinesController();
|
||||
var shell = Shell(stdout: shellLinesController.sink);
|
||||
var shell = Shell(
|
||||
stdout: shellLinesController.sink, stderr: shellLinesController.sink);
|
||||
|
||||
await shell.run('''
|
||||
$ffmpegPath $command
|
||||
''').then((result) {
|
||||
debugPrint('Analyse result: $result');
|
||||
});
|
||||
''');
|
||||
|
||||
return shellLinesController.stream;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
import 'package:notube/models/video.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:process_run/shell.dart';
|
||||
|
||||
import 'dart:developer';
|
||||
|
||||
class DLServices {
|
||||
late Directory tempDir;
|
||||
late String assetName;
|
||||
|
|
@ -57,7 +53,8 @@ class DLServices {
|
|||
}
|
||||
|
||||
Future<Stream> downloadFile(Video video) async {
|
||||
debugPrint('Downloading $url in $video.format format');
|
||||
debugPrint(
|
||||
'Downloading $url in ${video.format.format} format (${video.filename})');
|
||||
|
||||
/*
|
||||
https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X
|
||||
|
|
@ -66,12 +63,18 @@ class DLServices {
|
|||
|
||||
// get the format code from the Format enum
|
||||
var formatCmd =
|
||||
Format.values.firstWhere((e) => e.format == video.format).ytCmd;
|
||||
Format.values.firstWhere((e) => e.format == video.format.format).ytCmd;
|
||||
debugPrint('Format code: $formatCmd');
|
||||
|
||||
var cleanTitle = video.title.replaceAll(RegExp(r'[^\w\s]+'), '');
|
||||
File doneFile = File('temp/${video.filename}_tmp.mp4');
|
||||
if (doneFile.existsSync()) {
|
||||
debugPrint('File already downloaded');
|
||||
return Stream.fromIterable(['[EmbedThumbnail]']);
|
||||
}
|
||||
|
||||
var strType = convertedFormats.contains(video.format) ? 'tmp' : 'done';
|
||||
var command =
|
||||
'${video.url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --progress -o "temp/%(playlist)s/${cleanTitle}_${video.format}_tmp.%(ext)s" -f "$formatCmd"';
|
||||
'${video.url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --progress -o "temp/${video.filename}_$strType.%(ext)s" -f "$formatCmd"';
|
||||
|
||||
var shellLinesController = ShellLinesController();
|
||||
var shell = Shell(stdout: shellLinesController.sink, verbose: false);
|
||||
|
|
@ -88,7 +91,7 @@ class DLServices {
|
|||
var command = '${url.trim()} -q --flat-playlist -J';
|
||||
|
||||
var shellLinesController = ShellLinesController();
|
||||
var shell = Shell(stdout: shellLinesController.sink);
|
||||
var shell = Shell(stdout: shellLinesController.sink, verbose: false);
|
||||
|
||||
await shell.run('''
|
||||
$ytDlpPath $command
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import 'dart:math';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
import 'package:notube/models/video.dart';
|
||||
import 'package:notube/services/converter.dart';
|
||||
import 'package:notube/services/download.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:downloadsfolder/downloadsfolder.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
part 'videos_state.dart';
|
||||
|
||||
|
|
@ -28,6 +31,17 @@ class VideosCubit extends Cubit<VideosState> {
|
|||
emit(VideosState(videoList: _videoList));
|
||||
}
|
||||
|
||||
void changeStatus(Video video, String status) {
|
||||
int index = _videoList.indexWhere((v) => v.id == video.id);
|
||||
|
||||
if (index != -1) {
|
||||
_videoList[index] = video.copyWith(status: status);
|
||||
emit(VideosState(videoList: _videoList));
|
||||
} else {
|
||||
debugPrint('Video not found in the list');
|
||||
}
|
||||
}
|
||||
|
||||
void clearVideos() {
|
||||
_videoList.clear();
|
||||
emit(VideosState(videoList: _videoList));
|
||||
|
|
@ -38,10 +52,20 @@ class VideosCubit extends Cubit<VideosState> {
|
|||
emit(VideosState(videoList: _videoList));
|
||||
}
|
||||
|
||||
Future<void> startConverter() async {
|
||||
var videosToConvert = _videoList.where((video) =>
|
||||
video.status == 'downloaded' &&
|
||||
convertedFormats.contains(video.format));
|
||||
for (Video video in videosToConvert) {
|
||||
await convertVideo(video);
|
||||
}
|
||||
debugPrint('All videos converted');
|
||||
}
|
||||
|
||||
Future<void> startDownloader() async {
|
||||
dlService = await DLServices.init();
|
||||
downloadingVid =
|
||||
_videoList.where((video) => video.status != 'downloaded').take(3);
|
||||
_videoList.where((video) => video.status == 'pending').take(3);
|
||||
|
||||
debugPrint('Videos to download: ${downloadingVid.length}');
|
||||
while (downloadingVid.isNotEmpty && runningTasks < maxConcurrentTasks) {
|
||||
|
|
@ -53,6 +77,37 @@ class VideosCubit extends Cubit<VideosState> {
|
|||
_videoList.where((video) => video.status != 'downloaded');
|
||||
}
|
||||
|
||||
await startConverter();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
Directory defaultDownloadDirectory = await getDownloadDirectory();
|
||||
prefs.get('downloadFolder') ??
|
||||
await prefs.setString('downloadFolder', defaultDownloadDirectory.path);
|
||||
|
||||
final downloadDirectory = Directory(
|
||||
prefs.getString('downloadFolder') ?? defaultDownloadDirectory.path);
|
||||
|
||||
for (Video video in _videoList) {
|
||||
var cleanTitle = video.title.replaceAll(RegExp(r'[^\w\s]+'), '');
|
||||
final tmpFile =
|
||||
File('temp/${video.filename}_done.${video.format.extension}');
|
||||
|
||||
final newFile = File(
|
||||
'${downloadDirectory.path}/${cleanTitle}.${video.format.extension}');
|
||||
await tmpFile.rename(newFile.path);
|
||||
changeStatus(video, 'done');
|
||||
debugPrint('File moved to ${newFile.path}');
|
||||
}
|
||||
|
||||
final Directory directory = Directory('temp');
|
||||
final List<FileSystemEntity> files = directory.listSync();
|
||||
for (FileSystemEntity file in files) {
|
||||
final String dirFilename = p.basenameWithoutExtension(file.path);
|
||||
if (dirFilename.contains('_tmp')) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('All videos downloaded');
|
||||
/*
|
||||
for (Video video in videosLeft) {
|
||||
final shellStream = await dlService.downloadFile(video.url, video.format);
|
||||
|
|
@ -70,17 +125,59 @@ class VideosCubit extends Cubit<VideosState> {
|
|||
|
||||
Future convertVideo(Video video) async {
|
||||
converterService = await ConverterService.init();
|
||||
debugPrint('Converting ${video.title} to $video.format ');
|
||||
final shellStream =
|
||||
await converterService.convertFile(video.url, video.format);
|
||||
shellStream.listen((line) {
|
||||
debugPrint('Converting ${video.title} to ${video.format.format}');
|
||||
changeStatus(video, 'converting');
|
||||
final shellStream = await converterService.convertFile(video);
|
||||
var duration = '0.0';
|
||||
var completer = Completer<void>();
|
||||
|
||||
shellStream.listen(
|
||||
(line) {
|
||||
debugPrint(line);
|
||||
});
|
||||
//FFmpeg doesn't return any output for audio conversion
|
||||
if (video.format.extension == "mp3") {
|
||||
changeStatus(video, 'converted');
|
||||
if (!completer.isCompleted) {
|
||||
debugPrint('____Conversion audio completed___');
|
||||
runningTasks--;
|
||||
completer.complete();
|
||||
}
|
||||
} else {
|
||||
if (line.contains('Duration: ')) {
|
||||
var durationString =
|
||||
line.split('Duration: ')[1].split(',')[0].trim();
|
||||
duration = durationString;
|
||||
debugPrint('Duration: $duration');
|
||||
}
|
||||
if (line.contains('out_time_ms=')) {
|
||||
var timeString = line.split('out_time_ms=')[1];
|
||||
var time = timeString;
|
||||
debugPrint('Time: $time');
|
||||
changeStatus(video, 'converting $time on $duration');
|
||||
}
|
||||
if (line.contains('progress=end')) {
|
||||
changeStatus(video, 'converted');
|
||||
if (!completer.isCompleted) {
|
||||
debugPrint('____Conversion completed___');
|
||||
runningTasks--;
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
if (!completer.isCompleted) {
|
||||
completer
|
||||
.completeError(error); // Complete with error if an error occurs
|
||||
}
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future downloadVideo(Video video) async {
|
||||
debugPrint(
|
||||
'Downloading ${video.title} in ${video.format} format ${video.url}');
|
||||
'___Downloading ${video.title} in ${video.format} format ${video.filename}____');
|
||||
final shellStream = await dlService.downloadFile(video);
|
||||
var completer = Completer<void>();
|
||||
|
||||
|
|
@ -92,22 +189,19 @@ class VideosCubit extends Cubit<VideosState> {
|
|||
var percentString =
|
||||
line.split('[download]')[1].split(' of')[0].split('%')[0].trim();
|
||||
var percent = percentString == null ? 0 : double.parse(percentString);
|
||||
video.status = 'downloading $percent%';
|
||||
emit(VideosState(videoList: _videoList));
|
||||
changeStatus(video, 'downloading $percent%');
|
||||
}
|
||||
|
||||
if (line.contains('[EmbedThumbnail]')) {
|
||||
video.status = 'downloaded';
|
||||
video.filename =
|
||||
line.split('Adding thumbnail to "')[1].split('"')[0].trim() ?? '';
|
||||
emit(VideosState(videoList: _videoList));
|
||||
changeStatus(video, 'downloaded');
|
||||
|
||||
if (!completer.isCompleted) {
|
||||
debugPrint('Download completed');
|
||||
debugPrint('____Download completed___');
|
||||
runningTasks--;
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
debugPrint(line);
|
||||
//debugPrint(line);
|
||||
},
|
||||
onError: (error) {
|
||||
if (!completer.isCompleted) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import FlutterMacOS
|
|||
import Foundation
|
||||
|
||||
import device_info_plus
|
||||
import downloadsfolder
|
||||
import flutter_localization
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
|
|
@ -16,6 +17,7 @@ import window_manager
|
|||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
DownloadsfolderPlugin.register(with: registry.registrar(forPlugin: "DownloadsfolderPlugin"))
|
||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
|
|
|
|||
92
pubspec.lock
92
pubspec.lock
|
|
@ -18,7 +18,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.11.0"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bloc
|
||||
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||
|
|
@ -57,6 +57,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -65,6 +73,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
dartx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dartx
|
||||
sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
device_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -81,6 +97,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
diacritic:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: diacritic
|
||||
sha256: "96db5db6149cbe4aa3cfcbfd170aca9b7648639be7e48025f9d458517f807fe4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
downloadsfolder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: downloadsfolder
|
||||
sha256: e9987e56b998e3788047f977d31a1b50b4af58c388aefc3d157b9ac5c5d786a2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
easy_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -381,6 +413,54 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
permission_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.7"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.5"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -454,7 +534,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.1.9"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||
|
|
@ -570,6 +650,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
time:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: time
|
||||
sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ dependencies:
|
|||
flutter_bloc: ^8.1.6
|
||||
equatable: ^2.0.5
|
||||
json_annotation: ^4.9.0
|
||||
bloc: ^8.1.4
|
||||
shared_preferences: ^2.2.3
|
||||
downloadsfolder: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
|
|
@ -6,14 +6,20 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <downloadsfolder/downloadsfolder_plugin_c_api.h>
|
||||
#include <flutter_localization/flutter_localization_plugin_c_api.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DownloadsfolderPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DownloadsfolderPluginCApi"));
|
||||
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
downloadsfolder
|
||||
flutter_localization
|
||||
permission_handler_windows
|
||||
screen_retriever
|
||||
url_launcher_windows
|
||||
window_manager
|
||||
|
|
|
|||
Loading…
Reference in New Issue