All checks were successful
Build and Push Multi-Arch Docker Image / build-and-push (push) Successful in 18m34s
needs check, if converted files can be run on device Signed-off-by: Peter Siegmund <developer@mars3142.org>
114 lines
3.1 KiB
Dart
114 lines
3.1 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:injectable/injectable.dart';
|
|
|
|
@singleton
|
|
class VideoDownloader {
|
|
static const cacheDir = 'cache/video';
|
|
static const int maxWidth = 480;
|
|
static const int maxHeight = 320;
|
|
|
|
/// Downloads and resizes a YouTube video
|
|
/// Returns the path to the resized video file, or null on failure
|
|
Future<String?> downloadAndResize(String videoId) async {
|
|
final videoDir = Directory('$cacheDir/$videoId');
|
|
final originalVideoFile = File('${videoDir.path}/video_original.mp4');
|
|
final resizedVideoFile = File('${videoDir.path}/video.mp4');
|
|
|
|
// Check if resized video already exists in cache
|
|
if (await resizedVideoFile.exists()) {
|
|
return resizedVideoFile.path;
|
|
}
|
|
|
|
// Create cache directory if it doesn't exist
|
|
if (!await videoDir.exists()) {
|
|
await videoDir.create(recursive: true);
|
|
}
|
|
|
|
final youtubeUrl = 'https://www.youtube.com/watch?v=$videoId';
|
|
|
|
try {
|
|
// Use yt-dlp to download the video
|
|
final downloadResult = await Process.run(
|
|
'yt-dlp',
|
|
[
|
|
'--js-runtimes', 'node',
|
|
'-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
|
'-o', originalVideoFile.path,
|
|
'--merge-output-format', 'mp4',
|
|
youtubeUrl,
|
|
],
|
|
);
|
|
|
|
if (downloadResult.exitCode != 0) {
|
|
print('Failed to download video $videoId: ${downloadResult.stderr}');
|
|
await _cleanup(videoDir);
|
|
return null;
|
|
}
|
|
|
|
// Resize video to fit within maxWidth x maxHeight using ffmpeg
|
|
final resizeResult = await Process.run(
|
|
'ffmpeg',
|
|
[
|
|
'-i', originalVideoFile.path,
|
|
'-vf', 'scale=w=$maxWidth:h=$maxHeight:force_original_aspect_ratio=decrease',
|
|
'-c:v', 'libx264',
|
|
'-preset', 'medium',
|
|
'-crf', '23',
|
|
'-c:a', 'aac',
|
|
'-b:a', '128k',
|
|
'-movflags', '+faststart',
|
|
'-y',
|
|
resizedVideoFile.path,
|
|
],
|
|
);
|
|
|
|
if (resizeResult.exitCode != 0) {
|
|
print('Failed to resize video $videoId: ${resizeResult.stderr}');
|
|
await _cleanup(videoDir);
|
|
return null;
|
|
}
|
|
|
|
// Delete original video to save space
|
|
if (await originalVideoFile.exists()) {
|
|
await originalVideoFile.delete();
|
|
}
|
|
|
|
return resizedVideoFile.path;
|
|
} catch (e) {
|
|
print('Error processing video $videoId: $e');
|
|
await _cleanup(videoDir);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Check if video is already cached
|
|
Future<bool> isCached(String videoId) async {
|
|
final resizedVideoFile = File('$cacheDir/$videoId/video.mp4');
|
|
return resizedVideoFile.exists();
|
|
}
|
|
|
|
/// Get all cached video IDs
|
|
Future<List<String>> getCachedVideos() async {
|
|
final dir = Directory(cacheDir);
|
|
if (!await dir.exists()) {
|
|
return [];
|
|
}
|
|
|
|
final videos = <String>[];
|
|
await for (final entity in dir.list()) {
|
|
if (entity is Directory) {
|
|
final videoId = entity.path.split('/').last;
|
|
videos.add(videoId);
|
|
}
|
|
}
|
|
return videos;
|
|
}
|
|
|
|
Future<void> _cleanup(Directory videoDir) async {
|
|
if (await videoDir.exists()) {
|
|
await videoDir.delete(recursive: true);
|
|
}
|
|
}
|
|
}
|