mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
fix(cli,core,lint,producer): terminate ffprobe options at every call site
#2740 added `--` to one of nine independent ffprobe invocations, so the bug class it closed stayed open everywhere else while CI reported it fixed — the regression test asserts the argv of that single site. Reproduced on ffprobe 8.1.1: an asset named `-intro.mp4` probes fine through extractMediaMetadata but fails with "Missing argument for option 'intro.mp4'" in audio pad/trim (mid-render), `hyperframes init`, whisper duration probing and webmAlphaCheck. hevcPreviewLint catches and returns false, so a dash-prefixed HEVC preview silently passes the lint rule. Terminated at all of them: producer/services/render/audioPadTrim.ts (x2) producer/plan-parity-analysis.ts cli/commands/init.ts cli/utils/webmAlphaCheck.ts cli/whisper/transcribe.ts (x2) core/mediaGradeAnalyzer.ts lint/hevcPreviewLint.ts audioPadTrim's runFfprobeJson is a near-verbatim clone of the engine's runFfprobe and structurally cannot add the terminator itself, because callers bake the input path into `args`. It now asserts the terminator is present rather than letting a dash-prefixed path through, takes the same stdio ["ignore", ...] as the engine helper, and redacts its stderr — it was throwing raw ffprobe output, which echoes the input path, into logs and telemetry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8e41fa17b9
commit
47564ab94c
@@ -109,7 +109,7 @@ function probeVideo(filePath: string): VideoMeta | undefined {
|
|||||||
if (!ffprobePath) return undefined;
|
if (!ffprobePath) return undefined;
|
||||||
const raw = execFileSync(
|
const raw = execFileSync(
|
||||||
ffprobePath,
|
ffprobePath,
|
||||||
["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath],
|
["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "--", filePath],
|
||||||
{ encoding: "utf-8", timeout: 15_000 },
|
{ encoding: "utf-8", timeout: 15_000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ function probeWebmAlpha(filePath: string): WebmAlphaProbe {
|
|||||||
"stream=codec_name:stream_tags=alpha_mode",
|
"stream=codec_name:stream_tags=alpha_mode",
|
||||||
"-of",
|
"-of",
|
||||||
"json",
|
"json",
|
||||||
|
"--",
|
||||||
filePath,
|
filePath,
|
||||||
],
|
],
|
||||||
{ encoding: "utf-8", timeout: 15_000 },
|
{ encoding: "utf-8", timeout: 15_000 },
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ function getMediaDurationSeconds(filePath: string): number | null {
|
|||||||
"format=duration",
|
"format=duration",
|
||||||
"-of",
|
"-of",
|
||||||
"default=noprint_wrappers=1:nokey=1",
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
"--",
|
||||||
filePath,
|
filePath,
|
||||||
],
|
],
|
||||||
{ encoding: "utf-8", timeout: 10_000 },
|
{ encoding: "utf-8", timeout: 10_000 },
|
||||||
@@ -329,7 +330,7 @@ function isWav16kMono(filePath: string): boolean {
|
|||||||
if (!ffprobePath) return false;
|
if (!ffprobePath) return false;
|
||||||
const raw = execFileSync(
|
const raw = execFileSync(
|
||||||
ffprobePath,
|
ffprobePath,
|
||||||
["-v", "quiet", "-print_format", "json", "-show_streams", filePath],
|
["-v", "quiet", "-print_format", "json", "-show_streams", "--", filePath],
|
||||||
{ encoding: "utf-8", timeout: 10_000 },
|
{ encoding: "utf-8", timeout: 10_000 },
|
||||||
);
|
);
|
||||||
const parsed: {
|
const parsed: {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ function probeMedia(mediaPath: string, ffprobePath: string): GradeMediaProbe {
|
|||||||
"stream=color_space,color_transfer,color_primaries,pix_fmt,duration:format=duration",
|
"stream=color_space,color_transfer,color_primaries,pix_fmt,duration:format=duration",
|
||||||
"-of",
|
"-of",
|
||||||
"json",
|
"json",
|
||||||
|
"--",
|
||||||
mediaPath,
|
mediaPath,
|
||||||
],
|
],
|
||||||
{ encoding: "utf8", timeout: 5_000, stdio: ["ignore", "pipe", "pipe"] },
|
{ encoding: "utf8", timeout: 5_000, stdio: ["ignore", "pipe", "pipe"] },
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ async function probeIsHevc(ffprobePath: string, filePath: string): Promise<boole
|
|||||||
"stream=codec_name",
|
"stream=codec_name",
|
||||||
"-of",
|
"-of",
|
||||||
"json",
|
"json",
|
||||||
|
"--",
|
||||||
filePath,
|
filePath,
|
||||||
]);
|
]);
|
||||||
return hasHevcStream(JSON.parse(stdout));
|
return hasHevcStream(JSON.parse(stdout));
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ function probeMetadata(outputPath: string): PlanParityStreamMetadata {
|
|||||||
].join(":"),
|
].join(":"),
|
||||||
"-of",
|
"-of",
|
||||||
"json",
|
"json",
|
||||||
|
"--",
|
||||||
outputPath,
|
outputPath,
|
||||||
]);
|
]);
|
||||||
return normalizeFfprobeMetadata(JSON.parse(bytes.toString("utf-8")) as unknown);
|
return normalizeFfprobeMetadata(JSON.parse(bytes.toString("utf-8")) as unknown);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
trackChildProcess,
|
trackChildProcess,
|
||||||
type AudioMetadata,
|
type AudioMetadata,
|
||||||
} from "@hyperframes/engine";
|
} from "@hyperframes/engine";
|
||||||
|
import { redactTelemetryString } from "@hyperframes/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tolerance used to decide whether an audio file is already short enough to
|
* Tolerance used to decide whether an audio file is already short enough to
|
||||||
@@ -361,6 +362,7 @@ async function defaultProbeVideoFrameInfo(
|
|||||||
"stream=nb_frames,r_frame_rate",
|
"stream=nb_frames,r_frame_rate",
|
||||||
"-of",
|
"-of",
|
||||||
"json",
|
"json",
|
||||||
|
"--",
|
||||||
videoPath,
|
videoPath,
|
||||||
],
|
],
|
||||||
signal,
|
signal,
|
||||||
@@ -381,6 +383,7 @@ async function defaultProbeVideoFrameInfo(
|
|||||||
"stream=nb_read_packets,r_frame_rate",
|
"stream=nb_read_packets,r_frame_rate",
|
||||||
"-of",
|
"-of",
|
||||||
"json",
|
"json",
|
||||||
|
"--",
|
||||||
videoPath,
|
videoPath,
|
||||||
],
|
],
|
||||||
signal,
|
signal,
|
||||||
@@ -434,7 +437,13 @@ async function defaultRunFfmpeg(
|
|||||||
// ── ffprobe JSON runner (shared between fast/slow video probe paths) ─────
|
// ── ffprobe JSON runner (shared between fast/slow video probe paths) ─────
|
||||||
|
|
||||||
async function runFfprobeJson<T>(args: string[], signal?: AbortSignal): Promise<T> {
|
async function runFfprobeJson<T>(args: string[], signal?: AbortSignal): Promise<T> {
|
||||||
const proc = spawn(getFfprobeBinary(), args);
|
// Callers bake the input path into `args` (terminated with "--"), so this
|
||||||
|
// helper cannot add the terminator itself — assert they did rather than
|
||||||
|
// let a dash-prefixed path silently reach ffprobe as an option.
|
||||||
|
if (!args.includes("--")) {
|
||||||
|
throw new Error('[audioPadTrim] ffprobe args must terminate options with "--".');
|
||||||
|
}
|
||||||
|
const proc = spawn(getFfprobeBinary(), args, { stdio: ["ignore", "pipe", "pipe"] });
|
||||||
trackChildProcess(proc);
|
trackChildProcess(proc);
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
proc.stdout.on("data", (data: Buffer) => {
|
proc.stdout.on("data", (data: Buffer) => {
|
||||||
@@ -452,7 +461,9 @@ async function runFfprobeJson<T>(args: string[], signal?: AbortSignal): Promise<
|
|||||||
throw outcome.error ?? new Error(outcome.stderr);
|
throw outcome.error ?? new Error(outcome.stderr);
|
||||||
}
|
}
|
||||||
if (outcome.reason !== "exit" || outcome.exitCode !== 0) {
|
if (outcome.reason !== "exit" || outcome.exitCode !== 0) {
|
||||||
throw new Error(`ffprobe ${outcome.reason}: ${outcome.stderr}`);
|
// Redacted: raw ffprobe stderr echoes the input path, and this message
|
||||||
|
// reaches logs and telemetry.
|
||||||
|
throw new Error(`ffprobe ${outcome.reason}: ${redactTelemetryString(outcome.stderr, 2000)}`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(stdout) as T;
|
return JSON.parse(stdout) as T;
|
||||||
|
|||||||
Reference in New Issue
Block a user