From 42453d5786a42613e6b57fc75983abbe42fd241a Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Fri, 12 Jun 2026 22:56:48 -0400 Subject: [PATCH] Fix reel concat: force -f mp4 for the .tmp output path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The concat stage wrote to .mp4.tmp (for an atomic publish-rename), but ffmpeg infers the muxer from the output extension and can't map .tmp to a format — "Unable to choose an output format". Force the mp4 muxer explicitly so the temp extension is irrelevant. Segment render, NVENC, TTS, and scripting were already working end-to-end; this was the only failure, at the final join. Co-Authored-By: Claude Fable 5 --- src/reels/render.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/reels/render.rs b/src/reels/render.rs index ca39515..9643309 100644 --- a/src/reels/render.rs +++ b/src/reels/render.rs @@ -159,7 +159,9 @@ pub fn build_segment_args( /// Build the concat-demuxer args that join rendered segments losslessly. /// `+faststart` moves the moov atom up front so the reel streams immediately -/// on the mobile client. +/// on the mobile client. The output muxer is forced with `-f mp4` because we +/// write to a `.tmp` path (atomic publish) whose extension ffmpeg can't map to +/// a format on its own. pub fn build_concat_args(list_path: &str, out_path: &str) -> Vec { [ "-y", @@ -173,6 +175,8 @@ pub fn build_concat_args(list_path: &str, out_path: &str) -> Vec { "copy", "-movflags", "+faststart", + "-f", + "mp4", out_path, ] .iter() @@ -317,12 +321,19 @@ mod tests { } #[test] - fn concat_args_stream_copy_with_faststart() { - let args = build_concat_args("/tmp/list.txt", "/out.mp4"); + fn concat_args_stream_copy_with_faststart_and_forced_muxer() { + // Output goes to a .tmp path, so the muxer must be forced — ffmpeg + // can't infer mp4 from the extension (the bug this guards against). + let args = build_concat_args("/tmp/list.txt", "/out.mp4.tmp"); let joined = args.join(" "); assert!(joined.contains("-f concat -safe 0 -i /tmp/list.txt")); assert!(joined.contains("-c copy")); assert!(joined.contains("+faststart")); + assert!(joined.contains("-f mp4")); + // The forced muxer must come before the output path. + let f_mp4 = args.windows(2).position(|w| w == ["-f", "mp4"]).unwrap(); + let out = args.iter().position(|a| a == "/out.mp4.tmp").unwrap(); + assert!(f_mp4 < out); } #[test]