{"record":{"id":"eb13644df19efea9","repo":"babysor/MockingBird","slug":"ffprobe-failed-on-path-proc-stderr","errorCode":null,"errorMessage":"ffprobe failed on {path}: {proc.stderr}","messagePattern":"ffprobe failed on (.+?): (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/speak/scripts/render_timeline.py","lineNumber":135,"sourceCode":"    if proc.returncode != 0:\n        raise RuntimeError(f\"ffmpeg failed: {' '.join(cmd)}\\n{proc.stderr}\")\n\n\ndef ensure_ffmpeg() -> None:\n    if not shutil.which(\"ffmpeg\"):\n        raise RuntimeError(\"ffmpeg not found in PATH.\")\n\n\ndef probe_duration_ms(path: Path) -> float:\n    proc = subprocess.run(\n        [\n            \"ffprobe\", \"-v\", \"error\", \"-show_entries\", \"format=duration\",\n            \"-of\", \"default=noprint_wrappers=1:nokey=1\", str(path),\n        ],\n        capture_output=True, text=True,\n    )\n    if proc.returncode != 0:\n        raise RuntimeError(f\"ffprobe failed on {path}: {proc.stderr}\")\n    return float(proc.stdout.strip()) * 1000\n\n\ndef normalize_duration_pad_trim(inp: Path, outp: Path, target_ms: int) -> None:\n    \"\"\"Pad short audio then trim to exact target duration (Noiz backend).\"\"\"\n    sec = target_ms / 1000.0\n    _run_ff([\n        \"ffmpeg\", \"-y\", \"-i\", str(inp),\n        \"-af\", f\"apad=pad_dur={sec:.3f}\",\n        \"-t\", f\"{sec:.3f}\", str(outp),\n    ])\n\n\ndef normalize_duration_atempo(inp: Path, outp: Path, target_ms: int) -> None:\n    \"\"\"Use atempo to stretch/compress audio to target duration (Kokoro backend).\"\"\"\n    actual_ms = probe_duration_ms(inp)\n    if actual_ms <= 0:\n        normalize_duration_pad_trim(inp, outp, target_ms)","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/babysor/MockingBird/blob/28dc5e14f12d7c754612af2fde8e78a4b03f8616/skills/speak/scripts/render_timeline.py#L117-L153","documentation":"RuntimeError raised by probe_duration_ms when ffprobe (bundled with ffmpeg) exits non-zero while reading a media file's duration. Used before atempo time-stretching and after Kokoro TTS to get actual audio length.","triggerScenarios":"Pointing ffprobe at a non-media file, a corrupt/truncated audio file (e.g. incomplete TTS download), an unreadable path, or a format the installed ffprobe cannot parse.","commonSituations":"TTS output was written from an error response body instead of audio, partial file from a killed process, or zero-byte file from a failed upstream request.","solutions":["Run: ffprobe -v error -show_entries format=duration file.wav manually to see the error","Validate the file is real audio (check size > 0 and file type) before probing","Re-generate the offending audio segment","Ensure ffprobe is installed and on PATH (comes with ffmpeg)"],"exampleFix":"# before\nms = probe_duration_ms(seg_path)\n# after\nif not seg_path.exists() or seg_path.stat().st_size == 0:\n    raise RuntimeError(f\"empty segment: {seg_path}\")\nms = probe_duration_ms(seg_path)","handlingStrategy":"validation","validationCode":"p = Path(seg)\nassert p.exists() and p.stat().st_size > 0, f'bad audio file: {p}'","typeGuard":null,"tryCatchPattern":"try:\n    ms = probe_duration_ms(p)\nexcept RuntimeError:\n    ms = -1.0  # re-synthesize segment later","preventionTips":["Check file size before probing","Regenerate truncated segments","Keep ffmpeg/ffprobe versions in sync"],"tags":["ffprobe","audio","validation","subprocess"],"backgroundTag":"corrupt-media-file","analyzedSha":"28dc5e14f12d7c754612af2fde8e78a4b03f8616","analyzedAt":"2026-08-27T02:26:53.589Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}