From 655eac9ad68f2af1843b87845431dad024790f5a Mon Sep 17 00:00:00 2001 From: matt1432 Date: Fri, 15 Nov 2024 11:37:58 -0500 Subject: [PATCH] refactor(extract-subs): move to apps and split up code better --- apps/default.nix | 1 + .../subtitles => apps}/extract-subs/.envrc | 1 + apps/extract-subs/default.nix | 32 ++++ .../extract-subs/eslint.config.ts | 14 +- .../extract-subs/package-lock.json | Bin 80138 -> 91461 bytes apps/extract-subs/package.json | 22 +++ apps/extract-subs/src/app.ts | 157 ++++++++++++++++++ apps/extract-subs/src/ffprobe.ts | 8 + .../extract-subs/src}/lang-codes.ts | 0 apps/extract-subs/tsconfig.json | 10 ++ apps/tsconfig.json | 25 +++ apps/update/.envrc | 1 + apps/update/src/app.ts | 10 +- apps/update/src/misc.ts | 2 +- apps/update/tsconfig.json | 25 +-- .../subtitles/extract-subs/default.nix | 49 ------ .../modules/subtitles/extract-subs/main.ts | 114 ------------- .../subtitles/extract-subs/package.json | 18 -- .../subtitles/extract-subs/tsconfig.json | 19 --- 19 files changed, 273 insertions(+), 235 deletions(-) rename {devices/nos/modules/subtitles => apps}/extract-subs/.envrc (81%) create mode 100644 apps/extract-subs/default.nix rename {devices/nos/modules/subtitles => apps}/extract-subs/eslint.config.ts (97%) rename {devices/nos/modules/subtitles => apps}/extract-subs/package-lock.json (75%) create mode 100644 apps/extract-subs/package.json create mode 100644 apps/extract-subs/src/app.ts create mode 100644 apps/extract-subs/src/ffprobe.ts rename {devices/nos/modules/subtitles/extract-subs => apps/extract-subs/src}/lang-codes.ts (100%) create mode 100644 apps/extract-subs/tsconfig.json create mode 100644 apps/tsconfig.json delete mode 100644 devices/nos/modules/subtitles/extract-subs/default.nix delete mode 100755 devices/nos/modules/subtitles/extract-subs/main.ts delete mode 100644 devices/nos/modules/subtitles/extract-subs/package.json delete mode 100644 devices/nos/modules/subtitles/extract-subs/tsconfig.json diff --git a/apps/default.nix b/apps/default.nix index 62b603e5..17d8cb92 100644 --- a/apps/default.nix +++ b/apps/default.nix @@ -10,5 +10,6 @@ type = "app"; }; in { + extract-subs = mkApp ./extract-subs; updateFlake = mkApp ./update; } diff --git a/devices/nos/modules/subtitles/extract-subs/.envrc b/apps/extract-subs/.envrc similarity index 81% rename from devices/nos/modules/subtitles/extract-subs/.envrc rename to apps/extract-subs/.envrc index e74546be..ceec897a 100644 --- a/devices/nos/modules/subtitles/extract-subs/.envrc +++ b/apps/extract-subs/.envrc @@ -1 +1,2 @@ use flake $FLAKE#subtitles-dev +npm ci diff --git a/apps/extract-subs/default.nix b/apps/extract-subs/default.nix new file mode 100644 index 00000000..eabb774d --- /dev/null +++ b/apps/extract-subs/default.nix @@ -0,0 +1,32 @@ +{ + lib, + buildNpmPackage, + ffmpeg-full, + makeWrapper, + nodejs_latest, + ... +}: let + inherit (lib) concatMapStringsSep getBin; + + packageJSON = builtins.fromJSON (builtins.readFile ./package.json); +in + buildNpmPackage rec { + pname = packageJSON.name; + inherit (packageJSON) version; + + src = ./.; + npmDepsHash = "sha256-edIAvY03eA3hqPHjAXz8pq3M5NzekOAYAR4o7j/Wf5Y="; + + runtimeInputs = [ + ffmpeg-full + ]; + nativeBuildInputs = [makeWrapper]; + + postInstall = '' + wrapProgram $out/bin/${pname} \ + --prefix PATH : ${concatMapStringsSep ":" (p: getBin p) runtimeInputs} + ''; + + nodejs = nodejs_latest; + meta.mainProgram = pname; + } diff --git a/devices/nos/modules/subtitles/extract-subs/eslint.config.ts b/apps/extract-subs/eslint.config.ts similarity index 97% rename from devices/nos/modules/subtitles/extract-subs/eslint.config.ts rename to apps/extract-subs/eslint.config.ts index 0a3eea06..48b1f19a 100644 --- a/devices/nos/modules/subtitles/extract-subs/eslint.config.ts +++ b/apps/extract-subs/eslint.config.ts @@ -30,6 +30,13 @@ export default tseslint.config({ 'class-methods-use-this': 'off', '@stylistic/no-multiple-empty-lines': 'off', '@stylistic/jsx-indent-props': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'error', + '@stylistic/indent-binary-ops': 'off', + '@stylistic/max-statements-per-line': [ + 'error', + { max: 2 }, + ], // Pre-flat config '@typescript-eslint/no-unused-vars': [ @@ -64,12 +71,6 @@ export default tseslint.config({ ], }, ], - 'no-use-before-define': [ - 'error', - { - functions: false, - }, - ], 'block-scoped-var': [ 'error', ], @@ -255,6 +256,7 @@ export default tseslint.config({ '@stylistic/brace-style': [ 'warn', 'stroustrup', + { allowSingleLine: true }, ], '@stylistic/comma-dangle': [ 'warn', diff --git a/devices/nos/modules/subtitles/extract-subs/package-lock.json b/apps/extract-subs/package-lock.json similarity index 75% rename from devices/nos/modules/subtitles/extract-subs/package-lock.json rename to apps/extract-subs/package-lock.json index 06a022d53acf8f6171906ceadba46b3d035e35c1..91222e9fb86ed290424fa77f9a0bce7afdeac28c 100644 GIT binary patch delta 11815 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 N$dA6!yEP3h{eRkB`q%&f diff --git a/apps/extract-subs/package.json b/apps/extract-subs/package.json new file mode 100644 index 00000000..5369632c --- /dev/null +++ b/apps/extract-subs/package.json @@ -0,0 +1,22 @@ +{ + "name": "extract-subs", + "version": "0.0.0", + "bin": "out/bin/app.cjs", + "type": "module", + "scripts": { + "build": "node_ver=$(node -v); esbuild src/app.ts --bundle --platform=node --target=\"node${node_ver:1:2}\" --outfile=out/bin/app.cjs" + }, + "dependencies": { + "@eslint/js": "9.14.0", + "@stylistic/eslint-plugin": "2.10.1", + "@types/fluent-ffmpeg": "2.1.27", + "@types/node": "22.9.0", + "esbuild": "0.24.0", + "eslint": "9.14.0", + "eslint-plugin-jsdoc": "50.5.0", + "fluent-ffmpeg": "2.1.3", + "jiti": "2.4.0", + "typescript": "5.6.3", + "typescript-eslint": "8.14.0" + } +} diff --git a/apps/extract-subs/src/app.ts b/apps/extract-subs/src/app.ts new file mode 100644 index 00000000..21b7a716 --- /dev/null +++ b/apps/extract-subs/src/app.ts @@ -0,0 +1,157 @@ +import { spawnSync as spawn } from 'child_process'; + +import ffprobe from './ffprobe'; +import { ISO6393To1 } from './lang-codes'; + +/* Types */ +import { FfprobeStream } from 'fluent-ffmpeg'; + + +const SPAWN_OPTS = { + 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(','); + + +// Global Vars +const subIndexes: number[] = []; +let videoPath: string; +let baseName: string; + + +/** + * Gets the relative path to the subtitle file of a ffmpeg stream. + * + * @param sub the stream of the subtitles to extract + * @returns the path of the subtitle file + */ +const getSubPath = (sub: 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`; +}; + +/** + * Removes all subtitles streams from the video file. + */ +const removeContainerSubs = (): void => { + 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); +}; + +/** + * Extracts a sub of a video file to a subtitle file. + * + * @param sub the stream of the subtitles to extract + */ +const extractSub = (sub: FfprobeStream): void => { + const subFile = getSubPath(sub); + + spawn('ffmpeg', [ + '-i', videoPath, + '-map', `0:${sub.index}`, subFile, + ], SPAWN_OPTS); + + subIndexes.push(sub.index); +}; + +/** + * Sorts the list of streams to only keep subtitles + * that can be extracted. + * + * @param lang the language of the subtitles + * @param streams the streams + * @returns the streams that represent subtitles + */ +const findSubs = ( + lang: string, + streams: FfprobeStream[], +): FfprobeStream[] => { + const subs = streams.filter((s) => 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 + return subs.filter((s) => s.codec_name !== 'hdmv_pgs_subtitle'); +}; + +/** + * Where the magic happens. + */ +const main = async(): Promise => { + // Get rid of video extension + baseName = videoPath.split('/').at(-1)!.replace(/\.[^.]*$/, ''); + + // ffprobe the video file to see available sub tracks + const data = await ffprobe(videoPath); + + if (!data?.streams) { + console.error('Couldn\'t find streams in video file'); + + return; + } + + // Check for languages wanted + languages.forEach((lang) => { + const subs = findSubs(lang, data.streams); + + if (subs.length === 0) { + console.warn(`No subtitle tracks were found for ${lang}`); + + return; + } + + // Extract all subs + subs.forEach((sub) => { extractSub(sub); }); + }); + + removeContainerSubs(); +}; + + +// Check if there are 2 params +if (video && languages) { + videoPath = video; + main(); +} +else { + console.error('Error: no argument passed'); + process.exit(1); +} diff --git a/apps/extract-subs/src/ffprobe.ts b/apps/extract-subs/src/ffprobe.ts new file mode 100644 index 00000000..efdc4df8 --- /dev/null +++ b/apps/extract-subs/src/ffprobe.ts @@ -0,0 +1,8 @@ +import Ffmpeg from 'fluent-ffmpeg'; + + +export default (videoPath: string) => new Promise((resolve) => { + Ffmpeg.ffprobe(videoPath, (_e, data) => { + resolve(data); + }); +}); diff --git a/devices/nos/modules/subtitles/extract-subs/lang-codes.ts b/apps/extract-subs/src/lang-codes.ts similarity index 100% rename from devices/nos/modules/subtitles/extract-subs/lang-codes.ts rename to apps/extract-subs/src/lang-codes.ts diff --git a/apps/extract-subs/tsconfig.json b/apps/extract-subs/tsconfig.json new file mode 100644 index 00000000..2c06e799 --- /dev/null +++ b/apps/extract-subs/tsconfig.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../tsconfig.json", + "includes": [ + "*.ts", + "**/*.ts", + "*.js", + "**/*.js" + ] +} diff --git a/apps/tsconfig.json b/apps/tsconfig.json new file mode 100644 index 00000000..e1cb9ba7 --- /dev/null +++ b/apps/tsconfig.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + // Env + "target": "ESNext", + "lib": [ + "ESNext" + ], + // Module + "module": "nodenext", + "moduleResolution": "bundler", + "baseUrl": ".", + // Emit + "noEmit": true, + "newLine": "LF", + // Interop + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + // Type Checking + "strict": true, + "noImplicitAny": false + } +} diff --git a/apps/update/.envrc b/apps/update/.envrc index c79171c6..fd3dddda 100644 --- a/apps/update/.envrc +++ b/apps/update/.envrc @@ -1 +1,2 @@ use flake $FLAKE#node +npm ci diff --git a/apps/update/src/app.ts b/apps/update/src/app.ts index 00e15031..24b6d179 100644 --- a/apps/update/src/app.ts +++ b/apps/update/src/app.ts @@ -1,12 +1,12 @@ import { spawnSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; -import { parseArgs } from './lib.ts'; +import { parseArgs } from './lib'; -import { updateDocker } from './docker.ts'; -import { updateFirefoxAddons } from '././firefox.ts'; -import { updateFlakeInputs } from './flake.ts'; -import { updateCustomPackage, updateVuetorrent } from './misc.ts'; +import { updateDocker } from './docker'; +import { updateFirefoxAddons } from '././firefox'; +import { updateFlakeInputs } from './flake'; +import { updateCustomPackage, updateVuetorrent } from './misc'; /* Constants */ diff --git a/apps/update/src/misc.ts b/apps/update/src/misc.ts index 5b96c42d..9dbc501b 100644 --- a/apps/update/src/misc.ts +++ b/apps/update/src/misc.ts @@ -1,7 +1,7 @@ import { writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; -import { parseFetchurl } from './lib.ts'; +import { parseFetchurl } from './lib'; /* Constants */ diff --git a/apps/update/tsconfig.json b/apps/update/tsconfig.json index 8d125129..2c06e799 100644 --- a/apps/update/tsconfig.json +++ b/apps/update/tsconfig.json @@ -1,27 +1,6 @@ { - "compilerOptions": { - // Env - "target": "ESNext", - "lib": ["ESNext"], - // Module - "module": "nodenext", - "moduleResolution": "nodenext", - "allowImportingTsExtensions": true, - "baseUrl": ".", - // Emit - "noEmit": true, - "newLine": "LF", - // Interop - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - // Type Checking - "strict": true, - "noImplicitAny": false, - "allowJs": true, - "checkJs": true - }, + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../tsconfig.json", "includes": [ "*.ts", "**/*.ts", diff --git a/devices/nos/modules/subtitles/extract-subs/default.nix b/devices/nos/modules/subtitles/extract-subs/default.nix deleted file mode 100644 index ba5abdf3..00000000 --- a/devices/nos/modules/subtitles/extract-subs/default.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - buildNpmPackage, - ffmpeg-full, - nodejs_20, - typescript, - writeShellApplication, - ... -}: let - pname = "extract-subs"; - - extract-subs = buildNpmPackage { - name = "${pname}-npm"; - src = ./.; - npmDepsHash = "sha256-WXkg4e5Nh3+haCbm+XJ1CB7rsA2uV/7eZUaOUl/NVk0="; - - nativeBuildInputs = [ - nodejs_20 - typescript - ]; - - buildPhase = '' - tsc -p tsconfig.json - ''; - - installPhase = '' - mkdir -p $out/bin - mv node_modules package.json $out - - echo '#!/usr/bin/env node' > $out/bin/${pname} - cat ./build/main.js >> $out/bin/${pname} - rm ./build/main.js - chmod +x $out/bin/${pname} - - mv ./build/**.js $out/bin - ''; - }; -in - writeShellApplication { - name = pname; - - runtimeInputs = [ - ffmpeg-full - extract-subs - ]; - - text = '' - exec ${pname} "$@" - ''; - } diff --git a/devices/nos/modules/subtitles/extract-subs/main.ts b/devices/nos/modules/subtitles/extract-subs/main.ts deleted file mode 100755 index 3467fd98..00000000 --- a/devices/nos/modules/subtitles/extract-subs/main.ts +++ /dev/null @@ -1,114 +0,0 @@ -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); -} diff --git a/devices/nos/modules/subtitles/extract-subs/package.json b/devices/nos/modules/subtitles/extract-subs/package.json deleted file mode 100644 index 365d7343..00000000 --- a/devices/nos/modules/subtitles/extract-subs/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "devDependencies": { - "@eslint/js": "9.12.0", - "@stylistic/eslint-plugin": "2.9.0", - "@types/eslint__js": "8.42.3", - "@types/node": "22.7.5", - "eslint": "9.12.0", - "eslint-plugin-jsdoc": "50.3.2", - "fzf": "0.5.2", - "jiti": "2.3.3", - "typescript": "5.6.3", - "typescript-eslint": "8.8.1" - }, - "dependencies": { - "@types/fluent-ffmpeg": "2.1.27", - "fluent-ffmpeg": "2.1.3" - } -} diff --git a/devices/nos/modules/subtitles/extract-subs/tsconfig.json b/devices/nos/modules/subtitles/extract-subs/tsconfig.json deleted file mode 100644 index 41c8a16d..00000000 --- a/devices/nos/modules/subtitles/extract-subs/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNEXT", - "module": "commonjs", - "lib": [ - "ES2022" - ], - "outDir": "build", - "strict": true, - "moduleResolution": "node", - "baseUrl": ".", - "types": [ - "@types/fluent-ffmpeg" - ], - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true - } -}