243 lines
7.1 KiB
Dart
243 lines
7.1 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 {
|
|
debugPrint('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) {
|
|
debugPrint('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) {
|
|
print('FFmpeg is already installed.');
|
|
completer.complete(true);
|
|
result.map((e) {
|
|
debugPrint('Analyse result: $e');
|
|
FileLogger().d('Analyse result: ${e.toString()}');
|
|
FileLogger().d('stdout: ${e.stdout}');
|
|
FileLogger().d('stderr: ${e.stderr}');
|
|
});
|
|
}).catchError((
|
|
error,
|
|
stackTrace,
|
|
) {
|
|
completer.completeError(error);
|
|
});
|
|
} catch (error) {
|
|
completer.completeError(error);
|
|
}
|
|
}
|
|
|
|
Future<void> checkFFmpeg() async {
|
|
var result = await futureShell('ffmpeg -version');
|
|
debugPrint('$result');
|
|
|
|
try {
|
|
var result = await Process.run('ffmpeg', ['-version']);
|
|
if (result.exitCode == 0) {
|
|
print('FFmpeg is already installed.');
|
|
return;
|
|
}
|
|
|
|
debugPrint('RESULTS: $result');
|
|
|
|
print('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 {
|
|
print('Cannot install FFmpeg without sudo privileges.');
|
|
}
|
|
if (result.exitCode == 0) {
|
|
print('FFmpeg installed successfully.');
|
|
} else {
|
|
print('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) {
|
|
print('An error occurred: $e');
|
|
}
|
|
}
|
|
|
|
Future<Stream> downloadFile(Video video) async {
|
|
debugPrint(
|
|
'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;
|
|
debugPrint('Format code: $formatCmd');
|
|
|
|
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/${video.filename}_$strType.%(ext)s" -f "$formatCmd"';
|
|
|
|
var shellLinesController = ShellLinesController();
|
|
var shellErrorController = ShellLinesController();
|
|
|
|
var shell = Shell(
|
|
stdout: shellLinesController.sink, stderr: shellErrorController.sink);
|
|
|
|
debugPrint('Running $ytDlpPath $command');
|
|
shell.run('$ytDlpPath $command').then((result) {
|
|
result.map((e) {
|
|
debugPrint('Analyse result: $e');
|
|
FileLogger().d('Analyse result: ${e.toString()}');
|
|
FileLogger().d('stdout: ${e.stdout}');
|
|
FileLogger().d('stderr: ${e.stderr}');
|
|
});
|
|
}).catchError((
|
|
error,
|
|
stackTrace,
|
|
) {
|
|
if (error is ShellException) {
|
|
debugPrint('ShellException: ${error.message}');
|
|
FileLogger().e('ShellException error: ${error.result?.stderr}');
|
|
FileLogger().e('ShellException out: ${error.result?.stdout}');
|
|
} else {
|
|
debugPrint('Error: $error');
|
|
FileLogger().e('Error: $error');
|
|
}
|
|
});
|
|
|
|
return shellLinesController.stream;
|
|
}
|
|
|
|
Future analyseUrl(String url) async {
|
|
debugPrint('Analyse $url');
|
|
|
|
var command = '${url.trim()} -q --flat-playlist -J';
|
|
|
|
var shellLinesController = ShellLinesController();
|
|
var shellErrorController = ShellLinesController();
|
|
|
|
shellErrorController.stream.listen((event) {
|
|
debugPrint('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) {
|
|
debugPrint('Analyse result: $e');
|
|
FileLogger().d('Analyse result: ${e.toString()}');
|
|
FileLogger().d('stdout: ${e.stdout}');
|
|
FileLogger().d('stderr: ${e.stderr}');
|
|
});
|
|
}).catchError((
|
|
error,
|
|
stackTrace,
|
|
) {
|
|
if (error is ShellException) {
|
|
debugPrint('ShellException: ${error.message}');
|
|
FileLogger().e('ShellException error: ${error.result?.stderr}');
|
|
FileLogger().e('ShellException out: ${error.result?.stdout}');
|
|
} else {
|
|
debugPrint('Error: $error');
|
|
FileLogger().e('Error: $error');
|
|
}
|
|
});
|
|
|
|
return shellLinesController.stream;
|
|
}
|
|
}
|