diff --git a/devices/nos/modules/subtitles/syncing/node-syncsub/.eslintrc.json b/devices/nos/modules/subtitles/syncing/node-syncsub/.eslintrc.json index f3b55fe..0bdffac 100644 --- a/devices/nos/modules/subtitles/syncing/node-syncsub/.eslintrc.json +++ b/devices/nos/modules/subtitles/syncing/node-syncsub/.eslintrc.json @@ -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"], diff --git a/devices/nos/modules/subtitles/syncing/node-syncsub/lang-codes.ts b/devices/nos/modules/subtitles/syncing/node-syncsub/lang-codes.ts index ace9612..447efbc 100644 --- a/devices/nos/modules/subtitles/syncing/node-syncsub/lang-codes.ts +++ b/devices/nos/modules/subtitles/syncing/node-syncsub/lang-codes.ts @@ -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'], +]); diff --git a/devices/nos/modules/subtitles/syncing/node-syncsub/main.ts b/devices/nos/modules/subtitles/syncing/node-syncsub/main.ts index e1c295f..578bfc3 100755 --- a/devices/nos/modules/subtitles/syncing/node-syncsub/main.ts +++ b/devices/nos/modules/subtitles/syncing/node-syncsub/main.ts @@ -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); }