작업 기록의 경로는 기기 구분자로 · AI 예상 시간 '약 1~1분' 고침 (196 · 197)
- 196 (버그): Android 에서도 'jj_mkv\이름' - p.join - 197 (버그): 짧은 동영상의 '약 1~1분' → '1분 안', 같은 수면 하나 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음5
parent
6cb2dde089
commit
65c049d043
|
|
@ -65,6 +65,7 @@
|
|||
"0 으로": "Reset to 0",
|
||||
"10초 뒤로 (←)": "Back 10 seconds (←)",
|
||||
"10초 앞으로 (→)": "Forward 10 seconds (→)",
|
||||
"1분 안": "under 1 min",
|
||||
"5초 뒤로": "Back 5 seconds",
|
||||
"5초 앞으로": "Forward 5 seconds",
|
||||
"90° 회전 (시계 방향)": "Rotate 90° (clockwise)",
|
||||
|
|
@ -102,7 +103,7 @@
|
|||
"AI 자막 만들기{0}{1}": "Make AI subtitles{0}{1}",
|
||||
"AI 자막 시작: {0}": "AI subtitles started: {0}",
|
||||
"AI 자막 실패: {0}\n{1}": "AI subtitles failed: {0}\n{1}",
|
||||
"AI 자막 완료: {0} → {1}\\{2}_AI.srt 외 {3}개": "AI subtitles done: {0} → {1}\\{2}_AI.srt and {3} more",
|
||||
"AI 자막 완료: {0} → {1} 외 {2}개": "AI subtitles done: {0} → {1} and {2} more",
|
||||
"AI 자막 중단: {0}": "AI subtitles stopped: {0}",
|
||||
"AI 자막 취소: {0}": "AI subtitles cancelled: {0}",
|
||||
"AI 자막: {0}": "AI subtitles: {0}",
|
||||
|
|
@ -320,6 +321,7 @@
|
|||
"yt-dlp 의 YouTube 화질 목록 추출": "Extracts the YouTube quality list for yt-dlp",
|
||||
"{0} (AI 번역 ← {1})": "{0} (AI translation ← {1})",
|
||||
"{0} (기본): {1}": "{0} (default): {1}",
|
||||
"{0} (동영상 {1})": "{0} (videos: {1})",
|
||||
"{0} - {1} (약 {2}MB)": "{0} - {1} (about {2}MB)",
|
||||
"{0} / {1}장": "{0} / {1} images",
|
||||
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2} (change at the top of the main screen)",
|
||||
|
|
@ -1176,7 +1178,7 @@
|
|||
"시작 · 웹 브라우저": "Startup · web browser",
|
||||
"시작 메뉴에 등록": "Add to Start menu",
|
||||
"시작 안 함 (모니터링에서 시작)": "Don't start (start from Monitor)",
|
||||
"시작: {0} → {1}\\{2}{3}": "Started: {0} → {1}\\{2}{3}",
|
||||
"시작: {0} → {1}{2}": "Started: {0} → {1}{2}",
|
||||
"시작하는 중…": "Starting…",
|
||||
"시작하지 못했습니다: {0}": "Could not start: {0}",
|
||||
"시작할 동기화를 고르세요. 고르지 않은 것은 이번 실행 동안 멈춥니다 (모니터링 > lsync 에서 시작).": "Choose which syncs to start. The others stay paused for this session (start them in Monitor > lsync).",
|
||||
|
|
@ -1262,7 +1264,7 @@
|
|||
"앱이 켜져 있는 동안 늘 동기화합니다.": "Always syncs while the app is running.",
|
||||
"약 {0} 남음": "about {0} left",
|
||||
"약 {0}~{1}분": "about {0}–{1} min",
|
||||
"약 {0}~{1}분 (동영상 {2})": "about {0}–{1} min (videos: {2})",
|
||||
"약 {0}분": "about {0} min",
|
||||
"양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).": "Both ways (⇄) never deletes source files (--remove-source-files in the options is not used).",
|
||||
"양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).": "Both ways: newer files on the receiving side are not overwritten (-u).",
|
||||
"어둡게": "Darker",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
"0 으로": "0 に戻す",
|
||||
"10초 뒤로 (←)": "10秒戻る(←)",
|
||||
"10초 앞으로 (→)": "10秒進む(→)",
|
||||
"1분 안": "1 分以内",
|
||||
"5초 뒤로": "5秒戻る",
|
||||
"5초 앞으로": "5秒進む",
|
||||
"90° 회전 (시계 방향)": "90° 回転 (時計回り)",
|
||||
|
|
@ -102,7 +103,7 @@
|
|||
"AI 자막 만들기{0}{1}": "AI 字幕を作成{0}{1}",
|
||||
"AI 자막 시작: {0}": "AI 字幕開始: {0}",
|
||||
"AI 자막 실패: {0}\n{1}": "AI 字幕失敗: {0}\n{1}",
|
||||
"AI 자막 완료: {0} → {1}\\{2}_AI.srt 외 {3}개": "AI 字幕完了: {0} → {1}\\{2}_AI.srt ほか {3}個",
|
||||
"AI 자막 완료: {0} → {1} 외 {2}개": "AI 字幕完了: {0} → {1} ほか {2}個",
|
||||
"AI 자막 중단: {0}": "AI 字幕中断: {0}",
|
||||
"AI 자막 취소: {0}": "AI 字幕キャンセル: {0}",
|
||||
"AI 자막: {0}": "AI 字幕: {0}",
|
||||
|
|
@ -320,6 +321,7 @@
|
|||
"yt-dlp 의 YouTube 화질 목록 추출": "yt-dlp 用の YouTube 画質リストを取得",
|
||||
"{0} (AI 번역 ← {1})": "{0}(AI 翻訳 ← {1})",
|
||||
"{0} (기본): {1}": "{0}(既定): {1}",
|
||||
"{0} (동영상 {1})": "{0} (動画 {1})",
|
||||
"{0} - {1} (약 {2}MB)": "{0} - {1}(約 {2}MB)",
|
||||
"{0} / {1}장": "{0} / {1} 枚",
|
||||
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2}(メイン画面の上部で変更)",
|
||||
|
|
@ -1176,7 +1178,7 @@
|
|||
"시작 · 웹 브라우저": "起動・ウェブブラウザー",
|
||||
"시작 메뉴에 등록": "スタートメニューに登録",
|
||||
"시작 안 함 (모니터링에서 시작)": "開始しない (モニタリングで開始)",
|
||||
"시작: {0} → {1}\\{2}{3}": "開始: {0} → {1}\\{2}{3}",
|
||||
"시작: {0} → {1}{2}": "開始: {0} → {1}{2}",
|
||||
"시작하는 중…": "開始中…",
|
||||
"시작하지 못했습니다: {0}": "開始できませんでした: {0}",
|
||||
"시작할 동기화를 고르세요. 고르지 않은 것은 이번 실행 동안 멈춥니다 (모니터링 > lsync 에서 시작).": "開始する同期を選んでください。選ばなかったものは今回の実行中は停止します (モニタリング > lsync で開始)。",
|
||||
|
|
@ -1262,7 +1264,7 @@
|
|||
"앱이 켜져 있는 동안 늘 동기화합니다.": "アプリの起動中は常に同期します。",
|
||||
"약 {0} 남음": "残り約 {0}",
|
||||
"약 {0}~{1}분": "約 {0}~{1} 分",
|
||||
"약 {0}~{1}분 (동영상 {2})": "約 {0}~{1} 分 (動画 {2})",
|
||||
"약 {0}분": "約 {0} 分",
|
||||
"양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).": "双方向 (⇄) では元のファイルを削除しません (オプションの --remove-source-files は使いません)。",
|
||||
"양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).": "双方向: 受け取る側の方が新しいファイルは上書きしません (-u)。",
|
||||
"어둡게": "暗く",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
"0 으로": "归零",
|
||||
"10초 뒤로 (←)": "后退 10 秒(←)",
|
||||
"10초 앞으로 (→)": "前进 10 秒(→)",
|
||||
"1분 안": "1 分钟以内",
|
||||
"5초 뒤로": "后退 5 秒",
|
||||
"5초 앞으로": "前进 5 秒",
|
||||
"90° 회전 (시계 방향)": "旋转 90° (顺时针)",
|
||||
|
|
@ -102,7 +103,7 @@
|
|||
"AI 자막 만들기{0}{1}": "生成 AI 字幕{0}{1}",
|
||||
"AI 자막 시작: {0}": "AI 字幕开始: {0}",
|
||||
"AI 자막 실패: {0}\n{1}": "AI 字幕失败: {0}\n{1}",
|
||||
"AI 자막 완료: {0} → {1}\\{2}_AI.srt 외 {3}개": "AI 字幕完成: {0} → {1}\\{2}_AI.srt 等 {3} 个",
|
||||
"AI 자막 완료: {0} → {1} 외 {2}개": "AI 字幕完成: {0} → {1} 等 {2} 个",
|
||||
"AI 자막 중단: {0}": "AI 字幕中断: {0}",
|
||||
"AI 자막 취소: {0}": "AI 字幕已取消: {0}",
|
||||
"AI 자막: {0}": "AI 字幕: {0}",
|
||||
|
|
@ -320,6 +321,7 @@
|
|||
"yt-dlp 의 YouTube 화질 목록 추출": "提取 yt-dlp 的 YouTube 画质列表",
|
||||
"{0} (AI 번역 ← {1})": "{0}(AI 翻译 ← {1})",
|
||||
"{0} (기본): {1}": "{0}(默认): {1}",
|
||||
"{0} (동영상 {1})": "{0}(视频 {1})",
|
||||
"{0} - {1} (약 {2}MB)": "{0} - {1}(约 {2}MB)",
|
||||
"{0} / {1}장": "{0} / {1} 张",
|
||||
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2}(在主界面顶部更改)",
|
||||
|
|
@ -1176,7 +1178,7 @@
|
|||
"시작 · 웹 브라우저": "启动 · 网页浏览器",
|
||||
"시작 메뉴에 등록": "添加到开始菜单",
|
||||
"시작 안 함 (모니터링에서 시작)": "不开始 (在监控中开始)",
|
||||
"시작: {0} → {1}\\{2}{3}": "开始: {0} → {1}\\{2}{3}",
|
||||
"시작: {0} → {1}{2}": "开始: {0} → {1}{2}",
|
||||
"시작하는 중…": "正在开始…",
|
||||
"시작하지 못했습니다: {0}": "无法开始:{0}",
|
||||
"시작할 동기화를 고르세요. 고르지 않은 것은 이번 실행 동안 멈춥니다 (모니터링 > lsync 에서 시작).": "请选择要开始的同步。未选的在本次运行期间暂停 (可在 监控 > lsync 中开始)。",
|
||||
|
|
@ -1262,7 +1264,7 @@
|
|||
"앱이 켜져 있는 동안 늘 동기화합니다.": "应用运行期间始终同步。",
|
||||
"약 {0} 남음": "约剩 {0}",
|
||||
"약 {0}~{1}분": "约 {0}~{1} 分钟",
|
||||
"약 {0}~{1}분 (동영상 {2})": "约 {0}~{1} 分钟(视频 {2})",
|
||||
"약 {0}분": "约 {0} 分钟",
|
||||
"양쪽 (⇄) 에서는 원본 파일을 지우지 않습니다 (옵션의 --remove-source-files 는 쓰지 않음).": "双向 (⇄) 不会删除源文件(不使用选项中的 --remove-source-files)。",
|
||||
"양쪽을 함께: 받는 쪽이 더 새 파일은 덮어쓰지 않습니다 (-u).": "双向: 接收端较新的文件不会被覆盖 (-u)。",
|
||||
"어둡게": "调暗",
|
||||
|
|
|
|||
|
|
@ -990,7 +990,8 @@ class AppController extends ChangeNotifier {
|
|||
..status = JobStatus.ready
|
||||
..phase = null
|
||||
..progress = 0;
|
||||
_log(trf('AI 자막 완료: {0} → {1}\\{2}_AI.srt 외 {3}개', [v.fileName, outputFolderName, v.baseName, langs.length]));
|
||||
// 196: 경로는 기기의 구분자로 (Android 에서도 "jj_mkv\이름" 으로 보였다)
|
||||
_log(trf('AI 자막 완료: {0} → {1} 외 {2}개', [v.fileName, p.join(outputFolderName, '${v.baseName}_AI.srt'), langs.length]));
|
||||
} on AiCancelled {
|
||||
v
|
||||
..status = JobStatus.ready
|
||||
|
|
@ -1388,7 +1389,7 @@ class AppController extends ChangeNotifier {
|
|||
..progress = 0
|
||||
..message = null;
|
||||
notifyListeners();
|
||||
_log(trf('시작: {0} → {1}\\{2}' '{3}', [v.fileName, outputFolderName, p.basename(out), encode.reencode ? ' [${encode.codec.label} · ${encode.resolution.label} · ${encode.quality.label}${encode.adjusts ? ' · ${encode.adjustSummary}' : ''}]' : '']));
|
||||
_log(trf('시작: {0} → {1}{2}', [v.fileName, p.join(outputFolderName, p.basename(out)), encode.reencode ? ' [${encode.codec.label} · ${encode.resolution.label} · ${encode.quality.label}${encode.adjusts ? ' · ${encode.adjustSummary}' : ''}]' : '']));
|
||||
final copies = <SubtitleEntry, String>{};
|
||||
var started = false; // FFmpeg 가 출력 파일을 쓰기 시작했는지
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ const aiStartDelay = Duration(seconds: 3);
|
|||
String aiStartNotice(List<VideoItem> videos, AiOptions o, {required bool thenBuild}) {
|
||||
final known = videos.map((v) => v.info?.duration).whereType<Duration>().toList();
|
||||
final est = known.length == videos.length ? aiEstimateMinutes(known.fold(Duration.zero, (a, b) => a + b), aiTranslateCount(o)) : null;
|
||||
final what = [trf('동영상 {0}개', [videos.length]), if (est != null) trf('약 {0}~{1}분', [est.$1, est.$2])].join(' · ');
|
||||
final what = [trf('동영상 {0}개', [videos.length]), if (est != null) aiEstimateText(est)].join(' · ');
|
||||
return thenBuild ? trf('AI 자막 → MKV ({0}) 을 시작합니다', [what]) : trf('AI 자막 ({0}) 을 시작합니다', [what]);
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +64,14 @@ const aiColor = Color(0xFFB388FF);
|
|||
return ((h * (15 + 10 * translateLanguages)).ceil(), (h * (30 + 20 * translateLanguages)).ceil());
|
||||
}
|
||||
|
||||
/// 197: 예상 시간 글 - 짧은 동영상은 "약 1~1분" 처럼 같은 수가 두 번 보였다
|
||||
String aiEstimateText((int, int) est) {
|
||||
final (lo, hi) = est;
|
||||
if (hi <= 1) return tr('1분 안');
|
||||
if (lo == hi) return trf('약 {0}분', [lo]);
|
||||
return trf('약 {0}~{1}분', [lo, hi]);
|
||||
}
|
||||
|
||||
/// 번역할 언어 수 (원어와 같은 언어는 번역하지 않음, 원어 자동 감지면 하나는 원어로 본다)
|
||||
int aiTranslateCount(AiOptions o) => o.source == undetermined
|
||||
? (o.targets.length - 1).clamp(0, 99)
|
||||
|
|
@ -90,7 +98,7 @@ class AiPlanSummary extends StatelessWidget {
|
|||
tr('예상 시간'),
|
||||
est == null
|
||||
? tr('동영상 길이를 몰라 셀 수 없습니다 (영상 1시간 ≈ 음성인식 15~30분 + 언어당 번역 10~20분)')
|
||||
: trf('약 {0}~{1}분 (동영상 {2})', [est.$1, est.$2, hm(total)]),
|
||||
: trf('{0} (동영상 {1})', [aiEstimateText(est), hm(total)]),
|
||||
),
|
||||
];
|
||||
return Container(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ void main() {
|
|||
expect(aiEstimateMinutes(const Duration(hours: 1), 0), (15, 30));
|
||||
expect(aiEstimateMinutes(const Duration(hours: 2), 2), (70, 140));
|
||||
expect(aiEstimateMinutes(Duration.zero, 2), isNull);
|
||||
// 197: 짧은 동영상은 '약 1~1분' 이 아니라 '1분 안', 같은 수면 하나만
|
||||
expect(aiEstimateText(aiEstimateMinutes(const Duration(seconds: 30), 0)!), '1분 안');
|
||||
expect(aiEstimateText((3, 3)), '약 3분');
|
||||
expect(aiEstimateText((15, 30)), '약 15~30분');
|
||||
final ko = languageOf('ko'), en = languageOf('en'), ja = languageOf('ja');
|
||||
expect(aiTranslateCount(AiOptions(source: ko, targets: {ko, en, ja}, whisper: whisperModels.first)), 2);
|
||||
expect(aiTranslateCount(AiOptions(targets: {ko, en, ja}, whisper: whisperModels.first)), 2, reason: '자동 감지면 하나는 원어로');
|
||||
|
|
|
|||
Loading…
Reference in New Issue