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 <noreply@anthropic.com>
This commit is contained in:
parent
5b98c0538f
commit
e6a7aecd97
|
|
@ -545,6 +545,13 @@ public class VideoClippingProperties {
|
||||||
/** R12 cinematic motion blur (light frame blend) in styled shots. Emulates shutter-angle blur. */
|
/** R12 cinematic motion blur (light frame blend) in styled shots. Emulates shutter-angle blur. */
|
||||||
private boolean cinematicMotionBlur = true;
|
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). */
|
/** R13 snap montage cut boundaries onto the generated score's beat grid (cut-to-the-music). */
|
||||||
private boolean beatSyncEnabled = false;
|
private boolean beatSyncEnabled = false;
|
||||||
|
|
||||||
|
|
@ -787,6 +794,14 @@ public class VideoClippingProperties {
|
||||||
this.cinematicMotionBlur = cinematicMotionBlur;
|
this.cinematicMotionBlur = cinematicMotionBlur;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getMusicGenMaxSeconds() {
|
||||||
|
return musicGenMaxSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMusicGenMaxSeconds(double musicGenMaxSeconds) {
|
||||||
|
this.musicGenMaxSeconds = musicGenMaxSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isBeatSyncEnabled() {
|
public boolean isBeatSyncEnabled() {
|
||||||
return beatSyncEnabled;
|
return beatSyncEnabled;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ public class HighlightAssetPreparationService {
|
||||||
private final EditAssetProvider assetProvider;
|
private final EditAssetProvider assetProvider;
|
||||||
private final EditAssetLibrary assetLibrary;
|
private final EditAssetLibrary assetLibrary;
|
||||||
private final VideoClippingProperties.Editing.Assets assets;
|
private final VideoClippingProperties.Editing.Assets assets;
|
||||||
|
private final double musicGenMaxSeconds;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
public HighlightAssetPreparationService(VideoClippingProperties properties, HighlightProjectStore store,
|
public HighlightAssetPreparationService(VideoClippingProperties properties, HighlightProjectStore store,
|
||||||
|
|
@ -35,6 +36,7 @@ public class HighlightAssetPreparationService {
|
||||||
this.assetProvider = assetProvider;
|
this.assetProvider = assetProvider;
|
||||||
this.assetLibrary = assetLibrary;
|
this.assetLibrary = assetLibrary;
|
||||||
this.assets = properties.getEditing().getAssets();
|
this.assets = properties.getEditing().getAssets();
|
||||||
|
this.musicGenMaxSeconds = properties.getEditing().getMusicGenMaxSeconds();
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,9 +63,12 @@ public class HighlightAssetPreparationService {
|
||||||
copy(Path.of(music.get().path()), target);
|
copy(Path.of(music.get().path()), target);
|
||||||
resolvedAssets.add(target.toString());
|
resolvedAssets.add(target.toString());
|
||||||
} else {
|
} 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",
|
requestFiles.add(writeRequest(requestDirectory, projectId, highlight.highlightId(), "music",
|
||||||
"music_bed", highlight.musicDirection(), musicDirectory.resolve("music.wav"),
|
"music_bed", highlight.musicDirection(), musicDirectory.resolve("music.wav"),
|
||||||
highlight.targetDurationSeconds(), false));
|
musicSeconds, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!highlight.voiceover().isEmpty()) {
|
if (!highlight.voiceover().isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -631,7 +631,9 @@ public class HighlightFfmpegRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (music != null) {
|
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]");
|
filters.append("[").append(input).append(":a]");
|
||||||
if (musicCue != null) {
|
if (musicCue != null) {
|
||||||
double duration = musicCue.timelineEndSeconds() - musicCue.timelineStartSeconds();
|
double duration = musicCue.timelineEndSeconds() - musicCue.timelineStartSeconds();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue