From 9403b8690b118c14a4bff672e6ddfc1e5df6d8b1 Mon Sep 17 00:00:00 2001 From: Wayne Mao Date: Thu, 20 Nov 2025 14:36:03 +0800 Subject: [PATCH 1/4] feat: estimate the remaining time required for extraction --- extractSub.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/extractSub.js b/extractSub.js index 22fe0ac..397a712 100644 --- a/extractSub.js +++ b/extractSub.js @@ -1,4 +1,5 @@ import readline from 'readline'; +import { execSync } from 'child_process'; import ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; import ffmpeg from 'fluent-ffmpeg'; import {config} from "./config.js"; @@ -16,6 +17,15 @@ const getFFmpegPath = () => { } } +const formatSeconds = (totalSeconds) => { + if (!isFinite(totalSeconds) || totalSeconds < 0) totalSeconds = 0; + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = Math.floor(totalSeconds % 60); + const pad = (n) => String(n).padStart(2, '0'); + return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; +} + /** * "timemark":"00:06:52.07" * @param timemark @@ -32,6 +42,7 @@ export const extractSub = (filename, targetSubs) => { const mainSrt = `${removeExtension(filename)}.chs.srt`; const secondarySrt = `${removeExtension(filename)}.eng.srt`; const duration = targetSubs[0].duration; + let startTs = 0; ffmpeg.setFfmpegPath(getFFmpegPath()); ffmpeg(config.workdir + filename) @@ -42,11 +53,15 @@ export const extractSub = (filename, targetSubs) => { .run() .on('start', function (str) { console.log('正在提取字幕文件...', str); + startTs = Date.now(); }) .on('progress', function (progress) { - const progressPercent = Math.round((timemarkToSeconds(progress.timemark) / duration) * 100); + const fraction = Math.max(0, Math.min(1, timemarkToSeconds(progress.timemark) / duration)); + const progressPercent = Math.round((fraction) * 100); + const elapsedSec = startTs ? (Date.now() - startTs) / 1000 : 0; + const remainingSec = fraction > 0 ? elapsedSec * (1 - fraction) / fraction : 0; readline.cursorTo(process.stdout, 0); - process.stdout.write(`字幕提取中,进度:${(progressPercent || 0)}%`); + process.stdout.write(`字幕提取中,进度:${(progressPercent || 0)}% | 预计剩余:${formatSeconds(remainingSec)}`); }) .on('end', function (str) { console.log('\n字幕提取完成。'); From db0285d328236d80f42c525488cb201c5216920c Mon Sep 17 00:00:00 2001 From: Wayne Mao Date: Thu, 20 Nov 2025 14:36:40 +0800 Subject: [PATCH 2/4] chore: output all sub index when not match --- findSub.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/findSub.js b/findSub.js index b278615..887ec1b 100644 --- a/findSub.js +++ b/findSub.js @@ -3,20 +3,28 @@ export const findSub = (subTitles) => { const chsSub = findChiSub(subTitles); const engSub = findEngSub(subTitles); - if (chsSub === null || engSub === null) { - if (chsSub === null) { - console.log('没有找到简体中文字幕'); - } + if (chsSub) { + console.log('找到简体中文字幕,索引为:', chsSub.index); + } else { + console.log('没有找到简体中文字幕'); + } - if (engSub === null) { - console.log('没有找到英语字幕'); - } + if (engSub) { + console.log('找到英语字幕,索引为:', engSub.index); + } else { + console.log('没有找到英语字幕'); + } + + if (chsSub === null || engSub === null) { + // 打印所有可用的字幕信息,便于确认 + console.log('所有可用字幕信息如下:'); + subTitles.forEach((s) => { + console.log(`索引=${s.index}, code=${s.code}, name="${s.name}", duration=${s.duration}, frames=${s.frames}`); + }); throw new Error('字幕查找失败,中断执行'); } - console.log('找到简体中文字幕,索引为:', chsSub.index); - console.log('找到英语字幕,索引为:', engSub.index); console.log('时长:', chsSub.duration); return [chsSub, engSub]; @@ -29,6 +37,7 @@ export const findSub = (subTitles) => { * 查找策略是:先找 'chi', 如果数量大于1,则进一步找 "简体" */ const findChiSub = (subTitles) => { + // TBD: Consider to include 'chs' later const chineseSubtitles = subTitles.filter(subTitle => subTitle.code === 'chi'); if (chineseSubtitles.length === 0) { From 18e2e2d402a014c166604bc829ff516b91731117 Mon Sep 17 00:00:00 2001 From: Wayne Mao Date: Thu, 20 Nov 2025 14:56:32 +0800 Subject: [PATCH 3/4] feat: manually input index if no match --- findSub.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++------ index.js | 2 +- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/findSub.js b/findSub.js index 887ec1b..382c51c 100644 --- a/findSub.js +++ b/findSub.js @@ -1,7 +1,9 @@ -export const findSub = (subTitles) => { +import readline from 'readline'; + +export const findSub = async (subTitles) => { console.log('查找简体和英语字幕...'); - const chsSub = findChiSub(subTitles); - const engSub = findEngSub(subTitles); + let chsSub = findChiSub(subTitles); + let engSub = findEngSub(subTitles); if (chsSub) { console.log('找到简体中文字幕,索引为:', chsSub.index); @@ -15,20 +17,66 @@ export const findSub = (subTitles) => { console.log('没有找到英语字幕'); } - if (chsSub === null || engSub === null) { - // 打印所有可用的字幕信息,便于确认 + if (!chsSub || !engSub) { console.log('所有可用字幕信息如下:'); subTitles.forEach((s) => { console.log(`索引=${s.index}, code=${s.code}, name="${s.name}", duration=${s.duration}, frames=${s.frames}`); }); - throw new Error('字幕查找失败,中断执行'); + if (!chsSub) { + chsSub = await promptForSubIndex(subTitles, '中文'); + } + + if (!engSub) { + engSub = await promptForSubIndex(subTitles, '英文'); + } } + console.log('最终选择的简体中文字幕索引为:', chsSub.index); + console.log('最终选择的英语字幕索引为:', engSub.index); console.log('时长:', chsSub.duration); return [chsSub, engSub]; -} +}; + +const promptForSubIndex = (subTitles, label) => { + return new Promise((resolve) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const ask = () => { + rl.question(`请输入${label}字幕的索引(按回车退出): `, (answer) => { + const trimmed = answer.trim(); + if (trimmed === '') { + console.log('已退出。'); + rl.close(); + process.exit(0); + } + const value = Number(trimmed); + if (!Number.isInteger(value)) { + console.log('请输入有效的整数索引。'); + ask(); + return; + } + const target = subTitles.find((s) => s.index === value); + if (!target) { + console.log('未找到该索引对应的字幕,请重新输入。'); + ask(); + return; + } + rl.close(); + resolve({ + index: target.index, + duration: target.duration + }); + }); + }; + + ask(); + }); +}; /** * 目前看到的数据可能有: diff --git a/index.js b/index.js index fb68742..a86af0d 100755 --- a/index.js +++ b/index.js @@ -14,7 +14,7 @@ const main = async () => { for (const file of mediaFiles) { console.log(`正在处理:${file}`); const subTitles = await analyzeMedia(file); - const targetSubs = findSub(subTitles); + const targetSubs = await findSub(subTitles); const srts = await extractSub(file, targetSubs); subtitleMerge(config.workdir + srts[0], config.workdir + srts[1], `${config.workdir}${removeExtension(file)}.${config.srtTag}.srt`); deleteFile(srts[0]); From 4790b7fbc8d8641ff11356f662e585edfdcd02a5 Mon Sep 17 00:00:00 2001 From: Wayne Mao Date: Thu, 20 Nov 2025 15:01:40 +0800 Subject: [PATCH 4/4] chore: bump version --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0a8b76..39487d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dual-subtitle", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dual-subtitle", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", diff --git a/package.json b/package.json index 9713c32..27d1fe3 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "dual-subtitle", - "version": "0.4.0", + "version": "0.5.0", "main": "index.js", "type": "module", "bin": { "dual-subtitle": "index.js" }, "scripts": { - "test": "node index.js ./data/", + "test": "node index.js /Volumes/Download/一战再战[杜比视界版本][中文字幕].2025.2160p.iTunes.WEB-DL.DDP.5.1.Atmos.DV.H.265-DreamHD", "bumpVersion": "npx bbump" }, "author": "helloint",