JJ_MKVMaker/integration_test/translate_split_test.dart

127 lines
6.1 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/bookmarks_controller.dart';
import 'package:jj_mkvmaker/app/download_manager.dart';
import 'package:jj_mkvmaker/core/languages.dart';
import 'package:jj_mkvmaker/core/models.dart';
import 'package:jj_mkvmaker/core/output_paths.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/browser/browser_toolbar.dart';
import 'package:jj_mkvmaker/ui/browser_page.dart';
import 'support/temp_dir.dart';
import 'package:path/path.dart' as p;
/// 실제 NLLB + 실제 Edge(WebView2): 브라우저를 보면서 영어 자막 → 한국어 번역 → MKV 를
/// 대기열로 돌리고, 화면 분할 "작업 현황" 에서 진행을 확인 (모델이 JJ_MKVMAKER_MODELS 에 있어야 함)
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('브라우저 + 작업 현황: 영어 자막 → 한국어 번역 → MKV (대기열)', (tester) async {
final dir = testTempDir('jj_split_');
final video = p.join(dir.path, 'movie.mp4');
final r = await Process.run('ffmpeg', [
'-y', '-f', 'lavfi', '-i', 'testsrc=size=320x240:rate=10', '-f', 'lavfi', '-i', 'sine',
'-t', '8', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-c:a', 'aac', video,
]);
expect(r.exitCode, 0, reason: '${r.stderr}');
final en = p.join(dir.path, 'movie.en.srt');
File(en).writeAsStringSync('1\n00:00:01,000 --> 00:00:03,000\nGood morning, how are you today?\n\n'
'2\n00:00:04,000 --> 00:00:06,000\nI want to watch this movie with my family.\n\n');
final c = AppController(PlatformServices.create());
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
server.listen((req) async {
req.response
..headers.contentType = ContentType.html
..write('<html><head><title>JJ</title></head><body>hello</body></html>');
await req.response.close();
});
await tester.runAsync(() async {
await c.init();
await c.addVideos([video]);
});
c.settings.webViewDataDir = testTempDir('jj_webview_', heldByApp: true).path;
final v = c.videos.single;
// 같은 폴더의 movie.en.srt 가 자동으로 잡힘
final s = v.subtitles.firstWhere((x) => x.path != null && p.equals(x.path!, en));
// ignore: avoid_print
print('RESULT 자동 추가된 자막: ${s.displayName} 언어=${s.language.code}');
final bm = BookmarksController(p.join(dir.path, 'bm.json'));
final d = DownloadManager(backends: const [], settings: () => c.settings, readClipboard: () async => null);
await tester.pumpWidget(MaterialApp(
home: BrowserPage(c: c, bookmarks: bm, downloads: d, initialUrl: 'http://127.0.0.1:${server.port}/')));
await tester.pump(const Duration(milliseconds: 500));
// 번역 시작 + 바로 MKV 만들기 (대기열)
final sw = Stopwatch()..start();
final tr = c.translateSubtitle(v, s, {languageOf('ko')});
final mkv = c.buildAll();
await tester.pump();
expect(c.busy, isTrue);
expect(c.pendingJobs, ['MKV 만들기']);
// 화면 분할 열기 → 진행 표시
await tapToolbar(tester, '작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)', '작업 현황');
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('작업 현황'), findsOneWidget);
expect(find.textContaining('▶ 자막 번역'), findsOneWidget);
expect(find.textContaining('MKV 만들기 (대기)'), findsOneWidget);
final phases = <String>{};
while (c.busy) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 200)));
await tester.pump();
if (v.phase != null) phases.add(v.phase!.replaceAll(RegExp(r'\d+%'), '%'));
}
await tester.runAsync(() => Future.wait([tr, mkv]));
await tester.pump();
// ignore: avoid_print
print('RESULT 걸린 시간 ${sw.elapsedMilliseconds}ms, 단계: ${phases.join(' | ')}');
// ignore: avoid_print
print(c.logs.join('\n'));
expect(find.text('쉬는 중'), findsOneWidget);
expect(find.text('MKV 완성'), findsOneWidget);
final koPath = languageSubtitlePath(video, languageOf('ko'));
final ko = File(koPath).readAsStringSync();
// ignore: avoid_print
print('RESULT --- ko ---\n$ko');
expect(RegExp(r'[가-힣]').hasMatch(ko), isTrue);
expect(ko, contains('00:00:04,000 --> 00:00:06,000'));
expect(v.status, JobStatus.done, reason: v.message);
final info = await tester.runAsync(() => c.services.mediaTool.probe(outputMkvPath(video)));
final langs = info!.ofType('subtitle').map((x) => x.language).toList();
// ignore: avoid_print
print('RESULT MKV 자막 언어: $langs');
expect(langs, containsAll(['kor', 'eng']));
await tester.pumpWidget(const SizedBox());
d.dispose();
await server.close(force: true);
}, timeout: const Timeout(Duration(minutes: 15)));
}
/// 도구 막대 단추 누르기: 넓으면 [tip] 단추, 좁으면 (640 미만 - 시험 창 804 너비에 패널) [더 보기] 메뉴의 [item]
Future<void> tapToolbar(WidgetTester tester, String tip, String item) async {
if (find.byTooltip(tip).evaluate().isNotEmpty) {
await tester.tap(find.byTooltip(tip));
return;
}
// 도구 막대 안의 [더 보기] (앱 맨 위 줄에도 [더 보기] 가 있다)
await tester.tap(find.descendant(of: find.byType(BrowserToolbar), matching: find.byTooltip('더 보기')));
for (var i = 0; i < 5; i++) {
await tester.pump(const Duration(milliseconds: 100));
}
await tester.tap(find.text(item).last);
// 고른 결과는 메뉴가 닫힌 뒤에 들어온다 - 번역이 도는 동안은 그리기가 느려 정한 시간이 아니라 닫힐 때까지
for (var i = 0; i < 50 && find.byType(PopupMenuItem<String>).evaluate().isNotEmpty; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 50)));
await tester.pump(const Duration(milliseconds: 100));
}
await tester.pump(const Duration(milliseconds: 100));
}