Skip to content

Video export: keep encoded segments on failure, fall back to cuts, report ffmpeg exit status - #194

Merged
davior merged 2 commits into
mainfrom
claude/dazzling-mendel-df9o1x
Sep 30, 2026
Merged

davior merged 2 commits into
mainfrom
claude/dazzling-mendel-df9o1x

Conversation

@davior

@davior davior commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Problem

A video export failed with a bare ffmpeg failed:. Host logs showed the kernel OOM-killed ffmpeg during the crossfade (xfade) stitch (Circle transition) — the one step that holds every segment open at once — after all segments had already been encoded. Because the scratch dir is always removed and a retry is a new job, every segment was thrown away and had to be re-encoded.

Changes

  • ffmpeg.run(): the error now includes the exit status (killed by signal 9 (most likely out of memory)), and the full argv + stderr are logged. Previously -loglevel error + a signal kill produced an empty message.
  • video/shot_cache.py (new): encoded segments are filed under a fingerprint of the exact ffmpeg command plus input content (not the job id or whole options), as hard links. A retry — even with a different transition — reuses unchanged segments. Entries are discarded on success and pruned after 48h.
  • renderer.py: uses the cache per shot (replaying the "rendered without motion" warning on reuse); if the xfade stitch fails, it falls back to a plain cut join with a warning and rebuilds chapters/subtitles for the un-overlapped timeline.

Tests

  • tests/test_video_shot_cache.py – fingerprinting, hit/miss, plain-fallback marker, cleanup, prune.
  • tests/test_video_render_resume.py – drives real render() with a faked ffmpeg: crossfade fallback, failed render keeps segments and retry reuses them, segment-affecting changes miss the cache.
  • tests/test_video_ffmpeg.py – exit-status message.
  • Full backend suite: 1082 passed, 4 skipped.

Not verified against a real ffmpeg (none in the dev sandbox). Cache/fallback behaviour is covered with a fake; worth one real export on the server.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QkwxQ29m5Zfq3XfaEjDyeZ


Generated by Claude Code

With -loglevel error a process killed from outside (e.g. OOM) writes
nothing, so the UI showed a bare 'ffmpeg failed:'. Include the exit
status in the error and log the full command and stderr.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkwxQ29m5Zfq3XfaEjDyeZ
… if the crossfade stitch fails

A render that died at the stitch threw away every encoded segment, and a
retry (a new job) re-encoded all of them. Segments are now filed in a
content-keyed cache (hard links, pruned after 48h, discarded on success),
so a retry - even with a different transition - only encodes what changed.

The crossfade (xfade) stitch holds every segment open at once and is what
gets OOM-killed on a small host. If it fails, join with straight cuts and
warn rather than failing a render whose encoding is finished.

Also fixes the run() failure test added earlier, which had a class-scope bug.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkwxQ29m5Zfq3XfaEjDyeZ
@davior
davior marked this pull request as ready for review September 30, 2026 11:29
@davior
davior merged commit 1d6148f into main Sep 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants