Files
hyperframes/skills/media-use/scripts/lib/probe.mjs
T
Vance IngallsandClaude Opus 5 255cf92915 fix(skills,producer): terminate ffprobe options in shipped skill scripts
The contract test only walked packages/*/src and only .ts, so it could not see
the shipped agent tools under skills/**, which are .mjs/.cjs. 19 call sites
there and in package tests were still missing `--` immediately before the
input while the suite reported the bug class closed — a dash-prefixed filename
is parsed as an option and fails the same way.

Sweeps packages/, skills/ and scripts/ now, including .mjs/.cjs and test
files (dither.test.mjs was one of the broken sites). Excludes only the
contract test itself, which documents the contract with example argvs
including a deliberately misordered one.

Two guards were fixed while widening: the terminator must never be inserted
after `-i`, which consumes the next token (a blind pass hit an ffmpeg input
and a base64 -i), and comment prose describing a spawn is not a spawn.

Also routes every audioPadTrim probe failure through one sanitizer at the
boundary. runFfprobeJson scrubbed its own stderr, but
defaultProbeVideoFrameInfo threw `no video stream in ${videoPath}` raw into
the public PadTrimAudioResult.error, and an injected probe can throw anything.
The redaction unit tests all passed with the caller wiring deleted; the new
public-path regressions fail without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 02:23:35 -07:00

40 lines
1.3 KiB
JavaScript

import { execFileSync } from "node:child_process";
import { extname } from "node:path";
const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico"]);
export function probe(filePath) {
const ext = extname(filePath).toLowerCase();
if (ext === ".svg") return { width: null, height: null, duration: null, codec: "svg" };
try {
// execFileSync (no shell) so a hostile filename like `"; rm -rf ~; ".png`
// can't break out of the quoting — filePath is passed as a literal argv entry.
const raw = execFileSync(
"ffprobe",
["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "--", filePath],
{ encoding: "utf8", timeout: 5000 },
);
const info = JSON.parse(raw);
const stream = info.streams?.[0];
const format = info.format;
const isImage = IMAGE_EXT.has(ext);
const duration = isImage
? null
: parseFloat(format?.duration) || parseFloat(stream?.duration) || null;
const width = parseInt(stream?.width, 10) || null;
const height = parseInt(stream?.height, 10) || null;
const codec = stream?.codec_name || null;
return {
duration: duration != null ? Math.round(duration * 10) / 10 : null,
width,
height,
codec,
};
} catch {
return { duration: null, width: null, height: null, codec: null };
}
}