refactor(node-syncsub): take in a list of langs to sync
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
551691f061
commit
aeddb3eb92
3 changed files with 265 additions and 50 deletions
|
@ -28,7 +28,6 @@
|
|||
"default-param-last": ["error"],
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"func-names": ["warn", "never"],
|
||||
"func-style": ["warn", "expression"],
|
||||
"logical-assignment-operators": ["warn", "always"],
|
||||
"no-array-constructor": ["error"],
|
||||
"no-empty-function": ["warn"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const iso6391To3 = new Map([
|
||||
export const ISO6391To3 = new Map([
|
||||
['aa', 'aar'],
|
||||
['ab', 'abk'],
|
||||
['af', 'afr'],
|
||||
|
@ -184,3 +184,190 @@ export const iso6391To3 = new Map([
|
|||
['zh', 'zho'],
|
||||
['zu', 'zul'],
|
||||
]);
|
||||
|
||||
export const ISO6393To1 = new Map([
|
||||
['aar', 'aa'],
|
||||
['abk', 'ab'],
|
||||
['afr', 'af'],
|
||||
['aka', 'ak'],
|
||||
['amh', 'am'],
|
||||
['ara', 'ar'],
|
||||
['arg', 'an'],
|
||||
['asm', 'as'],
|
||||
['ava', 'av'],
|
||||
['ave', 'ae'],
|
||||
['aym', 'ay'],
|
||||
['aze', 'az'],
|
||||
['bak', 'ba'],
|
||||
['bam', 'bm'],
|
||||
['bel', 'be'],
|
||||
['ben', 'bn'],
|
||||
['bis', 'bi'],
|
||||
['bod', 'bo'],
|
||||
['bos', 'bs'],
|
||||
['bre', 'br'],
|
||||
['bul', 'bg'],
|
||||
['cat', 'ca'],
|
||||
['ces', 'cs'],
|
||||
['cha', 'ch'],
|
||||
['che', 'ce'],
|
||||
['chu', 'cu'],
|
||||
['chv', 'cv'],
|
||||
['cor', 'kw'],
|
||||
['cos', 'co'],
|
||||
['cre', 'cr'],
|
||||
['cym', 'cy'],
|
||||
['dan', 'da'],
|
||||
['deu', 'de'],
|
||||
['div', 'dv'],
|
||||
['dzo', 'dz'],
|
||||
['ell', 'el'],
|
||||
['eng', 'en'],
|
||||
['epo', 'eo'],
|
||||
['est', 'et'],
|
||||
['eus', 'eu'],
|
||||
['ewe', 'ee'],
|
||||
['fao', 'fo'],
|
||||
['fas', 'fa'],
|
||||
['fij', 'fj'],
|
||||
['fin', 'fi'],
|
||||
['fra', 'fr'],
|
||||
['fry', 'fy'],
|
||||
['ful', 'ff'],
|
||||
['gla', 'gd'],
|
||||
['gle', 'ga'],
|
||||
['glg', 'gl'],
|
||||
['glv', 'gv'],
|
||||
['grn', 'gn'],
|
||||
['guj', 'gu'],
|
||||
['hat', 'ht'],
|
||||
['hau', 'ha'],
|
||||
['hbs', 'sh'],
|
||||
['heb', 'he'],
|
||||
['her', 'hz'],
|
||||
['hin', 'hi'],
|
||||
['hmo', 'ho'],
|
||||
['hrv', 'hr'],
|
||||
['hun', 'hu'],
|
||||
['hye', 'hy'],
|
||||
['ibo', 'ig'],
|
||||
['ido', 'io'],
|
||||
['iii', 'ii'],
|
||||
['iku', 'iu'],
|
||||
['ile', 'ie'],
|
||||
['ina', 'ia'],
|
||||
['ind', 'id'],
|
||||
['ipk', 'ik'],
|
||||
['isl', 'is'],
|
||||
['ita', 'it'],
|
||||
['jav', 'jv'],
|
||||
['jpn', 'ja'],
|
||||
['kal', 'kl'],
|
||||
['kan', 'kn'],
|
||||
['kas', 'ks'],
|
||||
['kat', 'ka'],
|
||||
['kau', 'kr'],
|
||||
['kaz', 'kk'],
|
||||
['khm', 'km'],
|
||||
['kik', 'ki'],
|
||||
['kin', 'rw'],
|
||||
['kir', 'ky'],
|
||||
['kom', 'kv'],
|
||||
['kon', 'kg'],
|
||||
['kor', 'ko'],
|
||||
['kua', 'kj'],
|
||||
['kur', 'ku'],
|
||||
['lao', 'lo'],
|
||||
['lat', 'la'],
|
||||
['lav', 'lv'],
|
||||
['lim', 'li'],
|
||||
['lin', 'ln'],
|
||||
['lit', 'lt'],
|
||||
['ltz', 'lb'],
|
||||
['lub', 'lu'],
|
||||
['lug', 'lg'],
|
||||
['mah', 'mh'],
|
||||
['mal', 'ml'],
|
||||
['mar', 'mr'],
|
||||
['mkd', 'mk'],
|
||||
['mlg', 'mg'],
|
||||
['mlt', 'mt'],
|
||||
['mon', 'mn'],
|
||||
['mri', 'mi'],
|
||||
['msa', 'ms'],
|
||||
['mya', 'my'],
|
||||
['nau', 'na'],
|
||||
['nav', 'nv'],
|
||||
['nbl', 'nr'],
|
||||
['nde', 'nd'],
|
||||
['ndo', 'ng'],
|
||||
['nep', 'ne'],
|
||||
['nld', 'nl'],
|
||||
['nno', 'nn'],
|
||||
['nob', 'nb'],
|
||||
['nor', 'no'],
|
||||
['nya', 'ny'],
|
||||
['oci', 'oc'],
|
||||
['oji', 'oj'],
|
||||
['ori', 'or'],
|
||||
['orm', 'om'],
|
||||
['oss', 'os'],
|
||||
['pan', 'pa'],
|
||||
['pli', 'pi'],
|
||||
['pol', 'pl'],
|
||||
['por', 'pt'],
|
||||
['pus', 'ps'],
|
||||
['que', 'qu'],
|
||||
['roh', 'rm'],
|
||||
['ron', 'ro'],
|
||||
['run', 'rn'],
|
||||
['rus', 'ru'],
|
||||
['sag', 'sg'],
|
||||
['san', 'sa'],
|
||||
['sin', 'si'],
|
||||
['slk', 'sk'],
|
||||
['slv', 'sl'],
|
||||
['sme', 'se'],
|
||||
['smo', 'sm'],
|
||||
['sna', 'sn'],
|
||||
['snd', 'sd'],
|
||||
['som', 'so'],
|
||||
['sot', 'st'],
|
||||
['spa', 'es'],
|
||||
['sqi', 'sq'],
|
||||
['srd', 'sc'],
|
||||
['srp', 'sr'],
|
||||
['ssw', 'ss'],
|
||||
['sun', 'su'],
|
||||
['swa', 'sw'],
|
||||
['swe', 'sv'],
|
||||
['tah', 'ty'],
|
||||
['tam', 'ta'],
|
||||
['tat', 'tt'],
|
||||
['tel', 'te'],
|
||||
['tgk', 'tg'],
|
||||
['tgl', 'tl'],
|
||||
['tha', 'th'],
|
||||
['tir', 'ti'],
|
||||
['ton', 'to'],
|
||||
['tsn', 'tn'],
|
||||
['tso', 'ts'],
|
||||
['tuk', 'tk'],
|
||||
['tur', 'tr'],
|
||||
['twi', 'tw'],
|
||||
['uig', 'ug'],
|
||||
['ukr', 'uk'],
|
||||
['urd', 'ur'],
|
||||
['uzb', 'uz'],
|
||||
['ven', 've'],
|
||||
['vie', 'vi'],
|
||||
['vol', 'vo'],
|
||||
['wln', 'wa'],
|
||||
['wol', 'wo'],
|
||||
['xho', 'xh'],
|
||||
['yid', 'yi'],
|
||||
['yor', 'yo'],
|
||||
['zha', 'za'],
|
||||
['zho', 'zh'],
|
||||
['zul', 'zu'],
|
||||
]);
|
||||
|
|
|
@ -1,55 +1,98 @@
|
|||
import { readdir } from 'fs';
|
||||
import { ffprobe } from 'fluent-ffmpeg';
|
||||
import {
|
||||
mkdir,
|
||||
readdir as readDir,
|
||||
rename as mv,
|
||||
} from 'fs/promises';
|
||||
|
||||
import { ffprobe as ffProbe } from 'fluent-ffmpeg';
|
||||
import { spawn } from 'child_process';
|
||||
import { iso6391To3 } from './lang-codes';
|
||||
|
||||
const SUB_EXT_LENGTH = 7;
|
||||
import { ISO6391To3, ISO6393To1 } from './lang-codes';
|
||||
|
||||
|
||||
const FILE = process.argv[2];
|
||||
/**
|
||||
* These are the cli arguments
|
||||
*
|
||||
* @param directory 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 DIR = process.argv[2];
|
||||
let langs = process.argv[3].split(',');
|
||||
|
||||
const main = () => {
|
||||
const BASE_NAME = FILE.substring(
|
||||
FILE.lastIndexOf('/') + 1,
|
||||
FILE.length - SUB_EXT_LENGTH,
|
||||
);
|
||||
|
||||
const DIR = FILE.substring(0, FILE.lastIndexOf('/'));
|
||||
// Check if there are 2 params
|
||||
if (DIR && langs) {
|
||||
main();
|
||||
}
|
||||
else {
|
||||
console.error('Error: no argument passed');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
readdir(DIR, (_, files) => {
|
||||
const VIDEO = `${DIR}/${files.filter((f) =>
|
||||
f.includes(BASE_NAME) &&
|
||||
!f.endsWith('.nfo') &&
|
||||
!f.endsWith('.srt'))[0]}`;
|
||||
function getVideoPath(files: string[]) {
|
||||
const fileName = DIR.split('/').at(-1) ?? '';
|
||||
|
||||
ffprobe(VIDEO, (_e, data) => {
|
||||
const LANG = iso6391To3.get(FILE.split('.').at(-2) ?? 'en') ?? 'eng';
|
||||
return `${DIR}/${files.filter((f) =>
|
||||
f.includes(fileName) &&
|
||||
!f.endsWith('.nfo') &&
|
||||
!f.endsWith('.srt'))[0]}`;
|
||||
}
|
||||
|
||||
const OUT_FILE = `${BASE_NAME}.synced.${LANG.substring(0, 2)}.srt`;
|
||||
const OUT_PATH = `${DIR}/${OUT_FILE}`;
|
||||
async function main() {
|
||||
const files = await readDir(DIR);
|
||||
|
||||
if (files.includes(OUT_FILE)) {
|
||||
console.warn('Synced subtitles already exist, not doing anything');
|
||||
process.exit(0);
|
||||
const VIDEO = getVideoPath(files);
|
||||
const BASE_NAME = VIDEO.split('/').at(-1)?.replace(/\.[^.]*$/, '');
|
||||
|
||||
// Check if backup folder already exists and create it if not
|
||||
if (!files.some((f) => f.endsWith('.srt.bak'))) {
|
||||
await mkdir(`${DIR}/.srt.bak`);
|
||||
}
|
||||
else {
|
||||
const backups = await readDir(`${DIR}/.srt.bak`);
|
||||
|
||||
// Remove synced subtitles from the list to sync
|
||||
// langs - backups
|
||||
langs = langs.filter((n) => !backups
|
||||
.some((s) => n === ISO6391To3.get(s.split('.').at(-2) ?? '')));
|
||||
}
|
||||
|
||||
if (langs.length === 0) {
|
||||
console.warn('Subtitles have already been synced');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// ffprobe the video file to see available audio tracks
|
||||
ffProbe(VIDEO, (_e, data) => {
|
||||
const AVAIL_LANGS = data.streams
|
||||
.filter((s) => s.codec_type === 'audio')
|
||||
.map((s) => s['tags']['language']);
|
||||
|
||||
// Sync subtitles one by one
|
||||
langs.forEach(async(lang) => {
|
||||
const FILE_NAME = `${BASE_NAME}.${ISO6393To1.get(lang)}.srt`;
|
||||
const IN_FILE = `${DIR}/.srt.bak/${FILE_NAME}`;
|
||||
const OUT_FILE = `${DIR}/${FILE_NAME}`;
|
||||
|
||||
if (files.includes(FILE_NAME)) {
|
||||
await mv(OUT_FILE, IN_FILE);
|
||||
}
|
||||
else {
|
||||
// TODO: check data and extract sub
|
||||
}
|
||||
|
||||
const availLangs = data.streams
|
||||
.filter((s) => s.codec_type === 'audio')
|
||||
.map((s) => s['tags']['language']);
|
||||
|
||||
const cmd = [
|
||||
'--cli sync',
|
||||
`--sub-lang ${LANG}`,
|
||||
`--sub-lang ${lang}`,
|
||||
|
||||
`--ref-stream-by-lang ${availLangs.includes(LANG) ? LANG : availLangs[0]}`,
|
||||
`--ref-stream-by-lang ${AVAIL_LANGS.includes(lang) ?
|
||||
lang :
|
||||
AVAIL_LANGS[0]}`,
|
||||
'--ref-stream-by-type "audio"',
|
||||
|
||||
`--sub '${FILE}'`,
|
||||
`--out '${OUT_PATH}'`,
|
||||
// `--out '${OUT_PATH}'`,
|
||||
`--sub '${IN_FILE}'`,
|
||||
`--out '${OUT_FILE}'`,
|
||||
`--ref '${VIDEO}'`,
|
||||
|
||||
// '--overwrite',
|
||||
];
|
||||
|
||||
spawn('subsync', cmd, {
|
||||
|
@ -58,18 +101,4 @@ const main = () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (FILE) {
|
||||
if (FILE.includes('synced.srt')) {
|
||||
console.warn('Won\'t sync already synced subtitles, not doing anything');
|
||||
process.exit(0);
|
||||
}
|
||||
else {
|
||||
main();
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('Error: no argument passed');
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue