The HLS transcode encoded on NVENC but decoded the source on the CPU. For 4K sources, decoding is most of the work.
ffmpeg -y
+ -hwaccel cuda # only when NVENC is available (same gate as preview clips)
-i <source>
-c:v h264_nvenc ... -vf scale=... # unchanged; frames come back to system memory
Stream copy and the libx264 fallback are unchanged.
Evidence
During the post-#166 rebuild, 3 concurrent 4K transcodes ran at 210–240% CPU each, with the CPU at 0% idle, NVENC at 13% and NVDEC at 0%.
Timings for the same ffmpeg args on 10 s of 2025-USAF-MuseumRace.mp4 (4K H.264, ~117 Mbps), measured while those 3 rebuilds were also running:
A rotated phone clip (1440×1080, rotation 90) comes out at 1080×1440, upright, with 233/233 frames. Autorotate still applies.
ProRes, which NVDEC doesn't support, falls back to software decode and produces correct output.
Clippy is clean, and DATABASE_URL=":memory:" cargo test passes 1531/1531.
Merge Danger
Door: two-way
A single ffmpeg input flag. Reverting the commit restores CPU decoding. Playlists generated with it are identical to CPU-decoded ones, so they don't need regenerating either way.
Blast Radius: transcodes
This only affects HLS transcodes on hosts with NVENC. Possible risks: GPU memory pressure if HLS_CONCURRENCY is high next to other GPU work. Each decode session uses a few hundred MB; the 3 concurrent encodes used about 1.3 GB in total on the 3090. Also, a source that NVDEC accepts but decodes incorrectly would fail where CPU decoding succeeds. None of the tested sources did. Deploying restarts the service, which kills in-flight encodes. Their playlists were never written, so the next scan requeues them.
## Summary
The HLS transcode encoded on NVENC but decoded the source on the CPU. For 4K sources, decoding is most of the work.
```diff
ffmpeg -y
+ -hwaccel cuda # only when NVENC is available (same gate as preview clips)
-i <source>
-c:v h264_nvenc ... -vf scale=... # unchanged; frames come back to system memory
```
Stream copy and the libx264 fallback are unchanged.
## Evidence
During the post-#166 rebuild, 3 concurrent 4K transcodes ran at 210–240% CPU each, with the CPU at 0% idle, NVENC at 13% and NVDEC at 0%.
Timings for the same ffmpeg args on 10 s of `2025-USAF-MuseumRace.mp4` (4K H.264, ~117 Mbps), measured while those 3 rebuilds were also running:
- **Before:** `cpu-decode: wall=9.60s user=27.30s` → 1920×1080, 245 frames, segments `3.003 3.003 3.003 1.21`
**After:** `-hwaccel cuda: wall=3.64s user=6.14s` → 1920×1080, 245 frames, segments `3.003 3.003 3.003 1.21`
Edge cases, both with `-hwaccel cuda`:
- A rotated phone clip (1440×1080, rotation 90) comes out at 1080×1440, upright, with 233/233 frames. Autorotate still applies.
- ProRes, which NVDEC doesn't support, falls back to software decode and produces correct output.
Clippy is clean, and `DATABASE_URL=":memory:" cargo test` passes 1531/1531.
## Merge Danger
**Door:** two-way
A single ffmpeg input flag. Reverting the commit restores CPU decoding. Playlists generated with it are identical to CPU-decoded ones, so they don't need regenerating either way.
**Blast Radius:** transcodes
This only affects HLS transcodes on hosts with NVENC. Possible risks: GPU memory pressure if `HLS_CONCURRENCY` is high next to other GPU work. Each decode session uses a few hundred MB; the 3 concurrent encodes used about 1.3 GB in total on the 3090. Also, a source that NVDEC accepts but decodes incorrectly would fail where CPU decoding succeeds. None of the tested sources did. Deploying restarts the service, which kills in-flight encodes. Their playlists were never written, so the next scan requeues them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
When NVENC is available the HLS transcode encoded on the GPU but still
decoded on the CPU, since the command had no -hwaccel. Rebuilding the
4K homemade videos ran three ffmpegs at 210-240% CPU each with the
machine at 0% idle, while NVDEC sat at 0%.
Pass -hwaccel cuda when transcoding with NVENC, as generate_preview_clip
already does. Frames return to system memory, so autorotate and the
scale filter work unchanged; NVDEC-unsupported codecs fall back to
software decode.
10 s of 4K H.264 (2025-USAF-MuseumRace.mp4): 27.3 s -> 6.1 s CPU,
9.6 s -> 3.6 s wall, identical output (1920x1080, 245 frames, 3 s
segments). Also checked a 90-degree-rotated phone clip (comes out
1080x1440 upright, 233/233 frames) and ProRes (software fallback,
correct output).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cameron
merged commit 4745a945fe into master2026-10-13 16:13:00 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
The HLS transcode encoded on NVENC but decoded the source on the CPU. For 4K sources, decoding is most of the work.
Stream copy and the libx264 fallback are unchanged.
Evidence
During the post-#166 rebuild, 3 concurrent 4K transcodes ran at 210–240% CPU each, with the CPU at 0% idle, NVENC at 13% and NVDEC at 0%.
Timings for the same ffmpeg args on 10 s of
2025-USAF-MuseumRace.mp4(4K H.264, ~117 Mbps), measured while those 3 rebuilds were also running:cpu-decode: wall=9.60s user=27.30s→ 1920×1080, 245 frames, segments3.003 3.003 3.003 1.21After:
-hwaccel cuda: wall=3.64s user=6.14s→ 1920×1080, 245 frames, segments3.003 3.003 3.003 1.21Edge cases, both with
-hwaccel cuda:Clippy is clean, and
DATABASE_URL=":memory:" cargo testpasses 1531/1531.Merge Danger
Door: two-way
A single ffmpeg input flag. Reverting the commit restores CPU decoding. Playlists generated with it are identical to CPU-decoded ones, so they don't need regenerating either way.
Blast Radius: transcodes
This only affects HLS transcodes on hosts with NVENC. Possible risks: GPU memory pressure if
HLS_CONCURRENCYis high next to other GPU work. Each decode session uses a few hundred MB; the 3 concurrent encodes used about 1.3 GB in total on the 3090. Also, a source that NVDEC accepts but decodes incorrectly would fail where CPU decoding succeeds. None of the tested sources did. Deploying restarts the service, which kills in-flight encodes. Their playlists were never written, so the next scan requeues them.🤖 Generated with Claude Code