1
0
mirror of https://github.com/aNNiMON/ffmpegbot synced 2024-09-19 22:54:20 +03:00

Add audio spectrum output

This commit is contained in:
aNNiMON 2024-09-15 19:53:04 +03:00
parent 5ca02ba7d4
commit bebc9ce911
8 changed files with 78 additions and 34 deletions

View File

@ -5,15 +5,19 @@ import com.annimon.ffmpegbot.file.FilePath;
import com.annimon.ffmpegbot.session.FileType;
import com.annimon.ffmpegbot.session.FileTypes;
import com.annimon.ffmpegbot.session.MediaSession;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FFmpegCommandBuilder implements Visitor<MediaSession> {
private boolean discardAudio;
private String recipe;
private final List<String> audioCommands;
private final List<String> videoCommands;
private final List<String> audioFilters;
@ -26,6 +30,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
audioFilters = new ArrayList<>();
videoFilters = new ArrayList<>();
eq = new ArrayList<>();
recipe = "";
}
@Override
@ -145,6 +150,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
public void visit(OutputFormat p, MediaSession session) {
final String localFilename = session.getInputFile().getName();
String additionalExtension = "";
recipe = "";
switch (p.getValue()) {
case OutputFormat.VIDEO -> {
@ -159,6 +165,11 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
session.setFileType(FileType.AUDIO);
additionalExtension = ".mp3";
}
case OutputFormat.AUDIO_SPECTRUM -> {
session.setFileType(FileType.PHOTO);
additionalExtension = ".jpg";
recipe = OutputFormat.AUDIO_SPECTRUM;
}
}
if (localFilename.toLowerCase(Locale.ENGLISH).endsWith(additionalExtension)) {
@ -167,26 +178,43 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
session.setOutputFile(FilePath.outputFile(localFilename + additionalExtension));
}
private List<String> visitRecipe(String recipe) {
return switch (recipe) {
case OutputFormat.AUDIO_SPECTRUM -> List.of(
"-vn",
"-filter_complex",
Stream.concat(audioFilters.stream(), Stream.of("showspectrumpic=s=1200x640:mode=separate"))
.collect(Collectors.joining(",")),
"-frames:v", "1"
);
default -> List.of();
};
}
public String[] buildCommand(final @NotNull MediaSession session) {
final var commands = new ArrayList<String>();
commands.addAll(List.of("ffmpeg", "-loglevel", "error", "-stats"));
commands.addAll(session.getInputParams().asFFmpegCommands());
commands.addAll(List.of("-i", FilePath.inputDir() + "/" + session.getInputFile().getName()));
if (FileTypes.canContainAudio(session.getFileType())) {
commands.addAll(audioCommands);
if (!audioFilters.isEmpty()) {
commands.add("-af");
commands.add(String.join(",", audioFilters));
if (StringUtils.isNotEmpty(recipe)) {
commands.addAll(visitRecipe(recipe));
} else {
if (FileTypes.canContainAudio(session.getFileType())) {
commands.addAll(audioCommands);
if (!audioFilters.isEmpty()) {
commands.add("-af");
commands.add(String.join(",", audioFilters));
}
}
}
if (FileTypes.canContainVideo(session.getFileType())) {
commands.addAll(videoCommands);
if (!eq.isEmpty()) {
videoFilters.add("eq=" + String.join(":", eq));
}
if (!videoFilters.isEmpty()) {
commands.add("-vf");
commands.add(String.join(",", videoFilters));
if (FileTypes.canContainVideo(session.getFileType())) {
commands.addAll(videoCommands);
if (!eq.isEmpty()) {
videoFilters.add("eq=" + String.join(":", eq));
}
if (!videoFilters.isEmpty()) {
commands.add("-vf");
commands.add(String.join(",", videoFilters));
}
}
}
commands.addAll(List.of("-y", FilePath.outputDir() + "/" + session.getOutputFile().getName()));

View File

@ -34,6 +34,7 @@ public class FilePath {
case ANIMATION, VIDEO, VIDEO_NOTE -> "mp4";
case AUDIO -> "mp3";
case VOICE -> "ogg";
case PHOTO -> "jpg";
};
}
}

View File

@ -2,9 +2,8 @@ package com.annimon.ffmpegbot.parameters;
import com.annimon.ffmpegbot.commands.ffmpeg.Visitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.function.Predicate;
public class OutputFormat extends StringParameter {
public static final String ID = "output";
@ -12,15 +11,17 @@ public class OutputFormat extends StringParameter {
public static final String VIDEO = "VIDEO";
public static final String AUDIO = "AUDIO";
public static final String VIDEO_NOTE = "VIDEO NOTE";
public static final String AUDIO_SPECTRUM = "AUDIO SPECTRUM";
public OutputFormat(List<String> values, String initialValue) {
super(ID, "➡️ Output", values, initialValue);
}
public OutputFormat disableFormat(String format) {
public OutputFormat disableFormat(String... formats) {
if (possibleValues.size() <= 1) return this;
final Set<String> set = Set.of(formats);
final var values = possibleValues.stream()
.filter(f -> !Objects.equals(f, format))
.filter(Predicate.not(set::contains))
.map(Objects::toString)
.toList();
if (possibleValues.size() == values.size()) {
@ -29,14 +30,13 @@ public class OutputFormat extends StringParameter {
return new OutputFormat(values, values.get(0));
}
public OutputFormat enableFormat(String format) {
boolean contains = possibleValues.stream()
.anyMatch(f -> Objects.equals(f, format));
if (contains) return this;
final var values = new ArrayList<String>(possibleValues);
values.add(format);
return new OutputFormat(values, values.get(0));
public OutputFormat enableFormat(String... formats) {
final Set<String> newset = new LinkedHashSet<>(possibleValues);
if (newset.addAll(Set.of(formats))) {
final var values = new ArrayList<>(newset);
return new OutputFormat(new ArrayList<>(values), values.get(0));
}
return this;
}
@Override

View File

@ -13,7 +13,7 @@ public class AudioResolver implements ParametersResolver {
@Override
public void resolve(@NotNull Parameters parameters, @NotNull FileInfo fileInfo) {
final boolean hasAudio = switch (fileInfo.fileType()) {
case ANIMATION -> false;
case PHOTO, ANIMATION -> false;
case AUDIO, VOICE -> true;
case VIDEO, VIDEO_NOTE -> true; // TODO: add actual ffprobe check for audio
};
@ -44,18 +44,29 @@ public class AudioResolver implements ParametersResolver {
Optional<OutputFormat> outputFormat = parameters.findById(OutputFormat.ID, OutputFormat.class);
if (p.getValueAsPrimitive()) {
parameters.disableAll(parameterIds);
outputFormat.ifPresent(par -> parameters.add(par.disableFormat(OutputFormat.AUDIO)));
outputFormat.ifPresent(par -> parameters.add(par.disableFormat(OutputFormat.AUDIO, OutputFormat.AUDIO_SPECTRUM)));
} else {
parameters.enableAll(parameterIds);
outputFormat.ifPresent(par -> parameters.add(par.enableFormat(OutputFormat.AUDIO)));
outputFormat.ifPresent(par -> parameters.add(par.enableFormat(OutputFormat.AUDIO, OutputFormat.AUDIO_SPECTRUM)));
}
});
parameters.findById(OutputFormat.ID, OutputFormat.class)
.map(Parameter::getValue)
.ifPresent(format -> {
// Audio spectrum ignores audio commands (bitrate)
if (format.equals(OutputFormat.AUDIO_SPECTRUM)) {
parameters.disable(AudioBitrate.ID);
} else {
parameters.enable(AudioBitrate.ID);
}
});
}
private void disableAudioParam(@NotNull Parameters parameters, @NotNull FileType fileType) {
final boolean canAudioBeDisabled = switch (fileType) {
case ANIMATION, AUDIO, VOICE -> false;
case VIDEO, VIDEO_NOTE -> true;
case PHOTO, VIDEO, VIDEO_NOTE -> true;
};
if (canAudioBeDisabled) {
parameters.add(new DisableAudio());

View File

@ -15,7 +15,7 @@ public class OutputFormatResolver implements ParametersResolver {
public void resolve(@NotNull Parameters parameters, @NotNull FileInfo fileInfo) {
final var outputFormat = switch (fileInfo.fileType()) {
case VIDEO -> forVideo(fileInfo);
case VIDEO_NOTE -> new OutputFormat(List.of(VIDEO_NOTE, VIDEO, AUDIO), VIDEO_NOTE);
case VIDEO_NOTE -> new OutputFormat(List.of(VIDEO_NOTE, VIDEO, AUDIO, AUDIO_SPECTRUM), VIDEO_NOTE);
case ANIMATION -> forAnimation(fileInfo);
default -> null;
};
@ -32,6 +32,7 @@ public class OutputFormatResolver implements ParametersResolver {
types.add(VIDEO_NOTE);
}
types.add(AUDIO);
types.add(AUDIO_SPECTRUM);
return new OutputFormat(types, VIDEO);
}

View File

@ -4,7 +4,6 @@ import com.annimon.ffmpegbot.parameters.*;
import com.annimon.ffmpegbot.session.FileInfo;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class VideoResolver implements ParametersResolver {
@ -41,7 +40,8 @@ public class VideoResolver implements ParametersResolver {
VideoScale.ID,
VideoFrameRate.ID
);
if (Objects.equals(format, OutputFormat.AUDIO)) {
Set<String> audioOnly = Set.of(OutputFormat.AUDIO, OutputFormat.AUDIO_SPECTRUM);
if (audioOnly.contains(format)) {
parameters.disableAll(parameterIds);
} else {
parameters.enableAll(parameterIds);

View File

@ -1,6 +1,7 @@
package com.annimon.ffmpegbot.session;
public enum FileType {
PHOTO,
ANIMATION,
AUDIO,
VIDEO,

View File

@ -57,6 +57,7 @@ public class Resolver {
return switch (fileType) {
case ANIMATION -> Methods.sendAnimation();
case AUDIO -> Methods.sendAudio();
case PHOTO -> Methods.sendPhoto();
case VIDEO -> Methods.sendVideo();
case VIDEO_NOTE -> Methods.sendVideoNote();
case VOICE -> Methods.sendVoice();
@ -65,6 +66,7 @@ public class Resolver {
public static ActionType resolveAction(@NotNull FileType fileType) {
return switch (fileType) {
case PHOTO -> ActionType.UPLOAD_PHOTO;
case VIDEO -> ActionType.UPLOAD_VIDEO;
case VIDEO_NOTE -> ActionType.UPLOAD_VIDEO_NOTE;
case VOICE -> ActionType.UPLOAD_VOICE;