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.FileType;
|
||||||
import com.annimon.ffmpegbot.session.FileTypes;
|
import com.annimon.ffmpegbot.session.FileTypes;
|
||||||
import com.annimon.ffmpegbot.session.MediaSession;
|
import com.annimon.ffmpegbot.session.MediaSession;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
||||||
|
|
||||||
private boolean discardAudio;
|
private boolean discardAudio;
|
||||||
|
private String recipe;
|
||||||
private final List<String> audioCommands;
|
private final List<String> audioCommands;
|
||||||
private final List<String> videoCommands;
|
private final List<String> videoCommands;
|
||||||
private final List<String> audioFilters;
|
private final List<String> audioFilters;
|
||||||
@ -26,6 +30,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
|||||||
audioFilters = new ArrayList<>();
|
audioFilters = new ArrayList<>();
|
||||||
videoFilters = new ArrayList<>();
|
videoFilters = new ArrayList<>();
|
||||||
eq = new ArrayList<>();
|
eq = new ArrayList<>();
|
||||||
|
recipe = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -145,6 +150,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
|||||||
public void visit(OutputFormat p, MediaSession session) {
|
public void visit(OutputFormat p, MediaSession session) {
|
||||||
final String localFilename = session.getInputFile().getName();
|
final String localFilename = session.getInputFile().getName();
|
||||||
String additionalExtension = "";
|
String additionalExtension = "";
|
||||||
|
recipe = "";
|
||||||
|
|
||||||
switch (p.getValue()) {
|
switch (p.getValue()) {
|
||||||
case OutputFormat.VIDEO -> {
|
case OutputFormat.VIDEO -> {
|
||||||
@ -159,6 +165,11 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
|||||||
session.setFileType(FileType.AUDIO);
|
session.setFileType(FileType.AUDIO);
|
||||||
additionalExtension = ".mp3";
|
additionalExtension = ".mp3";
|
||||||
}
|
}
|
||||||
|
case OutputFormat.AUDIO_SPECTRUM -> {
|
||||||
|
session.setFileType(FileType.PHOTO);
|
||||||
|
additionalExtension = ".jpg";
|
||||||
|
recipe = OutputFormat.AUDIO_SPECTRUM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localFilename.toLowerCase(Locale.ENGLISH).endsWith(additionalExtension)) {
|
if (localFilename.toLowerCase(Locale.ENGLISH).endsWith(additionalExtension)) {
|
||||||
@ -167,11 +178,27 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
|||||||
session.setOutputFile(FilePath.outputFile(localFilename + additionalExtension));
|
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) {
|
public String[] buildCommand(final @NotNull MediaSession session) {
|
||||||
final var commands = new ArrayList<String>();
|
final var commands = new ArrayList<String>();
|
||||||
commands.addAll(List.of("ffmpeg", "-loglevel", "error", "-stats"));
|
commands.addAll(List.of("ffmpeg", "-loglevel", "error", "-stats"));
|
||||||
commands.addAll(session.getInputParams().asFFmpegCommands());
|
commands.addAll(session.getInputParams().asFFmpegCommands());
|
||||||
commands.addAll(List.of("-i", FilePath.inputDir() + "/" + session.getInputFile().getName()));
|
commands.addAll(List.of("-i", FilePath.inputDir() + "/" + session.getInputFile().getName()));
|
||||||
|
if (StringUtils.isNotEmpty(recipe)) {
|
||||||
|
commands.addAll(visitRecipe(recipe));
|
||||||
|
} else {
|
||||||
if (FileTypes.canContainAudio(session.getFileType())) {
|
if (FileTypes.canContainAudio(session.getFileType())) {
|
||||||
commands.addAll(audioCommands);
|
commands.addAll(audioCommands);
|
||||||
if (!audioFilters.isEmpty()) {
|
if (!audioFilters.isEmpty()) {
|
||||||
@ -189,6 +216,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
|||||||
commands.add(String.join(",", videoFilters));
|
commands.add(String.join(",", videoFilters));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
commands.addAll(List.of("-y", FilePath.outputDir() + "/" + session.getOutputFile().getName()));
|
commands.addAll(List.of("-y", FilePath.outputDir() + "/" + session.getOutputFile().getName()));
|
||||||
return commands.toArray(String[]::new);
|
return commands.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ public class FilePath {
|
|||||||
case ANIMATION, VIDEO, VIDEO_NOTE -> "mp4";
|
case ANIMATION, VIDEO, VIDEO_NOTE -> "mp4";
|
||||||
case AUDIO -> "mp3";
|
case AUDIO -> "mp3";
|
||||||
case VOICE -> "ogg";
|
case VOICE -> "ogg";
|
||||||
|
case PHOTO -> "jpg";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,8 @@ package com.annimon.ffmpegbot.parameters;
|
|||||||
|
|
||||||
import com.annimon.ffmpegbot.commands.ffmpeg.Visitor;
|
import com.annimon.ffmpegbot.commands.ffmpeg.Visitor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.function.Predicate;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class OutputFormat extends StringParameter {
|
public class OutputFormat extends StringParameter {
|
||||||
public static final String ID = "output";
|
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 VIDEO = "VIDEO";
|
||||||
public static final String AUDIO = "AUDIO";
|
public static final String AUDIO = "AUDIO";
|
||||||
public static final String VIDEO_NOTE = "VIDEO NOTE";
|
public static final String VIDEO_NOTE = "VIDEO NOTE";
|
||||||
|
public static final String AUDIO_SPECTRUM = "AUDIO SPECTRUM";
|
||||||
|
|
||||||
public OutputFormat(List<String> values, String initialValue) {
|
public OutputFormat(List<String> values, String initialValue) {
|
||||||
super(ID, "➡️ Output", values, initialValue);
|
super(ID, "➡️ Output", values, initialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputFormat disableFormat(String format) {
|
public OutputFormat disableFormat(String... formats) {
|
||||||
if (possibleValues.size() <= 1) return this;
|
if (possibleValues.size() <= 1) return this;
|
||||||
|
final Set<String> set = Set.of(formats);
|
||||||
final var values = possibleValues.stream()
|
final var values = possibleValues.stream()
|
||||||
.filter(f -> !Objects.equals(f, format))
|
.filter(Predicate.not(set::contains))
|
||||||
.map(Objects::toString)
|
.map(Objects::toString)
|
||||||
.toList();
|
.toList();
|
||||||
if (possibleValues.size() == values.size()) {
|
if (possibleValues.size() == values.size()) {
|
||||||
@ -29,14 +30,13 @@ public class OutputFormat extends StringParameter {
|
|||||||
return new OutputFormat(values, values.get(0));
|
return new OutputFormat(values, values.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputFormat enableFormat(String format) {
|
public OutputFormat enableFormat(String... formats) {
|
||||||
boolean contains = possibleValues.stream()
|
final Set<String> newset = new LinkedHashSet<>(possibleValues);
|
||||||
.anyMatch(f -> Objects.equals(f, format));
|
if (newset.addAll(Set.of(formats))) {
|
||||||
if (contains) return this;
|
final var values = new ArrayList<>(newset);
|
||||||
|
return new OutputFormat(new ArrayList<>(values), values.get(0));
|
||||||
final var values = new ArrayList<String>(possibleValues);
|
}
|
||||||
values.add(format);
|
return this;
|
||||||
return new OutputFormat(values, values.get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -13,7 +13,7 @@ public class AudioResolver implements ParametersResolver {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(@NotNull Parameters parameters, @NotNull FileInfo fileInfo) {
|
public void resolve(@NotNull Parameters parameters, @NotNull FileInfo fileInfo) {
|
||||||
final boolean hasAudio = switch (fileInfo.fileType()) {
|
final boolean hasAudio = switch (fileInfo.fileType()) {
|
||||||
case ANIMATION -> false;
|
case PHOTO, ANIMATION -> false;
|
||||||
case AUDIO, VOICE -> true;
|
case AUDIO, VOICE -> true;
|
||||||
case VIDEO, VIDEO_NOTE -> true; // TODO: add actual ffprobe check for audio
|
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);
|
Optional<OutputFormat> outputFormat = parameters.findById(OutputFormat.ID, OutputFormat.class);
|
||||||
if (p.getValueAsPrimitive()) {
|
if (p.getValueAsPrimitive()) {
|
||||||
parameters.disableAll(parameterIds);
|
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 {
|
} else {
|
||||||
parameters.enableAll(parameterIds);
|
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) {
|
private void disableAudioParam(@NotNull Parameters parameters, @NotNull FileType fileType) {
|
||||||
final boolean canAudioBeDisabled = switch (fileType) {
|
final boolean canAudioBeDisabled = switch (fileType) {
|
||||||
case ANIMATION, AUDIO, VOICE -> false;
|
case ANIMATION, AUDIO, VOICE -> false;
|
||||||
case VIDEO, VIDEO_NOTE -> true;
|
case PHOTO, VIDEO, VIDEO_NOTE -> true;
|
||||||
};
|
};
|
||||||
if (canAudioBeDisabled) {
|
if (canAudioBeDisabled) {
|
||||||
parameters.add(new DisableAudio());
|
parameters.add(new DisableAudio());
|
||||||
|
@ -15,7 +15,7 @@ public class OutputFormatResolver implements ParametersResolver {
|
|||||||
public void resolve(@NotNull Parameters parameters, @NotNull FileInfo fileInfo) {
|
public void resolve(@NotNull Parameters parameters, @NotNull FileInfo fileInfo) {
|
||||||
final var outputFormat = switch (fileInfo.fileType()) {
|
final var outputFormat = switch (fileInfo.fileType()) {
|
||||||
case VIDEO -> forVideo(fileInfo);
|
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);
|
case ANIMATION -> forAnimation(fileInfo);
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
@ -32,6 +32,7 @@ public class OutputFormatResolver implements ParametersResolver {
|
|||||||
types.add(VIDEO_NOTE);
|
types.add(VIDEO_NOTE);
|
||||||
}
|
}
|
||||||
types.add(AUDIO);
|
types.add(AUDIO);
|
||||||
|
types.add(AUDIO_SPECTRUM);
|
||||||
return new OutputFormat(types, VIDEO);
|
return new OutputFormat(types, VIDEO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import com.annimon.ffmpegbot.parameters.*;
|
|||||||
import com.annimon.ffmpegbot.session.FileInfo;
|
import com.annimon.ffmpegbot.session.FileInfo;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class VideoResolver implements ParametersResolver {
|
public class VideoResolver implements ParametersResolver {
|
||||||
@ -41,7 +40,8 @@ public class VideoResolver implements ParametersResolver {
|
|||||||
VideoScale.ID,
|
VideoScale.ID,
|
||||||
VideoFrameRate.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);
|
parameters.disableAll(parameterIds);
|
||||||
} else {
|
} else {
|
||||||
parameters.enableAll(parameterIds);
|
parameters.enableAll(parameterIds);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.annimon.ffmpegbot.session;
|
package com.annimon.ffmpegbot.session;
|
||||||
|
|
||||||
public enum FileType {
|
public enum FileType {
|
||||||
|
PHOTO,
|
||||||
ANIMATION,
|
ANIMATION,
|
||||||
AUDIO,
|
AUDIO,
|
||||||
VIDEO,
|
VIDEO,
|
||||||
|
@ -57,6 +57,7 @@ public class Resolver {
|
|||||||
return switch (fileType) {
|
return switch (fileType) {
|
||||||
case ANIMATION -> Methods.sendAnimation();
|
case ANIMATION -> Methods.sendAnimation();
|
||||||
case AUDIO -> Methods.sendAudio();
|
case AUDIO -> Methods.sendAudio();
|
||||||
|
case PHOTO -> Methods.sendPhoto();
|
||||||
case VIDEO -> Methods.sendVideo();
|
case VIDEO -> Methods.sendVideo();
|
||||||
case VIDEO_NOTE -> Methods.sendVideoNote();
|
case VIDEO_NOTE -> Methods.sendVideoNote();
|
||||||
case VOICE -> Methods.sendVoice();
|
case VOICE -> Methods.sendVoice();
|
||||||
@ -65,6 +66,7 @@ public class Resolver {
|
|||||||
|
|
||||||
public static ActionType resolveAction(@NotNull FileType fileType) {
|
public static ActionType resolveAction(@NotNull FileType fileType) {
|
||||||
return switch (fileType) {
|
return switch (fileType) {
|
||||||
|
case PHOTO -> ActionType.UPLOAD_PHOTO;
|
||||||
case VIDEO -> ActionType.UPLOAD_VIDEO;
|
case VIDEO -> ActionType.UPLOAD_VIDEO;
|
||||||
case VIDEO_NOTE -> ActionType.UPLOAD_VIDEO_NOTE;
|
case VIDEO_NOTE -> ActionType.UPLOAD_VIDEO_NOTE;
|
||||||
case VOICE -> ActionType.UPLOAD_VOICE;
|
case VOICE -> ActionType.UPLOAD_VOICE;
|
||||||
|
Loading…
Reference in New Issue
Block a user