notube-export/lib/services/download.dart

239 lines
7.2 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:get/get_navigation/src/nav2/get_router_delegate.dart';
import 'package:logger/logger.dart';
import 'package:path/path.dart';
import 'package:notube/constants.dart';
import 'package:notube/models/video.dart';
import 'package:notube/services/file_logger.dart';
import 'package:path_provider/path_provider.dart';
import 'package:process_run/shell.dart';
class DLServices {
late Directory tempDir;
late String assetName;
late String ytDlpPath;
String extractor = '';
String url = '';
bool isPlaylist = false;
String playlistTitle = '';
List<Video> _videos = [];
List<Video> get videos => _videos;
DLServices._();
static Future<DLServices> init() async {
FileLogger().d('Initializing DLServices');
var dlService = DLServices._();
await dlService._init();
return dlService;
}
Future<void> _init() async {
tempDir = await getTemporaryDirectory();
if (Platform.isWindows) {
//checkFFmpeg();
assetName = 'yt-dlp.exe';
} else if (Platform.isLinux) {
checkFFmpeg();
assetName = 'yt-dlp_linux';
} else if (Platform.isMacOS) {
checkFFmpeg();
assetName = 'yt-dlp_macos';
}
await copyExecutable();
}
Future<void> copyExecutable() async {
File tempFile = File('${tempDir.path}/$assetName');
ByteData data = await rootBundle.load('assets/executable/$assetName');
await tempFile.exists().then((value) {
if (!value) {
FileLogger().d('Copying $assetName to ${tempFile.path}');
tempFile.writeAsBytes(
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
}
});
ytDlpPath = tempFile.path;
if (Platform.isLinux || Platform.isMacOS) {
await Process.run('chmod', ['+x', ytDlpPath]);
}
var shell = Shell();
await shell.run('$ytDlpPath -U');
// await Process.run(ytDlpPath, ['-U'])
}
Future futureShell(String cmd) async {
var shell = Shell();
var completer = Completer();
try {
shell.run(cmd).then((result) {
FileLogger().d('FFmpeg is already installed.');
completer.complete(true);
result.map((e) {
FileLogger().d('Analyse result: $e');
FileLogger().d('Analyse result: ${e.toString()}');
FileLogger().w('stdout: ${e.stdout}');
FileLogger().e('stderr: ${e.stderr}');
});
}).catchError((
error,
stackTrace,
) {
completer.completeError(error);
});
} catch (error) {
completer.completeError(error);
}
}
Future<void> checkFFmpeg() async {
var result = await futureShell('ffmpeg -version');
FileLogger().d('$result');
if (result != null && result.exitCode == 0) {
FileLogger().d('FFmpeg is already installed.');
return;
}
try {
FileLogger().d('Installing FFmpeg...');
if (Platform.isLinux) {
result = await Process.run('sudo', ['-n', 'true']);
bool hasSudo = result.exitCode == 0;
if (hasSudo) {
result =
await Process.run('sudo', ['apt-get', 'install', 'ffmpeg', '-y']);
} else {
FileLogger().d('Cannot install FFmpeg without sudo privileges.');
}
if (result.exitCode == 0) {
FileLogger().d('FFmpeg installed successfully.');
} else {
FileLogger().d('Error installing FFmpeg: ${result.stderr}');
}
} else if (Platform.isMacOS) {
result = await Process.run('brew', ['install', 'ffmpeg']);
} else if (Platform.isWindows) {
Directory directory = await getApplicationDocumentsDirectory();
String assetsPath = join(directory.path, 'assets');
result = await Process.run('setx', [
'PATH',
'$assetsPath;%PATH%',
]);
}
} catch (e) {
FileLogger().e('An error occurred: $e');
}
}
Future<Stream> downloadFile(Video video) async {
FileLogger().d(
'Downloading $url in ${video.format.format} format (${video.filename})');
/*
https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X
https://youtube.com/watch?v=8bJBX-rrrn4
*/
// get the format code from the Format enum
var formatCmd =
Format.values.firstWhere((e) => e.format == video.format.format).ytCmd;
FileLogger().d('Format code: $formatCmd');
File doneFile = File('${tempDir.path}/${video.filename}_tmp.mp4');
if (doneFile.existsSync()) {
FileLogger().d('File already downloaded');
return Stream.fromIterable(['[EmbedThumbnail]']);
}
var strType = convertedFormats.contains(video.format) ? 'tmp' : 'done';
var isAudio = audioFormats.contains(video.format);
var command =
'${video.url.trim()}${isAudio ? '' : '--embed-subs --embed-thumbnail --embed-metadata'} --progress -o "${tempDir.path}/${video.filename}_$strType.%(ext)s" -f "$formatCmd"';
//--sub-langs "all,-live_chat"
var shellLinesController = ShellLinesController();
var shellErrorController = ShellLinesController();
var shell = Shell(
stdout: shellLinesController.sink, stderr: shellErrorController.sink);
FileLogger().d('Running $ytDlpPath $command');
shell.run('$ytDlpPath $command').then((result) {
result.map((e) {
FileLogger().d('Analyse result: $e');
FileLogger().d('Analyse result: ${e.toString()}');
FileLogger().w('stdout: ${e.stdout}');
FileLogger().e('stderr: ${e.stderr}');
});
}).catchError((
error,
stackTrace,
) {
if (error is ShellException) {
FileLogger().d('ShellException: ${error.message}');
FileLogger().e('ShellException error: ${error.result?.stderr}');
FileLogger().e('ShellException out: ${error.result?.stdout}');
} else {
FileLogger().e('Error: $error');
}
});
return shellLinesController.stream;
}
Future analyseUrl(String url) async {
FileLogger().d('Analyse $url');
var command = '${url.trim()} -q --flat-playlist -J';
var shellLinesController = ShellLinesController();
var shellErrorController = ShellLinesController();
shellErrorController.stream.listen((event) {
FileLogger().d('Analyse error: $event');
FileLogger().e('Analyse error: $event');
});
var shell = Shell(
stdout: shellLinesController.sink,
stderr: shellErrorController.sink,
verbose: false);
await shell.run('''
$ytDlpPath $command
''').then((result) {
result.map((e) {
FileLogger().d('Analyse result: $e');
FileLogger().d('Analyse result: ${e.toString()}');
FileLogger().w('stdout: ${e.stdout}');
FileLogger().e('stderr: ${e.stderr}');
});
}).catchError((
error,
stackTrace,
) {
if (error is ShellException) {
FileLogger().d('ShellException: ${error.message}');
FileLogger().e('ShellException error: ${error.result?.stderr}');
FileLogger().e('ShellException out: ${error.result?.stdout}');
} else {
FileLogger().d('Error: $error');
FileLogger().e('Error: $error');
}
});
return shellLinesController.stream;
}
}