/// MKV 묶음 · 다운로드 시험의 대역 (batch_test · batch_ui_test 가 같이 쓴다): 멈춰 두는 FFmpeg · 음성인식 · 번역 · 다운로드 엔진 library; import 'dart:async'; import 'dart:io'; import 'package:jj_mkvmaker/app/app_controller.dart'; import 'package:jj_mkvmaker/core/download_detect.dart'; import 'package:jj_mkvmaker/core/models.dart'; import 'package:jj_mkvmaker/core/srt.dart'; import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; import 'package:jj_mkvmaker/services/ai_services.dart'; import 'package:jj_mkvmaker/services/downloader.dart'; import 'package:jj_mkvmaker/services/media_tool.dart'; import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:path/path.dart' as p; class TestRecognizer implements SpeechRecognizer { @override void cancel() {} final heard = []; @override Future> transcribe(String wavPath, {required String modelPath, String language = 'auto', AiProgress? onProgress}) async { heard.add(p.basename(wavPath)); return [Cue(const Duration(seconds: 1), const Duration(seconds: 2), 'Hello everyone, this is a test.')]; } } /// 240: 취소될 때까지 끝나지 않는 FFmpeg (여럿이 동시에 도는 상태를 만든다) class HoldTool implements MediaTool { final runs = >{}; @override Future version() async => 'ffmpeg test'; @override Future> encoders() async => {'libx264'}; @override Future probe(String path) async => const MediaInfo(duration: Duration(seconds: 1)); @override Future runFfmpeg(List args, {Duration? duration, ProgressCallback? onProgress, Object? tag}) async { final c = runs[tag] = Completer(); try { await c.future; } finally { runs.remove(tag); } } void _stop(Completer? c) { if (c != null && !c.isCompleted) c.completeError(MediaToolException('사용자가 취소했습니다.')); } @override void cancel() => runs.values.toList().forEach(_stop); @override void cancelTagged(Object tag) => _stop(runs[tag]); } class TestTranslator implements Translator { @override Future load(String modelDir) async {} @override Future> translate(List lines, {required String source, required String target, AiProgress? onProgress}) async => [for (final l in lines) '[$target] $l']; @override void cancel() {} @override Future dispose() async {} } class TestDownloadBackend implements DownloadBackend { @override DownloadKind get kind => DownloadKind.video; @override Future<(String, List)?> expandPlaylist(String url) async => null; @override Future start(DownloadTask t, void Function() changed) async => t.state = DownloadState.downloading; @override Future pause(DownloadTask t) async {} @override Future cancel(DownloadTask t) async {} @override Future shutdown() async {} } /// 262: [HoldTool] 과 같지만, 끝내면 (결과 이름으로 바꿀) 임시 출력 파일을 쓴다 class EndTool extends HoldTool { final args = >{}; @override Future runFfmpeg(List a, {Duration? duration, ProgressCallback? onProgress, Object? tag}) async { args[tag] = a; await super.runFfmpeg(a, duration: duration, onProgress: onProgress, tag: tag); File(a.last).writeAsStringSync('NEW'); } /// [tag] 의 FFmpeg 를 성공으로 끝낸다 void finish(Object tag) => runs[tag]!.complete(); } AppController plainController() => AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));