From e6a7aecd972cc3f7e51681cf8be0069a354ed0d3 Mon Sep 17 00:00:00 2001 From: JSLMPR Date: Mon, 27 Jul 2026 03:08:15 +0200 Subject: [PATCH] fix(render): loop a bounded music bed so long reels render (MusicGen CPU cap) A multi-segment reel needs music as long as the reel, but MusicGen-small stalls on CPU past ~45s (confirmed on the 49s downhill/soccer reels). Decouple music-generation length from reel length: generate a bounded bed (music-gen-max-seconds, default 15s) and loop it across the timeline (-stream_loop -1 on the music input; the existing atrim bounds it to the cue). Reel length stays uncapped; music generation stays feasible. Verified: 7-segment 45s downhill reel now renders (14.9s bed looped, 1080x1920/24fps/yuv420p/-15.6 LUFS). Co-Authored-By: Claude Opus 4.8 --- .../config/VideoClippingProperties.java | 15 +++++++++++++++ .../editing/HighlightAssetPreparationService.java | 7 ++++++- .../editing/HighlightFfmpegRenderer.java | 4 +++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/videoclips/config/VideoClippingProperties.java b/src/main/java/org/example/videoclips/config/VideoClippingProperties.java index 54ede1f..19bcb08 100644 --- a/src/main/java/org/example/videoclips/config/VideoClippingProperties.java +++ b/src/main/java/org/example/videoclips/config/VideoClippingProperties.java @@ -545,6 +545,13 @@ public class VideoClippingProperties { /** R12 cinematic motion blur (light frame blend) in styled shots. Emulates shutter-angle blur. */ private boolean cinematicMotionBlur = true; + /** + * Max seconds of music to GENERATE. Local MusicGen can't reliably produce long scores on CPU (it stalls + * ~45s+), so a longer reel generates a bounded bed and the renderer loops it to fill the timeline. This + * decouples reel length from music-generation feasibility — the reel itself stays uncapped. + */ + private double musicGenMaxSeconds = 15.0; + /** R13 snap montage cut boundaries onto the generated score's beat grid (cut-to-the-music). */ private boolean beatSyncEnabled = false; @@ -787,6 +794,14 @@ public class VideoClippingProperties { this.cinematicMotionBlur = cinematicMotionBlur; } + public double getMusicGenMaxSeconds() { + return musicGenMaxSeconds; + } + + public void setMusicGenMaxSeconds(double musicGenMaxSeconds) { + this.musicGenMaxSeconds = musicGenMaxSeconds; + } + public boolean isBeatSyncEnabled() { return beatSyncEnabled; } diff --git a/src/main/java/org/example/videoclips/editing/HighlightAssetPreparationService.java b/src/main/java/org/example/videoclips/editing/HighlightAssetPreparationService.java index 06f9574..08e9f9a 100644 --- a/src/main/java/org/example/videoclips/editing/HighlightAssetPreparationService.java +++ b/src/main/java/org/example/videoclips/editing/HighlightAssetPreparationService.java @@ -26,6 +26,7 @@ public class HighlightAssetPreparationService { private final EditAssetProvider assetProvider; private final EditAssetLibrary assetLibrary; private final VideoClippingProperties.Editing.Assets assets; + private final double musicGenMaxSeconds; private final ObjectMapper objectMapper; public HighlightAssetPreparationService(VideoClippingProperties properties, HighlightProjectStore store, @@ -35,6 +36,7 @@ public class HighlightAssetPreparationService { this.assetProvider = assetProvider; this.assetLibrary = assetLibrary; this.assets = properties.getEditing().getAssets(); + this.musicGenMaxSeconds = properties.getEditing().getMusicGenMaxSeconds(); this.objectMapper = objectMapper; } @@ -61,9 +63,12 @@ public class HighlightAssetPreparationService { copy(Path.of(music.get().path()), target); resolvedAssets.add(target.toString()); } else { + // Generate a BOUNDED music bed (MusicGen stalls on long scores); the renderer loops it to fill the + // reel. The reel length itself is unaffected. + double musicSeconds = Math.min(highlight.targetDurationSeconds(), musicGenMaxSeconds); requestFiles.add(writeRequest(requestDirectory, projectId, highlight.highlightId(), "music", "music_bed", highlight.musicDirection(), musicDirectory.resolve("music.wav"), - highlight.targetDurationSeconds(), false)); + musicSeconds, false)); } if (!highlight.voiceover().isEmpty()) { diff --git a/src/main/java/org/example/videoclips/editing/HighlightFfmpegRenderer.java b/src/main/java/org/example/videoclips/editing/HighlightFfmpegRenderer.java index 9fc6040..08fba43 100644 --- a/src/main/java/org/example/videoclips/editing/HighlightFfmpegRenderer.java +++ b/src/main/java/org/example/videoclips/editing/HighlightFfmpegRenderer.java @@ -631,7 +631,9 @@ public class HighlightFfmpegRenderer { } } if (music != null) { - command.addAll(List.of("-i", music.toString())); + // Loop the (bounded) generated bed so it fills the whole reel; the atrim below bounds it to the cue. + // Lets a long reel use a short, feasible-to-generate score instead of one impractically-long score. + command.addAll(List.of("-stream_loop", "-1", "-i", music.toString())); filters.append("[").append(input).append(":a]"); if (musicCue != null) { double duration = musicCue.timelineEndSeconds() - musicCue.timelineStartSeconds();