diff --git a/lib/platform/windows/ytdlp_backend.dart b/lib/platform/windows/ytdlp_backend.dart index 3c2f7ac..b3a66e0 100644 --- a/lib/platform/windows/ytdlp_backend.dart +++ b/lib/platform/windows/ytdlp_backend.dart @@ -177,7 +177,11 @@ class YtDlpBackend implements DownloadBackend { // 영상 · 음성을 따로 받아도 하나의 진행률 · 크기로 보이게 합친다 final totals = YtDlpTotals(); var files = 0; + // 184-2: 사이트 추출기가 아닌 "generic" (직접 파일 등) 으로 받았는지 · 영상 · 음성을 합쳤는지 + var generic = false, merged = false; void onLine(String line) { + if (line.startsWith('[generic]')) generic = true; + if (line.startsWith('[Merger]')) merged = true; final prog = parseYtDlpProgress(line); if (prog != null) { totals.update(prog); @@ -246,7 +250,9 @@ class YtDlpBackend implements DownloadBackend { } // 184-2: 동영상이 아닌 직접 파일 (확장자로 미리 알 수 없던 주소) 을 다 받았는데 후처리에서 실패 - // 받은 파일을 원래 이름으로 남기고 "완료" (예전에는 다 받고도 "실패 · 받지 못했습니다" 와 "… [id].unknown_video") - if (isNonVideoPostprocessError(err) && await _keepAsPlainFile(t)) { + // 사이트 추출기 (youtube 등) 나 합치기 실패는 진짜 동영상이 망가진 것일 수 있어 실패 그대로 (이유를 보인다) + final plain = !merged && (generic || t.files.any((f) => f.endsWith('.unknown_video'))); + if (plain && isNonVideoPostprocessError(err) && await _keepAsPlainFile(t)) { changed(); return; } diff --git a/test/ytdlp_plain_file_test.dart b/test/ytdlp_plain_file_test.dart index 4362452..9a1caba 100644 --- a/test/ytdlp_plain_file_test.dart +++ b/test/ytdlp_plain_file_test.dart @@ -32,6 +32,7 @@ a = sys.argv[1:] d = a[a.index('-P') + 1] f = os.path.join(d, 'download [download].unknown_video') open(f, 'w').write('PK') +print('[generic] Extracting URL: ' + a[-1], flush=True) print('[download] Destination: ' + f, flush=True) sys.stderr.write('ERROR: Postprocessing: Error opening input files: Invalid data found when processing input\n') sys.exit(1) @@ -49,6 +50,30 @@ sys.exit(1) expect(File(p.join(out.path, 'download [download].unknown_video')).existsSync(), isFalse); }); + test('사이트 추출기 (youtube 등) 의 후처리 실패는 바꾸지 않는다 - 망가진 동영상을 "완료" 로 두지 않음', () async { + final dir = Directory.systemTemp.createTempSync('jj_plain_'); + addTearDown(() => dir.deleteSync(recursive: true)); + final script = File(p.join(dir.path, 'fake_ytdlp.py')) + ..writeAsStringSync(r''' +import os, sys +a = sys.argv[1:] +d = a[a.index('-P') + 1] +f = os.path.join(d, 'clip [abc].f137.mp4') +open(f, 'w').write('V') +print('[youtube] Extracting URL: ' + a[-1], flush=True) +print('[download] Destination: ' + f, flush=True) +sys.stderr.write('ERROR: Postprocessing: Error opening input files: Invalid data found when processing input\n') +sys.exit(1) +'''); + final backend = YtDlpBackend(ytdlp: 'python', prefixArgs: [script.path], deno: ''); + final t = DownloadTask( + id: '1', kind: DownloadKind.video, source: 'https://www.youtube.com/watch?v=abc&file=a.zip', dir: dir.path); + await backend.start(t, () {}); + expect(t.state, DownloadState.failed); + expect(t.error, contains('Postprocessing')); + expect(File(p.join(dir.path, 'clip [abc].f137.mp4')).existsSync(), isTrue, reason: '이름도 바꾸지 않음'); + }); + test('받은 파일이 없으면 그대로 실패', () async { final dir = Directory.systemTemp.createTempSync('jj_plain_'); addTearDown(() => dir.deleteSync(recursive: true));