mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-16 17:16:20 -07:00
feat: add subtitle generation and bundle Bun launcher runtime
- Add local subtitle generation and card timing review workflows - Package cross-platform Bun runtimes, launchers, licenses, and source - Consolidate release packaging and refresh v0.19.6 documentation
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import { Command } from 'commander';
|
||||
import type { Args } from '../types.js';
|
||||
import {
|
||||
isSubtitleGenerationModelId,
|
||||
SUBTITLE_GENERATION_MODELS,
|
||||
} from '../../src/shared/subtitle-generation-model-catalog.js';
|
||||
|
||||
export interface JellyfinInvocation {
|
||||
action?: string;
|
||||
@@ -20,6 +25,7 @@ export interface CommandActionInvocation {
|
||||
}
|
||||
|
||||
export interface CliInvocations {
|
||||
generateSubtitles?: Args['generateSubtitles'];
|
||||
jellyfinInvocation: JellyfinInvocation | null;
|
||||
configInvocation: CommandActionInvocation | null;
|
||||
settingsInvocation: CommandActionInvocation | null;
|
||||
@@ -120,6 +126,7 @@ function getTopLevelCommand(argv: string[]): { name: string; index: number } | n
|
||||
'mpv',
|
||||
'logs',
|
||||
'dictionary',
|
||||
'generate-subs',
|
||||
'dict',
|
||||
'stats',
|
||||
'sync',
|
||||
@@ -202,6 +209,7 @@ export function parseCliPrograms(
|
||||
let texthookerOpenBrowser = false;
|
||||
let doctorTriggered = false;
|
||||
let texthookerTriggered = false;
|
||||
let generateSubtitles: Args['generateSubtitles'];
|
||||
|
||||
const commandProgram = new Command();
|
||||
commandProgram
|
||||
@@ -226,6 +234,50 @@ export function parseCliPrograms(
|
||||
.argument('[target]', 'file, directory, or URL');
|
||||
applyRootOptions(rootProgram);
|
||||
|
||||
commandProgram
|
||||
.command('generate-subs')
|
||||
.description('Generate Japanese subtitles locally with whisper.cpp')
|
||||
.argument('[video]', 'Local media file, or the current mpv file if omitted')
|
||||
.option('--download-model', 'Download the selected managed model if missing')
|
||||
.option('--model-path <path>', 'Use an existing whisper.cpp model file')
|
||||
.option(
|
||||
'--model <name>',
|
||||
`Managed model: ${SUBTITLE_GENERATION_MODELS.map((model) => model.id).join(', ')}`,
|
||||
)
|
||||
.option('--output <path>', 'Save subtitles to this SRT path')
|
||||
.option('--audio-stream <index>', 'Absolute audio stream index from ffprobe')
|
||||
.action((mediaPath: string | undefined, options: Record<string, unknown>) => {
|
||||
const model = options.model;
|
||||
if (model !== undefined && !isSubtitleGenerationModelId(model)) {
|
||||
throw new Error(
|
||||
`Generation --model must be one of: ${SUBTITLE_GENERATION_MODELS.map((entry) => entry.id).join(', ')}.`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
options.modelPath !== undefined &&
|
||||
(model !== undefined || options.downloadModel === true)
|
||||
) {
|
||||
throw new Error(
|
||||
'Generation --model-path cannot be combined with --model or --download-model.',
|
||||
);
|
||||
}
|
||||
let audioStreamIndex: number | undefined;
|
||||
if (typeof options.audioStream === 'string') {
|
||||
audioStreamIndex = Number(options.audioStream);
|
||||
if (!/^\d+$/.test(options.audioStream) || !Number.isSafeInteger(audioStreamIndex)) {
|
||||
throw new Error('Generation --audio-stream must be a non-negative integer stream index.');
|
||||
}
|
||||
}
|
||||
generateSubtitles = {
|
||||
mediaPath,
|
||||
downloadModel: options.downloadModel === true,
|
||||
modelPath: typeof options.modelPath === 'string' ? options.modelPath : undefined,
|
||||
managedModel: model,
|
||||
outputPath: typeof options.output === 'string' ? options.output : undefined,
|
||||
audioStreamIndex,
|
||||
};
|
||||
});
|
||||
|
||||
commandProgram
|
||||
.command('jellyfin')
|
||||
.alias('jf')
|
||||
@@ -363,6 +415,7 @@ export function parseCliPrograms(
|
||||
.option('--json', 'Emit machine-readable NDJSON progress output')
|
||||
.option('--make-temp', 'Create a sync temp directory and print its path (used over SSH)')
|
||||
.option('--remove-temp <dir>', 'Remove a sync temp directory created by --make-temp')
|
||||
.option('--transfer-cache <key>', 'Reuse/save a received snapshot with temp helpers (internal)')
|
||||
.option('--ui', 'Open the SubMiner sync window')
|
||||
.option('--log-level <level>', 'Log level')
|
||||
.action((rawHost: string | undefined, options: Record<string, unknown>) => {
|
||||
@@ -384,6 +437,7 @@ export function parseCliPrograms(
|
||||
check ||
|
||||
makeTemp ||
|
||||
removeTemp ||
|
||||
options.transferCache !== undefined ||
|
||||
options.remoteCmd !== undefined ||
|
||||
options.db !== undefined ||
|
||||
options.json === true ||
|
||||
@@ -405,6 +459,8 @@ export function parseCliPrograms(
|
||||
if (merge) tokens.push('--merge', merge);
|
||||
if (makeTemp) tokens.push('--make-temp');
|
||||
if (removeTemp) tokens.push('--remove-temp', removeTemp);
|
||||
if (typeof options.transferCache === 'string')
|
||||
tokens.push('--transfer-cache', options.transferCache);
|
||||
if (push) tokens.push('--push');
|
||||
if (pull) tokens.push('--pull');
|
||||
if (check) tokens.push('--check');
|
||||
@@ -520,6 +576,7 @@ export function parseCliPrograms(
|
||||
options: selectedProgram.opts<Record<string, unknown>>(),
|
||||
rootTarget: rootProgram.processedArgs[0],
|
||||
invocations: {
|
||||
generateSubtitles,
|
||||
jellyfinInvocation,
|
||||
configInvocation,
|
||||
settingsInvocation,
|
||||
|
||||
Reference in New Issue
Block a user