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:
		@@ -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, "");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user