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:
parent
5ca02ba7d4
commit
bebc9ce911
@ -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,11 +178,27 @@ 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 (StringUtils.isNotEmpty(recipe)) {
|
||||
commands.addAll(visitRecipe(recipe));
|
||||
} else {
|
||||
if (FileTypes.canContainAudio(session.getFileType())) {
|
||||
commands.addAll(audioCommands);
|
||||
if (!audioFilters.isEmpty()) {
|
||||
@ -189,6 +216,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
||||
commands.add(String.join(",", videoFilters));
|
||||
}
|
||||
}
|
||||
}
|
||||
commands.addAll(List.of("-y", FilePath.outputDir() + "/" + session.getOutputFile().getName()));
|
||||
return commands.toArray(String[]::new);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ public class FilePath {
|
||||
case ANIMATION, VIDEO, VIDEO_NOTE -> "mp4";
|
||||
case AUDIO -> "mp3";
|
||||
case VOICE -> "ogg";
|
||||
case PHOTO -> "jpg";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.annimon.ffmpegbot.session;
|
||||
|
||||
public enum FileType {
|
||||
PHOTO,
|
||||
ANIMATION,
|
||||
AUDIO,
|
||||
VIDEO,
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user