Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
399 changes: 247 additions & 152 deletions README.md

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions bin/subcinode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs';
import { run } from '../src/run.js';
import { parseArgs } from '../src/args.js';
import { log, logLevels } from '../src/logger.js';

const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));

const parsed = parseArgs(process.argv.slice(2), { version: pkg.version });

run(parsed, { version: pkg.version }).then(
(result) => {
process.exitCode = result.errors.length ? 1 : 0;
},
(err) => {
log(`[FATAL: ${err.message}]`, logLevels.FATAL);
if (process.env.SUBCINODE_DEBUG) {
console.error(err);
}
process.exitCode = 1;
}
);
178 changes: 178 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 37 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "subcinode",
"version": "1.1.3",
"description": "Your subs, now.",
"version": "2.0.0",
"description": "Automatically download the right subtitles for your local video files, from the console.",
"type": "module",
"keywords": [
"subtitles",
"subcino",
Expand All @@ -14,16 +15,41 @@
"email": "alessandro.89@gmail.com",
"url": "https://www.alessandropiana.com"
},
"dependencies": {
"async": "^1.5.0",
"http": "0.0.0",
"jsonfile": "^2.2.3",
"langs": "^1.0.2",
"opensubtitles-api": "^2.3.0",
"subtitles-parser": "0.0.2"
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/alexis89x/subcinode.git"
},
"bugs": {
"url": "https://github.com/alexis89x/subcinode/issues"
},
"homepage": "https://github.com/alexis89x/subcinode#readme",
"engines": {
"node": ">=20"
},
"main": "src/run.js",
"exports": {
".": "./src/run.js",
"./providers": "./src/providers/index.js",
"./package.json": "./package.json"
},
"preferGlobal": true,
"bin": {
"subcinode": "subcino.js"
"subcinode": "bin/subcinode.js"
},
"files": [
"bin",
"src",
"README.md",
"LICENSE.txt"
],
"scripts": {
"test": "node --test",
"start": "node bin/subcinode.js"
},
"preferGlobal": true,
"dependencies": {
"commander": "^12.1.0",
"langs": "^2.0.0",
"subtitle": "^4.2.1"
}
}
117 changes: 117 additions & 0 deletions src/args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Command } from 'commander';

function splitList(value) {
return String(value)
.split(',')
.map((part) => part.trim())
.filter(Boolean);
}

/**
* Rewrites the legacy single-dash `-flag=value` token style into the standard
* `--flag value` form that {@link Command} understands, so documented invocations
* from older versions keep working.
*
* @param {string[]} argv user arguments (no `node`/script entries)
* @returns {string[]}
*/
export function normalizeLegacyArgv(argv) {
const out = [];
for (const token of argv) {
if (token.startsWith('--')) {
out.push(token);
continue;
}
const match = /^-([a-zA-Z]+)(?:=(.*))?$/.exec(token);
if (!match) {
out.push(token);
continue;
}

const name = match[1].toLowerCase();
const value = match[2];

switch (name) {
case 'langs':
case 'extensions':
case 'path':
case 'provider':
out.push(`--${name}`);
if (value !== undefined) {
out.push(value);
}
break;
case 'usesubs':
out.push(value === 'false' ? '--no-use-subs' : '--use-subs');
break;
case 'recursive':
out.push(value === 'false' ? '--no-recursive' : '--recursive');
break;
case 'save':
out.push('--save');
break;
case 'debug':
out.push('--debug');
break;
case 'settings':
out.push('--settings');
break;
default:
out.push(token);
}
}
return out;
}

/**
* Parses CLI arguments (legacy or modern form) into:
* `{ cli, save, onlySettings }`
* where `cli` contains only the options the user actually passed, ready to be merged
* onto the stored settings.
*
* @param {string[]} argv user arguments (typically `process.argv.slice(2)`)
* @param {{ version?: string, exit?: boolean }} [options]
*/
export function parseArgs(argv, options = {}) {
const program = new Command();
program
.name('subcinode')
.description('Download the correct subtitles for your local video files.')
.version(options.version || '0.0.0')
.option('--provider <name>', 'subtitle provider to use')
.option('--langs <list>', 'comma-separated language codes, or "all"', splitList)
.option('--extensions <list>', 'comma-separated video file extensions', splitList)
.option('--path <dir>', 'directory to scan (default: current directory)')
.option('--recursive', 'descend into sub-folders')
.option('--no-recursive', 'do not descend into sub-folders')
.option('--use-subs', 'save subtitles under a subs/ folder')
.option('--no-use-subs', 'save subtitles next to the video file')
.option('--save', 'persist the supplied options as the new defaults')
.option('--settings', 'print the effective settings and exit')
.option('--debug', 'verbose logging')
.allowExcessArguments(true)
.allowUnknownOption(true);

if (options.exit === false) {
program.exitOverride();
program.configureOutput({ writeErr: () => {}, writeOut: () => {} });
}

program.parse(normalizeLegacyArgv(argv), { from: 'user' });

const opts = program.opts();
const fromCli = (name) => program.getOptionValueSource(name) === 'cli';

const cli = {};
for (const key of ['provider', 'langs', 'extensions', 'path', 'recursive', 'useSubs', 'debug']) {
if (fromCli(key)) {
cli[key] = opts[key];
}
}

return {
cli,
save: Boolean(opts.save),
onlySettings: Boolean(opts.settings)
};
}
Loading
Loading