Skip to content

Commit eb3c93f

Browse files
authored
Feat/improve performance (#6)
* chore: append '/' if missing, to avoid read file error * doc: handle if no npx available * feat: improve performance * chore: separate subtitle-merge * chore: bump version
1 parent 0fe20be commit eb3c93f

10 files changed

Lines changed: 286 additions & 196 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
1. 进入视频所在目录
1212
2. 命令行执行:`npx dual-subtitle`
1313

14-
字幕文件会以`.chs-eng.srt`结尾
14+
注:有些环境(比如群晖)如果没有npx,可以试试用`npm exec dual-subtitle`代替。
15+
16+
生成的字幕文件会以`.chs-eng.srt`结尾。
1517

1618
## 介绍
1719
此工具主要是为了满足在Infuse上看流媒体视频时,能够显示双语字幕的需求。Infuse本身并不支持同时显示2条不同语言的字幕。

analyzeMedia.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
1+
import { execSync } from 'child_process';
12
import ffprobe from "ffprobe";
2-
import ffprobeStatic from "ffprobe-static";
3+
import ffprobeInstaller from '@ffprobe-installer/ffprobe';
34
import {config} from "./config.js";
45

6+
const getFFprobePath = () => {
7+
try {
8+
// 检查系统是否安装了 ffprobe
9+
execSync('ffprobe -version', { stdio: 'ignore' });
10+
// 如果能执行到这里,说明系统已安装
11+
console.log('使用本地ffprobe');
12+
return 'ffprobe'; // 返回系统命令
13+
} catch (e) {
14+
return ffprobeInstaller.path;
15+
}
16+
}
17+
518
export const analyzeMedia = (file) => {
19+
console.log(`获取字幕信息...`);
20+
const ffprobePath = getFFprobePath();
621
return new Promise((resolve, reject) => {
7-
ffprobe(config.workdir + file, { path: ffprobeStatic.path })
22+
ffprobe(config.workdir + file, { path: ffprobePath })
823
.then(function (info) {
924
// console.log(info);
10-
resolve(info);
25+
const subTitles = info.streams.filter((stream) => stream.codec_type === 'subtitle').map(stream => ({
26+
index: stream.index,
27+
code: stream.tags.language.toLowerCase(),
28+
name: stream.tags.title ? stream.tags.title.toLowerCase() : '',
29+
duration: Math.round(stream.duration),
30+
}));
31+
console.log(`找到 ${subTitles.length} 条字幕。`);
32+
resolve(subTitles);
1133
})
1234
.catch(function (err) {
1335
// console.error(err);

config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const config = {
55
};
66

77
if (process.argv.length > 2) {
8-
config.workdir = process.argv[2];
8+
config.workdir = process.argv[2].endsWith('/') ? process.argv[2] : process.argv[2] + '/';
99
}
1010

1111
export { config };

extractSub.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,48 @@ import ffmpeg from 'fluent-ffmpeg';
33
import {config} from "./config.js";
44
import {removeExtension} from "./utils.js";
55

6-
ffmpeg.setFfmpegPath(ffmpegInstaller.path);
6+
const getFFmpegPath = () => {
7+
try {
8+
// 检查系统是否安装了 ffprobe
9+
execSync('ffmpeg -version', { stdio: 'ignore' });
10+
// 如果能执行到这里,说明系统已安装
11+
console.log('使用本地ffmpeg');
12+
return 'ffmpeg'; // 返回系统命令
13+
} catch (e) {
14+
return ffmpegInstaller.path;
15+
}
16+
}
717

8-
export const extractSub = (filename, subIdx) => {
18+
/**
19+
* "timemark":"00:06:52.07"
20+
* @param timemark
21+
* @returns {number}
22+
*/
23+
const timemarkToSeconds = (timemark) => {
24+
const [hms, ms] = timemark.split('.');
25+
const [h, m, s] = hms.split(':');
26+
return (+h * 3600) + (+m * 60) + (+s) + (+ms / 100);
27+
}
28+
29+
export const extractSub = (filename, targetSubs) => {
930
return new Promise((resolve, reject) => {
1031
const mainSrt = `${removeExtension(filename)}.chs.srt`;
1132
const secondarySrt = `${removeExtension(filename)}.eng.srt`;
33+
const duration = targetSubs[0].duration;
1234

35+
ffmpeg.setFfmpegPath(getFFmpegPath());
1336
ffmpeg(config.workdir + filename)
1437
.output(config.workdir + mainSrt)
15-
.outputOptions(['-map', `0:${subIdx[0]}`, '-c', 'copy'])
38+
.outputOptions(['-map', `0:${targetSubs[0].index}`, '-c', 'copy'])
1639
.output(config.workdir + secondarySrt)
17-
.outputOptions(['-map', `0:${subIdx[1]}`, '-c', 'copy'])
40+
.outputOptions(['-map', `0:${targetSubs[1].index}`, '-c', 'copy'])
1841
.run()
1942
.on('start', function (str) {
2043
console.log('转换任务开始~', str);
2144
})
2245
.on('progress', function (progress) {
23-
console.log(`进行中,完成${(progress.percent || 0)}%`);
46+
const progressPercent = Math.round((timemarkToSeconds(progress.timemark) / duration) * 100);
47+
console.log(`进行中,完成${(progressPercent || 0)}%`);
2448
})
2549
.on('end', function (str) {
2650
console.log('转换任务完成!');

findIndex.js

Lines changed: 0 additions & 76 deletions
This file was deleted.

findSub.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
export const findSub = (subTitles) => {
2+
console.log('查找简体和英语字幕...');
3+
const chsSub = findChiSub(subTitles);
4+
const engSub = findEngSub(subTitles);
5+
6+
if (chsSub === null || engSub === null) {
7+
if (chsSub === null) {
8+
console.log('没有找到简体中文字幕');
9+
}
10+
11+
if (engSub === null) {
12+
console.log('没有找到英语字幕');
13+
}
14+
15+
throw new Error('字幕查找失败,中断执行');
16+
}
17+
18+
console.log('找到简体中文字幕,索引为:', chsSub.index);
19+
console.log('找到英语字幕,索引为:', engSub.index);
20+
console.log('时长:', chsSub.duration);
21+
22+
return [chsSub, engSub];
23+
}
24+
25+
/**
26+
* 目前看到的数据可能有:
27+
* chi,简体
28+
* chi,Simplified Chinese
29+
* 查找策略是:先找 'chi', 如果数量大于1,则进一步找 "简体"
30+
*/
31+
const findChiSub = (subTitles) => {
32+
const chineseSubtitles = subTitles.filter(subTitle => subTitle.code === 'chi');
33+
34+
if (chineseSubtitles.length === 0) {
35+
return null;
36+
}
37+
38+
if (chineseSubtitles.length === 1) {
39+
return {
40+
index: chineseSubtitles[0].index,
41+
duration: chineseSubtitles[0].duration
42+
};
43+
}
44+
45+
// If multiple Chinese subtitles, look for Simplified Chinese
46+
const targetSub = chineseSubtitles.find(subTitle =>
47+
subTitle.name.includes('简体') ||
48+
subTitle.name.includes('simplified')
49+
);
50+
51+
return targetSub ? {
52+
index: targetSub.index,
53+
duration: targetSub.duration
54+
} : null;
55+
}
56+
57+
/**
58+
* 目前看到的数据可能有:
59+
* 9,subrip,eng
60+
* 10,subrip,eng,SDH
61+
* 23,subrip,eng,English[CC]
62+
* 如果同时有SDH和非SDH版本,选非SDH版本。
63+
*/
64+
const findEngSub = (subTitles) => {
65+
const englishSubs = subTitles.filter(sub => sub.code === 'eng');
66+
67+
if (englishSubs.length === 0) return null;
68+
if (englishSubs.length === 1) return {
69+
index: englishSubs[0].index,
70+
duration: englishSubs[0].duration,
71+
};
72+
73+
// Filter out SDH subtitles if there are multiple English options
74+
const nonSDHSubs = englishSubs.filter(sub =>
75+
!sub.name.includes('sdh')
76+
);
77+
78+
// Return first non-SDH sub if available, otherwise first English sub
79+
const targetSub = nonSDHSubs[0] || englishSubs[0];
80+
return targetSub ? {
81+
index: targetSub.index,
82+
duration: targetSub.duration
83+
} : null;
84+
};

index.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
#!/usr/bin/env node
22
import fs from 'fs';
33
import path from 'path';
4+
import subtitleMerge from "subtitle-merge";
45
import {config} from './config.js';
5-
import {findIndex} from './findIndex.js';
6+
import {findSub} from './findSub.js';
67
import {analyzeMedia} from "./analyzeMedia.js";
78
import {extractSub} from "./extractSub.js";
8-
import {mergeSrtFiles} from "./mergeSub.js";
99
import {deleteFile, removeExtension} from "./utils.js";
1010

1111
const main = async () => {
1212
const mediaFiles = fs.readdirSync(config.workdir).filter((file) => config.exts.includes(path.extname(file)));
1313

1414
for (const file of mediaFiles) {
1515
console.log(`正在处理:${file}`);
16-
const mediaInfo = await analyzeMedia(file);
17-
const subIndex = findIndex(mediaInfo);
18-
const srts = await extractSub(file, subIndex);
19-
mergeSrtFiles(srts[0], srts[1], `${removeExtension(file)}.${config.srtTag}.srt`);
16+
const subTitles = await analyzeMedia(file);
17+
const targetSubs = findSub(subTitles);
18+
const srts = await extractSub(file, targetSubs);
19+
subtitleMerge(config.workdir + srts[0], config.workdir + srts[1], `${config.workdir}${removeExtension(file)}.${config.srtTag}.srt`);
2020
deleteFile(srts[0]);
2121
deleteFile(srts[1]);
2222
}

mergeSub.js

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)