tracksmith 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,7 +144,7 @@ async function extractCommand(opts, deps) {
144
144
  file: opts.file,
145
145
  track: opts.track,
146
146
  output
147
- }));
147
+ }), deps.stderrIsTTY ? { stream: "stdout" } : void 0);
148
148
  if (result.exitCode >= 2) throw new CliError(`mkvmerge failed (exit ${result.exitCode}):\n${result.stderr || result.stdout}`);
149
149
  return output;
150
150
  }
@@ -228,7 +228,7 @@ async function muxCommand(opts, deps) {
228
228
  trackName: opts.name,
229
229
  makeDefault: opts.makeDefault,
230
230
  output: opts.output
231
- }));
231
+ }), deps.stderrIsTTY ? { stream: "stdout" } : void 0);
232
232
  if (result.exitCode >= 2) throw new CliError(`mkvmerge failed (exit ${result.exitCode}):\n${result.stderr || result.stdout}`);
233
233
  return opts.output;
234
234
  }
@@ -300,7 +300,7 @@ async function testClipCommand(opts, deps) {
300
300
  durationSeconds,
301
301
  delayMs: opts.delayMs,
302
302
  output
303
- }));
303
+ }), deps.stderrIsTTY ? { stream: "stderr" } : void 0);
304
304
  if (result.exitCode !== 0) throw new CliError(`ffmpeg failed (exit ${result.exitCode}):\n${result.stderr || result.stdout}`);
305
305
  return output;
306
306
  }
@@ -534,8 +534,15 @@ function processStartError(program, error) {
534
534
  if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT" && isToolName(program)) return new CliError(`${program} is required but was not found on PATH. ${installHint(program)}`);
535
535
  return new CliError(`Failed to start ${program}: ${error instanceof Error ? error.message : String(error)}`);
536
536
  }
537
+ const writeToCliStderr = (chunk) => {
538
+ globalThis.process.stderr.write(chunk);
539
+ };
537
540
  var ProcessRunner = class {
538
- async run(argv) {
541
+ sink;
542
+ constructor(options = {}) {
543
+ this.sink = options.sink ?? writeToCliStderr;
544
+ }
545
+ async run(argv, options) {
539
546
  const [executable, ...args] = argv;
540
547
  if (executable === void 0) throw new CliError("Could not start process: missing executable");
541
548
  return await new Promise((resolve, reject) => {
@@ -550,9 +557,11 @@ var ProcessRunner = class {
550
557
  process.stderr.setEncoding("utf8");
551
558
  process.stdout.on("data", (chunk) => {
552
559
  stdout += chunk;
560
+ if (options?.stream === "stdout") this.sink(chunk);
553
561
  });
554
562
  process.stderr.on("data", (chunk) => {
555
563
  stderr += chunk;
564
+ if (options?.stream === "stderr") this.sink(chunk);
556
565
  });
557
566
  process.once("error", (error) => {
558
567
  reject(processStartError(executable, error));
@@ -572,6 +581,7 @@ var ProcessRunner = class {
572
581
  process.exitCode = await runCli(process.argv.slice(2), {
573
582
  runner: new ProcessRunner(),
574
583
  isTTY: process.stdin.isTTY ?? false,
584
+ stderrIsTTY: process.stderr.isTTY ?? false,
575
585
  confirm: createConfirmPrompt()
576
586
  });
577
587
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"tracksmith.js","names":[],"sources":["../src/types.ts","../src/output.ts","../src/paths.ts","../src/probe.ts","../src/commands/extract.ts","../src/commands/inspect.ts","../src/commands/mux.ts","../src/commands/test-clip.ts","../src/cli.ts","../src/prompt.ts","../src/tools.ts","../src/runner.ts","../src/tracksmith.ts"],"sourcesContent":["export interface RunResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface Runner {\n run(argv: readonly string[]): Promise<RunResult>;\n}\n\nexport interface Track {\n id: number;\n type: string;\n codec: string;\n language?: string;\n name?: string;\n channels?: number;\n isDefault: boolean;\n}\n\nexport class CliError extends Error {\n constructor(\n message: string,\n readonly exitCode: number = 1\n ) {\n super(message);\n this.name = 'CliError';\n }\n}\n\nexport interface CommandDeps {\n runner: Runner;\n isTTY: boolean;\n confirm: (message: string) => Promise<boolean>;\n exists?: (path: string) => boolean;\n}\n","import { existsSync, statSync } from 'node:fs';\nimport { basename, dirname, extname, join } from 'node:path';\n\nimport { CliError } from './types.ts';\n\nexport function stemPath(file: string): string {\n return join(dirname(file), basename(file, extname(file)));\n}\n\nexport function defaultExtractOutput(source: string, trackId: number): string {\n return `${stemPath(source)}.track${trackId}.mka`;\n}\n\nexport function defaultTestClipOutput(video: string): string {\n return `${stemPath(video)}.sync-test.mkv`;\n}\n\nexport function requireInputFile(\n path: string,\n exists: (path: string) => boolean = existsSync\n): void {\n if (!exists(path)) throw new CliError(`Input file not found: \"${path}\".`);\n}\n\nexport interface OverwriteContext {\n force: boolean;\n isTTY: boolean;\n confirm: (message: string) => Promise<boolean>;\n exists?: (path: string) => boolean;\n inputs?: readonly string[];\n}\n\nfunction sameFile(left: string, right: string): boolean {\n try {\n const leftStat = statSync(left, { bigint: true });\n const rightStat = statSync(right, { bigint: true });\n return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;\n } catch {\n return false;\n }\n}\n\nexport async function ensureWritable(path: string, ctx: OverwriteContext): Promise<void> {\n const aliasedInput = ctx.inputs?.find((input) => sameFile(path, input));\n if (aliasedInput !== undefined) {\n throw new CliError(\n `Output \"${path}\" is the same file as input \"${aliasedInput}\". Choose a different output path.`\n );\n }\n\n const exists = ctx.exists ?? existsSync;\n if (!exists(path)) return;\n if (ctx.force) return;\n if (!ctx.isTTY) {\n throw new CliError(\n `Output \"${path}\" already exists. Pass --force to overwrite (cannot prompt in a non-interactive session).`\n );\n }\n const approved = await ctx.confirm(`Output \"${path}\" already exists. Overwrite?`);\n if (!approved) throw new CliError('Aborted: existing output left untouched.');\n}\n","import { isAbsolute, resolve } from 'node:path';\n\nconst TOOL_OPERATOR_PREFIXES = '-+@=()[]';\n\nexport function toolPath(path: string): string {\n if (isAbsolute(path) || !TOOL_OPERATOR_PREFIXES.includes(path[0] ?? '')) return path;\n return resolve(path);\n}\n","import type { Runner, Track } from './types.ts';\n\nimport * as z from 'zod/mini';\n\nimport { toolPath } from './paths.ts';\nimport { CliError } from './types.ts';\n\nconst mkvmergePropertiesSchema = z.object({\n language: z.optional(z.string()),\n track_name: z.optional(z.string()),\n audio_channels: z.optional(z.number()),\n default_track: z.optional(z.boolean())\n});\n\nconst mkvmergeTrackSchema = z.object({\n id: z.int(),\n type: z.string(),\n codec: z.string(),\n properties: z.optional(mkvmergePropertiesSchema)\n});\n\nconst mkvmergeOutputSchema = z.object({\n tracks: z.array(mkvmergeTrackSchema)\n});\n\nexport function parseMkvmergeJson(json: string): Track[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new CliError('Could not parse mkvmerge -J output as JSON.');\n }\n const result = z.safeParse(mkvmergeOutputSchema, parsed);\n if (!result.success) throw new CliError('mkvmerge -J output has an unexpected structure.');\n\n return result.data.tracks.map((track) => ({\n id: track.id,\n type: track.type,\n codec: track.codec,\n language: track.properties?.language,\n name: track.properties?.track_name,\n channels: track.properties?.audio_channels,\n isDefault: track.properties?.default_track ?? false\n }));\n}\n\nexport async function probeTracks(runner: Runner, file: string): Promise<Track[]> {\n const result = await runner.run(['mkvmerge', '-J', toolPath(file)]);\n if (result.exitCode >= 2) {\n throw new CliError(\n `mkvmerge could not read \"${file}\" (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return parseMkvmergeJson(result.stdout);\n}\n\nfunction validAudioIds(tracks: Track[]): string {\n const ids = tracks.filter((track) => track.type === 'audio').map((track) => track.id);\n return `Valid audio track IDs: ${ids.length > 0 ? ids.join(', ') : 'none'}`;\n}\n\nexport function requireAudioTrack(tracks: Track[], id: number): Track {\n const track = tracks.find((candidate) => candidate.id === id);\n if (!track) throw new CliError(`Track ${id} does not exist. ${validAudioIds(tracks)}`);\n if (track.type !== 'audio')\n throw new CliError(`Track ${id} is ${track.type}, not audio. ${validAudioIds(tracks)}`);\n return track;\n}\n\nexport function audioRelativeIndex(tracks: Track[], id: number): number {\n const index = tracks\n .filter((track) => track.type === 'audio')\n .findIndex((track) => track.id === id);\n if (index === -1)\n throw new CliError(`Track ${id} is not an audio track. ${validAudioIds(tracks)}`);\n return index;\n}\n","import type { CommandDeps } from '../types.ts';\n\nimport { defaultExtractOutput, ensureWritable, requireInputFile } from '../output.ts';\nimport { toolPath } from '../paths.ts';\nimport { probeTracks, requireAudioTrack } from '../probe.ts';\nimport { CliError } from '../types.ts';\n\nexport function buildExtractArgs(opts: { file: string; track: number; output: string }): string[] {\n return [\n 'mkvmerge',\n '-o',\n toolPath(opts.output),\n '--audio-tracks',\n String(opts.track),\n '--no-video',\n '--no-subtitles',\n '--no-buttons',\n '--no-chapters',\n '--no-attachments',\n '--no-global-tags',\n toolPath(opts.file)\n ];\n}\n\nexport async function extractCommand(\n opts: { file: string; track: number; output?: string; force: boolean },\n deps: CommandDeps\n): Promise<string> {\n requireInputFile(opts.file, deps.exists);\n const tracks = await probeTracks(deps.runner, opts.file);\n requireAudioTrack(tracks, opts.track);\n const output = opts.output ?? defaultExtractOutput(opts.file, opts.track);\n await ensureWritable(output, {\n force: opts.force,\n isTTY: deps.isTTY,\n confirm: deps.confirm,\n exists: deps.exists,\n inputs: [opts.file]\n });\n const result = await deps.runner.run(\n buildExtractArgs({ file: opts.file, track: opts.track, output })\n );\n if (result.exitCode >= 2) {\n throw new CliError(\n `mkvmerge failed (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return output;\n}\n","import type { CommandDeps, Track } from '../types.ts';\n\nimport { requireInputFile } from '../output.ts';\nimport { probeTracks } from '../probe.ts';\n\nexport function formatTrackTable(tracks: Track[]): string {\n const header = ['ID', 'TYPE', 'CODEC', 'LANG', 'CH', 'DEFAULT', 'NAME'];\n const rows = tracks.map((track) => [\n String(track.id),\n track.type,\n track.codec,\n track.language ?? '-',\n track.channels !== undefined ? String(track.channels) : '-',\n track.isDefault ? 'yes' : '-',\n track.name ?? '-'\n ]);\n const all = [header, ...rows];\n const widths = header.map((_, column) => Math.max(...all.map((row) => row[column]!.length)));\n return all\n .map((row) =>\n row\n .map((cell, column) => cell.padEnd(widths[column]!))\n .join(' ')\n .trimEnd()\n )\n .join('\\n');\n}\n\nexport async function inspectCommand(file: string, deps: CommandDeps): Promise<string> {\n requireInputFile(file, deps.exists);\n return formatTrackTable(await probeTracks(deps.runner, file));\n}\n","import type { CommandDeps, Track } from '../types.ts';\n\nimport { ensureWritable, requireInputFile } from '../output.ts';\nimport { toolPath } from '../paths.ts';\nimport { probeTracks, requireAudioTrack } from '../probe.ts';\nimport { CliError } from '../types.ts';\n\nexport function resolveAudioTrackId(tracks: Track[], requested: number | undefined): number {\n if (requested !== undefined) return requireAudioTrack(tracks, requested).id;\n const audio = tracks.filter((track) => track.type === 'audio');\n if (audio.length === 1) return audio[0]!.id;\n if (audio.length === 0) throw new CliError('The audio input contains no audio tracks.');\n throw new CliError(\n `The audio input contains ${audio.length} audio tracks; pass --track <id> (see: tracksmith inspect <file>).`\n );\n}\n\nexport function buildMuxArgs(a: {\n video: string;\n audio: string;\n audioTrackId: number;\n delayMs: number;\n language?: string;\n trackName?: string;\n makeDefault: boolean;\n output: string;\n}): string[] {\n const id = String(a.audioTrackId);\n const args = [\n 'mkvmerge',\n '-o',\n toolPath(a.output),\n toolPath(a.video),\n '--audio-tracks',\n id,\n '--no-video',\n '--no-subtitles',\n '--no-buttons',\n '--no-chapters',\n '--no-attachments',\n '--no-global-tags'\n ];\n if (a.delayMs !== 0) args.push('--sync', `${id}:${a.delayMs}`);\n if (a.language !== undefined) args.push('--language', `${id}:${a.language}`);\n if (a.trackName !== undefined) args.push('--track-name', `${id}:${a.trackName}`);\n args.push('--default-track-flag', `${id}:${a.makeDefault ? 'yes' : 'no'}`);\n args.push(toolPath(a.audio));\n return args;\n}\n\nexport async function muxCommand(\n opts: {\n video: string;\n audio: string;\n track?: number;\n delayMs: number;\n language?: string;\n name?: string;\n makeDefault: boolean;\n output: string;\n force: boolean;\n },\n deps: CommandDeps\n): Promise<string> {\n requireInputFile(opts.video, deps.exists);\n requireInputFile(opts.audio, deps.exists);\n const audioTracks = await probeTracks(deps.runner, opts.audio);\n const audioTrackId = resolveAudioTrackId(audioTracks, opts.track);\n await ensureWritable(opts.output, {\n force: opts.force,\n isTTY: deps.isTTY,\n confirm: deps.confirm,\n exists: deps.exists,\n inputs: [opts.video, opts.audio]\n });\n const result = await deps.runner.run(\n buildMuxArgs({\n video: opts.video,\n audio: opts.audio,\n audioTrackId,\n delayMs: opts.delayMs,\n language: opts.language,\n trackName: opts.name,\n makeDefault: opts.makeDefault,\n output: opts.output\n })\n );\n if (result.exitCode >= 2) {\n throw new CliError(\n `mkvmerge failed (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return opts.output;\n}\n","import type { CommandDeps } from '../types.ts';\n\nimport { defaultTestClipOutput, ensureWritable, requireInputFile } from '../output.ts';\nimport { toolPath } from '../paths.ts';\nimport { audioRelativeIndex, probeTracks, requireAudioTrack } from '../probe.ts';\nimport { CliError } from '../types.ts';\n\nexport function parseTimeToSeconds(value: string): number {\n if (/^\\d+(\\.\\d+)?$/.test(value)) return Number(value);\n const match = /^(\\d+):([0-5]?\\d):([0-5]?\\d(?:\\.\\d+)?)$/.exec(value);\n if (!match)\n throw new CliError(`Invalid time \"${value}\". Use seconds (90 or 90.5) or HH:MM:SS[.ms].`);\n return Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]);\n}\n\nexport function buildTestClipArgs(a: {\n video: string;\n audio: string;\n audioStreamIndex: number;\n startSeconds: number;\n durationSeconds: number;\n delayMs: number;\n output: string;\n}): string[] {\n // -y is safe: our own overwrite policy (ensureWritable) has already run.\n return [\n 'ffmpeg',\n '-hide_banner',\n '-nostdin',\n '-ss',\n String(a.startSeconds),\n '-i',\n toolPath(a.video),\n '-itsoffset',\n String(a.delayMs / 1000),\n '-ss',\n String(a.startSeconds),\n '-i',\n toolPath(a.audio),\n '-map',\n '0:v:0',\n '-map',\n `1:a:${a.audioStreamIndex}`,\n '-t',\n String(a.durationSeconds),\n '-c:v',\n 'libx264',\n '-preset',\n 'veryfast',\n '-crf',\n '18',\n '-c:a',\n 'copy',\n '-sn',\n '-abort_on',\n 'empty_output_stream',\n '-y',\n toolPath(a.output)\n ];\n}\n\nexport async function testClipCommand(\n opts: {\n video: string;\n audio: string;\n track: number;\n start: string;\n duration: string;\n delayMs: number;\n output?: string;\n force: boolean;\n },\n deps: CommandDeps\n): Promise<string> {\n requireInputFile(opts.video, deps.exists);\n requireInputFile(opts.audio, deps.exists);\n const startSeconds = parseTimeToSeconds(opts.start);\n const durationSeconds = parseTimeToSeconds(opts.duration);\n if (durationSeconds <= 0) throw new CliError('--duration must be greater than zero.');\n const donorTracks = await probeTracks(deps.runner, opts.audio);\n requireAudioTrack(donorTracks, opts.track);\n const output = opts.output ?? defaultTestClipOutput(opts.video);\n await ensureWritable(output, {\n force: opts.force,\n isTTY: deps.isTTY,\n confirm: deps.confirm,\n exists: deps.exists,\n inputs: [opts.video, opts.audio]\n });\n const result = await deps.runner.run(\n buildTestClipArgs({\n video: opts.video,\n audio: opts.audio,\n audioStreamIndex: audioRelativeIndex(donorTracks, opts.track),\n startSeconds,\n durationSeconds,\n delayMs: opts.delayMs,\n output\n })\n );\n if (result.exitCode !== 0) {\n throw new CliError(\n `ffmpeg failed (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return output;\n}\n","import { parseArgs } from 'node:util';\n\nimport type { CommandDeps } from './types.ts';\n\nimport { extractCommand } from './commands/extract.ts';\nimport { inspectCommand } from './commands/inspect.ts';\nimport { muxCommand } from './commands/mux.ts';\nimport { testClipCommand } from './commands/test-clip.ts';\nimport { CliError } from './types.ts';\n\nconst USAGE = `tracksmith — inspect, extract, sync-test, and mux Matroska audio tracks\n\nUsage:\n tracksmith inspect <file>\n tracksmith extract <file> --track <id> [--output <file>] [--force]\n tracksmith test-clip --video <target> --audio <donor> --track <id> --start <time>\n [--duration 60] [--delay-ms 0] [--output <file>] [--force]\n tracksmith mux --video <target> --audio <mka-or-mkv> --output <file>\n [--track <id>] [--delay-ms 0] [--language eng] [--name <title>]\n [--default] [--force]\n\nNotes:\n --track is always the MKVToolNix track ID shown by \"tracksmith inspect\".\n Positive --delay-ms delays audio; negative advances it (e.g. --delay-ms -250).\n Times accept seconds (90, 90.5) or HH:MM:SS[.ms].\n Check sync near the beginning, middle, and end; a changing offset cannot be fixed by one delay.\n Requires ffmpeg and MKVToolNix (mkvmerge) on PATH.`;\n\nfunction parseIntStrict(value: string | undefined, flag: string): number {\n if (value === undefined) throw new CliError(`${flag} is required.`);\n if (!/^-?\\d+$/.test(value)) throw new CliError(`${flag} must be an integer, got \"${value}\".`);\n return Number(value);\n}\n\nfunction parseTrack(value: string | undefined): number {\n const track = parseIntStrict(value, '--track');\n if (track < 0) throw new CliError(`--track must be >= 0, got ${track}.`);\n return track;\n}\n\nfunction requireFilePositional(positionals: string[], command: 'inspect' | 'extract'): string {\n if (positionals.length === 0) throw new CliError(`${command} requires a file argument.`);\n if (positionals.length > 1) throw new CliError(`${command} accepts exactly one file argument.`);\n return positionals[0]!;\n}\n\n// Wraps a parseArgs call so parse failures become CliErrors. Takes a thunk\n// (rather than the options object) so parseArgs keeps its precise generic\n// inference over each command's inline options literal.\nfunction parseOrCliError<T>(parseFn: () => T): T {\n try {\n return parseFn();\n } catch (error) {\n throw new CliError(error instanceof Error ? error.message : String(error));\n }\n}\n\n// parseArgs treats a leading-dash value token (\"-250\") as an option, so fold\n// `--delay-ms -250` into the equals form it does accept.\nfunction normalizeDelayMs(args: string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i]!;\n const next = args[i + 1];\n if (arg === '--delay-ms' && next !== undefined && /^-\\d+$/.test(next)) {\n out.push(`--delay-ms=${next}`);\n i += 1;\n } else {\n out.push(arg);\n }\n }\n return out;\n}\n\nexport async function runCli(\n argv: string[],\n deps: CommandDeps,\n stdout: (line: string) => void = console.log\n): Promise<number> {\n const [command, ...rawRest] = argv;\n const rest = normalizeDelayMs(rawRest);\n try {\n switch (command) {\n case undefined: {\n stdout(USAGE);\n return 1;\n }\n case '--help':\n case '-h': {\n stdout(USAGE);\n return 0;\n }\n case 'inspect': {\n const { positionals } = parseOrCliError(() =>\n parseArgs({ args: rest, options: {}, allowPositionals: true, strict: true })\n );\n const file = requireFilePositional(positionals, 'inspect');\n stdout(await inspectCommand(file, deps));\n return 0;\n }\n case 'extract': {\n const { values, positionals } = parseOrCliError(() =>\n parseArgs({\n args: rest,\n options: {\n track: { type: 'string' },\n output: { type: 'string' },\n force: { type: 'boolean', default: false }\n },\n allowPositionals: true,\n strict: true\n })\n );\n const file = requireFilePositional(positionals, 'extract');\n const output = await extractCommand(\n {\n file,\n track: parseTrack(values.track),\n output: values.output,\n force: values.force ?? false\n },\n deps\n );\n stdout(`Wrote ${output}`);\n return 0;\n }\n case 'test-clip': {\n const { values } = parseOrCliError(() =>\n parseArgs({\n args: rest,\n options: {\n video: { type: 'string' },\n audio: { type: 'string' },\n track: { type: 'string' },\n start: { type: 'string' },\n duration: { type: 'string', default: '60' },\n 'delay-ms': { type: 'string', default: '0' },\n output: { type: 'string' },\n force: { type: 'boolean', default: false }\n },\n strict: true\n })\n );\n if (!values.video || !values.audio)\n throw new CliError('test-clip requires --video and --audio.');\n if (!values.start) throw new CliError('test-clip requires --start.');\n const output = await testClipCommand(\n {\n video: values.video,\n audio: values.audio,\n track: parseTrack(values.track),\n start: values.start,\n duration: values.duration ?? '60',\n delayMs: parseIntStrict(values['delay-ms'] ?? '0', '--delay-ms'),\n output: values.output,\n force: values.force ?? false\n },\n deps\n );\n stdout(`Wrote ${output}`);\n return 0;\n }\n case 'mux': {\n const { values } = parseOrCliError(() =>\n parseArgs({\n args: rest,\n options: {\n video: { type: 'string' },\n audio: { type: 'string' },\n track: { type: 'string' },\n 'delay-ms': { type: 'string', default: '0' },\n language: { type: 'string', default: 'eng' },\n name: { type: 'string' },\n default: { type: 'boolean', default: false },\n output: { type: 'string' },\n force: { type: 'boolean', default: false }\n },\n strict: true\n })\n );\n if (!values.video || !values.audio) throw new CliError('mux requires --video and --audio.');\n if (!values.output)\n throw new CliError('mux requires --output (no default output name for the final file).');\n const output = await muxCommand(\n {\n video: values.video,\n audio: values.audio,\n track: values.track === undefined ? undefined : parseTrack(values.track),\n delayMs: parseIntStrict(values['delay-ms'] ?? '0', '--delay-ms'),\n language: values.language,\n name: values.name,\n makeDefault: values.default ?? false,\n output: values.output,\n force: values.force ?? false\n },\n deps\n );\n stdout(`Wrote ${output}`);\n return 0;\n }\n default:\n throw new CliError(`Unknown command \"${command}\".\\n\\n${USAGE}`);\n }\n } catch (error) {\n if (error instanceof CliError) {\n console.error(`tracksmith: ${error.message}`);\n return error.exitCode;\n }\n throw error;\n }\n}\n","import { once } from 'node:events';\nimport { createInterface } from 'node:readline/promises';\n\nimport type { Readable, Writable } from 'node:stream';\n\nexport interface ConfirmPromptOptions {\n input?: Readable;\n output?: Writable;\n}\n\nexport function createConfirmPrompt(\n options: ConfirmPromptOptions = {}\n): (message: string) => Promise<boolean> {\n const input = options.input ?? process.stdin;\n const output = options.output ?? process.stdout;\n\n return async (message: string): Promise<boolean> => {\n const readline = createInterface({ input, output });\n\n try {\n const closed = once(readline, 'close').then(() => ({ kind: 'eof' }) as const);\n const answered = readline.question(`${message} [y/N] `).then(\n (value) => ({ kind: 'answer', value }) as const,\n () => ({ kind: 'eof' }) as const\n );\n const result = await Promise.race([answered, closed]);\n\n return result.kind === 'answer' && /^y(?:es)?$/i.test(result.value.trim());\n } finally {\n readline.close();\n }\n };\n}\n","export const TOOLS = ['ffmpeg', 'mkvmerge'] as const;\n\nexport type ToolName = (typeof TOOLS)[number];\n\nexport function isToolName(value: string): value is ToolName {\n return TOOLS.some((tool) => tool === value);\n}\n\nconst INSTALL_HINTS = {\n ffmpeg:\n 'Windows: winget install Gyan.FFmpeg or scoop install ffmpeg | macOS: brew install ffmpeg | Linux: sudo apt install ffmpeg or sudo pacman -S ffmpeg',\n mkvmerge:\n 'Windows: winget install MoritzBunkus.MKVToolNix or scoop install mkvtoolnix | macOS: brew install mkvtoolnix | Linux: sudo apt install mkvtoolnix or sudo pacman -S mkvtoolnix-cli'\n} satisfies Record<ToolName, string>;\n\nexport function installHint(tool: ToolName): string {\n return INSTALL_HINTS[tool];\n}\n","import { spawn } from 'node:child_process';\n\nimport type { Runner, RunResult } from './types.ts';\n\nimport { installHint, isToolName } from './tools.ts';\nimport { CliError } from './types.ts';\n\nexport function processStartError(program: string, error: unknown): CliError {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT' &&\n isToolName(program)\n ) {\n return new CliError(\n `${program} is required but was not found on PATH. ${installHint(program)}`\n );\n }\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError(`Failed to start ${program}: ${message}`);\n}\n\nexport class ProcessRunner implements Runner {\n async run(argv: readonly string[]): Promise<RunResult> {\n const [executable, ...args] = argv;\n if (executable === undefined) {\n throw new CliError('Could not start process: missing executable');\n }\n\n return await new Promise((resolve, reject) => {\n const process = spawn(executable, args, {\n stdio: ['ignore', 'pipe', 'pipe']\n });\n let stdout = '';\n let stderr = '';\n\n process.stdout.setEncoding('utf8');\n process.stderr.setEncoding('utf8');\n process.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n });\n process.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n });\n process.once('error', (error) => {\n reject(processStartError(executable, error));\n });\n process.once('close', (exitCode) => {\n resolve({ exitCode: exitCode ?? 1, stdout, stderr });\n });\n });\n }\n}\n","#!/usr/bin/env node\n\nimport { runCli } from './cli.ts';\nimport { createConfirmPrompt } from './prompt.ts';\nimport { ProcessRunner } from './runner.ts';\n\nprocess.exitCode = await runCli(process.argv.slice(2), {\n runner: new ProcessRunner(),\n isTTY: process.stdin.isTTY ?? false,\n confirm: createConfirmPrompt()\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"tracksmith.js","names":[],"sources":["../src/types.ts","../src/output.ts","../src/paths.ts","../src/probe.ts","../src/commands/extract.ts","../src/commands/inspect.ts","../src/commands/mux.ts","../src/commands/test-clip.ts","../src/cli.ts","../src/prompt.ts","../src/tools.ts","../src/runner.ts","../src/tracksmith.ts"],"sourcesContent":["export interface RunResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface RunOptions {\n stream?: 'stdout' | 'stderr';\n}\n\nexport interface Runner {\n run(argv: readonly string[], options?: RunOptions): Promise<RunResult>;\n}\n\nexport interface Track {\n id: number;\n type: string;\n codec: string;\n language?: string;\n name?: string;\n channels?: number;\n isDefault: boolean;\n}\n\nexport class CliError extends Error {\n constructor(\n message: string,\n readonly exitCode: number = 1\n ) {\n super(message);\n this.name = 'CliError';\n }\n}\n\nexport interface CommandDeps {\n runner: Runner;\n /** stdin is interactive — gates confirmation prompts. */\n isTTY: boolean;\n /** stderr is interactive — gates live tool progress output. */\n stderrIsTTY: boolean;\n confirm: (message: string) => Promise<boolean>;\n exists?: (path: string) => boolean;\n}\n","import { existsSync, statSync } from 'node:fs';\nimport { basename, dirname, extname, join } from 'node:path';\n\nimport { CliError } from './types.ts';\n\nexport function stemPath(file: string): string {\n return join(dirname(file), basename(file, extname(file)));\n}\n\nexport function defaultExtractOutput(source: string, trackId: number): string {\n return `${stemPath(source)}.track${trackId}.mka`;\n}\n\nexport function defaultTestClipOutput(video: string): string {\n return `${stemPath(video)}.sync-test.mkv`;\n}\n\nexport function requireInputFile(\n path: string,\n exists: (path: string) => boolean = existsSync\n): void {\n if (!exists(path)) throw new CliError(`Input file not found: \"${path}\".`);\n}\n\nexport interface OverwriteContext {\n force: boolean;\n isTTY: boolean;\n confirm: (message: string) => Promise<boolean>;\n exists?: (path: string) => boolean;\n inputs?: readonly string[];\n}\n\nfunction sameFile(left: string, right: string): boolean {\n try {\n const leftStat = statSync(left, { bigint: true });\n const rightStat = statSync(right, { bigint: true });\n return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;\n } catch {\n return false;\n }\n}\n\nexport async function ensureWritable(path: string, ctx: OverwriteContext): Promise<void> {\n const aliasedInput = ctx.inputs?.find((input) => sameFile(path, input));\n if (aliasedInput !== undefined) {\n throw new CliError(\n `Output \"${path}\" is the same file as input \"${aliasedInput}\". Choose a different output path.`\n );\n }\n\n const exists = ctx.exists ?? existsSync;\n if (!exists(path)) return;\n if (ctx.force) return;\n if (!ctx.isTTY) {\n throw new CliError(\n `Output \"${path}\" already exists. Pass --force to overwrite (cannot prompt in a non-interactive session).`\n );\n }\n const approved = await ctx.confirm(`Output \"${path}\" already exists. Overwrite?`);\n if (!approved) throw new CliError('Aborted: existing output left untouched.');\n}\n","import { isAbsolute, resolve } from 'node:path';\n\nconst TOOL_OPERATOR_PREFIXES = '-+@=()[]';\n\nexport function toolPath(path: string): string {\n if (isAbsolute(path) || !TOOL_OPERATOR_PREFIXES.includes(path[0] ?? '')) return path;\n return resolve(path);\n}\n","import type { Runner, Track } from './types.ts';\n\nimport * as z from 'zod/mini';\n\nimport { toolPath } from './paths.ts';\nimport { CliError } from './types.ts';\n\nconst mkvmergePropertiesSchema = z.object({\n language: z.optional(z.string()),\n track_name: z.optional(z.string()),\n audio_channels: z.optional(z.number()),\n default_track: z.optional(z.boolean())\n});\n\nconst mkvmergeTrackSchema = z.object({\n id: z.int(),\n type: z.string(),\n codec: z.string(),\n properties: z.optional(mkvmergePropertiesSchema)\n});\n\nconst mkvmergeOutputSchema = z.object({\n tracks: z.array(mkvmergeTrackSchema)\n});\n\nexport function parseMkvmergeJson(json: string): Track[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new CliError('Could not parse mkvmerge -J output as JSON.');\n }\n const result = z.safeParse(mkvmergeOutputSchema, parsed);\n if (!result.success) throw new CliError('mkvmerge -J output has an unexpected structure.');\n\n return result.data.tracks.map((track) => ({\n id: track.id,\n type: track.type,\n codec: track.codec,\n language: track.properties?.language,\n name: track.properties?.track_name,\n channels: track.properties?.audio_channels,\n isDefault: track.properties?.default_track ?? false\n }));\n}\n\nexport async function probeTracks(runner: Runner, file: string): Promise<Track[]> {\n const result = await runner.run(['mkvmerge', '-J', toolPath(file)]);\n if (result.exitCode >= 2) {\n throw new CliError(\n `mkvmerge could not read \"${file}\" (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return parseMkvmergeJson(result.stdout);\n}\n\nfunction validAudioIds(tracks: Track[]): string {\n const ids = tracks.filter((track) => track.type === 'audio').map((track) => track.id);\n return `Valid audio track IDs: ${ids.length > 0 ? ids.join(', ') : 'none'}`;\n}\n\nexport function requireAudioTrack(tracks: Track[], id: number): Track {\n const track = tracks.find((candidate) => candidate.id === id);\n if (!track) throw new CliError(`Track ${id} does not exist. ${validAudioIds(tracks)}`);\n if (track.type !== 'audio')\n throw new CliError(`Track ${id} is ${track.type}, not audio. ${validAudioIds(tracks)}`);\n return track;\n}\n\nexport function audioRelativeIndex(tracks: Track[], id: number): number {\n const index = tracks\n .filter((track) => track.type === 'audio')\n .findIndex((track) => track.id === id);\n if (index === -1)\n throw new CliError(`Track ${id} is not an audio track. ${validAudioIds(tracks)}`);\n return index;\n}\n","import type { CommandDeps } from '../types.ts';\n\nimport { defaultExtractOutput, ensureWritable, requireInputFile } from '../output.ts';\nimport { toolPath } from '../paths.ts';\nimport { probeTracks, requireAudioTrack } from '../probe.ts';\nimport { CliError } from '../types.ts';\n\nexport function buildExtractArgs(opts: { file: string; track: number; output: string }): string[] {\n return [\n 'mkvmerge',\n '-o',\n toolPath(opts.output),\n '--audio-tracks',\n String(opts.track),\n '--no-video',\n '--no-subtitles',\n '--no-buttons',\n '--no-chapters',\n '--no-attachments',\n '--no-global-tags',\n toolPath(opts.file)\n ];\n}\n\nexport async function extractCommand(\n opts: { file: string; track: number; output?: string; force: boolean },\n deps: CommandDeps\n): Promise<string> {\n requireInputFile(opts.file, deps.exists);\n const tracks = await probeTracks(deps.runner, opts.file);\n requireAudioTrack(tracks, opts.track);\n const output = opts.output ?? defaultExtractOutput(opts.file, opts.track);\n await ensureWritable(output, {\n force: opts.force,\n isTTY: deps.isTTY,\n confirm: deps.confirm,\n exists: deps.exists,\n inputs: [opts.file]\n });\n const result = await deps.runner.run(\n buildExtractArgs({ file: opts.file, track: opts.track, output }),\n deps.stderrIsTTY ? { stream: 'stdout' } : undefined\n );\n if (result.exitCode >= 2) {\n throw new CliError(\n `mkvmerge failed (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return output;\n}\n","import type { CommandDeps, Track } from '../types.ts';\n\nimport { requireInputFile } from '../output.ts';\nimport { probeTracks } from '../probe.ts';\n\nexport function formatTrackTable(tracks: Track[]): string {\n const header = ['ID', 'TYPE', 'CODEC', 'LANG', 'CH', 'DEFAULT', 'NAME'];\n const rows = tracks.map((track) => [\n String(track.id),\n track.type,\n track.codec,\n track.language ?? '-',\n track.channels !== undefined ? String(track.channels) : '-',\n track.isDefault ? 'yes' : '-',\n track.name ?? '-'\n ]);\n const all = [header, ...rows];\n const widths = header.map((_, column) => Math.max(...all.map((row) => row[column]!.length)));\n return all\n .map((row) =>\n row\n .map((cell, column) => cell.padEnd(widths[column]!))\n .join(' ')\n .trimEnd()\n )\n .join('\\n');\n}\n\nexport async function inspectCommand(file: string, deps: CommandDeps): Promise<string> {\n requireInputFile(file, deps.exists);\n return formatTrackTable(await probeTracks(deps.runner, file));\n}\n","import type { CommandDeps, Track } from '../types.ts';\n\nimport { ensureWritable, requireInputFile } from '../output.ts';\nimport { toolPath } from '../paths.ts';\nimport { probeTracks, requireAudioTrack } from '../probe.ts';\nimport { CliError } from '../types.ts';\n\nexport function resolveAudioTrackId(tracks: Track[], requested: number | undefined): number {\n if (requested !== undefined) return requireAudioTrack(tracks, requested).id;\n const audio = tracks.filter((track) => track.type === 'audio');\n if (audio.length === 1) return audio[0]!.id;\n if (audio.length === 0) throw new CliError('The audio input contains no audio tracks.');\n throw new CliError(\n `The audio input contains ${audio.length} audio tracks; pass --track <id> (see: tracksmith inspect <file>).`\n );\n}\n\nexport function buildMuxArgs(a: {\n video: string;\n audio: string;\n audioTrackId: number;\n delayMs: number;\n language?: string;\n trackName?: string;\n makeDefault: boolean;\n output: string;\n}): string[] {\n const id = String(a.audioTrackId);\n const args = [\n 'mkvmerge',\n '-o',\n toolPath(a.output),\n toolPath(a.video),\n '--audio-tracks',\n id,\n '--no-video',\n '--no-subtitles',\n '--no-buttons',\n '--no-chapters',\n '--no-attachments',\n '--no-global-tags'\n ];\n if (a.delayMs !== 0) args.push('--sync', `${id}:${a.delayMs}`);\n if (a.language !== undefined) args.push('--language', `${id}:${a.language}`);\n if (a.trackName !== undefined) args.push('--track-name', `${id}:${a.trackName}`);\n args.push('--default-track-flag', `${id}:${a.makeDefault ? 'yes' : 'no'}`);\n args.push(toolPath(a.audio));\n return args;\n}\n\nexport async function muxCommand(\n opts: {\n video: string;\n audio: string;\n track?: number;\n delayMs: number;\n language?: string;\n name?: string;\n makeDefault: boolean;\n output: string;\n force: boolean;\n },\n deps: CommandDeps\n): Promise<string> {\n requireInputFile(opts.video, deps.exists);\n requireInputFile(opts.audio, deps.exists);\n const audioTracks = await probeTracks(deps.runner, opts.audio);\n const audioTrackId = resolveAudioTrackId(audioTracks, opts.track);\n await ensureWritable(opts.output, {\n force: opts.force,\n isTTY: deps.isTTY,\n confirm: deps.confirm,\n exists: deps.exists,\n inputs: [opts.video, opts.audio]\n });\n const result = await deps.runner.run(\n buildMuxArgs({\n video: opts.video,\n audio: opts.audio,\n audioTrackId,\n delayMs: opts.delayMs,\n language: opts.language,\n trackName: opts.name,\n makeDefault: opts.makeDefault,\n output: opts.output\n }),\n deps.stderrIsTTY ? { stream: 'stdout' } : undefined\n );\n if (result.exitCode >= 2) {\n throw new CliError(\n `mkvmerge failed (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return opts.output;\n}\n","import type { CommandDeps } from '../types.ts';\n\nimport { defaultTestClipOutput, ensureWritable, requireInputFile } from '../output.ts';\nimport { toolPath } from '../paths.ts';\nimport { audioRelativeIndex, probeTracks, requireAudioTrack } from '../probe.ts';\nimport { CliError } from '../types.ts';\n\nexport function parseTimeToSeconds(value: string): number {\n if (/^\\d+(\\.\\d+)?$/.test(value)) return Number(value);\n const match = /^(\\d+):([0-5]?\\d):([0-5]?\\d(?:\\.\\d+)?)$/.exec(value);\n if (!match)\n throw new CliError(`Invalid time \"${value}\". Use seconds (90 or 90.5) or HH:MM:SS[.ms].`);\n return Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]);\n}\n\nexport function buildTestClipArgs(a: {\n video: string;\n audio: string;\n audioStreamIndex: number;\n startSeconds: number;\n durationSeconds: number;\n delayMs: number;\n output: string;\n}): string[] {\n // -y is safe: our own overwrite policy (ensureWritable) has already run.\n return [\n 'ffmpeg',\n '-hide_banner',\n '-nostdin',\n '-ss',\n String(a.startSeconds),\n '-i',\n toolPath(a.video),\n '-itsoffset',\n String(a.delayMs / 1000),\n '-ss',\n String(a.startSeconds),\n '-i',\n toolPath(a.audio),\n '-map',\n '0:v:0',\n '-map',\n `1:a:${a.audioStreamIndex}`,\n '-t',\n String(a.durationSeconds),\n '-c:v',\n 'libx264',\n '-preset',\n 'veryfast',\n '-crf',\n '18',\n '-c:a',\n 'copy',\n '-sn',\n '-abort_on',\n 'empty_output_stream',\n '-y',\n toolPath(a.output)\n ];\n}\n\nexport async function testClipCommand(\n opts: {\n video: string;\n audio: string;\n track: number;\n start: string;\n duration: string;\n delayMs: number;\n output?: string;\n force: boolean;\n },\n deps: CommandDeps\n): Promise<string> {\n requireInputFile(opts.video, deps.exists);\n requireInputFile(opts.audio, deps.exists);\n const startSeconds = parseTimeToSeconds(opts.start);\n const durationSeconds = parseTimeToSeconds(opts.duration);\n if (durationSeconds <= 0) throw new CliError('--duration must be greater than zero.');\n const donorTracks = await probeTracks(deps.runner, opts.audio);\n requireAudioTrack(donorTracks, opts.track);\n const output = opts.output ?? defaultTestClipOutput(opts.video);\n await ensureWritable(output, {\n force: opts.force,\n isTTY: deps.isTTY,\n confirm: deps.confirm,\n exists: deps.exists,\n inputs: [opts.video, opts.audio]\n });\n const result = await deps.runner.run(\n buildTestClipArgs({\n video: opts.video,\n audio: opts.audio,\n audioStreamIndex: audioRelativeIndex(donorTracks, opts.track),\n startSeconds,\n durationSeconds,\n delayMs: opts.delayMs,\n output\n }),\n deps.stderrIsTTY ? { stream: 'stderr' } : undefined\n );\n if (result.exitCode !== 0) {\n throw new CliError(\n `ffmpeg failed (exit ${result.exitCode}):\\n${result.stderr || result.stdout}`\n );\n }\n return output;\n}\n","import { parseArgs } from 'node:util';\n\nimport type { CommandDeps } from './types.ts';\n\nimport { extractCommand } from './commands/extract.ts';\nimport { inspectCommand } from './commands/inspect.ts';\nimport { muxCommand } from './commands/mux.ts';\nimport { testClipCommand } from './commands/test-clip.ts';\nimport { CliError } from './types.ts';\n\nconst USAGE = `tracksmith — inspect, extract, sync-test, and mux Matroska audio tracks\n\nUsage:\n tracksmith inspect <file>\n tracksmith extract <file> --track <id> [--output <file>] [--force]\n tracksmith test-clip --video <target> --audio <donor> --track <id> --start <time>\n [--duration 60] [--delay-ms 0] [--output <file>] [--force]\n tracksmith mux --video <target> --audio <mka-or-mkv> --output <file>\n [--track <id>] [--delay-ms 0] [--language eng] [--name <title>]\n [--default] [--force]\n\nNotes:\n --track is always the MKVToolNix track ID shown by \"tracksmith inspect\".\n Positive --delay-ms delays audio; negative advances it (e.g. --delay-ms -250).\n Times accept seconds (90, 90.5) or HH:MM:SS[.ms].\n Check sync near the beginning, middle, and end; a changing offset cannot be fixed by one delay.\n Requires ffmpeg and MKVToolNix (mkvmerge) on PATH.`;\n\nfunction parseIntStrict(value: string | undefined, flag: string): number {\n if (value === undefined) throw new CliError(`${flag} is required.`);\n if (!/^-?\\d+$/.test(value)) throw new CliError(`${flag} must be an integer, got \"${value}\".`);\n return Number(value);\n}\n\nfunction parseTrack(value: string | undefined): number {\n const track = parseIntStrict(value, '--track');\n if (track < 0) throw new CliError(`--track must be >= 0, got ${track}.`);\n return track;\n}\n\nfunction requireFilePositional(positionals: string[], command: 'inspect' | 'extract'): string {\n if (positionals.length === 0) throw new CliError(`${command} requires a file argument.`);\n if (positionals.length > 1) throw new CliError(`${command} accepts exactly one file argument.`);\n return positionals[0]!;\n}\n\n// Wraps a parseArgs call so parse failures become CliErrors. Takes a thunk\n// (rather than the options object) so parseArgs keeps its precise generic\n// inference over each command's inline options literal.\nfunction parseOrCliError<T>(parseFn: () => T): T {\n try {\n return parseFn();\n } catch (error) {\n throw new CliError(error instanceof Error ? error.message : String(error));\n }\n}\n\n// parseArgs treats a leading-dash value token (\"-250\") as an option, so fold\n// `--delay-ms -250` into the equals form it does accept.\nfunction normalizeDelayMs(args: string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i]!;\n const next = args[i + 1];\n if (arg === '--delay-ms' && next !== undefined && /^-\\d+$/.test(next)) {\n out.push(`--delay-ms=${next}`);\n i += 1;\n } else {\n out.push(arg);\n }\n }\n return out;\n}\n\nexport async function runCli(\n argv: string[],\n deps: CommandDeps,\n stdout: (line: string) => void = console.log\n): Promise<number> {\n const [command, ...rawRest] = argv;\n const rest = normalizeDelayMs(rawRest);\n try {\n switch (command) {\n case undefined: {\n stdout(USAGE);\n return 1;\n }\n case '--help':\n case '-h': {\n stdout(USAGE);\n return 0;\n }\n case 'inspect': {\n const { positionals } = parseOrCliError(() =>\n parseArgs({ args: rest, options: {}, allowPositionals: true, strict: true })\n );\n const file = requireFilePositional(positionals, 'inspect');\n stdout(await inspectCommand(file, deps));\n return 0;\n }\n case 'extract': {\n const { values, positionals } = parseOrCliError(() =>\n parseArgs({\n args: rest,\n options: {\n track: { type: 'string' },\n output: { type: 'string' },\n force: { type: 'boolean', default: false }\n },\n allowPositionals: true,\n strict: true\n })\n );\n const file = requireFilePositional(positionals, 'extract');\n const output = await extractCommand(\n {\n file,\n track: parseTrack(values.track),\n output: values.output,\n force: values.force ?? false\n },\n deps\n );\n stdout(`Wrote ${output}`);\n return 0;\n }\n case 'test-clip': {\n const { values } = parseOrCliError(() =>\n parseArgs({\n args: rest,\n options: {\n video: { type: 'string' },\n audio: { type: 'string' },\n track: { type: 'string' },\n start: { type: 'string' },\n duration: { type: 'string', default: '60' },\n 'delay-ms': { type: 'string', default: '0' },\n output: { type: 'string' },\n force: { type: 'boolean', default: false }\n },\n strict: true\n })\n );\n if (!values.video || !values.audio)\n throw new CliError('test-clip requires --video and --audio.');\n if (!values.start) throw new CliError('test-clip requires --start.');\n const output = await testClipCommand(\n {\n video: values.video,\n audio: values.audio,\n track: parseTrack(values.track),\n start: values.start,\n duration: values.duration ?? '60',\n delayMs: parseIntStrict(values['delay-ms'] ?? '0', '--delay-ms'),\n output: values.output,\n force: values.force ?? false\n },\n deps\n );\n stdout(`Wrote ${output}`);\n return 0;\n }\n case 'mux': {\n const { values } = parseOrCliError(() =>\n parseArgs({\n args: rest,\n options: {\n video: { type: 'string' },\n audio: { type: 'string' },\n track: { type: 'string' },\n 'delay-ms': { type: 'string', default: '0' },\n language: { type: 'string', default: 'eng' },\n name: { type: 'string' },\n default: { type: 'boolean', default: false },\n output: { type: 'string' },\n force: { type: 'boolean', default: false }\n },\n strict: true\n })\n );\n if (!values.video || !values.audio) throw new CliError('mux requires --video and --audio.');\n if (!values.output)\n throw new CliError('mux requires --output (no default output name for the final file).');\n const output = await muxCommand(\n {\n video: values.video,\n audio: values.audio,\n track: values.track === undefined ? undefined : parseTrack(values.track),\n delayMs: parseIntStrict(values['delay-ms'] ?? '0', '--delay-ms'),\n language: values.language,\n name: values.name,\n makeDefault: values.default ?? false,\n output: values.output,\n force: values.force ?? false\n },\n deps\n );\n stdout(`Wrote ${output}`);\n return 0;\n }\n default:\n throw new CliError(`Unknown command \"${command}\".\\n\\n${USAGE}`);\n }\n } catch (error) {\n if (error instanceof CliError) {\n console.error(`tracksmith: ${error.message}`);\n return error.exitCode;\n }\n throw error;\n }\n}\n","import { once } from 'node:events';\nimport { createInterface } from 'node:readline/promises';\n\nimport type { Readable, Writable } from 'node:stream';\n\nexport interface ConfirmPromptOptions {\n input?: Readable;\n output?: Writable;\n}\n\nexport function createConfirmPrompt(\n options: ConfirmPromptOptions = {}\n): (message: string) => Promise<boolean> {\n const input = options.input ?? process.stdin;\n const output = options.output ?? process.stdout;\n\n return async (message: string): Promise<boolean> => {\n const readline = createInterface({ input, output });\n\n try {\n const closed = once(readline, 'close').then(() => ({ kind: 'eof' }) as const);\n const answered = readline.question(`${message} [y/N] `).then(\n (value) => ({ kind: 'answer', value }) as const,\n () => ({ kind: 'eof' }) as const\n );\n const result = await Promise.race([answered, closed]);\n\n return result.kind === 'answer' && /^y(?:es)?$/i.test(result.value.trim());\n } finally {\n readline.close();\n }\n };\n}\n","export const TOOLS = ['ffmpeg', 'mkvmerge'] as const;\n\nexport type ToolName = (typeof TOOLS)[number];\n\nexport function isToolName(value: string): value is ToolName {\n return TOOLS.some((tool) => tool === value);\n}\n\nconst INSTALL_HINTS = {\n ffmpeg:\n 'Windows: winget install Gyan.FFmpeg or scoop install ffmpeg | macOS: brew install ffmpeg | Linux: sudo apt install ffmpeg or sudo pacman -S ffmpeg',\n mkvmerge:\n 'Windows: winget install MoritzBunkus.MKVToolNix or scoop install mkvtoolnix | macOS: brew install mkvtoolnix | Linux: sudo apt install mkvtoolnix or sudo pacman -S mkvtoolnix-cli'\n} satisfies Record<ToolName, string>;\n\nexport function installHint(tool: ToolName): string {\n return INSTALL_HINTS[tool];\n}\n","import { spawn } from 'node:child_process';\n\nimport type { RunOptions, Runner, RunResult } from './types.ts';\n\nimport { installHint, isToolName } from './tools.ts';\nimport { CliError } from './types.ts';\n\nexport function processStartError(program: string, error: unknown): CliError {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT' &&\n isToolName(program)\n ) {\n return new CliError(\n `${program} is required but was not found on PATH. ${installHint(program)}`\n );\n }\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError(`Failed to start ${program}: ${message}`);\n}\n\nconst writeToCliStderr = (chunk: string): void => {\n globalThis.process.stderr.write(chunk);\n};\n\nexport class ProcessRunner implements Runner {\n private readonly sink: (chunk: string) => void;\n\n constructor(options: { sink?: (chunk: string) => void } = {}) {\n this.sink = options.sink ?? writeToCliStderr;\n }\n\n async run(argv: readonly string[], options?: RunOptions): Promise<RunResult> {\n const [executable, ...args] = argv;\n if (executable === undefined) {\n throw new CliError('Could not start process: missing executable');\n }\n\n return await new Promise((resolve, reject) => {\n const process = spawn(executable, args, {\n stdio: ['ignore', 'pipe', 'pipe']\n });\n let stdout = '';\n let stderr = '';\n\n process.stdout.setEncoding('utf8');\n process.stderr.setEncoding('utf8');\n process.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n if (options?.stream === 'stdout') this.sink(chunk);\n });\n process.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n if (options?.stream === 'stderr') this.sink(chunk);\n });\n process.once('error', (error) => {\n reject(processStartError(executable, error));\n });\n process.once('close', (exitCode) => {\n resolve({ exitCode: exitCode ?? 1, stdout, stderr });\n });\n });\n }\n}\n","#!/usr/bin/env node\n\nimport { runCli } from './cli.ts';\nimport { createConfirmPrompt } from './prompt.ts';\nimport { ProcessRunner } from './runner.ts';\n\nprocess.exitCode = await runCli(process.argv.slice(2), {\n runner: new ProcessRunner(),\n isTTY: process.stdin.isTTY ?? false,\n stderrIsTTY: process.stderr.isTTY ?? false,\n confirm: createConfirmPrompt()\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracksmith",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for inspecting, extracting, syncing, and muxing Matroska audio tracks.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,10 +60,10 @@
60
60
  "@changesets/cli": "3.0.1",
61
61
  "@types/node": "^22.20.1",
62
62
  "typescript": "^7.0.0",
63
- "vite": "npm:@voidzero-dev/vite-plus-core@0.2.6",
64
- "vite-plus": "0.2.6"
63
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
64
+ "vite-plus": "0.3.0"
65
65
  },
66
66
  "overrides": {
67
- "vite": "npm:@voidzero-dev/vite-plus-core@0.2.6"
67
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0"
68
68
  }
69
69
  }