[conv] ffmpeg full conversion
This commit is contained in:
parent
3eb4d7b905
commit
f97b20da49
|
|
@ -8,8 +8,14 @@ const colorDarkRed = Color.fromRGBO(153, 0, 0, 1);
|
||||||
const colorMainBlue = Color(0xFF1f74ad);
|
const colorMainBlue = Color(0xFF1f74ad);
|
||||||
|
|
||||||
enum Format {
|
enum Format {
|
||||||
mp3(format: 'MP3'),
|
mp3(
|
||||||
mp3HD(format: 'MP3 HD'),
|
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'),
|
mp4(format: 'MP4'),
|
||||||
mp4HD(
|
mp4HD(
|
||||||
ytCmd:
|
ytCmd:
|
||||||
|
|
@ -19,16 +25,36 @@ enum Format {
|
||||||
ytCmd:
|
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",
|
"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'),
|
format: 'MP4 2K'),
|
||||||
tGP(format: '3GP'),
|
tGP(
|
||||||
flv(format: 'FLV'),
|
format: '3GP',
|
||||||
m4a;
|
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 ytCmd;
|
||||||
final String format;
|
final String format;
|
||||||
|
final String ffmpegCmd;
|
||||||
|
final String extension;
|
||||||
|
|
||||||
const Format({
|
const Format({
|
||||||
this.ytCmd =
|
this.ytCmd =
|
||||||
'"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"',
|
'"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"',
|
||||||
this.format = 'MP4',
|
this.format = 'MP4',
|
||||||
|
this.ffmpegCmd = '',
|
||||||
|
this.extension = '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const convertedFormats = [
|
||||||
|
Format.mp3,
|
||||||
|
Format.mp3HD,
|
||||||
|
Format.tGP,
|
||||||
|
Format.flv,
|
||||||
|
];
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:notube/constants.dart';
|
||||||
import 'package:notube/models/video.dart';
|
import 'package:notube/models/video.dart';
|
||||||
import 'package:notube/services/download.dart';
|
import 'package:notube/services/download.dart';
|
||||||
|
|
||||||
|
|
@ -12,7 +14,7 @@ class DlFormCubit extends Cubit<DlFormState> {
|
||||||
|
|
||||||
late DLServices dlService;
|
late DLServices dlService;
|
||||||
|
|
||||||
void setFormat(String newFormat) {
|
void setFormat(Format newFormat) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
format: newFormat,
|
format: newFormat,
|
||||||
));
|
));
|
||||||
|
|
@ -39,17 +41,15 @@ class DlFormCubit extends Cubit<DlFormState> {
|
||||||
var playlistTitle = dataInfos['title'];
|
var playlistTitle = dataInfos['title'];
|
||||||
for (var videoTmp in dataInfos['entries']) {
|
for (var videoTmp in dataInfos['entries']) {
|
||||||
var video = Video.fromJson(videoTmp);
|
var video = Video.fromJson(videoTmp);
|
||||||
video.copyWith(
|
videos.add(video.copyWith(
|
||||||
format: state.format,
|
format: state.format,
|
||||||
);
|
filename: '${video.id}_${state.format.format}'));
|
||||||
videos.add(video);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var video = Video.fromJson(dataInfos);
|
var video = Video.fromJson(dataInfos);
|
||||||
video.copyWith(
|
videos.add(video.copyWith(
|
||||||
format: state.format,
|
format: state.format,
|
||||||
);
|
filename: '${video.id}_${state.format.format}'));
|
||||||
videos.add(video);
|
|
||||||
}
|
}
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isParsed: true,
|
isParsed: true,
|
||||||
|
|
@ -60,7 +60,7 @@ class DlFormCubit extends Cubit<DlFormState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearVideos() {
|
void clearForm() {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
videos: [],
|
videos: [],
|
||||||
isParsed: false,
|
isParsed: false,
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,21 @@ part of 'dl_form_cubit.dart';
|
||||||
class DlFormState extends Equatable {
|
class DlFormState extends Equatable {
|
||||||
const DlFormState({
|
const DlFormState({
|
||||||
this.url = '',
|
this.url = '',
|
||||||
this.format = 'MP3',
|
this.format = Format.mp4,
|
||||||
this.videos = const [],
|
this.videos = const [],
|
||||||
this.isParsed = false,
|
this.isParsed = false,
|
||||||
this.extractor = '',
|
this.extractor = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String url;
|
final String url;
|
||||||
final String format;
|
final Format format;
|
||||||
final bool isParsed;
|
final bool isParsed;
|
||||||
final List<Video> videos;
|
final List<Video> videos;
|
||||||
final String extractor;
|
final String extractor;
|
||||||
|
|
||||||
DlFormState copyWith({
|
DlFormState copyWith({
|
||||||
String? url,
|
String? url,
|
||||||
String? format,
|
Format? format,
|
||||||
List<Video>? videos,
|
List<Video>? videos,
|
||||||
bool? isParsed,
|
bool? isParsed,
|
||||||
String? extractor,
|
String? extractor,
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class DebugDlFormState extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text('Url: $url'),
|
Text('Url: $url'),
|
||||||
Text('Format: $format'),
|
Text('Format: ${format.format}'),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,14 @@ class DropdownFormat extends StatelessWidget {
|
||||||
|
|
||||||
// dropdown below..
|
// dropdown below..
|
||||||
child: DropdownButton<String>(
|
child: DropdownButton<String>(
|
||||||
value: dlFormState.format,
|
value: dlFormState.format.format,
|
||||||
elevation: 16,
|
elevation: 16,
|
||||||
style: const TextStyle(color: colorMainWhite),
|
style: const TextStyle(color: colorMainWhite),
|
||||||
dropdownColor: colorMainBlue,
|
dropdownColor: colorMainBlue,
|
||||||
onChanged: (String? value) {
|
onChanged: (String? value) {
|
||||||
context.read<DlFormCubit>().setFormat(value!);
|
var format =
|
||||||
|
Format.values.firstWhere((element) => element.format == value);
|
||||||
|
context.read<DlFormCubit>().setFormat(format!);
|
||||||
},
|
},
|
||||||
underline: Container(),
|
underline: Container(),
|
||||||
items: Format.values.map<DropdownMenuItem<String>>((Format format) {
|
items: Format.values.map<DropdownMenuItem<String>>((Format format) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class _SubmitButtonState extends State<SubmitButton> {
|
||||||
debugPrint('Adding video: $video');
|
debugPrint('Adding video: $video');
|
||||||
context.read<VideosCubit>().addVideo(video);
|
context.read<VideosCubit>().addVideo(video);
|
||||||
}
|
}
|
||||||
context.read<DlFormCubit>().clearVideos();
|
context.read<DlFormCubit>().clearForm();
|
||||||
context.read<VideosCubit>().startDownloader();
|
context.read<VideosCubit>().startDownloader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:notube/constants.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class Video extends Equatable {
|
class Video extends Equatable {
|
||||||
|
|
@ -9,7 +10,7 @@ class Video extends Equatable {
|
||||||
this.extractor = '',
|
this.extractor = '',
|
||||||
this.isParsed = false,
|
this.isParsed = false,
|
||||||
this.status = 'pending',
|
this.status = 'pending',
|
||||||
this.format = 'MP4 HD',
|
this.format = Format.mp4,
|
||||||
this.title = '',
|
this.title = '',
|
||||||
this.thumbnail = '',
|
this.thumbnail = '',
|
||||||
this.description = '',
|
this.description = '',
|
||||||
|
|
@ -24,7 +25,7 @@ class Video extends Equatable {
|
||||||
final String extractor;
|
final String extractor;
|
||||||
final bool isParsed;
|
final bool isParsed;
|
||||||
final String status;
|
final String status;
|
||||||
final String format;
|
final Format format;
|
||||||
final String title;
|
final String title;
|
||||||
final String thumbnail;
|
final String thumbnail;
|
||||||
final String description;
|
final String description;
|
||||||
|
|
@ -54,7 +55,7 @@ class Video extends Equatable {
|
||||||
String? extractor,
|
String? extractor,
|
||||||
bool? isParsed,
|
bool? isParsed,
|
||||||
String? status,
|
String? status,
|
||||||
String? format,
|
Format? format,
|
||||||
String? title,
|
String? title,
|
||||||
String? thumbnail,
|
String? thumbnail,
|
||||||
String? description,
|
String? description,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class Home extends StatelessWidget {
|
||||||
),
|
),
|
||||||
title: Text(currentVideo.title),
|
title: Text(currentVideo.title),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${currentVideo.status} - ${currentVideo.format}'),
|
'${currentVideo.status} - ${currentVideo.format.format}'),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:process_run/process_run.dart';
|
import 'package:process_run/process_run.dart';
|
||||||
|
import 'package:notube/models/video.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
class ConverterService {
|
class ConverterService {
|
||||||
late Directory tempDir;
|
late Directory tempDir;
|
||||||
|
|
@ -39,18 +42,47 @@ class ConverterService {
|
||||||
ffmpegPath = tempFile.path;
|
ffmpegPath = tempFile.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Stream> convertFile(String inputPath, String outputPath) async {
|
Future<File?> getTmpFile(String filename) async {
|
||||||
debugPrint('Converting $inputPath to $outputPath');
|
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 shellLinesController = ShellLinesController();
|
||||||
var shell = Shell(stdout: shellLinesController.sink);
|
var shell = Shell(
|
||||||
|
stdout: shellLinesController.sink, stderr: shellLinesController.sink);
|
||||||
|
|
||||||
await shell.run('''
|
await shell.run('''
|
||||||
$ffmpegPath $command
|
$ffmpegPath $command
|
||||||
''').then((result) {
|
''');
|
||||||
debugPrint('Analyse result: $result');
|
|
||||||
});
|
|
||||||
|
|
||||||
return shellLinesController.stream;
|
return shellLinesController.stream;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ class DLServices {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Stream> downloadFile(Video video) async {
|
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
|
https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X
|
||||||
|
|
@ -62,12 +63,18 @@ class DLServices {
|
||||||
|
|
||||||
// get the format code from the Format enum
|
// get the format code from the Format enum
|
||||||
var formatCmd =
|
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');
|
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 =
|
var command =
|
||||||
'${video.url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --progress -o "temp/${video.id}_${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 shellLinesController = ShellLinesController();
|
||||||
var shell = Shell(stdout: shellLinesController.sink, verbose: false);
|
var shell = Shell(stdout: shellLinesController.sink, verbose: false);
|
||||||
|
|
@ -84,7 +91,7 @@ class DLServices {
|
||||||
var command = '${url.trim()} -q --flat-playlist -J';
|
var command = '${url.trim()} -q --flat-playlist -J';
|
||||||
|
|
||||||
var shellLinesController = ShellLinesController();
|
var shellLinesController = ShellLinesController();
|
||||||
var shell = Shell(stdout: shellLinesController.sink);
|
var shell = Shell(stdout: shellLinesController.sink, verbose: false);
|
||||||
|
|
||||||
await shell.run('''
|
await shell.run('''
|
||||||
$ytDlpPath $command
|
$ytDlpPath $command
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/converter.dart';
|
||||||
import 'package:notube/services/download.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';
|
part 'videos_state.dart';
|
||||||
|
|
||||||
|
|
@ -28,6 +31,17 @@ class VideosCubit extends Cubit<VideosState> {
|
||||||
emit(VideosState(videoList: _videoList));
|
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() {
|
void clearVideos() {
|
||||||
_videoList.clear();
|
_videoList.clear();
|
||||||
emit(VideosState(videoList: _videoList));
|
emit(VideosState(videoList: _videoList));
|
||||||
|
|
@ -38,10 +52,20 @@ class VideosCubit extends Cubit<VideosState> {
|
||||||
emit(VideosState(videoList: _videoList));
|
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 {
|
Future<void> startDownloader() async {
|
||||||
dlService = await DLServices.init();
|
dlService = await DLServices.init();
|
||||||
downloadingVid =
|
downloadingVid =
|
||||||
_videoList.where((video) => video.status != 'downloaded').take(3);
|
_videoList.where((video) => video.status == 'pending').take(3);
|
||||||
|
|
||||||
debugPrint('Videos to download: ${downloadingVid.length}');
|
debugPrint('Videos to download: ${downloadingVid.length}');
|
||||||
while (downloadingVid.isNotEmpty && runningTasks < maxConcurrentTasks) {
|
while (downloadingVid.isNotEmpty && runningTasks < maxConcurrentTasks) {
|
||||||
|
|
@ -53,6 +77,37 @@ class VideosCubit extends Cubit<VideosState> {
|
||||||
_videoList.where((video) => video.status != 'downloaded');
|
_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) {
|
for (Video video in videosLeft) {
|
||||||
final shellStream = await dlService.downloadFile(video.url, video.format);
|
final shellStream = await dlService.downloadFile(video.url, video.format);
|
||||||
|
|
@ -70,17 +125,59 @@ class VideosCubit extends Cubit<VideosState> {
|
||||||
|
|
||||||
Future convertVideo(Video video) async {
|
Future convertVideo(Video video) async {
|
||||||
converterService = await ConverterService.init();
|
converterService = await ConverterService.init();
|
||||||
debugPrint('Converting ${video.title} to $video.format ');
|
debugPrint('Converting ${video.title} to ${video.format.format}');
|
||||||
final shellStream =
|
changeStatus(video, 'converting');
|
||||||
await converterService.convertFile(video.url, video.format);
|
final shellStream = await converterService.convertFile(video);
|
||||||
shellStream.listen((line) {
|
var duration = '0.0';
|
||||||
debugPrint(line);
|
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 {
|
Future downloadVideo(Video video) async {
|
||||||
debugPrint(
|
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);
|
final shellStream = await dlService.downloadFile(video);
|
||||||
var completer = Completer<void>();
|
var completer = Completer<void>();
|
||||||
|
|
||||||
|
|
@ -92,22 +189,19 @@ class VideosCubit extends Cubit<VideosState> {
|
||||||
var percentString =
|
var percentString =
|
||||||
line.split('[download]')[1].split(' of')[0].split('%')[0].trim();
|
line.split('[download]')[1].split(' of')[0].split('%')[0].trim();
|
||||||
var percent = percentString == null ? 0 : double.parse(percentString);
|
var percent = percentString == null ? 0 : double.parse(percentString);
|
||||||
video.status = 'downloading $percent%';
|
changeStatus(video, 'downloading $percent%');
|
||||||
emit(VideosState(videoList: _videoList));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.contains('[EmbedThumbnail]')) {
|
if (line.contains('[EmbedThumbnail]')) {
|
||||||
video.status = 'downloaded';
|
changeStatus(video, 'downloaded');
|
||||||
video.filename =
|
|
||||||
line.split('Adding thumbnail to "')[1].split('"')[0].trim() ?? '';
|
|
||||||
emit(VideosState(videoList: _videoList));
|
|
||||||
if (!completer.isCompleted) {
|
if (!completer.isCompleted) {
|
||||||
debugPrint('Download completed');
|
debugPrint('____Download completed___');
|
||||||
runningTasks--;
|
runningTasks--;
|
||||||
completer.complete();
|
completer.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debugPrint(line);
|
//debugPrint(line);
|
||||||
},
|
},
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
if (!completer.isCompleted) {
|
if (!completer.isCompleted) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
|
import downloadsfolder
|
||||||
import flutter_localization
|
import flutter_localization
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
|
@ -16,6 +17,7 @@ import window_manager
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
|
DownloadsfolderPlugin.register(with: registry.registrar(forPlugin: "DownloadsfolderPlugin"))
|
||||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
|
|
||||||
90
pubspec.lock
90
pubspec.lock
|
|
@ -57,6 +57,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.18.0"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -65,6 +73,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
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:
|
device_info_plus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -81,6 +97,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
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:
|
easy_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -381,6 +413,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
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:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -454,7 +534,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.9"
|
version: "0.1.9"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||||
|
|
@ -570,6 +650,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.7.2"
|
||||||
|
time:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: time
|
||||||
|
sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ dependencies:
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
bloc: ^8.1.4
|
bloc: ^8.1.4
|
||||||
|
shared_preferences: ^2.2.3
|
||||||
|
downloadsfolder: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,20 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <downloadsfolder/downloadsfolder_plugin_c_api.h>
|
||||||
#include <flutter_localization/flutter_localization_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 <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
DownloadsfolderPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("DownloadsfolderPluginCApi"));
|
||||||
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
||||||
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
downloadsfolder
|
||||||
flutter_localization
|
flutter_localization
|
||||||
|
permission_handler_windows
|
||||||
screen_retriever
|
screen_retriever
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
window_manager
|
window_manager
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue