From 83b1923e257d30e69fcbe18bc51298741c6f8e9a Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Tue, 13 Oct 2026 10:58:13 -0400 Subject: [PATCH] fix(video): keep source frame timing when transcoding to HLS Variable-frame-rate sources (phone clips that change capture rate, screen recordings) report r_frame_rate as their 1/90000 timebase. The HLS transcode used the mpegts muxer's default CFR mode, so ffmpeg duplicated frames to fill 90000 fps: on prod, three 2.6-5 s phone clips became 3-second "segments" of up to 446 MB / 250k frames. Players download the whole thing before starting and can't seek within it. -fps_mode vfr passes source timestamps through. Verified on 20220208_164356_01.mp4: 233 frames over 2.578 s in, 233 frames over 2.578 s out, 5.9 MB vs 221 MB. The copy path already keeps timestamps and is unchanged. Requires ffmpeg >= 5.1 (prod runs 8.0.1). Co-Authored-By: Claude Opus 5.5 --- src/video/actors.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/video/actors.rs b/src/video/actors.rs index 009aa4a..7a22a5a 100644 --- a/src/video/actors.rs +++ b/src/video/actors.rs @@ -668,6 +668,12 @@ impl Handler for PlaylistGenerator { // segment starts on a keyframe — accurate seeking without // players having to decode from a prior segment. cmd.arg("-force_key_frames").arg("expr:gte(t,n_forced*3)"); + // Keep source frame timestamps. Variable-frame-rate phone + // clips and screen recordings report r_frame_rate as their + // timebase (90000/1); the mpegts muxer's default CFR mode + // then duplicates frames to fill it — a 2.6 s clip came out + // as 233k frames / 221 MB. vfr leaves timing untouched. + cmd.arg("-fps_mode").arg("vfr"); } // -f hls is required because the playlist is written to a .tmp