nixos-configs/devices/nos/modules/subtitles/extract-subs/main.ts
matt1432 021e233da1
All checks were successful
Discord / discord commits (push) Has been skipped
chore: bump all package.json files
2024-10-12 13:09:59 -04:00

114 lines
3.2 KiB
TypeScript
Executable file

import Ffmpeg from 'fluent-ffmpeg';
import { spawnSync as spawn } from 'child_process';
import { ISO6393To1 } from './lang-codes';
const SPAWN_OPTS = {
shell: true,
stdio: [process.stdin, process.stdout, process.stderr],
};
/**
* These are the cli arguments
*
* @param videoPath the directory in which we want to sync the subtitles
* @param languages a comma-separated list of languages (3 letters) to sync the subtitles
*/
const video = process.argv[2];
const languages = process.argv[3]?.split(',');
const getSubPath = (baseName: string, sub: Ffmpeg.FfprobeStream): string => {
const language = ISO6393To1.get(sub.tags.language);
const forced = sub.disposition?.forced === 0 ?
'' :
'.forced';
const hearingImpaired = sub.disposition?.hearing_impaired === 0 ?
'' :
'.sdh';
return `${baseName}${forced}.${language}${hearingImpaired}.srt`;
};
const main = (videoPath: string) => {
const subIndexes: number[] = [];
const baseName = videoPath.split('/').at(-1)!.replace(/\.[^.]*$/, '');
// ffprobe the video file to see available sub tracks
Ffmpeg.ffprobe(videoPath, (_e, data) => {
if (!data?.streams) {
console.error('Couldn\'t find streams in video file');
return;
}
languages.forEach((lang) => {
let subs = data.streams.filter((s) => {
return s['tags'] &&
s['tags']['language'] &&
s['tags']['language'] === lang &&
s.codec_type === 'subtitle';
});
const pgs = subs.filter((s) => s.codec_name === 'hdmv_pgs_subtitle');
// If we only have PGS subs, warn user
if (pgs.length === subs.length) {
console.warn(`No SRT subtitle tracks were found for ${lang}`);
}
// Remove PGS streams from subs
subs = subs.filter((s) => s.codec_name !== 'hdmv_pgs_subtitle');
if (subs.length === 0) {
console.warn(`No subtitle tracks were found for ${lang}`);
return;
}
subs.forEach((sub) => {
const subFile = getSubPath(baseName, sub);
// Extract subtitle
spawn('ffmpeg', [
'-i', `'${videoPath}'`,
'-map', `"0:${sub.index}"`, `'${subFile}'`,
], SPAWN_OPTS);
subIndexes.push(sub.index);
});
});
// Delete subtitles from video
spawn('mv', [
`'${videoPath}'`,
`'${videoPath}.bak'`,
], SPAWN_OPTS);
spawn('ffmpeg', [
'-i', `'${videoPath}.bak'`,
'-map', '0',
...subIndexes.map((i) => [
'-map', `-0:${i}`,
]).flat(),
'-c', 'copy', `'${videoPath}'`,
], SPAWN_OPTS);
spawn('rm', [
`'${videoPath}.bak'`,
], SPAWN_OPTS);
});
};
const escapePath = (p: string): string => p.replaceAll("'", "'\\''");
// Check if there are 2 params
if (video && languages) {
main(escapePath(video));
}
else {
console.error('Error: no argument passed');
process.exit(1);
}