Conversation
Concatenated JPEGs are a valid MJPEG byte stream, so ZtDecoder.decode() now feeds every frame's JPEG payload into one `ffmpeg -f mjpeg -i pipe:0 -vf scale=W:H -f rawvideo -pix_fmt rgb24 pipe:1` call instead of spawning a subprocess per frame (_decode_jpeg -> _decode_jpegs). Falls back to blank frames (with a warning) if the batch decode fails or under-produces, matching the old per-frame failure behavior. Verification: - Byte-parity: synthetic 5-frame .zt fixture decoded via the new batched path vs. the old per-frame logic (reimplemented standalone for comparison) — output bytes identical, frame-to-cursor mapping correct. - Speedup: 120-frame synthetic clip, 3.925s (old, per-frame) -> 0.039s (new, batched) — 100.5x. - Full suite: 2927 tests — 2922 pass, 4 skip, 1 fail (test_nvml_init_state_logs_resolved_tuple — pre-existing, unrelated: NVML shared library absent in this sandbox). Split out of Lexonight1#229 per review — self-contained, no dependency on that PR's PlayVideo threading split. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@gavindi — first, an apology: you split this out of #229 exactly as the review asked, verified byte-parity, measured it, and then got seven weeks of silence. That's on me, not you. Second, the harder part. Your diagnosis was right — spawning one ffmpeg per frame was the bottleneck, and batching into a single MJPEG stream is the correct fix. In the meantime So this PR is superseded — not because it was wrong, but because you pointed at the right bottleneck and the eventual fix went past it. Your 100x would have been a real improvement on the code as it stood. I'm leaving it open rather than closing it myself; it's your work and your call. If you want to look at what landed, it's Genuinely sorry for the wait. #229 is still live and I'll come back to it properly. |
Summary
Split out of #229 per review — this half was called clean and self-contained, no dependency on that PR's PlayVideo threading split.
Concatenated JPEGs are a valid MJPEG byte stream, so
ZtDecoder.decode()now feeds every frame's JPEG payload into oneffmpeg -f mjpeg -i pipe:0 -vf scale=W:H -f rawvideo -pix_fmt rgb24 pipe:1call instead of spawning a subprocess per frame (_decode_jpeg→_decode_jpegs). Falls back to blank frames (with a warning) if the batch decode fails or under-produces, matching the old per-frame failure behaviour..ztfixture decoded via the new batched path vs. the old per-frame logic (reimplemented standalone for comparison) — output bytes identical, frame-to-cursor mapping correct.Test plan
ruff check src/trcc/services/media.py— cleanpyright src/trcc/services/media.py— 0 errorspytest tests/ -k "video or zt or media"— 124 passed