134 lines
6.8 KiB
Dart
134 lines
6.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:image/image.dart' as img;
|
|
import 'package:jj_mkvmaker/app/ai_jobs.dart';
|
|
import 'package:jj_mkvmaker/app/ai_local_engine.dart';
|
|
import 'package:jj_mkvmaker/app/ai_upscale.dart';
|
|
import 'package:jj_mkvmaker/app/app_controller.dart';
|
|
import 'package:jj_mkvmaker/app/settings.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/platform_services.dart';
|
|
import 'package:jj_mkvmaker/ui/ai_upscale_ui.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// 176: 한 장 해상도 올리기도 작업 (AiJobs) 으로 - 작업 알림 · 앞 서비스 · 작업 현황에 나오고 화면을 떠나도 끝난다
|
|
void main() {
|
|
late Directory tmp;
|
|
late String input;
|
|
late Completer<void> engine;
|
|
late AiUpscaler Function() upscaler;
|
|
setUp(() {
|
|
tmp = Directory.systemTemp.createTempSync('jj_up_job_');
|
|
input = p.join(tmp.path, 'page.png');
|
|
File(input).writeAsBytesSync(img.encodePng(img.Image(width: 8, height: 6)));
|
|
engine = Completer<void>();
|
|
upscaler = () => AiUpscaler(
|
|
device: const SdDevice('sd-cli', 'cpu', 'CPU'),
|
|
modelPath: 'm.pth',
|
|
modelId: 'esrgan-x4plus',
|
|
runner: (exe, args, {onProgress, onStart}) async {
|
|
await engine.future; // 엔진이 도는 동안
|
|
File(args[args.indexOf('-o') + 1]).writeAsBytesSync(img.encodePng(img.Image(width: 32, height: 24)));
|
|
return const [];
|
|
},
|
|
);
|
|
});
|
|
tearDown(() {
|
|
AiJobs.instance.done(''); // 남은 [보기] 정리
|
|
tmp.deleteSync(recursive: true);
|
|
});
|
|
|
|
test('176: 해상도 올리기 메모리 설정 - 적게 20% · 보통 30% (처음 값) · 많이 45%, 저장', () {
|
|
expect(AppSettings().aiImage.upMemory, 'normal');
|
|
expect(AppSettings.fromJson((AppSettings()..aiImage.upMemory = 'high').toJson()).aiImage.upMemory, 'high');
|
|
expect(AppSettings.fromJson({'aiUpMemory': 'x'}).aiImage.upMemory, 'normal');
|
|
// S10 (약 15GB): 적게 320 · 보통 384 · 많이 512
|
|
expect([for (final m in ['low', 'normal', 'high']) androidUpscaleTile(memTotalMb: 15000, share: upscaleMemoryShare(m))], [320, 384, 512]);
|
|
expect(upscaleTileMemoryMb(512), 6600);
|
|
// 빠르기 기록은 메모리 설정마다 따로 (Android CPU 만)
|
|
final a = AiUpscaler(device: const SdDevice('x', 'cpu', 'CPU'), modelPath: 'm', modelId: 'esrgan-x4plus', memory: 'low');
|
|
expect(upscaleSpeedKey(a), Platform.isAndroid ? 'cpu|esrgan-x4plus|low' : 'cpu|esrgan-x4plus');
|
|
});
|
|
|
|
test('타일 크기: 기기 메모리의 20% 안 (S10 16GB → 320), 메모리를 모르면 큰 그림만 256', () {
|
|
expect(androidUpscaleTile(memTotalMb: 15000, share: 0.20), 320);
|
|
expect(androidUpscaleTile(memTotalMb: 40000, share: 0.20), 512);
|
|
expect(androidUpscaleTile(memTotalMb: 6000, share: 0.20), 192);
|
|
expect(androidUpscaleTile(longSide: 1024), 256);
|
|
// 192: 메모리가 작은 기기 (P11 4GB, MemTotal 약 3.5GB) 는 셋 다 가장 작은 타일 - 같은 값을 세 번 보이지 않고 같다고 알린다
|
|
final small = upscaleMemoryLines(3500);
|
|
expect(small.where((l) => l.contains('모두 같습니다')), hasLength(1));
|
|
expect(small.where((l) => l.startsWith('적게') || l.startsWith('많이')), isEmpty);
|
|
// 16GB 는 셋이 다르다
|
|
expect(upscaleMemoryLines(15000).where((l) => l.contains('같')), isEmpty);
|
|
// 일부만 같으면 그 줄에 (보통과 같음)
|
|
expect(upscaleMemoryLines(5000).where((l) => l.contains('(보통과 같음)')), isNotEmpty);
|
|
expect(androidUpscaleTile(longSide: 800), 512);
|
|
});
|
|
|
|
testWidgets('[이 장] 은 작업 목록에 오르고, [뒤에서 계속] 으로 화면을 떠나도 끝나며 [보기] 로 비교 · 저장', (tester) async {
|
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), createUpscaler: upscaler));
|
|
var finished = false;
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (ctx) => TextButton(
|
|
onPressed: () => upscaleOne(ctx, c, input: input, nameFrom: input, outDir: tmp.path).whenComplete(() => finished = true),
|
|
child: const Text('올리기'),
|
|
),
|
|
),
|
|
),
|
|
));
|
|
await tester.tap(find.text('올리기'));
|
|
for (var i = 0; i < 20 && !AiJobs.instance.busy; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
// 작업 목록 (알림 · 작업 현황) 에 오른다
|
|
expect(AiJobs.instance.busy, isTrue);
|
|
expect(AiJobs.instance.statusLine, contains('page.png'));
|
|
// 화면을 떠난다 (진행 창만 닫힘, 작업은 계속)
|
|
await tester.tap(find.text('뒤에서 계속'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('AI 해상도 올리기'), findsNothing);
|
|
expect(AiJobs.instance.busy, isTrue);
|
|
// 엔진이 끝나면 비교 화면을 띄우지 않고 [보기] 를 남긴다
|
|
engine.complete();
|
|
// 시험의 가짜 시간 안에서 시작한 일이라 기다리지 말고 끝날 때까지 돌린다
|
|
for (var i = 0; i < 200 && !finished; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
expect(finished, isTrue);
|
|
await tester.pumpAndSettle();
|
|
expect(AiJobs.instance.busy, isFalse);
|
|
expect(AiJobs.instance.lastDone, contains('page.png'));
|
|
expect(find.byType(UpscaleCompareView), findsNothing);
|
|
final view = AiJobs.instance.lastDoneView;
|
|
expect(view, isNotNull);
|
|
// [보기] → 비교 화면 → [저장]
|
|
var viewed = false;
|
|
view!().whenComplete(() => viewed = true);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(UpscaleCompareView), findsOneWidget);
|
|
await tester.runAsync(() => tester.tap(find.widgetWithText(FilledButton, '저장')));
|
|
for (var i = 0; i < 100 && !viewed; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
expect(viewed, isTrue);
|
|
final saved = [for (final f in tmp.listSync(recursive: true).whereType<File>()) if (!p.equals(f.path, input)) f.path];
|
|
expect(saved, isNotEmpty, reason: '저장됨 (원본 옆, 새 파일)');
|
|
expect(AiJobs.instance.lastDoneView, isNull, reason: '[보기] 는 한 번');
|
|
// 작업 현황 줄도 저장한 곳으로 ([열기] 가 그 폴더)
|
|
expect(AiJobs.instance.lastDone, '저장했습니다: ${saved.single}');
|
|
expect(AiJobs.instance.lastDoneDir, p.dirname(saved.single));
|
|
ScaffoldMessenger.of(tester.element(find.text('올리기'))).clearSnackBars();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
}
|