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; | package net.knarcraft.ffmpegconverter; | ||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.converter.AnimeConverter; |  | ||||||
| import net.knarcraft.ffmpegconverter.converter.AudioConverter; | import net.knarcraft.ffmpegconverter.converter.AudioConverter; | ||||||
| import net.knarcraft.ffmpegconverter.converter.Converter; | import net.knarcraft.ffmpegconverter.converter.Converter; | ||||||
| import net.knarcraft.ffmpegconverter.converter.DownScaleConverter; | 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.MkvH265ReducedConverter; | ||||||
| import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed; | import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed; | ||||||
| import net.knarcraft.ffmpegconverter.converter.VideoConverter; | import net.knarcraft.ffmpegconverter.converter.VideoConverter; | ||||||
|  | import net.knarcraft.ffmpegconverter.converter.WebAnimeConverter; | ||||||
| import net.knarcraft.ffmpegconverter.converter.WebVideoConverter; | import net.knarcraft.ffmpegconverter.converter.WebVideoConverter; | ||||||
|  | import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference; | ||||||
| import net.knarcraft.ffmpegconverter.utility.FileUtil; | import net.knarcraft.ffmpegconverter.utility.FileUtil; | ||||||
| import net.knarcraft.ffmpegconverter.utility.ListUtil; | import net.knarcraft.ffmpegconverter.utility.ListUtil; | ||||||
| import net.knarcraft.ffmpegconverter.utility.OutputUtil; | import net.knarcraft.ffmpegconverter.utility.OutputUtil; | ||||||
| @@ -189,9 +190,9 @@ class Main { | |||||||
|         String[] audioLanguage = new String[]{"jpn", "0"}; |         String[] audioLanguage = new String[]{"jpn", "0"}; | ||||||
|         String[] subtitleLanguage = new String[]{"eng", "0"}; |         String[] subtitleLanguage = new String[]{"eng", "0"}; | ||||||
|         boolean toStereo = true; |         boolean toStereo = true; | ||||||
|         boolean preventSigns = true; |         MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID; | ||||||
|         int forcedAudioIndex = -1; |         int forcedAudioIndex = 0; | ||||||
|         int forcedSubtitleIndex = -1; |         int forcedSubtitleIndex = 0; | ||||||
|         String subtitleNameFilter = ""; |         String subtitleNameFilter = ""; | ||||||
|  |  | ||||||
|         if (!input.isEmpty()) { |         if (!input.isEmpty()) { | ||||||
| @@ -204,7 +205,7 @@ class Main { | |||||||
|             toStereo = Boolean.parseBoolean(input.get(2)); |             toStereo = Boolean.parseBoolean(input.get(2)); | ||||||
|         } |         } | ||||||
|         if (input.size() > 3) { |         if (input.size() > 3) { | ||||||
|             preventSigns = Boolean.parseBoolean(input.get(3)); |             subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase()); | ||||||
|         } |         } | ||||||
|         try { |         try { | ||||||
|             if (input.size() > 4) { |             if (input.size() > 4) { | ||||||
| @@ -220,8 +221,8 @@ class Main { | |||||||
|         if (input.size() > 6) { |         if (input.size() > 6) { | ||||||
|             subtitleNameFilter = input.get(6); |             subtitleNameFilter = input.get(6); | ||||||
|         } |         } | ||||||
|         return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, toStereo, |         return new WebAnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, toStereo, | ||||||
|                 preventSigns, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter); |                 subtitlePreference, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,13 @@ | |||||||
| package net.knarcraft.ffmpegconverter.container; | package net.knarcraft.ffmpegconverter.container; | ||||||
|  |  | ||||||
|  | import net.knarcraft.ffmpegconverter.streams.AudioStream; | ||||||
| import net.knarcraft.ffmpegconverter.streams.StreamObject; | import net.knarcraft.ffmpegconverter.streams.StreamObject; | ||||||
|  | import net.knarcraft.ffmpegconverter.streams.SubtitleStream; | ||||||
|  | import net.knarcraft.ffmpegconverter.streams.VideoStream; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -13,4 +17,56 @@ import java.util.List; | |||||||
|  * @param parsedStreams <p>The streams that were parsed from the files</p> |  * @param parsedStreams <p>The streams that were parsed from the files</p> | ||||||
|  */ |  */ | ||||||
| public record StreamProbeResult(@NotNull List<File> parsedFiles, @NotNull List<StreamObject> parsedStreams) { | 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; | package net.knarcraft.ffmpegconverter.converter; | ||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | 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.FFMpegHelper; | ||||||
| import net.knarcraft.ffmpegconverter.utility.FileUtil; | import net.knarcraft.ffmpegconverter.utility.FileUtil; | ||||||
| import net.knarcraft.ffmpegconverter.utility.OutputUtil; | import net.knarcraft.ffmpegconverter.utility.OutputUtil; | ||||||
| @@ -13,8 +9,6 @@ import org.jetbrains.annotations.Nullable; | |||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.IOException; | 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. |  * 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 |      * 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); |         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 |     @Override | ||||||
|     public void convert(@NotNull File file) throws IOException { |     public void convert(@NotNull File file) throws IOException { | ||||||
|         processFile(file.getParentFile(), file); |         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.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | ||||||
| import net.knarcraft.ffmpegconverter.streams.AudioStream; | import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; | ||||||
| import net.knarcraft.ffmpegconverter.streams.StreamObject; | 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 net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -31,16 +35,17 @@ public class AudioConverter extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|  |  | ||||||
|         if (this.debug) { |         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 |         //Gets the first audio stream from the file and adds it to the output file | ||||||
|         AudioStream audioStream = getNthAudioSteam(streams, 0); |         modules.add(new NthAudioStreamModule(probeResult.getAudioStreams(), 0)); | ||||||
|         FFMpegHelper.addAudioStream(command, audioStream, false); |         modules.add(new SetOutputFileModule(outFile)); | ||||||
|         command.setOutputFile(outFile); |  | ||||||
|  |  | ||||||
|  |         new ModuleExecutor(command, modules).execute(); | ||||||
|         return command.getResult(); |         return command.getResult(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,11 +2,21 @@ package net.knarcraft.ffmpegconverter.converter; | |||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | 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.StreamObject; | ||||||
| import net.knarcraft.ffmpegconverter.streams.VideoStream; | import net.knarcraft.ffmpegconverter.streams.VideoStream; | ||||||
| import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -37,24 +47,31 @@ public class DownScaleConverter extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<StreamObject> streams = probeResult.parsedStreams(); | ||||||
|         VideoStream videoStream = getNthVideoStream(streams, 0); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|         if (videoStream == null || (videoStream.getWidth() <= newWidth && videoStream.getHeight() <= newHeight)) { |         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]; |             return new String[0]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|         if (this.debug) { |         if (this.debug) { | ||||||
|             FFMpegHelper.addDebugArguments(command, 50, 120); |             modules.add(new DebugModule(0, 0)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //Add all streams without re-encoding |         //Add all streams without re-encoding | ||||||
|         FFMpegHelper.mapAllStreams(command, streams); |         modules.add(new MapAllModule<>(streams)); | ||||||
|         command.addOutputFileOption("-c:a", "copy"); |         modules.add(new CopyAudioModule()); | ||||||
|         command.addOutputFileOption("-c:s", "copy"); |         modules.add(new CopySubtitlesModule()); | ||||||
|         command.addOutputFileOption("-vf", "scale=" + newWidth + ":" + newHeight); |         modules.add(new ScaleModule(this.newWidth, this.newHeight)); | ||||||
|         command.addOutputFileOption("-crf", "20"); |         modules.add(new SetQualityModule(20, "p7")); | ||||||
|         command.addOutputFileOption("-preset", "slow"); |         modules.add(new SetOutputFileModule(outFile)); | ||||||
|         command.setOutputFile(outFile); |         new ModuleExecutor(command, modules).execute(); | ||||||
|         return command.getResult(); |         return command.getResult(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,10 +2,14 @@ package net.knarcraft.ffmpegconverter.converter; | |||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | ||||||
| import net.knarcraft.ffmpegconverter.streams.AudioStream; | import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; | ||||||
| import net.knarcraft.ffmpegconverter.streams.StreamObject; | import net.knarcraft.ffmpegconverter.converter.module.DebugModule; | ||||||
| import net.knarcraft.ffmpegconverter.streams.SubtitleStream; | import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor; | ||||||
| import net.knarcraft.ffmpegconverter.streams.VideoStream; | 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 net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
| @@ -49,34 +53,29 @@ public class MKVToMP4Transcoder extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|  |  | ||||||
|         if (this.debug) { |         if (this.debug) { | ||||||
|             FFMpegHelper.addDebugArguments(command, 50, 120); |             modules.add(new DebugModule(0, 0)); | ||||||
|         } |  | ||||||
|  |  | ||||||
|         //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"); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Copy stream info |         // Copy stream info | ||||||
|         command.addOutputFileOption("-c", "copy"); |         modules.add(new CopyAllModule()); | ||||||
|  |  | ||||||
|         //Add streams to output file |         //Add streams to output file | ||||||
|         FFMpegHelper.addAudioStream(command, audioStream, false); |         if (!probeResult.getAudioStreams().isEmpty()) { | ||||||
|         FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream); |             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(); |         return command.getResult(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,13 +2,21 @@ package net.knarcraft.ffmpegconverter.converter; | |||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | 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.StreamObject; | ||||||
| import net.knarcraft.ffmpegconverter.streams.SubtitleStream; |  | ||||||
| import net.knarcraft.ffmpegconverter.streams.VideoStream; |  | ||||||
| import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -37,46 +45,44 @@ public class MkvH264Converter extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|  |  | ||||||
|         if (this.debug) { |         if (this.debug) { | ||||||
|             FFMpegHelper.addDebugArguments(command, 50, 120); |             modules.add(new DebugModule(0, 0)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Map video if present |         // Map video if present | ||||||
|         List<StreamObject> videoStreams = filterStreamsByType(streams, VideoStream.class); |         List<StreamObject> videoStreams = new ArrayList<>(probeResult.getVideoStreams()); | ||||||
|         if (!videoStreams.isEmpty()) { |         if (!videoStreams.isEmpty()) { | ||||||
|             for (StreamObject streamObject : videoStreams) { |             for (StreamObject streamObject : videoStreams) { | ||||||
|                 if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h266")) { |                 if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h265")) { | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 FFMpegHelper.addH26xHardwareDecoding(command); |                 modules.add(new H26XDecodeModule()); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             FFMpegHelper.mapAllStreams(command, videoStreams); |             modules.add(new MapAllModule<>(videoStreams)); | ||||||
|             FFMpegHelper.addH264HardwareEncoding(command, 17); |             modules.add(new H264HardwareEncodingModule(17)); | ||||||
|             command.addOutputFileOption("-movflags", "+faststart"); |             modules.add(new FastStartModule()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Map audio if present |         // Map audio if present | ||||||
|         List<StreamObject> audioStreams = filterStreamsByType(streams, AudioStream.class); |         if (!probeResult.getAudioStreams().isEmpty()) { | ||||||
|         if (!audioStreams.isEmpty()) { |             modules.add(new MapAllModule<>(probeResult.getAudioStreams())); | ||||||
|             FFMpegHelper.mapAllStreams(command, audioStreams); |             modules.add(new CopyAudioModule()); | ||||||
|             command.addOutputFileOption("-c:a", "copy"); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Map subtitles if present |         // Map subtitles if present | ||||||
|         List<StreamObject> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class); |         List<StreamObject> subtitleStreams = new ArrayList<>(probeResult.getSubtitleStreams()); | ||||||
|         if (!subtitleStreams.isEmpty()) { |         if (!subtitleStreams.isEmpty()) { | ||||||
|             FFMpegHelper.mapAllStreams(command, subtitleStreams); |             modules.add(new MapAllModule<>(probeResult.getSubtitleStreams())); | ||||||
|             command.addOutputFileOption("-c:s", "copy"); |             modules.add(new CopySubtitlesModule()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         command.addOutputFileOption("-f", "matroska"); |         modules.add(new SetOutputFileModule(outFile)); | ||||||
|  |         new ModuleExecutor(command, modules).execute(); | ||||||
|         command.setOutputFile(outFile); |  | ||||||
|         return command.getResult(); |         return command.getResult(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,14 @@ package net.knarcraft.ffmpegconverter.converter; | |||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | 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.AudioStream; | ||||||
| import net.knarcraft.ffmpegconverter.streams.StreamObject; | import net.knarcraft.ffmpegconverter.streams.StreamObject; | ||||||
| import net.knarcraft.ffmpegconverter.streams.SubtitleStream; | import net.knarcraft.ffmpegconverter.streams.SubtitleStream; | ||||||
| @@ -9,6 +17,7 @@ import net.knarcraft.ffmpegconverter.streams.VideoStream; | |||||||
| import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -37,14 +46,14 @@ public class MkvH265ReducedConverter extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|  |  | ||||||
|         if (this.debug) { |         if (this.debug) { | ||||||
|             FFMpegHelper.addDebugArguments(command, 50, 120); |             modules.add(new DebugModule(0, 0)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Map video if present |         // Map video if present | ||||||
|         List<StreamObject> videoStreams = filterStreamsByType(streams, VideoStream.class); |         List<VideoStream> videoStreams = probeResult.getVideoStreams(); | ||||||
|         if (!videoStreams.isEmpty()) { |         if (!videoStreams.isEmpty()) { | ||||||
|             for (StreamObject streamObject : videoStreams) { |             for (StreamObject streamObject : videoStreams) { | ||||||
|                 if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h266")) { |                 if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h266")) { | ||||||
| @@ -55,27 +64,27 @@ public class MkvH265ReducedConverter extends AbstractConverter { | |||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             FFMpegHelper.mapAllStreams(command, videoStreams); |             modules.add(new H265HardwareEncodingModule(19)); | ||||||
|             FFMpegHelper.addH265HardwareEncoding(command, 17); |             modules.add(new FastStartModule()); | ||||||
|             command.addOutputFileOption("-movflags", "+faststart"); |             modules.add(new MapAllModule<>(videoStreams)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Map audio if present |         // Map audio if present | ||||||
|         List<StreamObject> audioStreams = filterStreamsByType(streams, AudioStream.class); |         List<AudioStream> audioStreams = probeResult.getAudioStreams(); | ||||||
|         if (!audioStreams.isEmpty()) { |         if (!audioStreams.isEmpty()) { | ||||||
|             FFMpegHelper.mapAllStreams(command, audioStreams); |             modules.add(new MapAllModule<>(audioStreams)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Map subtitles if present |         // Map subtitles if present | ||||||
|         List<StreamObject> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class); |         List<SubtitleStream> subtitleStreams = probeResult.getSubtitleStreams(); | ||||||
|         if (!subtitleStreams.isEmpty()) { |         if (!subtitleStreams.isEmpty()) { | ||||||
|             FFMpegHelper.mapAllStreams(command, subtitleStreams); |             modules.add(new MapAllModule<>(subtitleStreams)); | ||||||
|             command.addOutputFileOption("-c:s", "copy"); |             modules.add(new CopySubtitlesModule()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         command.addOutputFileOption("-f", "matroska"); |         modules.add(new SetOutputFileModule(outFile)); | ||||||
|  |  | ||||||
|         command.setOutputFile(outFile); |         new ModuleExecutor(command, modules).execute(); | ||||||
|         return command.getResult(); |         return command.getResult(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,10 +2,17 @@ package net.knarcraft.ffmpegconverter.converter; | |||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | 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 net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -34,18 +41,19 @@ public class SubtitleEmbed extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|          |  | ||||||
|         FFMpegHelper.mapAllStreams(command, streams); |  | ||||||
|         command.addOutputFileOption("-c:a", "copy"); |  | ||||||
|         command.addOutputFileOption("-c:v", "copy"); |  | ||||||
|         command.addOutputFileOption("-c:s", "mov_text"); |  | ||||||
|  |  | ||||||
|         if (this.debug) { |         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(); |         return command.getResult(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,10 +2,17 @@ package net.knarcraft.ffmpegconverter.converter; | |||||||
|  |  | ||||||
| import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | import net.knarcraft.ffmpegconverter.container.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | 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.streams.StreamObject; | ||||||
| import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -30,16 +37,20 @@ public class VideoConverter extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); | ||||||
|  |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|  |  | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<StreamObject> streams = probeResult.parsedStreams(); | ||||||
|         if (this.debug) { |         if (this.debug) { | ||||||
|             FFMpegHelper.addDebugArguments(command, 50, 120); |             modules.add(new DebugModule(0, 0)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //Add all streams without re-encoding |         //Add all streams without re-encoding | ||||||
|         FFMpegHelper.mapAllStreams(command, streams); |         modules.add(new MapAllModule<>(streams)); | ||||||
|         command.addOutputFileOption("-c", "copy"); |         modules.add(new CopyAllModule()); | ||||||
|  |  | ||||||
|         command.setOutputFile(outFile); |         modules.add(new SetOutputFileModule(outFile)); | ||||||
|  |  | ||||||
|  |         new ModuleExecutor(command, modules).execute(); | ||||||
|         return command.getResult(); |         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.FFMpegCommand; | ||||||
| import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | import net.knarcraft.ffmpegconverter.container.StreamProbeResult; | ||||||
| import net.knarcraft.ffmpegconverter.streams.AudioStream; | import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; | ||||||
| import net.knarcraft.ffmpegconverter.streams.StreamObject; | 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.SubtitleStream; | ||||||
| import net.knarcraft.ffmpegconverter.streams.VideoStream; | import net.knarcraft.ffmpegconverter.streams.VideoStream; | ||||||
| import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import static net.knarcraft.ffmpegconverter.utility.FFMpegHelper.getNthSteam; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * A simple converter for web-video |  * A simple converter for web-video | ||||||
|  */ |  */ | ||||||
| @@ -38,27 +46,31 @@ public class WebVideoConverter extends AbstractConverter { | |||||||
|     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, |     public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, | ||||||
|                                               @NotNull String outFile) { |                                               @NotNull String outFile) { | ||||||
|         FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles()); |         FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles()); | ||||||
|         List<StreamObject> streams = probeResult.parsedStreams(); |         List<ConverterModule> modules = new ArrayList<>(); | ||||||
|  |  | ||||||
|         if (this.debug) { |         if (this.debug) { | ||||||
|             FFMpegHelper.addDebugArguments(command, 50, 120); |             modules.add(new DebugModule(0, 0)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //Get first streams from the file |         //Get first streams from the file | ||||||
|         SubtitleStream subtitleStream = getNthSubtitleStream(streams, 0); |         SubtitleStream subtitleStream = getNthSteam(probeResult.getSubtitleStreams(), 0); | ||||||
|         VideoStream videoStream = getNthVideoStream(streams, 0); |         VideoStream videoStream = getNthSteam(probeResult.getVideoStreams(), 0); | ||||||
|         AudioStream audioStream = getNthAudioSteam(streams, 0); |  | ||||||
|  |  | ||||||
|         if (videoStream == null) { |         if (videoStream == null) { | ||||||
|             throw new IllegalArgumentException("The selected video stream does not exist."); |             throw new IllegalArgumentException("The selected video stream does not exist."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //Add streams to output |         if (subtitleStream != null) { | ||||||
|         FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream); |             modules.add(new BurnSubtitleModule(subtitleStream, videoStream, true)); | ||||||
|         if (audioStream != null) { |         } else { | ||||||
|             FFMpegHelper.addAudioStream(command, audioStream, true); |             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(); |         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 |     @Override | ||||||
|     public @NotNull StreamType getStreamType() { |     public char streamTypeCharacter() { | ||||||
|         return StreamType.AUDIO; |         return 'a'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -53,14 +53,6 @@ public interface StreamObject { | |||||||
|      */ |      */ | ||||||
|     boolean isDefault(); |     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 |      * Gets the title of the subtitle stream | ||||||
|      * |      * | ||||||
| @@ -69,4 +61,11 @@ public interface StreamObject { | |||||||
|     @NotNull |     @NotNull | ||||||
|     String getTitle(); |     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 org.jetbrains.annotations.Nullable; | ||||||
|  |  | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Set; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * A class for representing stream tags that might be found for streams in video files |  * 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 |      * 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 |      * 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 |      * 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 |      * 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 |      * 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_TYPE("codec_type"), | ||||||
|     CODEC_TAG_STRING("codec_tag_string", StreamType.ALL), |  | ||||||
|     CODEC_TAG("codec_tag", StreamType.ALL), |     /** | ||||||
|     SAMPLE_FMT("sample_fmt", Set.of(StreamType.AUDIO)), |      * <p>Applicable for all 3 stream types</p> | ||||||
|     SAMPLE_RATE("sample_rate", Set.of(StreamType.AUDIO)), |      */ | ||||||
|  |     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 |      * 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 |      * 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)), |     CHANNEL_LAYOUT("channel_layout"), | ||||||
|     BITS_PER_SAMPLE("bits_per_sample", Set.of(StreamType.AUDIO)), |  | ||||||
|     INITIAL_PADDING("initial_padding", Set.of(StreamType.AUDIO)), |     /** | ||||||
|  |      * <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 |      * The viewable video width | ||||||
|  |      * | ||||||
|  |      * <p>Applicable for video and subtitle streams</p> | ||||||
|      */ |      */ | ||||||
|     WIDTH("width", StreamType.VIDEO_SUBTITLE), |     WIDTH("width"), | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * The viewable video height |      * 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 |      * 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 |      * 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)), |     CODED_HEIGHT("coded_height"), | ||||||
|     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)), |      * <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 |      * 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 |      * 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 |      * 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 |      * The quality level of the video stream | ||||||
|  |      * | ||||||
|  |      * <p>Applicable for video streams</p> | ||||||
|      */ |      */ | ||||||
|     LEVEL("level", Set.of(StreamType.VIDEO)), |     LEVEL("level"), | ||||||
|     COLOR_RANGE("color_range", Set.of(StreamType.VIDEO)), |  | ||||||
|  |     /** | ||||||
|  |      * <p>Applicable for video streams</p> | ||||||
|  |      */ | ||||||
|  |     COLOR_RANGE("color_range"), | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * How colors are stored in the video stream's file |      * 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_SPACE("color_space"), | ||||||
|     COLOR_TRANSFER("color_transfer", Set.of(StreamType.VIDEO)), |  | ||||||
|     COLOR_PRIMARIES("color_primaries", Set.of(StreamType.VIDEO)), |     /** | ||||||
|     CHROMA_LOCATION("chroma_location", Set.of(StreamType.VIDEO)), |      * <p>Applicable for video streams</p> | ||||||
|     FIELD_ORDER("field_order", Set.of(StreamType.VIDEO)), |      */ | ||||||
|     REFS("refs", Set.of(StreamType.VIDEO)), |     COLOR_TRANSFER("color_transfer"), | ||||||
|     IS_AVC("is_avc", Set.of(StreamType.VIDEO)), |  | ||||||
|     NAL_LENGTH_SIZE("nal_length_size", Set.of(StreamType.VIDEO)), |     /** | ||||||
|     ID("id", StreamType.ALL), |      * <p>Applicable for video streams</p> | ||||||
|     R_FRAME_RATE("r_frame_rate", StreamType.ALL), |      */ | ||||||
|     AVERAGE_FRAME_RATE("avg_frame_rate", StreamType.ALL), |     COLOR_PRIMARIES("color_primaries"), | ||||||
|     TIME_BASE("time_base", StreamType.ALL), |  | ||||||
|     START_PTS("start_pts", StreamType.ALL), |     /** | ||||||
|     START_TIME("start_time", StreamType.ALL), |      * <p>Applicable for video streams</p> | ||||||
|     DURATION_TS("duration_ts", StreamType.ALL), |      */ | ||||||
|     DURATION("duration", StreamType.ALL), |     CHROMA_LOCATION("chroma_location"), | ||||||
|     BIT_RATE("bit_rate", StreamType.ALL), |  | ||||||
|     MAX_BIT_RATE("max_bit_rate", StreamType.ALL), |     /** | ||||||
|     BITS_PER_RAW_SAMPLE("bits_per_raw_sample", StreamType.ALL), |      * <p>Applicable for video streams</p> | ||||||
|     NB_FRAMES("nb_frames", StreamType.ALL), |      */ | ||||||
|     NB_READ_FRAMES("nb_read_frames", StreamType.ALL), |     FIELD_ORDER("field_order"), | ||||||
|     NB_READ_PACKETS("nb_read_packets", StreamType.ALL), |  | ||||||
|     EXTRA_DATA_SIZE("extradata_size", StreamType.VIDEO_SUBTITLE), |     /** | ||||||
|     DISPOSITION_DEFAULT("DISPOSITION:default", StreamType.ALL), |      * <p>Applicable for video streams</p> | ||||||
|     DISPOSITION_DUB("DISPOSITION:dub", StreamType.ALL), |      */ | ||||||
|     DISPOSITION_ORIGINAL("DISPOSITION:original", StreamType.ALL), |     REFS("refs"), | ||||||
|     DISPOSITION_COMMENT("DISPOSITION:comment", StreamType.ALL), |  | ||||||
|     DISPOSITION_LYRICS("DISPOSITION:lyrics", StreamType.ALL), |     /** | ||||||
|     DISPOSITION_KARAOKE("DISPOSITION:karaoke", StreamType.ALL), |      * <p>Applicable for video streams</p> | ||||||
|     DISPOSITION_FORCED("DISPOSITION:forced", StreamType.ALL), |      */ | ||||||
|     DISPOSITION_HEARING_IMPAIRED("DISPOSITION:hearing_impaired", StreamType.ALL), |     IS_AVC("is_avc"), | ||||||
|     DISPOSITION_VISUAL_IMPAIRED("DISPOSITION:visual_impaired", StreamType.ALL), |  | ||||||
|     DISPOSITION_CLEAN_EFFECTS("DISPOSITION:clean_effects", StreamType.ALL), |     /** | ||||||
|     DISPOSITION_ATTACHED_PIC("DISPOSITION:attached_pic", StreamType.ALL), |      * <p>Applicable for video streams</p> | ||||||
|     DISPOSITION_TIMED_THUMBNAILS("DISPOSITION:timed_thumbnails", StreamType.ALL), |      */ | ||||||
|     DISPOSITION_NON_DIEGETIC("DISPOSITION:non_diegetic", StreamType.ALL), |     NAL_LENGTH_SIZE("nal_length_size"), | ||||||
|     DISPOSITION_CAPTIONS("DISPOSITION:captions", StreamType.ALL), |  | ||||||
|     DISPOSITION_DESCRIPTIONS("DISPOSITION:descriptions", StreamType.ALL), |     /** | ||||||
|     DISPOSITION_METADATA("DISPOSITION:metadata", StreamType.ALL), |      * <p>Applicable for all 3 stream types</p> | ||||||
|     DISPOSITION_DEPENDENT("DISPOSITION:dependent", StreamType.ALL), |      */ | ||||||
|     DISPOSITION_STILL_IMAGE("DISPOSITION:still_image", StreamType.ALL), |     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 |      * 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 |      * The title of an audio stream | ||||||
|  |      * | ||||||
|  |      * <p>Applicable for all 3 stream types</p> | ||||||
|      */ |      */ | ||||||
|     TAG_TITLE("TAG:title", StreamType.ALL), |     TAG_TITLE("TAG:title"), | ||||||
|     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), |      * <p>Applicable for all 3 stream types</p> | ||||||
|     TAG_NUMBER_OF_BYTES_ENG("TAG:NUMBER_OF_BYTES-eng", StreamType.ALL), |      */ | ||||||
|     TAG_SOURCE_ID_ENG("TAG:SOURCE_ID-eng", StreamType.ALL), |     TAG_BPS_ENG("TAG:BPS-eng"), | ||||||
|     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), |      * <p>Applicable for all 3 stream types</p> | ||||||
|     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_DURATION_ENG("TAG:DURATION-eng"), | ||||||
|     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)), |      * <p>Applicable for all 3 stream types</p> | ||||||
|     TAG_DURATION("TAG:DURATION", StreamType.ALL), |      */ | ||||||
|  |     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 static final Map<String, StreamTag> tagLookup = new HashMap<>(); | ||||||
|  |  | ||||||
|     private final @NotNull String tagString; |     private final @NotNull String tagString; | ||||||
|     private final Set<StreamType> applicableFor; |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Instantiates a new stream tag |      * Instantiates a new stream tag | ||||||
|      * |      * | ||||||
|      * @param tagString <p>The tag string ffmpeg prints to specify this tag</p> |      * @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.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 |     @Override | ||||||
|     public @NotNull StreamType getStreamType() { |     public char streamTypeCharacter() { | ||||||
|         return StreamType.SUBTITLE; |         return 's'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -45,8 +45,8 @@ public class VideoStream extends AbstractStream implements StreamObject { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public @NotNull StreamType getStreamType() { |     public char streamTypeCharacter() { | ||||||
|         return StreamType.VIDEO; |         return 'v'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -162,61 +162,6 @@ public final class FFMpegHelper { | |||||||
|         return output.toString(); |         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 |      * 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 command <p>The command to add the mappings to</p> | ||||||
|      * @param streams <p>The streams to map</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) { |         for (StreamObject stream : streams) { | ||||||
|             mapStream(command, stream); |             mapStream(command, stream); | ||||||
|         } |         } | ||||||
| @@ -283,7 +229,7 @@ public final class FFMpegHelper { | |||||||
|      * @param fileName <p>The filename to escape.</p> |      * @param fileName <p>The filename to escape.</p> | ||||||
|      * @return <p>A filename with known special characters escaped.</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("\\\\", "\\\\\\\\\\\\\\\\") |         return fileName.replaceAll("\\\\", "\\\\\\\\\\\\\\\\") | ||||||
|                 .replaceAll("'", "'\\\\\\\\\\\\\''") |                 .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 streams <p>A list of streams</p> | ||||||
|      * @param subtitleStream <p>The external image subtitle stream to burn in</p> |      * @param n       <p>The index of the audio stream to get</p> | ||||||
|      * @param videoStream    <p>The video stream to burn the subtitle into</p> |      * @return <p>The first audio stream found, or null if no audio streams were found</p> | ||||||
|      */ |      */ | ||||||
|     private static void addBurnedInImageSubtitle(@NotNull FFMpegCommand command, |     public static <G extends StreamObject> G getNthSteam(@NotNull List<G> streams, int n) { | ||||||
|                                                  @NotNull SubtitleStream subtitleStream, |         if (n < 0) { | ||||||
|                                                  @NotNull VideoStream videoStream) { |             throw new IllegalArgumentException("N cannot be negative!"); | ||||||
|         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", |         G stream = null; | ||||||
|                         subtitleStream.getInputIndex(), subtitleStream.getAbsoluteIndex(), videoStream.getWidth(), |         if (streams.size() > n) { | ||||||
|                         videoStream.getHeight(), videoStream.getWidth(), videoStream.getHeight(), |             stream = streams.get(n); | ||||||
|                         videoStream.getInputIndex(), videoStream.getAbsoluteIndex())); |         } else if (!streams.isEmpty()) { | ||||||
|         command.addOutputFileOption("-profile:v", "baseline"); |             stream = streams.get(0); | ||||||
|  |         } | ||||||
|  |         return stream; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -1,8 +1,5 @@ | |||||||
| package net.knarcraft.ffmpegconverter.utility; | package net.knarcraft.ffmpegconverter.utility; | ||||||
|  |  | ||||||
| import org.jetbrains.annotations.NotNull; |  | ||||||
| import org.jetbrains.annotations.Nullable; |  | ||||||
|  |  | ||||||
| import java.io.BufferedReader; | import java.io.BufferedReader; | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.IOException; | 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