Adds converter modules and sorters
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good
Converters are now the result of combining multiple modules, and running them in the selected sequence. I hope to be able to completely remove converters by replacing them with templates that simply run some modules in a sequence, before running FFmpeg. Then I want to be able to parse a command into a template, so users have full control of what a template does. Sorters each sort a list of streams after a single criteria. They can be chained in order to create complex chained sorters.
This commit is contained in:
parent
376d5655f2
commit
461c7552b3
@ -1,6 +1,5 @@
|
||||
package net.knarcraft.ffmpegconverter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.converter.AnimeConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.AudioConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.Converter;
|
||||
import net.knarcraft.ffmpegconverter.converter.DownScaleConverter;
|
||||
@ -9,7 +8,9 @@ import net.knarcraft.ffmpegconverter.converter.MkvH264Converter;
|
||||
import net.knarcraft.ffmpegconverter.converter.MkvH265ReducedConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed;
|
||||
import net.knarcraft.ffmpegconverter.converter.VideoConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.WebAnimeConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.WebVideoConverter;
|
||||
import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
|
||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
|
||||
import net.knarcraft.ffmpegconverter.utility.ListUtil;
|
||||
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
|
||||
@ -189,9 +190,9 @@ class Main {
|
||||
String[] audioLanguage = new String[]{"jpn", "0"};
|
||||
String[] subtitleLanguage = new String[]{"eng", "0"};
|
||||
boolean toStereo = true;
|
||||
boolean preventSigns = true;
|
||||
int forcedAudioIndex = -1;
|
||||
int forcedSubtitleIndex = -1;
|
||||
MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID;
|
||||
int forcedAudioIndex = 0;
|
||||
int forcedSubtitleIndex = 0;
|
||||
String subtitleNameFilter = "";
|
||||
|
||||
if (!input.isEmpty()) {
|
||||
@ -204,7 +205,7 @@ class Main {
|
||||
toStereo = Boolean.parseBoolean(input.get(2));
|
||||
}
|
||||
if (input.size() > 3) {
|
||||
preventSigns = Boolean.parseBoolean(input.get(3));
|
||||
subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase());
|
||||
}
|
||||
try {
|
||||
if (input.size() > 4) {
|
||||
@ -220,8 +221,8 @@ class Main {
|
||||
if (input.size() > 6) {
|
||||
subtitleNameFilter = input.get(6);
|
||||
}
|
||||
return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, toStereo,
|
||||
preventSigns, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter);
|
||||
return new WebAnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, toStereo,
|
||||
subtitlePreference, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
package net.knarcraft.ffmpegconverter.container;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -13,4 +17,56 @@ import java.util.List;
|
||||
* @param parsedStreams <p>The streams that were parsed from the files</p>
|
||||
*/
|
||||
public record StreamProbeResult(@NotNull List<File> parsedFiles, @NotNull List<StreamObject> parsedStreams) {
|
||||
|
||||
/**
|
||||
* Gets all probed subtitle streams
|
||||
*
|
||||
* @return <p>All probed subtitle streams</p>
|
||||
*/
|
||||
@NotNull
|
||||
public List<SubtitleStream> getSubtitleStreams() {
|
||||
return filterStreamsByType(this.parsedStreams, SubtitleStream.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all probed audio streams
|
||||
*
|
||||
* @return <p>All probed audio streams</p>
|
||||
*/
|
||||
@NotNull
|
||||
public List<AudioStream> getAudioStreams() {
|
||||
return filterStreamsByType(this.parsedStreams, AudioStream.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all probed video streams
|
||||
*
|
||||
* @return <p>All probed video streams</p>
|
||||
*/
|
||||
@NotNull
|
||||
public List<VideoStream> getVideoStreams() {
|
||||
return filterStreamsByType(this.parsedStreams, VideoStream.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters parsed streams into one of the stream types
|
||||
*
|
||||
* @param streams <p>A list of stream objects.</p>
|
||||
* @param clazz <p>The class to filter</p>
|
||||
* @param <G> <p>The correct object type for the streams with the selected codec type.</p>
|
||||
* @return <p>A potentially shorter list of streams.</p>
|
||||
*/
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
private <G extends StreamObject> List<G> filterStreamsByType(@NotNull List<StreamObject> streams,
|
||||
@NotNull Class<?> clazz) {
|
||||
List<G> newStreams = new ArrayList<>();
|
||||
for (StreamObject stream : streams) {
|
||||
if (stream.getClass() == clazz) {
|
||||
newStreams.add((G) stream);
|
||||
}
|
||||
}
|
||||
return newStreams;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
|
||||
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
|
||||
@ -13,8 +9,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implements all methods which can be useful for any implementation of a converter.
|
||||
@ -43,79 +37,6 @@ public abstract class AbstractConverter implements Converter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters parsed streams into one of the stream types
|
||||
*
|
||||
* @param streams <p>A list of stream objects.</p>
|
||||
* @param clazz <p>The class to filter</p>
|
||||
* @param <G> <p>The correct object type for the streams with the selected codec type.</p>
|
||||
* @return <p>A potentially shorter list of streams.</p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <G extends StreamObject> List<G> filterStreamsByType(List<StreamObject> streams, Class<?> clazz) {
|
||||
List<G> newStreams = new ArrayList<>();
|
||||
for (StreamObject stream : streams) {
|
||||
if (stream.getClass() == clazz) {
|
||||
newStreams.add((G) stream);
|
||||
}
|
||||
}
|
||||
return newStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters and sorts audio streams according to chosen languages
|
||||
*
|
||||
* @param audioStreams <p>A list of audio streams.</p>
|
||||
* @param audioLanguages <p>A list of languages.</p>
|
||||
* @return <p>A list containing just audio tracks of chosen languages, sorted in order of languages.</p>
|
||||
*/
|
||||
static List<AudioStream> filterAudioStreams(List<AudioStream> audioStreams, String[] audioLanguages) {
|
||||
return sortStreamsByLanguage(audioStreams, audioLanguages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters and sorts subtitle streams according to chosen languages
|
||||
*
|
||||
* @param subtitleStreams <p>A list of subtitle streams.</p>
|
||||
* @param subtitleLanguages <p>A list of languages.</p>
|
||||
* @param preventSignsAndSongs <p>Whether partial subtitles should be avoided.</p>
|
||||
* @param subtitleNameFilter <p>The filter to use for forcing streams of a given subtitle group</p>
|
||||
* @return <p>A list containing just subtitles of chosen languages, sorted in order of languages.</p>
|
||||
*/
|
||||
static List<SubtitleStream> filterSubtitleStreams(List<SubtitleStream> subtitleStreams, String[] subtitleLanguages,
|
||||
boolean preventSignsAndSongs, String subtitleNameFilter) {
|
||||
List<SubtitleStream> sorted = sortStreamsByLanguage(subtitleStreams, subtitleLanguages);
|
||||
sorted.removeIf((stream) -> preventSignsAndSongs && !stream.getIsFullSubtitle());
|
||||
//Filter by name of subtitle group, PGS or similar
|
||||
if (!subtitleNameFilter.trim().isEmpty()) {
|
||||
sorted.removeIf((stream) -> !stream.getTitle().contains(subtitleNameFilter));
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts subtitle streams according to chosen languages and removes non-matching languages
|
||||
*
|
||||
* @param streams <p>A list of streams to sort.</p>
|
||||
* @param languages <p>The languages chosen by the user.</p>
|
||||
* @param <G> <p>The type of streams to sort.</p>
|
||||
* @return <p>A sorted version of the list.</p>
|
||||
*/
|
||||
private static <G extends StreamObject> List<G> sortStreamsByLanguage(List<G> streams, String[] languages) {
|
||||
List<G> sorted = new ArrayList<>();
|
||||
for (String language : languages) {
|
||||
for (G stream : streams) {
|
||||
String streamLanguage = stream.getLanguage();
|
||||
if (language.equals("*") || (streamLanguage.equals("und") && language.equals("0")) ||
|
||||
streamLanguage.equals(language)) {
|
||||
sorted.add(stream);
|
||||
}
|
||||
}
|
||||
streams.removeAll(sorted);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads streams from a file, and converts it to a mp4 file
|
||||
*
|
||||
@ -146,69 +67,6 @@ public abstract class AbstractConverter implements Converter {
|
||||
FFMpegHelper.runProcess(processBuilder, folder, "\n", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nth audio stream from a list of streams
|
||||
*
|
||||
* @param streams <p>A list of all streams</p>
|
||||
* @param n <p>The index of the audio stream to get</p>
|
||||
* @return <p>The first audio stream found or null if no audio streams were found</p>
|
||||
*/
|
||||
protected AudioStream getNthAudioSteam(List<StreamObject> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
List<AudioStream> audioStreams = filterStreamsByType(streams, AudioStream.class);
|
||||
AudioStream audioStream = null;
|
||||
if (audioStreams.size() > n) {
|
||||
audioStream = audioStreams.get(n);
|
||||
} else if (!audioStreams.isEmpty()) {
|
||||
audioStream = audioStreams.get(0);
|
||||
}
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nth subtitle stream from a list of streams
|
||||
*
|
||||
* @param streams <p>A list of all streams</p>
|
||||
* @param n <p>The index of the subtitle stream to get</p>
|
||||
* @return <p>The first subtitle stream found or null if no subtitle streams were found</p>
|
||||
*/
|
||||
protected SubtitleStream getNthSubtitleStream(List<StreamObject> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
List<SubtitleStream> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
SubtitleStream subtitleStream = null;
|
||||
if (subtitleStreams.size() > n) {
|
||||
subtitleStream = subtitleStreams.get(n);
|
||||
} else if (!subtitleStreams.isEmpty()) {
|
||||
subtitleStream = subtitleStreams.get(0);
|
||||
}
|
||||
return subtitleStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nth video stream from a list of streams
|
||||
*
|
||||
* @param streams <p>A list of all streams</p>
|
||||
* @param n <p>The index of the video stream to get</p>
|
||||
* @return <p>The nth video stream, or null if no video streams were found</p>
|
||||
*/
|
||||
protected @Nullable VideoStream getNthVideoStream(@NotNull List<StreamObject> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
List<VideoStream> videoStreams = filterStreamsByType(streams, VideoStream.class);
|
||||
VideoStream videoStream = null;
|
||||
if (videoStreams.size() > n) {
|
||||
videoStream = videoStreams.get(n);
|
||||
} else if (!videoStreams.isEmpty()) {
|
||||
videoStream = videoStreams.get(0);
|
||||
}
|
||||
return videoStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convert(@NotNull File file) throws IOException {
|
||||
processFile(file.getParentFile(), file);
|
||||
|
@ -1,95 +0,0 @@
|
||||
package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A converter mainly designed for converting anime to web-playable mp4
|
||||
*/
|
||||
public class AnimeConverter extends AbstractConverter {
|
||||
|
||||
private final String[] audioLanguages;
|
||||
private final String[] subtitleLanguages;
|
||||
private final boolean toStereo;
|
||||
private final boolean preventSignsAndSongs;
|
||||
private final int forcedAudioIndex;
|
||||
private final int forcedSubtitleIndex;
|
||||
private final String subtitleNameFilter;
|
||||
|
||||
/**
|
||||
* Instantiates a new anime converter
|
||||
*
|
||||
* @param ffprobePath <p>Path/command to ffprobe.</p>
|
||||
* @param ffmpegPath <p>Path/command to ffmpeg.</p>
|
||||
* @param audioLanguages <p>List of wanted audio languages in descending order.</p>
|
||||
* @param subtitleLanguages <p>List of wanted subtitle languages in descending order.</p>
|
||||
* @param toStereo <p>Convert video with several audio channels to stereo.</p>
|
||||
* @param preventSignsAndSongs <p>Prevent subtitles only converting signs and songs (not speech).</p>
|
||||
* @param forcedAudioIndex <p>A specific audio stream to force. 0-indexed from the first audio stream found</p>
|
||||
* @param forcedSubtitleIndex <p>A specific subtitle stream to force. 0-indexed for the first subtitle stream found</p>
|
||||
*/
|
||||
public AnimeConverter(String ffprobePath, String ffmpegPath, String[] audioLanguages, String[] subtitleLanguages,
|
||||
boolean toStereo, boolean preventSignsAndSongs, int forcedAudioIndex, int forcedSubtitleIndex,
|
||||
String subtitleNameFilter) {
|
||||
super("mp4");
|
||||
this.ffprobePath = ffprobePath;
|
||||
this.ffmpegPath = ffmpegPath;
|
||||
this.audioLanguages = audioLanguages;
|
||||
this.subtitleLanguages = subtitleLanguages;
|
||||
this.toStereo = toStereo;
|
||||
this.preventSignsAndSongs = preventSignsAndSongs;
|
||||
this.forcedAudioIndex = forcedAudioIndex;
|
||||
this.forcedSubtitleIndex = forcedSubtitleIndex;
|
||||
this.subtitleNameFilter = subtitleNameFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
}
|
||||
|
||||
//Get the first audio stream in accordance with chosen languages
|
||||
List<AudioStream> audioStreams = filterAudioStreams(filterStreamsByType(streams, AudioStream.class),
|
||||
this.audioLanguages);
|
||||
AudioStream audioStream = getNthAudioSteam(new ArrayList<>(audioStreams), Math.max(this.forcedAudioIndex, 0));
|
||||
|
||||
//Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
|
||||
List<SubtitleStream> allSubtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
List<SubtitleStream> subtitleStreams = filterSubtitleStreams(allSubtitleStreams, this.subtitleLanguages,
|
||||
this.preventSignsAndSongs, this.subtitleNameFilter);
|
||||
SubtitleStream subtitleStream = getNthSubtitleStream(new ArrayList<>(subtitleStreams),
|
||||
Math.max(this.forcedSubtitleIndex, 0));
|
||||
|
||||
//Get the first video stream
|
||||
VideoStream videoStream = getNthVideoStream(streams, 0);
|
||||
if (videoStream == null) {
|
||||
throw new IllegalArgumentException("The selected video stream does not exist");
|
||||
}
|
||||
|
||||
//Add streams to output file
|
||||
FFMpegHelper.addAudioStream(command, audioStream, this.toStereo);
|
||||
FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream);
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValidFormats() {
|
||||
return this.videoFormats;
|
||||
}
|
||||
|
||||
}
|
@ -2,11 +2,15 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.NthAudioStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -31,16 +35,17 @@ public class AudioConverter extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
//Gets the first audio stream from the file and adds it to the output file
|
||||
AudioStream audioStream = getNthAudioSteam(streams, 0);
|
||||
FFMpegHelper.addAudioStream(command, audioStream, false);
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new NthAudioStreamModule(probeResult.getAudioStreams(), 0));
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,21 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopySubtitlesModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.ScaleModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetQualityModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -37,24 +47,31 @@ public class DownScaleConverter extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
VideoStream videoStream = getNthVideoStream(streams, 0);
|
||||
if (videoStream == null || (videoStream.getWidth() <= newWidth && videoStream.getHeight() <= newHeight)) {
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
VideoStream videoStream = FFMpegHelper.getNthSteam(probeResult.getVideoStreams(), 0);
|
||||
|
||||
if (videoStream == null) {
|
||||
throw new IllegalArgumentException("The selected file has no video stream");
|
||||
}
|
||||
|
||||
// Simply ignore files below, or at, the target resolution
|
||||
if (videoStream.getWidth() <= this.newWidth && videoStream.getHeight() <= this.newHeight) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
//Add all streams without re-encoding
|
||||
FFMpegHelper.mapAllStreams(command, streams);
|
||||
command.addOutputFileOption("-c:a", "copy");
|
||||
command.addOutputFileOption("-c:s", "copy");
|
||||
command.addOutputFileOption("-vf", "scale=" + newWidth + ":" + newHeight);
|
||||
command.addOutputFileOption("-crf", "20");
|
||||
command.addOutputFileOption("-preset", "slow");
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new MapAllModule<>(streams));
|
||||
modules.add(new CopyAudioModule());
|
||||
modules.add(new CopySubtitlesModule());
|
||||
modules.add(new ScaleModule(this.newWidth, this.newHeight));
|
||||
modules.add(new SetQualityModule(20, "p7"));
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,14 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.NthAudioStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.NthSubtitleStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.NthVideoStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -49,34 +53,29 @@ public class MKVToMP4Transcoder extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
}
|
||||
|
||||
//Get the nth audio stream
|
||||
List<AudioStream> audioStreams = filterStreamsByType(streams, AudioStream.class);
|
||||
AudioStream audioStream = getNthAudioSteam(new ArrayList<>(audioStreams), Math.max(this.audioStreamIndex, 0));
|
||||
|
||||
//Get the nth subtitle stream
|
||||
List<SubtitleStream> allSubtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
SubtitleStream subtitleStream = getNthSubtitleStream(new ArrayList<>(allSubtitleStreams),
|
||||
Math.max(this.subtitleStreamIndex, 0));
|
||||
|
||||
//Get the nth video stream
|
||||
VideoStream videoStream = getNthVideoStream(streams, Math.max(this.videoStreamIndex, 0));
|
||||
if (videoStream == null) {
|
||||
throw new IllegalArgumentException("The selected video stream was not found");
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
// Copy stream info
|
||||
command.addOutputFileOption("-c", "copy");
|
||||
modules.add(new CopyAllModule());
|
||||
|
||||
//Add streams to output file
|
||||
FFMpegHelper.addAudioStream(command, audioStream, false);
|
||||
FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream);
|
||||
if (!probeResult.getAudioStreams().isEmpty()) {
|
||||
modules.add(new NthAudioStreamModule(probeResult.getAudioStreams(), this.audioStreamIndex));
|
||||
}
|
||||
if (!probeResult.getVideoStreams().isEmpty()) {
|
||||
modules.add(new NthVideoStreamModule(probeResult.getVideoStreams(), this.videoStreamIndex));
|
||||
}
|
||||
if (!probeResult.getSubtitleStreams().isEmpty()) {
|
||||
modules.add(new NthSubtitleStreamModule(probeResult.getSubtitleStreams(), this.subtitleStreamIndex));
|
||||
}
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,21 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.hardwarecoding.H264HardwareEncodingModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.hardwarecoding.H26XDecodeModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopySubtitlesModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.FastStartModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -37,46 +45,44 @@ public class MkvH264Converter extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
// Map video if present
|
||||
List<StreamObject> videoStreams = filterStreamsByType(streams, VideoStream.class);
|
||||
List<StreamObject> videoStreams = new ArrayList<>(probeResult.getVideoStreams());
|
||||
if (!videoStreams.isEmpty()) {
|
||||
for (StreamObject streamObject : videoStreams) {
|
||||
if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h266")) {
|
||||
if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h265")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFMpegHelper.addH26xHardwareDecoding(command);
|
||||
modules.add(new H26XDecodeModule());
|
||||
break;
|
||||
}
|
||||
|
||||
FFMpegHelper.mapAllStreams(command, videoStreams);
|
||||
FFMpegHelper.addH264HardwareEncoding(command, 17);
|
||||
command.addOutputFileOption("-movflags", "+faststart");
|
||||
modules.add(new MapAllModule<>(videoStreams));
|
||||
modules.add(new H264HardwareEncodingModule(17));
|
||||
modules.add(new FastStartModule());
|
||||
}
|
||||
|
||||
// Map audio if present
|
||||
List<StreamObject> audioStreams = filterStreamsByType(streams, AudioStream.class);
|
||||
if (!audioStreams.isEmpty()) {
|
||||
FFMpegHelper.mapAllStreams(command, audioStreams);
|
||||
command.addOutputFileOption("-c:a", "copy");
|
||||
if (!probeResult.getAudioStreams().isEmpty()) {
|
||||
modules.add(new MapAllModule<>(probeResult.getAudioStreams()));
|
||||
modules.add(new CopyAudioModule());
|
||||
}
|
||||
|
||||
// Map subtitles if present
|
||||
List<StreamObject> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
List<StreamObject> subtitleStreams = new ArrayList<>(probeResult.getSubtitleStreams());
|
||||
if (!subtitleStreams.isEmpty()) {
|
||||
FFMpegHelper.mapAllStreams(command, subtitleStreams);
|
||||
command.addOutputFileOption("-c:s", "copy");
|
||||
modules.add(new MapAllModule<>(probeResult.getSubtitleStreams()));
|
||||
modules.add(new CopySubtitlesModule());
|
||||
}
|
||||
|
||||
command.addOutputFileOption("-f", "matroska");
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,14 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.hardwarecoding.H265HardwareEncodingModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopySubtitlesModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.FastStartModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
@ -9,6 +17,7 @@ import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -37,14 +46,14 @@ public class MkvH265ReducedConverter extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
// Map video if present
|
||||
List<StreamObject> videoStreams = filterStreamsByType(streams, VideoStream.class);
|
||||
List<VideoStream> videoStreams = probeResult.getVideoStreams();
|
||||
if (!videoStreams.isEmpty()) {
|
||||
for (StreamObject streamObject : videoStreams) {
|
||||
if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h266")) {
|
||||
@ -55,27 +64,27 @@ public class MkvH265ReducedConverter extends AbstractConverter {
|
||||
break;
|
||||
}
|
||||
|
||||
FFMpegHelper.mapAllStreams(command, videoStreams);
|
||||
FFMpegHelper.addH265HardwareEncoding(command, 17);
|
||||
command.addOutputFileOption("-movflags", "+faststart");
|
||||
modules.add(new H265HardwareEncodingModule(19));
|
||||
modules.add(new FastStartModule());
|
||||
modules.add(new MapAllModule<>(videoStreams));
|
||||
}
|
||||
|
||||
// Map audio if present
|
||||
List<StreamObject> audioStreams = filterStreamsByType(streams, AudioStream.class);
|
||||
List<AudioStream> audioStreams = probeResult.getAudioStreams();
|
||||
if (!audioStreams.isEmpty()) {
|
||||
FFMpegHelper.mapAllStreams(command, audioStreams);
|
||||
modules.add(new MapAllModule<>(audioStreams));
|
||||
}
|
||||
|
||||
// Map subtitles if present
|
||||
List<StreamObject> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
List<SubtitleStream> subtitleStreams = probeResult.getSubtitleStreams();
|
||||
if (!subtitleStreams.isEmpty()) {
|
||||
FFMpegHelper.mapAllStreams(command, subtitleStreams);
|
||||
command.addOutputFileOption("-c:s", "copy");
|
||||
modules.add(new MapAllModule<>(subtitleStreams));
|
||||
modules.add(new CopySubtitlesModule());
|
||||
}
|
||||
|
||||
command.addOutputFileOption("-f", "matroska");
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,17 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.MovTextModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -34,18 +41,19 @@ public class SubtitleEmbed extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
|
||||
FFMpegHelper.mapAllStreams(command, streams);
|
||||
command.addOutputFileOption("-c:a", "copy");
|
||||
command.addOutputFileOption("-c:v", "copy");
|
||||
command.addOutputFileOption("-c:s", "mov_text");
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new MapAllModule<>(probeResult.parsedStreams()));
|
||||
modules.add(new CopyAudioModule());
|
||||
modules.add(new CopyAudioModule());
|
||||
modules.add(new MovTextModule());
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,17 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAllModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -30,16 +37,20 @@ public class VideoConverter extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
//Add all streams without re-encoding
|
||||
FFMpegHelper.mapAllStreams(command, streams);
|
||||
command.addOutputFileOption("-c", "copy");
|
||||
modules.add(new MapAllModule<>(streams));
|
||||
modules.add(new CopyAllModule());
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,119 @@
|
||||
package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.AddStereoAudioStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.BurnSubtitleModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.SelectSingleStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.sorter.AudioLanguageSorter;
|
||||
import net.knarcraft.ffmpegconverter.converter.sorter.MinimalSubtitleSorter;
|
||||
import net.knarcraft.ffmpegconverter.converter.sorter.StreamSorter;
|
||||
import net.knarcraft.ffmpegconverter.converter.sorter.SubtitleLanguageSorter;
|
||||
import net.knarcraft.ffmpegconverter.converter.sorter.SubtitleTitleSorter;
|
||||
import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.knarcraft.ffmpegconverter.utility.FFMpegHelper.getNthSteam;
|
||||
|
||||
/**
|
||||
* A converter mainly designed for converting anime to web-playable mp4
|
||||
*/
|
||||
public class WebAnimeConverter extends AbstractConverter {
|
||||
|
||||
private final String[] audioLanguages;
|
||||
private final String[] subtitleLanguages;
|
||||
private final boolean toStereo;
|
||||
private final MinimalSubtitlePreference subtitlePreference;
|
||||
private final int forcedAudioIndex;
|
||||
private final int forcedSubtitleIndex;
|
||||
private final String subtitleNameFilter;
|
||||
|
||||
/**
|
||||
* Instantiates a new anime converter
|
||||
*
|
||||
* @param ffprobePath <p>Path/command to ffprobe.</p>
|
||||
* @param ffmpegPath <p>Path/command to ffmpeg.</p>
|
||||
* @param audioLanguages <p>List of wanted audio languages in descending order.</p>
|
||||
* @param subtitleLanguages <p>List of wanted subtitle languages in descending order.</p>
|
||||
* @param toStereo <p>Convert video with several audio channels to stereo.</p>
|
||||
* @param subtitlePreference <p>How minimal subtitles should be prioritized</p>
|
||||
* @param forcedAudioIndex <p>A specific audio stream to force. 0-indexed from the first audio stream found</p>
|
||||
* @param forcedSubtitleIndex <p>A specific subtitle stream to force. 0-indexed for the first subtitle stream found</p>
|
||||
*/
|
||||
public WebAnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, @NotNull String[] audioLanguages,
|
||||
@NotNull String[] subtitleLanguages, boolean toStereo,
|
||||
@NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex,
|
||||
int forcedSubtitleIndex, @NotNull String subtitleNameFilter) {
|
||||
super("mp4");
|
||||
this.ffprobePath = ffprobePath;
|
||||
this.ffmpegPath = ffmpegPath;
|
||||
this.audioLanguages = audioLanguages;
|
||||
this.subtitleLanguages = subtitleLanguages;
|
||||
this.toStereo = toStereo;
|
||||
this.subtitlePreference = subtitlePreference;
|
||||
this.forcedAudioIndex = forcedAudioIndex;
|
||||
this.forcedSubtitleIndex = forcedSubtitleIndex;
|
||||
this.subtitleNameFilter = subtitleNameFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles());
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
if (this.debug) {
|
||||
modules.add(new DebugModule(0, 0));
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
}
|
||||
|
||||
//Get the first audio stream in accordance with chosen languages
|
||||
AudioStream audioStream = getNthSteam(new AudioLanguageSorter(this.audioLanguages).sort(
|
||||
probeResult.getAudioStreams()), this.forcedAudioIndex);
|
||||
if (audioStream == null) {
|
||||
throw new IllegalArgumentException("The given input resulted in no audio stream being selected");
|
||||
}
|
||||
if (this.toStereo) {
|
||||
modules.add(new AddStereoAudioStreamModule(audioStream, true));
|
||||
} else {
|
||||
modules.add(new SelectSingleStreamModule(audioStream));
|
||||
}
|
||||
|
||||
//Get the first video stream
|
||||
VideoStream videoStream = getNthSteam(probeResult.getVideoStreams(), 0);
|
||||
|
||||
//Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
|
||||
StreamSorter<SubtitleStream> subtitleSorter = new SubtitleTitleSorter(this.subtitleNameFilter)
|
||||
.append(new MinimalSubtitleSorter(this.subtitlePreference))
|
||||
.append(new SubtitleLanguageSorter(this.subtitleLanguages));
|
||||
SubtitleStream subtitleStream = getNthSteam(subtitleSorter.chainSort(probeResult.getSubtitleStreams()),
|
||||
this.forcedSubtitleIndex);
|
||||
|
||||
if (subtitleStream != null && videoStream != null) {
|
||||
modules.add(new BurnSubtitleModule(subtitleStream, videoStream, true));
|
||||
} else if (videoStream != null) {
|
||||
modules.add(new SelectSingleStreamModule(videoStream));
|
||||
} else {
|
||||
throw new IllegalArgumentException("The selected video stream does not exist!");
|
||||
}
|
||||
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
command.setOutputFile(outFile);
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValidFormats() {
|
||||
return this.videoFormats;
|
||||
}
|
||||
|
||||
}
|
@ -2,15 +2,23 @@ package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.BurnSubtitleModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.NthAudioStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.mapping.SelectSingleStreamModule;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.knarcraft.ffmpegconverter.utility.FFMpegHelper.getNthSteam;
|
||||
|
||||
/**
|
||||
* A simple converter for web-video
|
||||
*/
|
||||
@ -38,27 +46,31 @@ public class WebVideoConverter extends AbstractConverter {
|
||||
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
|
||||
@NotNull String outFile) {
|
||||
FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles());
|
||||
List<StreamObject> streams = probeResult.parsedStreams();
|
||||
List<ConverterModule> modules = new ArrayList<>();
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
modules.add(new DebugModule(0, 0));
|
||||
}
|
||||
|
||||
//Get first streams from the file
|
||||
SubtitleStream subtitleStream = getNthSubtitleStream(streams, 0);
|
||||
VideoStream videoStream = getNthVideoStream(streams, 0);
|
||||
AudioStream audioStream = getNthAudioSteam(streams, 0);
|
||||
SubtitleStream subtitleStream = getNthSteam(probeResult.getSubtitleStreams(), 0);
|
||||
VideoStream videoStream = getNthSteam(probeResult.getVideoStreams(), 0);
|
||||
|
||||
if (videoStream == null) {
|
||||
throw new IllegalArgumentException("The selected video stream does not exist.");
|
||||
}
|
||||
|
||||
//Add streams to output
|
||||
FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream);
|
||||
if (audioStream != null) {
|
||||
FFMpegHelper.addAudioStream(command, audioStream, true);
|
||||
if (subtitleStream != null) {
|
||||
modules.add(new BurnSubtitleModule(subtitleStream, videoStream, true));
|
||||
} else {
|
||||
modules.add(new SelectSingleStreamModule(videoStream));
|
||||
}
|
||||
modules.add(new NthAudioStreamModule(probeResult.getAudioStreams(), 0));
|
||||
|
||||
command.setOutputFile(outFile);
|
||||
modules.add(new SetOutputFileModule(outFile));
|
||||
|
||||
new ModuleExecutor(command, modules).execute();
|
||||
return command.getResult();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module that adds some arguments to a ffmpeg command
|
||||
*/
|
||||
public interface ConverterModule {
|
||||
|
||||
/**
|
||||
* Adds this module's arguments to the given command
|
||||
*
|
||||
* @param command <p>The command to add to</p>
|
||||
*/
|
||||
void addArguments(@NotNull FFMpegCommand command);
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for adding options useful for debugging
|
||||
*/
|
||||
public class DebugModule implements ConverterModule {
|
||||
|
||||
private double startTime = 50;
|
||||
private double duration = 120;
|
||||
|
||||
/**
|
||||
* Instantiates a new debug module
|
||||
*
|
||||
* @param startTime <p>The time to start at</p>
|
||||
* @param duration <p>The time to stop at</p>
|
||||
*/
|
||||
public DebugModule(double startTime, double duration) {
|
||||
if (startTime > 0) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
if (duration > 0) {
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addInputFileOption("-ss", String.valueOf(this.startTime));
|
||||
command.addOutputFileOption("-t", String.valueOf(this.duration));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An executor for executing a list of modules
|
||||
*/
|
||||
public class ModuleExecutor {
|
||||
|
||||
private final FFMpegCommand command;
|
||||
private final List<ConverterModule> modules;
|
||||
|
||||
/**
|
||||
* Instantiates a new module executor
|
||||
*
|
||||
* @param command <p>The command to alter</p>
|
||||
* @param modules <p>The models to execute</p>
|
||||
*/
|
||||
public ModuleExecutor(@NotNull FFMpegCommand command, @NotNull List<ConverterModule> modules) {
|
||||
this.command = command;
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds arguments for all the specified modules to the FFMpeg command
|
||||
*/
|
||||
public void execute() {
|
||||
for (ConverterModule module : this.modules) {
|
||||
module.addArguments(this.command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.hardwarecoding;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for setting output as h264, accelerated with Nvidia hardware
|
||||
*/
|
||||
public class H264HardwareEncodingModule implements ConverterModule {
|
||||
|
||||
private final int quality;
|
||||
|
||||
/**
|
||||
* Instantiates a new h264 hardware encoding module
|
||||
*
|
||||
* @param quality <p>The crf quality to use</p>
|
||||
*/
|
||||
public H264HardwareEncodingModule(int quality) {
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
FFMpegHelper.addH264HardwareEncoding(command, this.quality);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.hardwarecoding;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for setting output as h265 (hevc), accelerated with Nvidia hardware
|
||||
*/
|
||||
public class H265HardwareEncodingModule implements ConverterModule {
|
||||
|
||||
private final int quality;
|
||||
|
||||
/**
|
||||
* Instantiates a new h265 (hevc) hardware encoding module
|
||||
*
|
||||
* @param quality <p>The crf quality to use</p>
|
||||
*/
|
||||
public H265HardwareEncodingModule(int quality) {
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
FFMpegHelper.addH265HardwareEncoding(command, this.quality);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.hardwarecoding;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for enabling h264 and hevc decoding
|
||||
*/
|
||||
public class H26XDecodeModule implements ConverterModule {
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addInputFileOption("-hwaccel", "cuda");
|
||||
command.addInputFileOption("-hwaccel_output_format", "cuda");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for adding an audio stream, and converting it to stereo
|
||||
*/
|
||||
public class AddStereoAudioStreamModule implements ConverterModule {
|
||||
|
||||
private final AudioStream audioStream;
|
||||
private final boolean mapAudio;
|
||||
|
||||
/**
|
||||
* Instantiates a new add stereo audio stream module
|
||||
*
|
||||
* @param audioStream <p>The audio stream to add and convert to stereo</p>
|
||||
* @param mapAudio <p>Whether to map the given audio stream (only disable if mapped elsewhere)</p>
|
||||
*/
|
||||
public AddStereoAudioStreamModule(@NotNull AudioStream audioStream, boolean mapAudio) {
|
||||
this.audioStream = audioStream;
|
||||
this.mapAudio = mapAudio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
if (mapAudio) {
|
||||
FFMpegHelper.mapStream(command, audioStream);
|
||||
}
|
||||
if (audioStream.getChannels() > 2) {
|
||||
command.addOutputFileOption("-af", "pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for burning the selected subtitle into a video
|
||||
*/
|
||||
public class BurnSubtitleModule implements ConverterModule {
|
||||
|
||||
private final SubtitleStream subtitleStream;
|
||||
private final VideoStream videoStream;
|
||||
private final boolean mapVideo;
|
||||
|
||||
/**
|
||||
* Instantiates a subtitle burning converter
|
||||
*
|
||||
* @param subtitleStream <p>The subtitle stream to burn to a video stream</p>
|
||||
* @param videoStream <p>The video stream to burn into</p>
|
||||
* @param mapVideo <p>Whether to map the given video stream (only disable if mapped elsewhere)</p>
|
||||
*/
|
||||
public BurnSubtitleModule(@NotNull SubtitleStream subtitleStream, @NotNull VideoStream videoStream,
|
||||
boolean mapVideo) {
|
||||
this.subtitleStream = subtitleStream;
|
||||
this.videoStream = videoStream;
|
||||
this.mapVideo = mapVideo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
if (mapVideo) {
|
||||
FFMpegHelper.mapStream(command, videoStream);
|
||||
}
|
||||
|
||||
if (subtitleStream.getIsImageSubtitle()) {
|
||||
command.addOutputFileOption("-filter_complex",
|
||||
String.format("[%d:%d]scale=width=%d:height=%d,crop=w=%d:h=%d:x=0:y=out_h[sub];[%d:%d][sub]overlay",
|
||||
subtitleStream.getInputIndex(), subtitleStream.getAbsoluteIndex(), videoStream.getWidth(),
|
||||
videoStream.getHeight(), videoStream.getWidth(), videoStream.getHeight(),
|
||||
videoStream.getInputIndex(), videoStream.getAbsoluteIndex()));
|
||||
command.addOutputFileOption("-profile:v", "baseline");
|
||||
} else {
|
||||
String safeFileName = FFMpegHelper.escapeSpecialCharactersInFileName(
|
||||
command.getInputFiles().get(subtitleStream.getInputIndex()));
|
||||
String subtitleCommand = String.format("subtitles='%s':si=%d", safeFileName,
|
||||
subtitleStream.getRelativeIndex());
|
||||
command.addOutputFileOption("-vf", subtitleCommand);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A module for mapping all the given streams
|
||||
*/
|
||||
public class MapAllModule<K extends StreamObject> implements ConverterModule {
|
||||
|
||||
final List<K> streams;
|
||||
|
||||
/**
|
||||
* Instantiates a new map all module
|
||||
*
|
||||
* @param streams <p>The streams to map</p>
|
||||
*/
|
||||
public MapAllModule(@NotNull List<K> streams) {
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
FFMpegHelper.mapAllStreams(command, this.streams);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A module for selecting and mapping the nth audio stream available
|
||||
*/
|
||||
public class NthAudioStreamModule implements ConverterModule {
|
||||
|
||||
private final int n;
|
||||
private final List<AudioStream> allStreams;
|
||||
|
||||
/**
|
||||
* Instantiates a new n-th audio stream module
|
||||
*
|
||||
* @param allStreams <p>All available streams</p>
|
||||
* @param n <p>The index of the stream the user wants</p>
|
||||
*/
|
||||
public NthAudioStreamModule(@NotNull List<AudioStream> allStreams, int n) {
|
||||
this.allStreams = allStreams;
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
AudioStream audioStream = FFMpegHelper.getNthSteam(this.allStreams, this.n);
|
||||
if (audioStream != null) {
|
||||
FFMpegHelper.mapStream(command, audioStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Selected audio stream does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A module for selecting and mapping the nth subtitle stream available
|
||||
*/
|
||||
public class NthSubtitleStreamModule implements ConverterModule {
|
||||
private final int n;
|
||||
private final List<SubtitleStream> allStreams;
|
||||
|
||||
/**
|
||||
* Instantiates a new n-th video stream module
|
||||
*
|
||||
* @param allStreams <p>All available streams</p>
|
||||
* @param n <p>The index of the stream the user wants</p>
|
||||
*/
|
||||
public NthSubtitleStreamModule(@NotNull List<SubtitleStream> allStreams, int n) {
|
||||
this.allStreams = allStreams;
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
SubtitleStream subtitleStream = FFMpegHelper.getNthSteam(this.allStreams, this.n);
|
||||
if (subtitleStream != null) {
|
||||
FFMpegHelper.mapStream(command, subtitleStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Selected subtitle stream does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class NthVideoStreamModule implements ConverterModule {
|
||||
|
||||
private final int n;
|
||||
private final List<VideoStream> allStreams;
|
||||
|
||||
/**
|
||||
* Instantiates a new n-th video stream module
|
||||
*
|
||||
* @param allStreams <p>All available streams</p>
|
||||
* @param n <p>The index of the stream the user wants</p>
|
||||
*/
|
||||
public NthVideoStreamModule(@NotNull List<VideoStream> allStreams, int n) {
|
||||
this.allStreams = allStreams;
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
VideoStream videoStream = FFMpegHelper.getNthSteam(this.allStreams, this.n);
|
||||
if (videoStream != null) {
|
||||
FFMpegHelper.mapStream(command, videoStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Selected video stream does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for selecting and mapping a single stream
|
||||
*/
|
||||
public class SelectSingleStreamModule implements ConverterModule {
|
||||
|
||||
private final StreamObject stream;
|
||||
|
||||
/**
|
||||
* Instantiates a new select single stream module
|
||||
*
|
||||
* @param stream <p>The stream to map to the output file</p>
|
||||
*/
|
||||
public SelectSingleStreamModule(@NotNull StreamObject stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
FFMpegHelper.mapStream(command, stream);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.mapping;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A module for selecting one or more audio streams, sorted by language
|
||||
*/
|
||||
public class SetDefaultStreamModule<K extends StreamObject> implements ConverterModule {
|
||||
|
||||
private final List<K> streams;
|
||||
private final int defaultStream;
|
||||
|
||||
/**
|
||||
* Instantiates a new language sorted audio stream module
|
||||
*
|
||||
* @param streams <p>All input streams</p>
|
||||
* @param defaultStream <p>The index of the output stream to set as default</p>
|
||||
*/
|
||||
public SetDefaultStreamModule(@NotNull List<K> streams, int defaultStream) {
|
||||
this.streams = streams;
|
||||
this.defaultStream = defaultStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
for (int i = 0; i < streams.size(); i++) {
|
||||
K stream = streams.get(i);
|
||||
char defaultModifier;
|
||||
if (i == defaultStream) {
|
||||
defaultModifier = '+';
|
||||
} else {
|
||||
defaultModifier = '-';
|
||||
}
|
||||
command.addOutputFileOption(String.format("-disposition:%s:%d", stream.streamTypeCharacter(), i),
|
||||
String.format("%sdefault", defaultModifier));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for making FFMpeg copy all codecs
|
||||
*/
|
||||
public class CopyAllModule implements ConverterModule {
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-c", "copy");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for making FFMpeg copy the audio codec
|
||||
*/
|
||||
public class CopyAudioModule implements ConverterModule {
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-c:a", "copy");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for making FFMpeg copy the subtitle codec
|
||||
*/
|
||||
public class CopySubtitlesModule implements ConverterModule {
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-c:s", "copy");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for adding the fast start flag (immediate playback)
|
||||
*/
|
||||
public class FastStartModule implements ConverterModule {
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-movflags", "+faststart");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for setting subtitle codec to mov_text (necessary for embedding in .mp4 files)
|
||||
*/
|
||||
public class MovTextModule implements ConverterModule {
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-c:s", "mov_text");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for scaling the resolution of video streams
|
||||
*/
|
||||
public class ScaleModule implements ConverterModule {
|
||||
|
||||
final int newWidth;
|
||||
final int newHeight;
|
||||
|
||||
/**
|
||||
* Instantiates a new scale module
|
||||
*
|
||||
* @param newWidth <p>The new width of the video stream</p>
|
||||
* @param newHeight <p>The new height of the video stream</p>
|
||||
*/
|
||||
public ScaleModule(int newWidth, int newHeight) {
|
||||
this.newWidth = newWidth;
|
||||
this.newHeight = newHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-vf", "scale=" + this.newWidth + ":" + this.newHeight);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for setting the output file
|
||||
*/
|
||||
public class SetOutputFileModule implements ConverterModule {
|
||||
|
||||
private final String outputFile;
|
||||
|
||||
/**
|
||||
* Instantiates a new set output file module
|
||||
*
|
||||
* @param outputFile <p>The output file to set</p>
|
||||
*/
|
||||
public SetOutputFileModule(@NotNull String outputFile) {
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.setOutputFile(this.outputFile);
|
||||
if (FileUtil.getExtension(this.outputFile).equals("mkv")) {
|
||||
command.addOutputFileOption("-f", "matroska");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.module.output;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A module for setting output quality
|
||||
*/
|
||||
public class SetQualityModule implements ConverterModule {
|
||||
|
||||
private final int crf;
|
||||
private final String preset;
|
||||
|
||||
/**
|
||||
* Instantiates a new quality module
|
||||
*
|
||||
* @param crf <p>The CRF to set. 0 = lossless, 51 = terrible, 17 is visually lossless</p>
|
||||
* @param preset <p>The preset to use (p1-p7, p7 is slowest and best)</p>
|
||||
*/
|
||||
public SetQualityModule(int crf, @NotNull String preset) {
|
||||
this.crf = crf;
|
||||
this.preset = preset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArguments(@NotNull FFMpegCommand command) {
|
||||
command.addOutputFileOption("-crf", String.valueOf(crf));
|
||||
command.addOutputFileOption("-preset", this.preset);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.sorter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstract stream sorter, making implementation easier
|
||||
*
|
||||
* @param <L> <p>The type of streams this sorter sorts</p>
|
||||
*/
|
||||
public abstract class AbstractSorter<L extends StreamObject> implements StreamSorter<L> {
|
||||
|
||||
protected StreamSorter<L> nextSorter = null;
|
||||
|
||||
@Override
|
||||
public @NotNull StreamSorter<L> prepend(@NotNull StreamSorter<L> other) {
|
||||
this.nextSorter = other;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StreamSorter<L> append(@NotNull StreamSorter<L> other) {
|
||||
StreamSorter<L> end = other;
|
||||
while (end != null && end.hasChainElement()) {
|
||||
end = end.getNextInChain();
|
||||
}
|
||||
if (end == null) {
|
||||
throw new IllegalStateException("Other cannot be null. Something is wrong!");
|
||||
}
|
||||
end.setNextInChain(this);
|
||||
return other;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<L> chainSort(@NotNull List<L> input) {
|
||||
List<L> sorted = this.sort(input);
|
||||
if (nextSorter != null) {
|
||||
return nextSorter.chainSort(sorted);
|
||||
} else {
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChainElement() {
|
||||
return this.nextSorter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public StreamSorter<L> getNextInChain() {
|
||||
return this.nextSorter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextInChain(@NotNull StreamSorter<L> next) {
|
||||
this.nextSorter = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts subtitle streams according to chosen languages and removes non-matching languages
|
||||
*
|
||||
* @param streams <p>A list of streams to sort.</p>
|
||||
* @param languages <p>The languages chosen by the user.</p>
|
||||
* @param <G> <p>The type of streams to sort.</p>
|
||||
* @return <p>A sorted version of the list.</p>
|
||||
*/
|
||||
@NotNull
|
||||
protected <G extends StreamObject> List<G> sortStreamsByLanguage(@NotNull List<G> streams,
|
||||
@NotNull String[] languages) {
|
||||
List<G> sorted = new ArrayList<>();
|
||||
for (String language : languages) {
|
||||
for (G stream : streams) {
|
||||
String streamLanguage = stream.getLanguage();
|
||||
if (language.equals("*") || (streamLanguage.equals("und") && language.equals("0")) ||
|
||||
streamLanguage.equals(language)) {
|
||||
sorted.add(stream);
|
||||
}
|
||||
}
|
||||
streams.removeAll(sorted);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.sorter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A sorter for sorting audio streams by language
|
||||
*/
|
||||
public class AudioLanguageSorter extends AbstractSorter<AudioStream> {
|
||||
|
||||
private final String[] languageOrder;
|
||||
|
||||
/**
|
||||
* Instantiates a new audio language sorter
|
||||
*
|
||||
* @param languageOrder <p>The order of preference for audio languages</p>
|
||||
*/
|
||||
public AudioLanguageSorter(@NotNull String[] languageOrder) {
|
||||
this.languageOrder = languageOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<AudioStream> sort(@NotNull List<AudioStream> input) {
|
||||
return sortStreamsByLanguage(input, this.languageOrder);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.sorter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A sorter for sorting/filtering subtitles by a minimal subtitle preference
|
||||
*/
|
||||
public class MinimalSubtitleSorter extends AbstractSorter<SubtitleStream> {
|
||||
|
||||
private final MinimalSubtitlePreference minimalSubtitlePreference;
|
||||
|
||||
/**
|
||||
* Instantiates a new minimal subtitle preference sorter
|
||||
*
|
||||
* @param minimalSubtitlePreference <p>The minimal subtitle preference sort/filter by</p>
|
||||
*/
|
||||
public MinimalSubtitleSorter(@NotNull MinimalSubtitlePreference minimalSubtitlePreference) {
|
||||
this.minimalSubtitlePreference = minimalSubtitlePreference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<SubtitleStream> sort(@NotNull List<SubtitleStream> input) {
|
||||
// Split all subtitles into full and minimal
|
||||
List<SubtitleStream> fullSubtitles = new ArrayList<>();
|
||||
List<SubtitleStream> minimalSubtitles = new ArrayList<>();
|
||||
for (SubtitleStream subtitleStream : input) {
|
||||
if (subtitleStream.getIsFullSubtitle()) {
|
||||
fullSubtitles.add(subtitleStream);
|
||||
} else {
|
||||
minimalSubtitles.add(subtitleStream);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort/filter subtitles based on full and minimal
|
||||
switch (this.minimalSubtitlePreference) {
|
||||
case REJECT -> {
|
||||
// Only return full subtitles
|
||||
return fullSubtitles;
|
||||
}
|
||||
case REQUIRE -> {
|
||||
// Only return minimal subtitles
|
||||
return minimalSubtitles;
|
||||
}
|
||||
case NO_PREFERENCE -> {
|
||||
// Don't change order
|
||||
return input;
|
||||
}
|
||||
case PREFER -> {
|
||||
// Sort minimal subtitles first, and full subtitles last
|
||||
minimalSubtitles.addAll(fullSubtitles);
|
||||
return minimalSubtitles;
|
||||
}
|
||||
case AVOID -> {
|
||||
// Sort full subtitles first, and minimal subtitles last
|
||||
fullSubtitles.addAll(minimalSubtitles);
|
||||
return fullSubtitles;
|
||||
}
|
||||
default -> throw new IllegalStateException("Unknown enum value encountered");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.sorter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An interface describing a chaining-capable stream sorter
|
||||
*
|
||||
* @param <K> <p>The type of stream this sorter sorts</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface StreamSorter<K extends StreamObject> {
|
||||
|
||||
/**
|
||||
* Prepends this stream sorter to another
|
||||
*
|
||||
* <p>This stream sorter's next in chain will be set to other</p>
|
||||
*
|
||||
* @param other <p>The stream sorter to prepend</p>
|
||||
* @return <p>A reference to the first stream sorter in the current chain</p>
|
||||
*/
|
||||
@NotNull
|
||||
StreamSorter<K> prepend(@NotNull StreamSorter<K> other);
|
||||
|
||||
/**
|
||||
* Appends this stream sorter to another
|
||||
*
|
||||
* <p>This stream sorter is added to the end of other's chain</p>
|
||||
*
|
||||
* @param other <p>The stream sorter to append to this one</p>
|
||||
* @return <p>A reference to the first stream sorter in the current chain</p>
|
||||
*/
|
||||
@NotNull
|
||||
StreamSorter<K> append(@NotNull StreamSorter<K> other);
|
||||
|
||||
/**
|
||||
* Sorts the given input streams using this sorter only
|
||||
*
|
||||
* @param input <p>The input to sort</p>
|
||||
* @return <p>The sorted input</p>
|
||||
*/
|
||||
@NotNull
|
||||
List<K> sort(@NotNull List<K> input);
|
||||
|
||||
/**
|
||||
* Sorts the given input streams using all sorters in the chain
|
||||
*
|
||||
* @param input <p>The input to sort</p>
|
||||
* @return <p>The sorted input</p>
|
||||
*/
|
||||
@NotNull
|
||||
List<K> chainSort(@NotNull List<K> input);
|
||||
|
||||
/**
|
||||
* Gets whether this stream sorter has a sorter set as the next in its chain
|
||||
*
|
||||
* @return <p>True if a next chain item exists</p>
|
||||
*/
|
||||
boolean hasChainElement();
|
||||
|
||||
/**
|
||||
* Gets the next item in this stream sorter's chain
|
||||
*
|
||||
* @return <p>The next item in the chain</p>
|
||||
*/
|
||||
@Nullable
|
||||
StreamSorter<K> getNextInChain();
|
||||
|
||||
/**
|
||||
* Sets the given stream sorter as the next in the sorter chain
|
||||
*
|
||||
* @param next <p>The next item in the sorter chain</p>
|
||||
*/
|
||||
void setNextInChain(@NotNull StreamSorter<K> next);
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.sorter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A sorter for sorting subtitles by language
|
||||
*/
|
||||
public class SubtitleLanguageSorter extends AbstractSorter<SubtitleStream> {
|
||||
|
||||
private final String[] languageOrder;
|
||||
|
||||
/**
|
||||
* Instantiates a new subtitle language sorter
|
||||
*
|
||||
* @param languageOrder <p>The order of preference for subtitle languages</p>
|
||||
*/
|
||||
public SubtitleLanguageSorter(@NotNull String[] languageOrder) {
|
||||
this.languageOrder = languageOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<SubtitleStream> sort(@NotNull List<SubtitleStream> input) {
|
||||
return sortStreamsByLanguage(new ArrayList<>(input), this.languageOrder);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package net.knarcraft.ffmpegconverter.converter.sorter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
/**
|
||||
* A sorter for filtering subtitle streams by title
|
||||
*/
|
||||
public class SubtitleTitleSorter extends AbstractSorter<SubtitleStream> {
|
||||
|
||||
private final String titleFilter;
|
||||
|
||||
/**
|
||||
* Instantiates a new subtitle title sorter
|
||||
*
|
||||
* <p>If a simple string, or invalid RegEx is given, any stream containing the titleFilter in its title will be
|
||||
* retained. If a valid RegEx is given, any stream matching the titleFilter is retained.</p>
|
||||
*
|
||||
* @param titleFilter <p>The filter to use. RegEx match, or a string the title must contain.</p>
|
||||
*/
|
||||
public SubtitleTitleSorter(@NotNull String titleFilter) {
|
||||
this.titleFilter = titleFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<SubtitleStream> sort(@NotNull List<SubtitleStream> input) {
|
||||
List<SubtitleStream> output = new ArrayList<>(input);
|
||||
if (!this.titleFilter.trim().isEmpty()) {
|
||||
if (isValidRegularExpression(this.titleFilter) && hasSpecialRegexCharacters(this.titleFilter)) {
|
||||
output.removeIf((stream) -> !stream.getTitle().matches(this.titleFilter));
|
||||
} else {
|
||||
output.removeIf((stream) -> !stream.getTitle().contains(this.titleFilter));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given string is a valid regular expression
|
||||
*
|
||||
* @param input <p>The string to check</p>
|
||||
* @return <p>True if the given string has no invalid expressions</p>
|
||||
*/
|
||||
private static boolean isValidRegularExpression(@NotNull String input) {
|
||||
try {
|
||||
Pattern.compile(input);
|
||||
return true;
|
||||
} catch (PatternSyntaxException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the input string has any RegEx special characters
|
||||
*
|
||||
* @param input <p>The input to check</p>
|
||||
* @return <p>True if RegEx characters exist in the string</p>
|
||||
*/
|
||||
private static boolean hasSpecialRegexCharacters(String input) {
|
||||
Pattern regexSpecialCharacters = Pattern.compile("[\\\\.\\[\\]{}()<>*+\\-=!?^$|]");
|
||||
return regexSpecialCharacters.matcher(input).find();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.knarcraft.ffmpegconverter.property;
|
||||
|
||||
/**
|
||||
* A representation for different preferences related to minimal subtitles
|
||||
*
|
||||
* <p>Minimal subtitles are also referred to as partial subtitles or signs and songs. For Japanese media, they are aimed
|
||||
* at users that understand the spoken language, but struggles with reading, or when things are said too fast or in an
|
||||
* odd rhythm (singing). In american movies, some partial subtitles only translate non-english spoken language. Some
|
||||
* dubbed movies have subtitles that only cover signs, text or logos in the original language.</p>
|
||||
*/
|
||||
public enum MinimalSubtitlePreference {
|
||||
|
||||
/**
|
||||
* Only map minimal subtitles
|
||||
*/
|
||||
REQUIRE,
|
||||
|
||||
/**
|
||||
* Prefer minimal subtitles when available
|
||||
*/
|
||||
PREFER,
|
||||
|
||||
/**
|
||||
* Don't do any changes in sorting based on minimal subtitles
|
||||
*/
|
||||
NO_PREFERENCE,
|
||||
|
||||
/**
|
||||
* Avoid minimal subtitles, unless it's the only available choice
|
||||
*/
|
||||
AVOID,
|
||||
|
||||
/**
|
||||
* Don't include minimal subtitles, no matter what
|
||||
*/
|
||||
REJECT,
|
||||
|
||||
}
|
@ -34,8 +34,8 @@ public class AudioStream extends AbstractStream implements StreamObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StreamType getStreamType() {
|
||||
return StreamType.AUDIO;
|
||||
public char streamTypeCharacter() {
|
||||
return 'a';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -53,14 +53,6 @@ public interface StreamObject {
|
||||
*/
|
||||
boolean isDefault();
|
||||
|
||||
/**
|
||||
* Gets the stream type of this stream object
|
||||
*
|
||||
* @return <p>The stream type for this stream</p>
|
||||
*/
|
||||
@NotNull
|
||||
StreamType getStreamType();
|
||||
|
||||
/**
|
||||
* Gets the title of the subtitle stream
|
||||
*
|
||||
@ -69,4 +61,11 @@ public interface StreamObject {
|
||||
@NotNull
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* Gets the character ffmpeg uses for this type of stream
|
||||
*
|
||||
* @return <p>The character used to specify this type of stream</p>
|
||||
*/
|
||||
char streamTypeCharacter();
|
||||
|
||||
}
|
||||
|
@ -4,9 +4,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A class for representing stream tags that might be found for streams in video files
|
||||
@ -15,181 +13,461 @@ public enum StreamTag {
|
||||
|
||||
/**
|
||||
* The absolute index of this stream in the file
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
INDEX("index", StreamType.ALL),
|
||||
INDEX("index"),
|
||||
/**
|
||||
* The name of the codec, useful for identification
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
CODEC_NAME("codec_name=", StreamType.ALL),
|
||||
CODEC_NAME("codec_name="),
|
||||
|
||||
/**
|
||||
* The long name of the codec, useful for displaying information
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
CODEC_LONG_NAME("codec_long_name", StreamType.ALL),
|
||||
CODEC_LONG_NAME("codec_long_name"),
|
||||
|
||||
/**
|
||||
* The profile the encoder is set to
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
PROFILE("profile", StreamType.ALL),
|
||||
PROFILE("profile"),
|
||||
|
||||
/**
|
||||
* Whether the type of codec for the stream is audio, video or subtitle
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
CODEC_TYPE("codec_type", StreamType.ALL),
|
||||
CODEC_TAG_STRING("codec_tag_string", StreamType.ALL),
|
||||
CODEC_TAG("codec_tag", StreamType.ALL),
|
||||
SAMPLE_FMT("sample_fmt", Set.of(StreamType.AUDIO)),
|
||||
SAMPLE_RATE("sample_rate", Set.of(StreamType.AUDIO)),
|
||||
CODEC_TYPE("codec_type"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
CODEC_TAG_STRING("codec_tag_string"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
CODEC_TAG("codec_tag"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for audio streams</p>
|
||||
*/
|
||||
SAMPLE_FMT("sample_fmt"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for audio streams</p>
|
||||
*/
|
||||
SAMPLE_RATE("sample_rate"),
|
||||
|
||||
/**
|
||||
* The number of channels in an audio stream
|
||||
*
|
||||
* <p>Applicable for audio streams</p>
|
||||
*/
|
||||
CHANNELS("channels", Set.of(StreamType.AUDIO)),
|
||||
CHANNELS("channels"),
|
||||
|
||||
/**
|
||||
* Human-recognizable term for the number of audio channels, such as stereo, mono or surround
|
||||
*
|
||||
* <p>Applicable for audio streams</p>
|
||||
*/
|
||||
CHANNEL_LAYOUT("channel_layout", Set.of(StreamType.AUDIO)),
|
||||
BITS_PER_SAMPLE("bits_per_sample", Set.of(StreamType.AUDIO)),
|
||||
INITIAL_PADDING("initial_padding", Set.of(StreamType.AUDIO)),
|
||||
CHANNEL_LAYOUT("channel_layout"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for audio streams</p>
|
||||
*/
|
||||
BITS_PER_SAMPLE("bits_per_sample"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for audio streams</p>
|
||||
*/
|
||||
INITIAL_PADDING("initial_padding"),
|
||||
|
||||
/**
|
||||
* The viewable video width
|
||||
*
|
||||
* <p>Applicable for video and subtitle streams</p>
|
||||
*/
|
||||
WIDTH("width", StreamType.VIDEO_SUBTITLE),
|
||||
WIDTH("width"),
|
||||
|
||||
/**
|
||||
* The viewable video height
|
||||
*
|
||||
* <p>Applicable for video and subtitle streams</p>
|
||||
*/
|
||||
HEIGHT("height", StreamType.VIDEO_SUBTITLE),
|
||||
HEIGHT("height"),
|
||||
|
||||
/**
|
||||
* The original video width, before any padding was applied to account for resolution multiples
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
CODED_WIDTH("coded_width", Set.of(StreamType.VIDEO)),
|
||||
CODED_WIDTH("coded_width"),
|
||||
|
||||
/**
|
||||
* The original video height, before any padding was applied to account for resolution multiples
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
CODED_HEIGHT("coded_height", Set.of(StreamType.VIDEO)),
|
||||
CLOSED_CAPTIONS("closed_captions", Set.of(StreamType.VIDEO)),
|
||||
FILM_GRAIN("film_grain", Set.of(StreamType.VIDEO)),
|
||||
HAS_B_FRAMES("has_b_frames", Set.of(StreamType.VIDEO)),
|
||||
CODED_HEIGHT("coded_height"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
CLOSED_CAPTIONS("closed_captions"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
FILM_GRAIN("film_grain"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
HAS_B_FRAMES("has_b_frames"),
|
||||
|
||||
/**
|
||||
* The aspect ratio used to stretch the video for playback
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
SAMPLE_ASPECT_RATIO("sample_aspect_ratio", Set.of(StreamType.VIDEO)),
|
||||
SAMPLE_ASPECT_RATIO("sample_aspect_ratio"),
|
||||
|
||||
/**
|
||||
* The aspect ratio of the video stream
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
DISPLAY_ASPECT_RATIO("display_aspect_ratio", Set.of(StreamType.VIDEO)),
|
||||
DISPLAY_ASPECT_RATIO("display_aspect_ratio"),
|
||||
|
||||
/**
|
||||
* The pixel format used for the video stream
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
PIX_FMT("pix_fmt", Set.of(StreamType.VIDEO)),
|
||||
PIX_FMT("pix_fmt"),
|
||||
|
||||
/**
|
||||
* The quality level of the video stream
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
LEVEL("level", Set.of(StreamType.VIDEO)),
|
||||
COLOR_RANGE("color_range", Set.of(StreamType.VIDEO)),
|
||||
LEVEL("level"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
COLOR_RANGE("color_range"),
|
||||
|
||||
/**
|
||||
* How colors are stored in the video stream's file
|
||||
*
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
COLOR_SPACE("color_space", Set.of(StreamType.VIDEO)),
|
||||
COLOR_TRANSFER("color_transfer", Set.of(StreamType.VIDEO)),
|
||||
COLOR_PRIMARIES("color_primaries", Set.of(StreamType.VIDEO)),
|
||||
CHROMA_LOCATION("chroma_location", Set.of(StreamType.VIDEO)),
|
||||
FIELD_ORDER("field_order", Set.of(StreamType.VIDEO)),
|
||||
REFS("refs", Set.of(StreamType.VIDEO)),
|
||||
IS_AVC("is_avc", Set.of(StreamType.VIDEO)),
|
||||
NAL_LENGTH_SIZE("nal_length_size", Set.of(StreamType.VIDEO)),
|
||||
ID("id", StreamType.ALL),
|
||||
R_FRAME_RATE("r_frame_rate", StreamType.ALL),
|
||||
AVERAGE_FRAME_RATE("avg_frame_rate", StreamType.ALL),
|
||||
TIME_BASE("time_base", StreamType.ALL),
|
||||
START_PTS("start_pts", StreamType.ALL),
|
||||
START_TIME("start_time", StreamType.ALL),
|
||||
DURATION_TS("duration_ts", StreamType.ALL),
|
||||
DURATION("duration", StreamType.ALL),
|
||||
BIT_RATE("bit_rate", StreamType.ALL),
|
||||
MAX_BIT_RATE("max_bit_rate", StreamType.ALL),
|
||||
BITS_PER_RAW_SAMPLE("bits_per_raw_sample", StreamType.ALL),
|
||||
NB_FRAMES("nb_frames", StreamType.ALL),
|
||||
NB_READ_FRAMES("nb_read_frames", StreamType.ALL),
|
||||
NB_READ_PACKETS("nb_read_packets", StreamType.ALL),
|
||||
EXTRA_DATA_SIZE("extradata_size", StreamType.VIDEO_SUBTITLE),
|
||||
DISPOSITION_DEFAULT("DISPOSITION:default", StreamType.ALL),
|
||||
DISPOSITION_DUB("DISPOSITION:dub", StreamType.ALL),
|
||||
DISPOSITION_ORIGINAL("DISPOSITION:original", StreamType.ALL),
|
||||
DISPOSITION_COMMENT("DISPOSITION:comment", StreamType.ALL),
|
||||
DISPOSITION_LYRICS("DISPOSITION:lyrics", StreamType.ALL),
|
||||
DISPOSITION_KARAOKE("DISPOSITION:karaoke", StreamType.ALL),
|
||||
DISPOSITION_FORCED("DISPOSITION:forced", StreamType.ALL),
|
||||
DISPOSITION_HEARING_IMPAIRED("DISPOSITION:hearing_impaired", StreamType.ALL),
|
||||
DISPOSITION_VISUAL_IMPAIRED("DISPOSITION:visual_impaired", StreamType.ALL),
|
||||
DISPOSITION_CLEAN_EFFECTS("DISPOSITION:clean_effects", StreamType.ALL),
|
||||
DISPOSITION_ATTACHED_PIC("DISPOSITION:attached_pic", StreamType.ALL),
|
||||
DISPOSITION_TIMED_THUMBNAILS("DISPOSITION:timed_thumbnails", StreamType.ALL),
|
||||
DISPOSITION_NON_DIEGETIC("DISPOSITION:non_diegetic", StreamType.ALL),
|
||||
DISPOSITION_CAPTIONS("DISPOSITION:captions", StreamType.ALL),
|
||||
DISPOSITION_DESCRIPTIONS("DISPOSITION:descriptions", StreamType.ALL),
|
||||
DISPOSITION_METADATA("DISPOSITION:metadata", StreamType.ALL),
|
||||
DISPOSITION_DEPENDENT("DISPOSITION:dependent", StreamType.ALL),
|
||||
DISPOSITION_STILL_IMAGE("DISPOSITION:still_image", StreamType.ALL),
|
||||
COLOR_SPACE("color_space"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
COLOR_TRANSFER("color_transfer"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
COLOR_PRIMARIES("color_primaries"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
CHROMA_LOCATION("chroma_location"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
FIELD_ORDER("field_order"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
REFS("refs"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
IS_AVC("is_avc"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
NAL_LENGTH_SIZE("nal_length_size"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
ID("id"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
R_FRAME_RATE("r_frame_rate"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
AVERAGE_FRAME_RATE("avg_frame_rate"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TIME_BASE("time_base"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
START_PTS("start_pts"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
START_TIME("start_time"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DURATION_TS("duration_ts"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DURATION("duration"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
BIT_RATE("bit_rate"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
MAX_BIT_RATE("max_bit_rate"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
BITS_PER_RAW_SAMPLE("bits_per_raw_sample"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
NB_FRAMES("nb_frames"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
NB_READ_FRAMES("nb_read_frames"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
NB_READ_PACKETS("nb_read_packets"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video and subtitle streams</p>
|
||||
*/
|
||||
EXTRA_DATA_SIZE("extradata_size"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_DEFAULT("DISPOSITION:default"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_DUB("DISPOSITION:dub"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_ORIGINAL("DISPOSITION:original"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_COMMENT("DISPOSITION:comment"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_LYRICS("DISPOSITION:lyrics"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_KARAOKE("DISPOSITION:karaoke"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_FORCED("DISPOSITION:forced"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_HEARING_IMPAIRED("DISPOSITION:hearing_impaired"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_VISUAL_IMPAIRED("DISPOSITION:visual_impaired"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_CLEAN_EFFECTS("DISPOSITION:clean_effects"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_ATTACHED_PIC("DISPOSITION:attached_pic"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_TIMED_THUMBNAILS("DISPOSITION:timed_thumbnails"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_NON_DIEGETIC("DISPOSITION:non_diegetic"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_CAPTIONS("DISPOSITION:captions"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_DESCRIPTIONS("DISPOSITION:descriptions"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_METADATA("DISPOSITION:metadata"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_DEPENDENT("DISPOSITION:dependent"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
DISPOSITION_STILL_IMAGE("DISPOSITION:still_image"),
|
||||
|
||||
/**
|
||||
* The language of the stream
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_LANGUAGE("TAG:language", StreamType.ALL),
|
||||
TAG_LANGUAGE("TAG:language"),
|
||||
|
||||
/**
|
||||
* The title of an audio stream
|
||||
*
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_TITLE("TAG:title", StreamType.ALL),
|
||||
TAG_BPS_ENG("TAG:BPS-eng", StreamType.ALL),
|
||||
TAG_DURATION_ENG("TAG:DURATION-eng", StreamType.ALL),
|
||||
TAG_NUMBER_OF_FRAMES_ENG("TAG:NUMBER_OF_FRAMES-eng", StreamType.ALL),
|
||||
TAG_NUMBER_OF_BYTES_ENG("TAG:NUMBER_OF_BYTES-eng", StreamType.ALL),
|
||||
TAG_SOURCE_ID_ENG("TAG:SOURCE_ID-eng", StreamType.ALL),
|
||||
TAG_SOURCE_ID("TAG:SOURCE_ID", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_APP_ENG("TAG:_STATISTICS_WRITING_APP-eng", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_APP("TAG:_STATISTICS_WRITING_APP", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_DATE_UTC_ENG("TAG:_STATISTICS_WRITING_DATE_UTC-eng", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_DATE_UTC("TAG:_STATISTICS_WRITING_DATE_UTC", StreamType.ALL),
|
||||
TAG_STATISTICS_TAGS_ENG("TAG:_STATISTICS_TAGS-eng", StreamType.ALL),
|
||||
TAG_STATISTICS_TAGS("TAG:_STATISTICS_TAGS", StreamType.ALL),
|
||||
TAG_ENCODER("TAG:ENCODER", Set.of(StreamType.VIDEO)),
|
||||
TAG_DURATION("TAG:DURATION", StreamType.ALL),
|
||||
TAG_TITLE("TAG:title"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_BPS_ENG("TAG:BPS-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_DURATION_ENG("TAG:DURATION-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_NUMBER_OF_FRAMES_ENG("TAG:NUMBER_OF_FRAMES-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_NUMBER_OF_BYTES_ENG("TAG:NUMBER_OF_BYTES-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_SOURCE_ID_ENG("TAG:SOURCE_ID-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_SOURCE_ID("TAG:SOURCE_ID"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_STATISTICS_WRITING_APP_ENG("TAG:_STATISTICS_WRITING_APP-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_STATISTICS_WRITING_APP("TAG:_STATISTICS_WRITING_APP"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_STATISTICS_WRITING_DATE_UTC_ENG("TAG:_STATISTICS_WRITING_DATE_UTC-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_STATISTICS_WRITING_DATE_UTC("TAG:_STATISTICS_WRITING_DATE_UTC"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_STATISTICS_TAGS_ENG("TAG:_STATISTICS_TAGS-eng"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_STATISTICS_TAGS("TAG:_STATISTICS_TAGS"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for video streams</p>
|
||||
*/
|
||||
TAG_ENCODER("TAG:ENCODER"),
|
||||
|
||||
/**
|
||||
* <p>Applicable for all 3 stream types</p>
|
||||
*/
|
||||
TAG_DURATION("TAG:DURATION"),
|
||||
;
|
||||
|
||||
private static final Map<String, StreamTag> tagLookup = new HashMap<>();
|
||||
|
||||
private final @NotNull String tagString;
|
||||
private final Set<StreamType> applicableFor;
|
||||
|
||||
/**
|
||||
* Instantiates a new stream tag
|
||||
*
|
||||
* @param tagString <p>The tag string ffmpeg prints to specify this tag</p>
|
||||
*/
|
||||
StreamTag(@NotNull String tagString, @NotNull Set<StreamType> applicableFor) {
|
||||
StreamTag(@NotNull String tagString) {
|
||||
this.tagString = tagString;
|
||||
this.applicableFor = applicableFor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the types of streams this tag is applicable for
|
||||
*
|
||||
* @return <p>The types of streams this tag is applicable for</p>
|
||||
*/
|
||||
@NotNull
|
||||
public Set<StreamType> getApplicableFor() {
|
||||
return new HashSet<>(this.applicableFor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,36 +0,0 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A specifier for a type of stream
|
||||
*/
|
||||
public enum StreamType {
|
||||
|
||||
/**
|
||||
* A video stream
|
||||
*/
|
||||
VIDEO,
|
||||
|
||||
/**
|
||||
* An audio stream
|
||||
*/
|
||||
AUDIO,
|
||||
|
||||
/**
|
||||
* A subtitle stream
|
||||
*/
|
||||
SUBTITLE,
|
||||
;
|
||||
|
||||
/**
|
||||
* A set of all stream types
|
||||
*/
|
||||
public static final Set<StreamType> ALL = Set.of(VIDEO, AUDIO, SUBTITLE);
|
||||
|
||||
/**
|
||||
* A set of the video and subtitle stream types
|
||||
*/
|
||||
public static final Set<StreamType> VIDEO_SUBTITLE = Set.of(VIDEO, SUBTITLE);
|
||||
|
||||
}
|
@ -61,8 +61,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StreamType getStreamType() {
|
||||
return StreamType.SUBTITLE;
|
||||
public char streamTypeCharacter() {
|
||||
return 's';
|
||||
}
|
||||
|
||||
}
|
@ -45,8 +45,8 @@ public class VideoStream extends AbstractStream implements StreamObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StreamType getStreamType() {
|
||||
return StreamType.VIDEO;
|
||||
public char streamTypeCharacter() {
|
||||
return 'v';
|
||||
}
|
||||
|
||||
}
|
@ -162,61 +162,6 @@ public final class FFMpegHelper {
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an audio track to a ffmpeg command's output
|
||||
*
|
||||
* @param command <p>The command to add the audio track to</p>
|
||||
* @param audioStream <p>The audio stream to be mapped</p>
|
||||
* @param toStereo <p>Whether to convert the audio stream to stereo</p>
|
||||
*/
|
||||
public static void addAudioStream(@NotNull FFMpegCommand command, @NotNull AudioStream audioStream, boolean toStereo) {
|
||||
mapStream(command, audioStream);
|
||||
if (toStereo && audioStream.getChannels() > 2) {
|
||||
command.addOutputFileOption("-af", "pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subtitles and video mapping to a command
|
||||
*
|
||||
* @param command <p>The list containing the rest of the command.</p>
|
||||
* @param subtitleStream <p>The subtitle stream to be used.</p>
|
||||
* @param videoStream <p>The video stream to be used.</p>
|
||||
*/
|
||||
public static void addSubtitleAndVideoStream(@NotNull FFMpegCommand command, @Nullable SubtitleStream subtitleStream,
|
||||
@NotNull VideoStream videoStream) {
|
||||
//No appropriate subtitle was found. Just add the video stream.
|
||||
if (subtitleStream == null) {
|
||||
mapStream(command, videoStream);
|
||||
return;
|
||||
}
|
||||
|
||||
//Add the correct command arguments depending on the subtitle type
|
||||
if (!subtitleStream.getIsImageSubtitle()) {
|
||||
addBurnedInSubtitle(command, subtitleStream, videoStream);
|
||||
} else {
|
||||
addBurnedInImageSubtitle(command, subtitleStream, videoStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subtitle commands to a command list
|
||||
*
|
||||
* @param command <p>The list containing the FFmpeg commands.</p>
|
||||
* @param subtitleStream <p>The subtitle stream to add.</p>
|
||||
* @param videoStream <p>The video stream to burn the subtitle into.</p>
|
||||
*/
|
||||
private static void addBurnedInSubtitle(@NotNull FFMpegCommand command, @NotNull SubtitleStream subtitleStream,
|
||||
@NotNull VideoStream videoStream) {
|
||||
mapStream(command, videoStream);
|
||||
|
||||
String safeFileName = escapeSpecialCharactersInFileName(
|
||||
command.getInputFiles().get(subtitleStream.getInputIndex()));
|
||||
String subtitleCommand = String.format("subtitles='%s':si=%d", safeFileName,
|
||||
subtitleStream.getRelativeIndex());
|
||||
command.addOutputFileOption("-vf", subtitleCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds arguments for converting a file to h264 using hardware acceleration
|
||||
*
|
||||
@ -259,8 +204,9 @@ public final class FFMpegHelper {
|
||||
*
|
||||
* @param command <p>The command to add the mappings to</p>
|
||||
* @param streams <p>The streams to map</p>
|
||||
* @param <K> <p>The type of stream object to map</p>
|
||||
*/
|
||||
public static void mapAllStreams(@NotNull FFMpegCommand command, @NotNull List<StreamObject> streams) {
|
||||
public static <K extends StreamObject> void mapAllStreams(@NotNull FFMpegCommand command, @NotNull List<K> streams) {
|
||||
for (StreamObject stream : streams) {
|
||||
mapStream(command, stream);
|
||||
}
|
||||
@ -283,7 +229,7 @@ public final class FFMpegHelper {
|
||||
* @param fileName <p>The filename to escape.</p>
|
||||
* @return <p>A filename with known special characters escaped.</p>
|
||||
*/
|
||||
private static String escapeSpecialCharactersInFileName(String fileName) {
|
||||
public static String escapeSpecialCharactersInFileName(String fileName) {
|
||||
return fileName.replaceAll("\\\\", "\\\\\\\\\\\\\\\\")
|
||||
.replaceAll("'", "'\\\\\\\\\\\\\''")
|
||||
.replaceAll("%", "\\\\\\\\\\\\%")
|
||||
@ -293,21 +239,23 @@ public final class FFMpegHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds external image subtitle commands to a command list
|
||||
* Gets the nth stream from a list of streams
|
||||
*
|
||||
* @param command <p>The FFMPEG command to modify</p>
|
||||
* @param subtitleStream <p>The external image subtitle stream to burn in</p>
|
||||
* @param videoStream <p>The video stream to burn the subtitle into</p>
|
||||
* @param streams <p>A list of streams</p>
|
||||
* @param n <p>The index of the audio stream to get</p>
|
||||
* @return <p>The first audio stream found, or null if no audio streams were found</p>
|
||||
*/
|
||||
private static void addBurnedInImageSubtitle(@NotNull FFMpegCommand command,
|
||||
@NotNull SubtitleStream subtitleStream,
|
||||
@NotNull VideoStream videoStream) {
|
||||
command.addOutputFileOption("-filter_complex",
|
||||
String.format("[%d:%d]scale=width=%d:height=%d,crop=w=%d:h=%d:x=0:y=out_h[sub];[%d:%d][sub]overlay",
|
||||
subtitleStream.getInputIndex(), subtitleStream.getAbsoluteIndex(), videoStream.getWidth(),
|
||||
videoStream.getHeight(), videoStream.getWidth(), videoStream.getHeight(),
|
||||
videoStream.getInputIndex(), videoStream.getAbsoluteIndex()));
|
||||
command.addOutputFileOption("-profile:v", "baseline");
|
||||
public static <G extends StreamObject> G getNthSteam(@NotNull List<G> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
G stream = null;
|
||||
if (streams.size() > n) {
|
||||
stream = streams.get(n);
|
||||
} else if (!streams.isEmpty()) {
|
||||
stream = streams.get(0);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,5 @@
|
||||
package net.knarcraft.ffmpegconverter.utility;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -1,14 +0,0 @@
|
||||
package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
public class AnimeConverterTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
new AnimeConverter("ffprobe", "ffmpeg",
|
||||
new String[]{"jpn", "eng", "nor", "swe"}, new String[]{"nor", "eng", "swe", "fin"}, false,
|
||||
false, -1, -1, "");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user