import '../../core/encode_options.dart'; import 'records.dart'; import 'settings_group.dart'; /// 약속 (설정 A): 환경 설정 > MKV · 자막 만들기 (저장 위치 · 이동 버튼 · 인코딩 · AI 자막 · OpenSubtitles) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다. /// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키) class MkvSettings implements SettingsGroup { /// MKV·자막 저장 위치. null 이면 동영상이 있는 폴더 아래 jj_mkv String? outputRoot; /// MKV 세부 정보 오른쪽 아래의 이동 버튼들 (표시 이름 · 폴더). 세 번 누르기는 첫 번째 버튼의 폴더로. /// 비어 있으면 [이동] 을 처음 누를 때 폴더를 골라 하나 만든다. List moveTargets = []; /// AI 자막: 매번 설정 창을 띄울지 (끄면 마지막 설정으로 바로 시작) bool askAiOptions = true; /// OpenSubtitles (인터넷 자막 검색) - 키는 필수, 아이디·비밀번호는 선택 (받기 횟수 늘어남) String openSubtitlesKey = ''; String openSubtitlesUser = ''; String openSubtitlesPassword = ''; /// 동시에 만드는 MKV 수 (0 = 무제한) int maxParallelJobs = 5; /// 폰 세로 (좁은 화면) MKV 목록 크기: 'auto' 목록은 내용만큼 · 자세히 칸에 최소 높이 (처음 값) · 'small' · 'large' (관리자 판단 10/17) String compactList = 'auto'; /// P0: MKV 를 만들 때 결과 MKV 가 이미 있으면 - 'ask' 묻기 (처음 값) · 'overwrite' 덮어쓰기 · 'skip' 건너뛰기 · 'rename' 다른 이름. /// 덮어쓰기여도 다 만든 뒤에만 바꿔 넣는다 (도중에 취소 · 실패하면 예전 것 그대로) String existing = 'ask'; /// 47: MKV 목록에서 세 번 누르면 'move' (확인 뒤 이동 폴더로) · 'none' (아무것도 안 함) String tripleTapAction = 'move'; /// 마지막 인코딩 설정 EncodeSettings encode = const EncodeSettings(); /// 마지막 AI 자막 설정 String aiSource = 'und'; List aiTargets = ['ko', 'en', 'ja']; String aiWhisper = 'whisper-base'; /// 색 보정 값 (-100 ~ 100) static int _adj(Object? v) => ((v as num?)?.round() ?? 0).clamp(-100, 100); @override void write(Map j, {required bool plainSecrets}) { j['mkvOutputRoot'] = outputRoot; j['moveTargets'] = [for (final t in moveTargets) t.toJson()]; j['askAiOptions'] = askAiOptions; // 비밀 값은 안전 저장소에 (54). 파일에는 저장해 두었는지만 j['openSubtitlesSaved'] = { 'key': openSubtitlesKey.isNotEmpty, 'user': openSubtitlesUser.isNotEmpty, 'password': openSubtitlesPassword.isNotEmpty, }; if (plainSecrets) { j['openSubtitlesKey'] = openSubtitlesKey; j['openSubtitlesUser'] = openSubtitlesUser; j['openSubtitlesPassword'] = openSubtitlesPassword; } j['maxParallelJobs'] = maxParallelJobs; j['mkvCompactList'] = compactList; j['mkvExisting'] = existing; j['tripleTapAction'] = tripleTapAction; j['encode'] = { 'codec': encode.codec.name, 'resolution': encode.resolution.name, 'quality': encode.quality.name, 'frame': encode.frame.name, 'fit': encode.fit.name, 'rotate': encode.rotate.name, 'brightness': encode.brightness, 'contrast': encode.contrast, 'saturation': encode.saturation, 'temperature': encode.temperature, }; j['aiSource'] = aiSource; j['aiTargets'] = aiTargets; j['aiWhisper'] = aiWhisper; } @override void read(Map j) { final e = (j['encode'] as Map?) ?? const {}; outputRoot = j['mkvOutputRoot'] as String?; moveTargets = [ for (final x in (j['moveTargets'] as List?) ?? const []) if (x is Map && x['dir'] is String) MoveTarget.fromJson(x), // 예전 판의 이동 폴더 하나 → "이동" 버튼으로 if (j['moveTargets'] == null && j['moveTargetDir'] is String) MoveTarget('이동', j['moveTargetDir'] as String), ]; askAiOptions = j['askAiOptions'] as bool? ?? true; openSubtitlesKey = j['openSubtitlesKey'] as String? ?? ''; openSubtitlesUser = j['openSubtitlesUser'] as String? ?? ''; openSubtitlesPassword = j['openSubtitlesPassword'] as String? ?? ''; maxParallelJobs = (j['maxParallelJobs'] as num?)?.toInt() ?? 5; compactList = const ['auto', 'small', 'large'].contains(j['mkvCompactList']) ? j['mkvCompactList'] as String : 'auto'; existing = const ['rename', 'overwrite', 'skip'].contains(j['mkvExisting']) ? j['mkvExisting'] as String : 'ask'; tripleTapAction = j['tripleTapAction'] == 'none' ? 'none' : 'move'; encode = EncodeSettings( codec: pickEnum(VideoCodecChoice.values, e['codec'], VideoCodecChoice.copy), resolution: pickEnum(ResolutionChoice.values, e['resolution'], ResolutionChoice.original), quality: pickEnum(QualityChoice.values, e['quality'], QualityChoice.normal), frame: pickEnum(FrameChoice.values, e['frame'], FrameChoice.original), fit: pickEnum(FitChoice.values, e['fit'], FitChoice.fill), rotate: pickEnum(RotateChoice.values, e['rotate'], RotateChoice.none), brightness: _adj(e['brightness']), contrast: _adj(e['contrast']), saturation: _adj(e['saturation']), temperature: _adj(e['temperature']), ); aiSource = j['aiSource'] as String? ?? 'und'; aiTargets = (j['aiTargets'] as List?)?.cast() ?? ['ko', 'en', 'ja']; aiWhisper = j['aiWhisper'] as String? ?? 'whisper-base'; } }