forked from jsl/video_editing_poc
Add edit audio mixing
This commit is contained in:
parent
46a4982ef9
commit
a3ca4826f5
|
|
@ -799,7 +799,7 @@ It writes the script only and requires an imported audio file.
|
|||
16. [x] Add `PUT /v1/edit-projects/{projectId}/plan`.
|
||||
17. [x] Add simple segment rendering without transitions.
|
||||
18. [x] Add concat-based final timeline rendering.
|
||||
19. [ ] Add music and voiceover audio mixing.
|
||||
19. [x] Add music and voiceover audio mixing.
|
||||
20. [ ] Add render manifest with command metadata and output details.
|
||||
21. [ ] Add basic transition support: cut, crossfade, fade in, fade out.
|
||||
22. [ ] Add SFX cue support.
|
||||
|
|
|
|||
|
|
@ -64,7 +64,15 @@ public class FfmpegEditRenderer implements EditRenderer {
|
|||
Path timeline = work.resolve("timeline-video.mp4");
|
||||
run(concatCommand(concatFile, timeline));
|
||||
Path output = store.projectDirectory(projectId).resolve("final.mp4");
|
||||
copy(timeline, output);
|
||||
Path audioDirectory = store.projectDirectory(projectId).resolve("audio");
|
||||
Path music = audioDirectory.resolve("music.wav");
|
||||
Path voiceover = audioDirectory.resolve("voiceover.wav");
|
||||
if (Files.isRegularFile(music) || Files.isRegularFile(voiceover)) {
|
||||
run(audioMixCommand(timeline, Files.isRegularFile(music) ? music : null,
|
||||
Files.isRegularFile(voiceover) ? voiceover : null, output));
|
||||
} else {
|
||||
copy(timeline, output);
|
||||
}
|
||||
projectService.updateProject(projectId, EditProjectStatus.RENDERED, Path.of(project.inputDirectory()), null);
|
||||
}
|
||||
|
||||
|
|
@ -91,6 +99,31 @@ public class FfmpegEditRenderer implements EditRenderer {
|
|||
"-i", concatFile.toString(), "-c", "copy", output.toString());
|
||||
}
|
||||
|
||||
List<String> audioMixCommand(Path timeline, Path music, Path voiceover, Path output) {
|
||||
List<String> command = new ArrayList<>(List.of(properties.getFfmpegBinary(), "-hide_banner", "-y",
|
||||
"-i", timeline.toString()));
|
||||
List<String> labels = new ArrayList<>(List.of("[0:a]"));
|
||||
int input = 1;
|
||||
StringBuilder filters = new StringBuilder();
|
||||
if (music != null) {
|
||||
command.addAll(List.of("-i", music.toString()));
|
||||
filters.append("[").append(input).append(":a]volume=0.25[music];");
|
||||
labels.add("[music]");
|
||||
input++;
|
||||
}
|
||||
if (voiceover != null) {
|
||||
command.addAll(List.of("-i", voiceover.toString()));
|
||||
filters.append("[").append(input).append(":a]volume=1.0[voice];");
|
||||
labels.add("[voice]");
|
||||
}
|
||||
filters.append(String.join("", labels)).append("amix=inputs=").append(labels.size())
|
||||
.append(":duration=first:dropout_transition=2[a]");
|
||||
command.addAll(List.of("-filter_complex", filters.toString(), "-map", "0:v:0", "-map", "[a]",
|
||||
"-c:v", "copy", "-c:a", "aac", "-b:a", properties.getAudioBitrate(),
|
||||
"-ar", Integer.toString(properties.getAudioSampleRate()), output.toString()));
|
||||
return List.copyOf(command);
|
||||
}
|
||||
|
||||
private void writeConcatFile(Path file, List<Path> segments) {
|
||||
String content = segments.stream()
|
||||
.map(path -> "file '" + path.toAbsolutePath().toString().replace("'", "'\\''") + "'")
|
||||
|
|
|
|||
|
|
@ -38,4 +38,19 @@ class FfmpegEditRendererTest {
|
|||
assertThat(command).containsSubsequence("-f", "concat", "-safe", "0", "-i", "concat.txt");
|
||||
assertThat(command).containsSubsequence("-c", "copy", "timeline-video.mp4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildsMusicAndVoiceoverMixCommand() {
|
||||
VideoClippingProperties properties = new VideoClippingProperties();
|
||||
FfmpegEditRenderer renderer = new FfmpegEditRenderer(properties, mock(EditProjectStore.class),
|
||||
mock(EditProjectService.class), mock(EditPlanValidator.class), command -> null);
|
||||
|
||||
var command = renderer.audioMixCommand(Path.of("timeline.mp4"), Path.of("music.wav"),
|
||||
Path.of("voiceover.wav"), Path.of("final.mp4"));
|
||||
|
||||
assertThat(command).containsSubsequence("-i", "timeline.mp4", "-i", "music.wav", "-i", "voiceover.wav");
|
||||
assertThat(command).contains("[1:a]volume=0.25[music];[2:a]volume=1.0[voice];"
|
||||
+ "[0:a][music][voice]amix=inputs=3:duration=first:dropout_transition=2[a]");
|
||||
assertThat(command).containsSubsequence("-c:v", "copy", "-c:a", "aac");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue