From be532725c8170b92a38a04cbb8c1bfa7990406b6 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Tue, 13 Oct 2026 12:12:08 -0400 Subject: [PATCH] perf(video): decode HLS transcode sources on the GPU 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 --- src/video/actors.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/video/actors.rs b/src/video/actors.rs index e45c77e..a884e69 100644 --- a/src/video/actors.rs +++ b/src/video/actors.rs @@ -633,14 +633,24 @@ impl Handler for PlaylistGenerator { // ffmpeg writes them next to the playlist (relative refs). let playlist_tmp = format!("{}.tmp", playlist_file); + let nvenc = !use_copy && crate::video::ffmpeg::is_nvenc_available().await; + let mut cmd = tokio::process::Command::new("ffmpeg"); - cmd.arg("-y").arg("-i").arg(&video_file); + cmd.arg("-y"); + if nvenc { + // Decode on NVDEC too. Without it a 4K source is decoded on + // the CPU and only the encode runs on the GPU: ~4.5x the CPU + // time. Frames come back to system memory, so the CPU + // filters (autorotate, scale) still apply, and ffmpeg falls + // back to software decode for codecs NVDEC lacks (ProRes). + cmd.arg("-hwaccel").arg("cuda"); + } + cmd.arg("-i").arg(&video_file); if use_copy { cmd.arg("-c:v").arg("copy"); cmd.arg("-c:a").arg("aac"); } else { - let nvenc = crate::video::ffmpeg::is_nvenc_available().await; if nvenc { // NVENC: no CRF, use VBR + target CQ. p1 = fastest // preset — prioritizes encoder throughput over bitrate -- 2.54.0