From 380a1b800a7e9ef46ecba6f82142504ee6fcd6b0 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Sun, 19 May 2024 10:16:51 +0200 Subject: [PATCH] Improves configuration handling and stuff --- .../ffmpegconverter/FFMpegConvert.java | 114 ++++--------- .../ffmpegconverter/config/ConfigHandler.java | 29 +++- .../ffmpegconverter/config/ConfigKey.java | 99 +++++++++++- .../ffmpegconverter/config/Configuration.java | 150 ++++++++++++++++++ .../converter/AbstractConverter.java | 17 +- .../converter/AnimeConverter.java | 58 ++++--- .../converter/MkvH264Converter.java | 9 +- .../converter/MkvH265ReducedConverter.java | 3 +- .../converter/SubtitleEmbed.java | 8 +- .../converter/WebAnimeConverter.java | 16 +- .../module/output/CopyAudioModule.java | 42 ++++- .../output/SetStreamLanguageModule.java | 38 +++++ .../converter/sorter/AbstractSorter.java | 2 +- .../converter/sorter/AudioLanguageSorter.java | 4 +- .../sorter/SubtitleLanguageSorter.java | 4 +- .../converter/sorter/SubtitleTitleSorter.java | 2 +- .../AvailableHardwareEncoderHandler.java | 11 +- .../streams/AbstractStream.java | 40 ++++- .../ffmpegconverter/streams/StreamObject.java | 18 +++ .../streams/SubtitleStream.java | 8 +- .../ffmpegconverter/utility/ConfigHelper.java | 60 +++++++ src/main/resources/conf/config.properties | 14 +- 22 files changed, 598 insertions(+), 148 deletions(-) create mode 100644 src/main/java/net/knarcraft/ffmpegconverter/config/Configuration.java create mode 100644 src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/SetStreamLanguageModule.java create mode 100644 src/main/java/net/knarcraft/ffmpegconverter/utility/ConfigHelper.java diff --git a/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java b/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java index caf4c08..ac27f46 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java @@ -1,7 +1,6 @@ package net.knarcraft.ffmpegconverter; -import net.knarcraft.ffmpegconverter.config.ConfigHandler; -import net.knarcraft.ffmpegconverter.config.ConfigKey; +import net.knarcraft.ffmpegconverter.config.Configuration; import net.knarcraft.ffmpegconverter.converter.AnimeConverter; import net.knarcraft.ffmpegconverter.converter.AudioConverter; import net.knarcraft.ffmpegconverter.converter.Converter; @@ -15,9 +14,7 @@ import net.knarcraft.ffmpegconverter.converter.WebAnimeConverter; import net.knarcraft.ffmpegconverter.converter.WebVideoConverter; import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference; import net.knarcraft.ffmpegconverter.utility.FileUtil; -import net.knarcraft.ffmpegconverter.utility.ListUtil; import net.knarcraft.ffmpegconverter.utility.OutputUtil; -import org.apache.commons.configuration2.Configuration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,19 +35,10 @@ public class FFMpegConvert { private static final String FFMPEG_PATH = "ffmpeg"; //Can be just ffmpeg if it's in the path private static final Scanner READER = new Scanner(System.in, StandardCharsets.UTF_8); private static Converter converter = null; - private static final ConfigHandler configHandler = new ConfigHandler(); - private static boolean debug = false; - private static boolean useHardwareAcceleration = false; + private static final Configuration configuration = new Configuration(); public static void main(@NotNull String[] arguments) throws IOException { - Configuration configuration = configHandler.load(); - if (configuration.containsKey(ConfigKey.DEBUG.toString())) { - debug = configuration.getBoolean(ConfigKey.DEBUG.toString()); - } - OutputUtil.setDebug(debug); - if (configuration.containsKey(ConfigKey.USE_HARDWARE_ACCELERATION.toString())) { - useHardwareAcceleration = configuration.getBoolean(ConfigKey.USE_HARDWARE_ACCELERATION.toString()); - } + OutputUtil.setDebug(configuration.isDebugEnabled()); converter = loadConverter(); @@ -85,27 +73,10 @@ public class FFMpegConvert { * @return

The configuration handler

*/ @NotNull - public static ConfigHandler getConfigHandler() { - return configHandler; + public static Configuration getConfiguration() { + return configuration; } - /** - * Gets whether debug mode is enabled - * - * @return

True if debug mode is enabled

- */ - public static boolean isDebugEnabled() { - return debug; - } - - /** - * Gets whether hardware accelerated encoding is enabled - * - * @return

True if hardware accelerated encoding is enabled

- */ - public static boolean useHardwareAcceleration() { - return useHardwareAcceleration; - } /** * Asks the user which converter they want, and assigns a converter instance to the converter variable @@ -230,51 +201,37 @@ public class FFMpegConvert { */ @Nullable private static Converter generateAnimeConverter() { - OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Minimal subtitle " + - "preference REQUIRE/PREFER/NO_PREFERENCE/AVOID/REJECT] [Forced audio index 0-n] " + - "[Forced subtitle index 0-n] [Force video encoding true/false] [Force audio encoding true/false] " + - "[Subtitle name filter]\nYour input: "); - List input = readInput(8); - String[] audioLanguage = new String[]{"jpn", "nor", "eng", "0"}; - String[] subtitleLanguage = new String[]{"nob", "nor", "eng", "jpn", "0"}; - MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID; + OutputUtil.println("[Forced audio index 0-n] [Forced subtitle index 0-n] [Force video encoding true/false] " + + "[Force audio encoding true/false] [Subtitle name filter]\nYour input: "); + List input = readInput(5); int forcedAudioIndex = 0; int forcedSubtitleIndex = 0; String subtitleNameFilter = ""; boolean forceVideoEncoding = false; boolean forceAudioEncoding = false; - if (!input.isEmpty()) { - audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0)); - } - if (input.size() > 1) { - subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1)); - } - if (input.size() > 2) { - subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(2).toUpperCase()); - } try { - if (input.size() > 3) { - forcedAudioIndex = Integer.parseInt(input.get(3)); + if (!input.isEmpty()) { + forcedAudioIndex = Integer.parseInt(input.get(0)); } - if (input.size() > 4) { - forcedSubtitleIndex = Integer.parseInt(input.get(4)); + if (input.size() > 1) { + forcedSubtitleIndex = Integer.parseInt(input.get(1)); } } catch (NumberFormatException exception) { OutputUtil.println("Forced audio or subtitle index is not a number"); return null; } - if (input.size() > 5) { - forceVideoEncoding = Boolean.parseBoolean(input.get(5)); + if (input.size() > 2) { + forceVideoEncoding = Boolean.parseBoolean(input.get(2)); } - if (input.size() > 6) { - forceAudioEncoding = Boolean.parseBoolean(input.get(6)); + if (input.size() > 3) { + forceAudioEncoding = Boolean.parseBoolean(input.get(3)); } - if (input.size() > 7) { - subtitleNameFilter = input.get(7); + if (input.size() > 4) { + subtitleNameFilter = input.get(4); } - return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, subtitlePreference, - forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter, forceVideoEncoding, forceAudioEncoding); + return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter, + forceVideoEncoding, forceAudioEncoding); } /** @@ -284,12 +241,9 @@ public class FFMpegConvert { */ @Nullable private static Converter generateWebAnimeConverter() { - OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stereo if " + - "necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " + + OutputUtil.println("[Convert to stereo if necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " + "[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: "); - List input = readInput(7); - String[] audioLanguage = new String[]{"jpn", "0"}; - String[] subtitleLanguage = new String[]{"eng", "0"}; + List input = readInput(5); boolean toStereo = true; MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID; int forcedAudioIndex = 0; @@ -297,32 +251,26 @@ public class FFMpegConvert { String subtitleNameFilter = ""; if (!input.isEmpty()) { - audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0)); + toStereo = Boolean.parseBoolean(input.get(0)); } if (input.size() > 1) { - subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1)); - } - if (input.size() > 2) { - toStereo = Boolean.parseBoolean(input.get(2)); - } - if (input.size() > 3) { - subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase()); + subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(1).toUpperCase()); } try { - if (input.size() > 4) { - forcedAudioIndex = Integer.parseInt(input.get(4)); + if (input.size() > 2) { + forcedAudioIndex = Integer.parseInt(input.get(2)); } - if (input.size() > 5) { - forcedSubtitleIndex = Integer.parseInt(input.get(5)); + if (input.size() > 3) { + forcedSubtitleIndex = Integer.parseInt(input.get(3)); } } catch (NumberFormatException exception) { OutputUtil.println("Forced audio or subtitle index is not a number"); return null; } - if (input.size() > 6) { - subtitleNameFilter = input.get(6); + if (input.size() > 4) { + subtitleNameFilter = input.get(4); } - return new WebAnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, toStereo, + return new WebAnimeConverter(FFPROBE_PATH, FFMPEG_PATH, toStereo, subtitlePreference, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter); } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigHandler.java b/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigHandler.java index 0f23de8..3ea271d 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigHandler.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigHandler.java @@ -8,10 +8,12 @@ import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; import org.apache.commons.configuration2.builder.fluent.Configurations; import org.apache.commons.configuration2.ex.ConfigurationException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.util.HashMap; import java.util.Map; /** @@ -23,6 +25,18 @@ public class ConfigHandler { private final Configurations configurations = new Configurations(); private final File settingsFile = new File(configFolder, "config.properties"); private FileBasedConfigurationBuilder builder = configurations.propertiesBuilder(settingsFile); + private final Map, Object> optionValues = new HashMap<>(); + + /** + * Gets the current value of a configuration option + * + * @param key

The configuration key to get the value of

+ * @return

The current value

+ */ + @Nullable + public Object getValue(@NotNull ConfigKey key) { + return optionValues.get(key); + } /** * Gets a writable configuration used for changing settings @@ -64,11 +78,9 @@ public class ConfigHandler { /** * Loads the saved configuration file - * - * @return

The loaded configuration

*/ - @NotNull - public Configuration load() throws IOException { + public void load() throws IOException { + optionValues.clear(); Configuration configuration; if (!settingsFile.exists()) { configuration = new PropertiesConfiguration(); @@ -86,7 +98,14 @@ public class ConfigHandler { } // Reload contents in the builder builder = configurations.propertiesBuilder(settingsFile); - return configuration; + + for (@NotNull ConfigKey key : ConfigKey.values()) { + if (configuration.containsKey(key.toString())) { + optionValues.put(key, configuration.get(key.getType(), key.toString())); + } else { + optionValues.put(key, key.getDefaultValue()); + } + } } } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java b/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java index 544a972..18ee56b 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java @@ -2,31 +2,106 @@ package net.knarcraft.ffmpegconverter.config; import org.jetbrains.annotations.NotNull; +import java.util.HashSet; +import java.util.Set; + /** * A representation of all configuration keys */ -public enum ConfigKey { +public class ConfigKey { + + private static final Set> allKeys = new HashSet<>(); /** * The configuration key for the list of hardware-accelerated encoders available on the system */ - HARDWARE_ACCELERATED_ENCODERS("encoders-hardware-accelerated"), + public static final ConfigKey HARDWARE_ACCELERATED_ENCODERS = new ConfigKey<>("encoders-hardware-accelerated", String.class, "qsv,cuda,dxva2,d3d11va,opencl,vulkan"); /** * The configuration key for toggling debug mode */ - DEBUG("debug"), + public static final ConfigKey DEBUG = createKey("debug", Boolean.class, false); /** - * The configuration key for toggling hardware acceleration + * The configuration key for toggling hardware encoding */ - USE_HARDWARE_ACCELERATION("hardware-acceleration"), - ; + public static final ConfigKey USE_HARDWARE_ENCODING = createKey("hardware-acceleration-encode", Boolean.class, false); + + /** + * The configuration key for toggling hardware decoding + */ + public static final ConfigKey USE_HARDWARE_DECODING = createKey("hardware-acceleration-decode", Boolean.class, true); + + /** + * The configuration key for toggling forced flac encoding + */ + public static final ConfigKey ENCODE_FLAC_ALWAYS = createKey("encode-flac-always", Boolean.class, false); + + /** + * The configuration key for anime audio languages + */ + public static final ConfigKey AUDIO_LANGUAGES_ANIME = createKey("audio-languages-anime", String.class, "jpn,eng,*"); + + /** + * The configuration key for anime subtitle languages + */ + public static final ConfigKey SUBTITLE_LANGUAGES_ANIME = createKey("subtitle-languages-anime", String.class, "eng,*"); + + /** + * The configuration key for the minimal subtitle preference + */ + public static final ConfigKey MINIMAL_SUBTITLE_PREFERENCE = createKey("minimal-subtitle-preference", String.class, "AVOID"); private final String configKey; + private final T defaultValue; + private final Class type; - ConfigKey(@NotNull String configKey) { + /** + * Instantiates a new config key + * + * @param configKey

The storage key in the configuration file

+ * @param type

The type of this option's value

+ * @param defaultValue

The default value of this option

+ */ + private ConfigKey(@NotNull String configKey, @NotNull Class type, @NotNull T defaultValue) { this.configKey = configKey; + this.type = type; + this.defaultValue = defaultValue; + } + + /** + * Creates a new config key + * + * @param configKey

The storage key in the configuration file

+ * @param type

The type of this option's value

+ * @param defaultValue

The default value of this option

+ * @param

The type of key to create

+ * @return

The new config key

+ */ + private static ConfigKey createKey(@NotNull String configKey, @NotNull Class type, @NotNull F defaultValue) { + ConfigKey key = new ConfigKey<>(configKey, type, defaultValue); + allKeys.add(key); + return key; + } + + /** + * Gets the type of this option's value + * + * @return

The type of the value

+ */ + @NotNull + public Class getType() { + return this.type; + } + + /** + * Gets the default value for the option represented by this key + * + * @return

The default value

+ */ + @NotNull + public T getDefaultValue() { + return this.defaultValue; } @Override @@ -34,4 +109,14 @@ public enum ConfigKey { return this.configKey; } + /** + * Gets all configuration keys + * + * @return

All configuration keys

+ */ + @NotNull + public static ConfigKey[] values() { + return allKeys.toArray(new ConfigKey[0]); + } + } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/config/Configuration.java b/src/main/java/net/knarcraft/ffmpegconverter/config/Configuration.java new file mode 100644 index 0000000..422aaa0 --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/config/Configuration.java @@ -0,0 +1,150 @@ +package net.knarcraft.ffmpegconverter.config; + +import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference; +import net.knarcraft.ffmpegconverter.utility.ConfigHelper; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.List; + +/** + * A configuration used for FFMpegConvert + */ +public class Configuration { + + private final ConfigHandler configHandler; + private boolean debug; + private boolean useHardwareEncoding; + private boolean useHardwareDecoding; + private List hardwareEncoders; + private boolean alwaysEncodeFlac; + private List animeAudioLanguages; + private List animeSubtitleLanguages; + private MinimalSubtitlePreference minimalSubtitlePreference; + + /** + * Instantiates and loads a new configuration + */ + public Configuration() { + this.configHandler = new ConfigHandler(); + try { + this.configHandler.load(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + load(); + } + + /** + * Loads/reloads this configuration + */ + public void load() { + try { + this.configHandler.load(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + debug = ConfigHelper.asBoolean(configHandler.getValue(ConfigKey.DEBUG)); + useHardwareEncoding = ConfigHelper.asBoolean(configHandler.getValue(ConfigKey.USE_HARDWARE_ENCODING)); + useHardwareDecoding = ConfigHelper.asBoolean(configHandler.getValue(ConfigKey.USE_HARDWARE_DECODING)); + hardwareEncoders = ConfigHelper.asStringList(configHandler.getValue(ConfigKey.HARDWARE_ACCELERATED_ENCODERS)); + alwaysEncodeFlac = ConfigHelper.asBoolean(configHandler.getValue(ConfigKey.ENCODE_FLAC_ALWAYS)); + animeAudioLanguages = ConfigHelper.asStringList(configHandler.getValue(ConfigKey.AUDIO_LANGUAGES_ANIME)); + animeSubtitleLanguages = ConfigHelper.asStringList(configHandler.getValue(ConfigKey.SUBTITLE_LANGUAGES_ANIME)); + try { + minimalSubtitlePreference = MinimalSubtitlePreference.valueOf(String.valueOf(configHandler.getValue( + ConfigKey.MINIMAL_SUBTITLE_PREFERENCE))); + } catch (IllegalArgumentException | NullPointerException exception) { + minimalSubtitlePreference = MinimalSubtitlePreference.AVOID; + } + } + + /** + * Gets the underlying config handler for this configuration + * + * @return

The underlying config handler

+ */ + @NotNull + public ConfigHandler getConfigHandler() { + return this.configHandler; + } + + /** + * Gets whether debug mode is enabled + * + * @return

True if debug mode is enabled

+ */ + public boolean isDebugEnabled() { + return this.debug; + } + + /** + * Gets whether hardware accelerated encoding is enabled + * + * @return

True if hardware accelerated encoding is enabled

+ */ + public boolean useHardwareEncoding() { + return this.useHardwareEncoding; + } + + /** + * Gets whether hardware accelerated decoding is enabled + * + * @return

True if hardware accelerated decoding is enabled

+ */ + public boolean useHardwareDecoding() { + return this.useHardwareDecoding; + } + + /** + * Gets whether FLAC audio tracks should always be re-encoded + * + * @return

Whether FLAC tracks should always be re-encoded

+ */ + public boolean alwaysEncodeFlac() { + return this.alwaysEncodeFlac; + } + + /** + * Gets all hardware encoders defined in the configuration + * + * @return

The specified hardware encoders

+ */ + @NotNull + public List hardwareEncoders() { + return this.hardwareEncoders; + } + + /** + * Gets the audio language priorities for usage when converting anime + * + * @return

The anime audio language priorities

+ */ + @NotNull + public List getAnimeAudioLanguages() { + return this.animeAudioLanguages; + } + + /** + * Gets the subtitle language priorities for usage when converting anime + * + * @return

The anime subtitle language priorities

+ */ + @NotNull + public List getAnimeSubtitleLanguages() { + return this.animeSubtitleLanguages; + } + + /** + * Gets the preference for minimal subtitles + * + * @return

The minimal subtitle preference

+ */ + @NotNull + public MinimalSubtitlePreference getMinimalSubtitlePreference() { + return this.minimalSubtitlePreference; + } + +} diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/AbstractConverter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/AbstractConverter.java index b82118b..5e117d1 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/AbstractConverter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/AbstractConverter.java @@ -4,6 +4,7 @@ import net.knarcraft.ffmpegconverter.FFMpegConvert; import net.knarcraft.ffmpegconverter.container.FFMpegCommand; import net.knarcraft.ffmpegconverter.container.StreamProbeResult; import net.knarcraft.ffmpegconverter.handler.AvailableHardwareEncoderHandler; +import net.knarcraft.ffmpegconverter.streams.StreamObject; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import net.knarcraft.ffmpegconverter.utility.FileHelper; import net.knarcraft.ffmpegconverter.utility.FileUtil; @@ -21,7 +22,7 @@ import java.util.List; */ public abstract class AbstractConverter implements Converter { - final boolean debug = FFMpegConvert.isDebugEnabled(); + final boolean debug = FFMpegConvert.getConfiguration().isDebugEnabled(); private final String newExtension; protected String ffprobePath; protected String ffmpegPath; @@ -136,4 +137,18 @@ public abstract class AbstractConverter implements Converter { return encoderHandler.availableHardwareEncodings(); } + /** + * Sets the output indexes for the given streams + * + *

This will basically mark the given streams' order as the order they will appear in the output file. This is + * used when specifying specific codecs per-stream in the output file.

+ * + * @param streams

The streams to set the output indexes for

+ */ + protected void setOutputIndexes(@NotNull List streams) { + for (int i = 0; i < streams.size(); i++) { + streams.get(i).setOutputIndex(i); + } + } + } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java index 8d99581..66d1ad1 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java @@ -1,6 +1,7 @@ package net.knarcraft.ffmpegconverter.converter; import net.knarcraft.ffmpegconverter.FFMpegConvert; +import net.knarcraft.ffmpegconverter.config.Configuration; import net.knarcraft.ffmpegconverter.container.FFMpegCommand; import net.knarcraft.ffmpegconverter.container.StreamProbeResult; import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; @@ -16,6 +17,7 @@ import net.knarcraft.ffmpegconverter.converter.module.output.CopyVideoModule; import net.knarcraft.ffmpegconverter.converter.module.output.FastStartModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetQualityModule; +import net.knarcraft.ffmpegconverter.converter.module.output.SetStreamLanguageModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetVideoCodecModule; import net.knarcraft.ffmpegconverter.converter.sorter.AudioLanguageSorter; import net.knarcraft.ffmpegconverter.converter.sorter.ForcedFirstSorter; @@ -29,10 +31,12 @@ 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 net.knarcraft.ffmpegconverter.utility.OutputUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -40,8 +44,8 @@ import java.util.List; */ public class AnimeConverter extends AbstractConverter { - private final String[] audioLanguages; - private final String[] subtitleLanguages; + private final List audioLanguages; + private final List subtitleLanguages; private final MinimalSubtitlePreference subtitlePreference; private final int forcedAudioIndex; private final int forcedSubtitleIndex; @@ -54,25 +58,21 @@ public class AnimeConverter extends AbstractConverter { * * @param ffprobePath

Path/command to ffprobe.

* @param ffmpegPath

Path/command to ffmpeg.

- * @param audioLanguages

List of wanted audio languages in descending order.

- * @param subtitleLanguages

List of wanted subtitle languages in descending order.

- * @param subtitlePreference

How minimal subtitles should be prioritized

* @param forcedAudioIndex

A specific audio stream to force as default. 0-indexed from the first audio stream found

* @param forcedSubtitleIndex

A specific subtitle stream to force as default. 0-indexed for the first subtitle stream found

* @param forceVideoEncoding

Whether to enforce encoding on the video, even if already hevc

* @param forceAudioEncoding

Whether to convert audio to web-playable, even though there should be no need

*/ - public AnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, @NotNull String[] audioLanguages, - @NotNull String[] subtitleLanguages, - @NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex, + public AnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, int forcedAudioIndex, int forcedSubtitleIndex, @NotNull String subtitleNameFilter, boolean forceVideoEncoding, boolean forceAudioEncoding) { super("mkv"); + Configuration configuration = FFMpegConvert.getConfiguration(); this.ffprobePath = ffprobePath; this.ffmpegPath = ffmpegPath; - this.audioLanguages = audioLanguages; - this.subtitleLanguages = subtitleLanguages; - this.subtitlePreference = subtitlePreference; + this.audioLanguages = configuration.getAnimeAudioLanguages(); + this.subtitleLanguages = configuration.getAnimeSubtitleLanguages(); + this.subtitlePreference = configuration.getMinimalSubtitlePreference(); this.forcedAudioIndex = forcedAudioIndex; this.forcedSubtitleIndex = forcedSubtitleIndex; this.subtitleNameFilter = subtitleNameFilter; @@ -85,6 +85,7 @@ public class AnimeConverter extends AbstractConverter { public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, @NotNull String outFile) { + Configuration configuration = FFMpegConvert.getConfiguration(); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); List modules = new ArrayList<>(); if (this.debug) { @@ -92,16 +93,20 @@ public class AnimeConverter extends AbstractConverter { } modules.add(new FastStartModule()); - //Get the first video stream - modules.add(new MapAllModule<>(probeResult.getVideoStreams())); + //Map all video streams + List videoStreams = probeResult.getVideoStreams(); + modules.add(new MapAllModule<>(videoStreams)); + setOutputIndexes(videoStreams); //Get the first audio stream in accordance with chosen languages StreamSorter audioSorter = new AudioLanguageSorter(this.audioLanguages) .append(new ForcedFirstSorter<>(this.forcedAudioIndex)) .append(new SpecialAudioSorter(MinimalSubtitlePreference.REJECT)); List sortedAudio = audioSorter.chainSort(probeResult.getAudioStreams()); + setOutputIndexes(sortedAudio); modules.add(new MapAllModule<>(sortedAudio)); modules.add(new SetDefaultStreamModule<>(sortedAudio, 0)); + modules.add(new SetStreamLanguageModule<>(sortedAudio)); //Get the first subtitle stream in accordance with chosen languages and signs and songs prevention StreamSorter subtitleSorter = new SubtitleTitleSorter( @@ -109,13 +114,24 @@ public class AnimeConverter extends AbstractConverter { .append(new SubtitleLanguageSorter(this.subtitleLanguages)) .append(new MinimalSubtitleSorter(this.subtitlePreference)) .append(new ForcedFirstSorter<>(this.forcedSubtitleIndex)); - List sorted = subtitleSorter.chainSort(probeResult.getSubtitleStreams()); - modules.add(new MapAllModule<>(sorted)); - modules.add(new SetDefaultStreamModule<>(sorted, 0)); + List sortedSubtitles = subtitleSorter.chainSort(probeResult.getSubtitleStreams()); + setOutputIndexes(sortedSubtitles); + modules.add(new MapAllModule<>(sortedSubtitles)); + modules.add(new SetDefaultStreamModule<>(sortedSubtitles, 0)); + modules.add(new SetStreamLanguageModule<>(sortedSubtitles)); + OutputUtil.printDebug("Subtitle preference: " + this.subtitleLanguages + ", Subtitle name filter: " + + this.subtitleNameFilter + ", Minimal Subtitle Preference: " + this.subtitlePreference.name() + + ", Sorted order: " + Arrays.toString(sortedSubtitles.toArray())); - modules.add(new HardwareDecodeModule()); - if (!this.forceAudioEncoding) { - modules.add(new CopyAudioModule()); + if (configuration.useHardwareDecoding()) { + modules.add(new HardwareDecodeModule()); + } + + boolean encodeFlac = this.forceAudioEncoding || FFMpegConvert.getConfiguration().alwaysEncodeFlac(); + for (AudioStream audioStream : sortedAudio) { + if (!encodeFlac || !audioStream.getCodecName().equalsIgnoreCase("flac")) { + modules.add(new CopyAudioModule(audioStream)); + } } modules.add(new CopySubtitlesModule()); @@ -129,10 +145,10 @@ public class AnimeConverter extends AbstractConverter { if (encodingNecessary || this.forceVideoEncoding) { List availableHWAcceleration = getAvailableHardwareEncodingMethods(); - if (FFMpegConvert.useHardwareAcceleration() && availableHWAcceleration.contains("qsv")) { + if (configuration.useHardwareEncoding() && availableHWAcceleration.contains("qsv")) { modules.add(new SetVideoCodecModule("hevc_qsv")); modules.add(new SetQualityModule(17, "veryslow")); - } else if (FFMpegConvert.useHardwareAcceleration() && availableHWAcceleration.contains("cuda")) { + } else if (configuration.useHardwareEncoding() && availableHWAcceleration.contains("cuda")) { modules.add(new H265HardwareEncodingModule(20)); } else { modules.add(new SetVideoCodecModule("hevc")); diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH264Converter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH264Converter.java index 82afdf4..4a51198 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH264Converter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH264Converter.java @@ -12,6 +12,7 @@ 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.AudioStream; import net.knarcraft.ffmpegconverter.streams.StreamObject; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import org.jetbrains.annotations.NotNull; @@ -64,9 +65,11 @@ public class MkvH264Converter extends AbstractConverter { } // Map audio if present - if (!probeResult.getAudioStreams().isEmpty()) { - modules.add(new MapAllModule<>(probeResult.getAudioStreams())); - modules.add(new CopyAudioModule()); + List audioStreams = probeResult.getAudioStreams(); + if (!audioStreams.isEmpty()) { + modules.add(new MapAllModule<>(audioStreams)); + setOutputIndexes(audioStreams); + modules.add(new CopyAudioModule(audioStreams)); } // Map subtitles if present diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH265ReducedConverter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH265ReducedConverter.java index e6b0a42..33fba68 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH265ReducedConverter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/MkvH265ReducedConverter.java @@ -69,7 +69,8 @@ public class MkvH265ReducedConverter extends AbstractConverter { List audioStreams = probeResult.getAudioStreams(); if (!audioStreams.isEmpty()) { modules.add(new MapAllModule<>(audioStreams)); - modules.add(new CopyAudioModule()); + setOutputIndexes(audioStreams); + modules.add(new CopyAudioModule(audioStreams)); } // Map subtitles if present diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java index c671214..a91ed0b 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java @@ -8,8 +8,10 @@ import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor; import net.knarcraft.ffmpegconverter.converter.module.hardwarecoding.HardwareDecodeModule; import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule; import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule; +import net.knarcraft.ffmpegconverter.converter.module.output.CopyVideoModule; import net.knarcraft.ffmpegconverter.converter.module.output.MovTextModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; +import net.knarcraft.ffmpegconverter.streams.AudioStream; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -53,8 +55,10 @@ public class SubtitleEmbed extends AbstractConverter { modules.add(new MapAllModule<>(probeResult.parsedStreams())); modules.add(new HardwareDecodeModule()); - modules.add(new CopyAudioModule()); - modules.add(new CopyAudioModule()); + List audioStreams = probeResult.getAudioStreams(); + setOutputIndexes(audioStreams); + modules.add(new CopyAudioModule(audioStreams)); + modules.add(new CopyVideoModule()); modules.add(new MovTextModule()); modules.add(new SetOutputFileModule(outFile)); diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/WebAnimeConverter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/WebAnimeConverter.java index 66cad0a..c3f0442 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/WebAnimeConverter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/WebAnimeConverter.java @@ -1,5 +1,7 @@ package net.knarcraft.ffmpegconverter.converter; +import net.knarcraft.ffmpegconverter.FFMpegConvert; +import net.knarcraft.ffmpegconverter.config.Configuration; import net.knarcraft.ffmpegconverter.container.FFMpegCommand; import net.knarcraft.ffmpegconverter.container.StreamProbeResult; import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; @@ -33,8 +35,8 @@ import static net.knarcraft.ffmpegconverter.utility.FFMpegHelper.getNthSteam; */ public class WebAnimeConverter extends AbstractConverter { - private final String[] audioLanguages; - private final String[] subtitleLanguages; + private final List audioLanguages; + private final List subtitleLanguages; private final boolean toStereo; private final MinimalSubtitlePreference subtitlePreference; private final int forcedAudioIndex; @@ -46,22 +48,20 @@ public class WebAnimeConverter extends AbstractConverter { * * @param ffprobePath

Path/command to ffprobe.

* @param ffmpegPath

Path/command to ffmpeg.

- * @param audioLanguages

List of wanted audio languages in descending order.

- * @param subtitleLanguages

List of wanted subtitle languages in descending order.

* @param toStereo

Convert video with several audio channels to stereo.

* @param subtitlePreference

How minimal subtitles should be prioritized

* @param forcedAudioIndex

A specific audio stream to force. 0-indexed from the first audio stream found

* @param forcedSubtitleIndex

A specific subtitle stream to force. 0-indexed for the first subtitle stream found

*/ - public WebAnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, @NotNull String[] audioLanguages, - @NotNull String[] subtitleLanguages, boolean toStereo, + public WebAnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, boolean toStereo, @NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex, int forcedSubtitleIndex, @NotNull String subtitleNameFilter) { super("mp4"); + Configuration configuration = FFMpegConvert.getConfiguration(); this.ffprobePath = ffprobePath; this.ffmpegPath = ffmpegPath; - this.audioLanguages = audioLanguages; - this.subtitleLanguages = subtitleLanguages; + this.audioLanguages = configuration.getAnimeAudioLanguages(); + this.subtitleLanguages = configuration.getAnimeSubtitleLanguages(); this.toStereo = toStereo; this.subtitlePreference = subtitlePreference; this.forcedAudioIndex = forcedAudioIndex; diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyAudioModule.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyAudioModule.java index 45a77e4..3b3610e 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyAudioModule.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyAudioModule.java @@ -2,16 +2,56 @@ package net.knarcraft.ffmpegconverter.converter.module.output; import net.knarcraft.ffmpegconverter.container.FFMpegCommand; import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; +import net.knarcraft.ffmpegconverter.streams.AudioStream; +import net.knarcraft.ffmpegconverter.streams.StreamObject; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * A module for making FFMpeg copy the audio codec */ public class CopyAudioModule implements ConverterModule { + private final List streams; + + /** + * Instantiates a new copy audio module + * + * @param streams

The streams to specify the copy flag for, or null to not use a per-stream selector

+ */ + public CopyAudioModule(@NotNull List streams) { + this.streams = streams; + } + + /** + * Instantiates a new copy audio module + * + * @param stream

The stream to specify the copy flag for

+ */ + public CopyAudioModule(@NotNull AudioStream stream) { + this.streams = List.of(stream); + } + + /** + * Instantiates a new copy audio module + */ + public CopyAudioModule() { + this.streams = null; + } + @Override public void addArguments(@NotNull FFMpegCommand command) { - command.addOutputFileOption("-c:a", "copy"); + if (this.streams != null) { + for (StreamObject streamObject : this.streams) { + int outputIndex = streamObject.getOutputIndex(); + if (outputIndex != -1) { + command.addOutputFileOption("-c:a:" + outputIndex, "copy"); + } + } + } else { + command.addOutputFileOption("-c:a", "copy"); + } } } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/SetStreamLanguageModule.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/SetStreamLanguageModule.java new file mode 100644 index 0000000..c97a38e --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/SetStreamLanguageModule.java @@ -0,0 +1,38 @@ +package net.knarcraft.ffmpegconverter.converter.module.output; + +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 setting the language of some streams + * + * @param

The type of stream to set language for

+ */ +public class SetStreamLanguageModule implements ConverterModule { + + private final List streams; + + /** + * Instantiates a new set stream language module + * + * @param streams

The streams to set language for

+ */ + public SetStreamLanguageModule(@NotNull List streams) { + this.streams = streams; + } + + @Override + public void addArguments(@NotNull FFMpegCommand command) { + for (StreamObject stream : this.streams) { + if (!stream.getLanguage().equalsIgnoreCase("und") && !stream.getLanguage().isBlank()) { + command.addOutputFileOption(String.format("-metadata:s:%s:%d", stream.streamTypeCharacter(), + stream.getOutputIndex()), String.format("language=%s", stream.getLanguage())); + } + } + } + +} diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AbstractSorter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AbstractSorter.java index 57ef3ad..7a06e3e 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AbstractSorter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AbstractSorter.java @@ -71,7 +71,7 @@ public abstract class AbstractSorter implements StreamSo */ @NotNull protected List sortStreamsByLanguage(@NotNull List streams, - @NotNull String[] languages) { + @NotNull List languages) { List sorted = new ArrayList<>(); for (String language : languages) { for (G stream : streams) { diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AudioLanguageSorter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AudioLanguageSorter.java index c87cc2a..dc330d5 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AudioLanguageSorter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/AudioLanguageSorter.java @@ -10,14 +10,14 @@ import java.util.List; */ public class AudioLanguageSorter extends AbstractSorter { - private final String[] languageOrder; + private final List languageOrder; /** * Instantiates a new audio language sorter * * @param languageOrder

The order of preference for audio languages

*/ - public AudioLanguageSorter(@NotNull String[] languageOrder) { + public AudioLanguageSorter(@NotNull List languageOrder) { this.languageOrder = languageOrder; } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleLanguageSorter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleLanguageSorter.java index f584c0d..91f62de 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleLanguageSorter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleLanguageSorter.java @@ -11,14 +11,14 @@ import java.util.List; */ public class SubtitleLanguageSorter extends AbstractSorter { - private final String[] languageOrder; + private final List languageOrder; /** * Instantiates a new subtitle language sorter * * @param languageOrder

The order of preference for subtitle languages

*/ - public SubtitleLanguageSorter(@NotNull String[] languageOrder) { + public SubtitleLanguageSorter(@NotNull List languageOrder) { this.languageOrder = languageOrder; } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleTitleSorter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleTitleSorter.java index 2161027..b3837bd 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleTitleSorter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/SubtitleTitleSorter.java @@ -45,7 +45,7 @@ public class SubtitleTitleSorter extends AbstractSorter { boolean isRegEx = isValidRegularExpression(filter) && hasSpecialRegexCharacters(filter); - if (FFMpegConvert.isDebugEnabled()) { + if (FFMpegConvert.getConfiguration().isDebugEnabled()) { System.out.println("Filtering subtitles by filter " + filter + ". RegEx is " + isRegEx); } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java b/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java index 9b014b5..3aeadb4 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java @@ -4,7 +4,6 @@ import net.knarcraft.ffmpegconverter.FFMpegConvert; import net.knarcraft.ffmpegconverter.config.ConfigHandler; import net.knarcraft.ffmpegconverter.config.ConfigKey; import net.knarcraft.ffmpegconverter.utility.OutputUtil; -import org.apache.commons.configuration2.Configuration; import org.apache.commons.configuration2.PropertiesConfiguration; import org.jetbrains.annotations.NotNull; @@ -17,7 +16,7 @@ import java.util.List; */ public record AvailableHardwareEncoderHandler(@NotNull List availableHardwareEncodings) { - private static final ConfigHandler configHandler = FFMpegConvert.getConfigHandler(); + private static final ConfigHandler configHandler = FFMpegConvert.getConfiguration().getConfigHandler(); /** * Gets all hardware encodings @@ -44,7 +43,7 @@ public record AvailableHardwareEncoderHandler(@NotNull List availableHar */ public void save() { PropertiesConfiguration configuration = configHandler.getWritableConfiguration(); - configuration.setProperty(ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString(), this.availableHardwareEncodings); + configuration.setProperty(ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString(), String.join(",", this.availableHardwareEncodings)); configHandler.writeConfiguration(); OutputUtil.printDebug("Saved available hardware encoder handler"); } @@ -56,14 +55,12 @@ public record AvailableHardwareEncoderHandler(@NotNull List availableHar */ @NotNull public static AvailableHardwareEncoderHandler load() { - Configuration configuration; try { - configuration = configHandler.load(); + configHandler.load(); } catch (IOException e) { throw new RuntimeException(e); } - List getEncodings = configuration.getList(String.class, ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString()); - return new AvailableHardwareEncoderHandler(getEncodings); + return new AvailableHardwareEncoderHandler(FFMpegConvert.getConfiguration().hardwareEncoders()); } } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/streams/AbstractStream.java b/src/main/java/net/knarcraft/ffmpegconverter/streams/AbstractStream.java index f6f62a9..52f542c 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/streams/AbstractStream.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/streams/AbstractStream.java @@ -17,6 +17,7 @@ public abstract class AbstractStream implements StreamObject { protected final String language; protected final boolean isDefault; protected final String title; + protected int outputIndex; /** * Instantiates a new abstract stream @@ -28,11 +29,12 @@ public abstract class AbstractStream implements StreamObject { protected AbstractStream(@NotNull Map streamInfo, int inputIndex, int relativeIndex) { this.codecName = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_NAME), ""); this.absoluteIndex = ValueParsingHelper.parseInt(streamInfo.get(StreamTag.INDEX), -1); - this.language = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_LANGUAGE), "und"); + this.language = parseLanguage(streamInfo); this.isDefault = ValueParsingHelper.parseBoolean(streamInfo.get(StreamTag.DISPOSITION_DEFAULT), false); this.title = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_TITLE), ""); this.inputIndex = inputIndex; this.relativeIndex = relativeIndex; + this.outputIndex = -1; } @Override @@ -73,4 +75,40 @@ public abstract class AbstractStream implements StreamObject { return this.inputIndex; } + @Override + public int getOutputIndex() { + return this.outputIndex; + } + + @Override + public void setOutputIndex(int newIndex) { + this.outputIndex = newIndex; + } + + /** + * Parses the correct language of a stream + * + *

As some people tend to set weird incorrect languages for anime streams, this tries to correct that by + * detecting streams that are actually in english, but set to something else.

+ * + * @param streamInfo

All info about the stream

+ * @return

The actual language

+ */ + @NotNull + private String parseLanguage(@NotNull Map streamInfo) { + String languageString = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_LANGUAGE), "und"); + String title = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_TITLE), ""); + if (languageString.equalsIgnoreCase("zxx") || + (title.toLowerCase().matches(".*english.*") && languageString.equalsIgnoreCase("jpn"))) { + return "eng"; + } + return languageString; + } + + @Override + @NotNull + public String toString() { + return this.title + " | Input index: " + inputIndex + " | Language: " + language; + } + } \ No newline at end of file diff --git a/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamObject.java b/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamObject.java index 95c1437..44fcb13 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamObject.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamObject.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.NotNull; /** * An object describing a generic video file stream */ +@SuppressWarnings("unused") public interface StreamObject { /** @@ -68,4 +69,21 @@ public interface StreamObject { */ char streamTypeCharacter(); + /** + * Gets the index of this stream in the output file + * + * @return

The index of this stream in the output file, or -1 if not set

+ */ + int getOutputIndex(); + + /** + * Sets the index of this stream in the output file + * + *

After sorting and selecting streams, setting the output index makes it much easier to set properties for this + * specific stream, without having to rely on loops

+ * + * @param newIndex

The new output index

+ */ + void setOutputIndex(int newIndex); + } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java b/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java index 0dbe9da..81adee0 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java @@ -51,7 +51,7 @@ public class SubtitleStream extends AbstractStream implements StreamObject { */ private boolean checkIfIsFullSubtitle() { String titleLowercase = getTitle().toLowerCase().trim(); - return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z]+songs?.*") && + return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z+]+songs?.*") && !titleLowercase.matches(".*songs?[ &/a-z]+si(gn|ng)s?.*") && !titleLowercase.matches(".*forced.*") && !titleLowercase.matches(".*s&s.*") && @@ -68,4 +68,10 @@ public class SubtitleStream extends AbstractStream implements StreamObject { return 's'; } + @Override + @NotNull + public String toString() { + return super.toString() + " | Is full: " + this.isFullSubtitle; + } + } \ No newline at end of file diff --git a/src/main/java/net/knarcraft/ffmpegconverter/utility/ConfigHelper.java b/src/main/java/net/knarcraft/ffmpegconverter/utility/ConfigHelper.java new file mode 100644 index 0000000..829b978 --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/utility/ConfigHelper.java @@ -0,0 +1,60 @@ +package net.knarcraft.ffmpegconverter.utility; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A helper class for dealing with configuration value types + */ +public final class ConfigHelper { + + private ConfigHelper() { + + } + + /** + * Gets the given value as a string list + * + * @param value

The raw string list value

+ * @return

The value as a string list, or null if not compatible

+ */ + public static @NotNull List asStringList(@Nullable Object value) { + if (value == null) { + return new ArrayList<>(); + } + if (value instanceof String string) { + return List.of((string).split(",")); + } else if (value instanceof List list) { + List strings = new ArrayList<>(); + for (Object object : list) { + strings.add(String.valueOf(object)); + } + return strings; + } else { + return new ArrayList<>(); + } + } + + /** + * Gets the given value as a boolean + * + *

This will throw an exception if used for a non-boolean value

+ * + * @param value

The object value to get

+ * @return

The value of the given object as a boolean

+ * @throws ClassCastException

If the given value is not a boolean

+ */ + public static boolean asBoolean(@Nullable Object value) throws ClassCastException { + if (value instanceof Boolean booleanValue) { + return booleanValue; + } else if (value instanceof String) { + return Boolean.parseBoolean((String) value); + } else { + throw new ClassCastException(); + } + } + +} diff --git a/src/main/resources/conf/config.properties b/src/main/resources/conf/config.properties index 513139f..e23f1ec 100644 --- a/src/main/resources/conf/config.properties +++ b/src/main/resources/conf/config.properties @@ -3,4 +3,16 @@ debug=false # Enabling hardware acceleration will try to use hardware acceleration for converters where it's available. Note that # software encoders generally produce a lower file-size relative to the output quality. -hardware-acceleration=false \ No newline at end of file +hardware-acceleration-encode=false +# Hardware decoding can often speed up the conversion, but might be troublesome at times +hardware-acceleration-decode=true +# The available hardware encoders +encoders-hardware-accelerated=qsv,cuda,vaapi,dxva2,d3d11va,opencl,vulkan,d3d12va +# As FLAC can increase file size significantly, this option enabled automatic re-encode of flac tracks +encode-flac-always=false +# The preference for audio languages when converting anime (0 = undefined, * = any) +audio-languages-anime=jpn,eng,* +# The preference for subtitle languages when converting anime (0 = undefined, * = any) +subtitle-languages-anime=eng,* +# The preference for minimal subtitles, AKA Signs & Songs (REQUIRE/PREFER/NO_PREFERENCE/AVOID/REJECT) +minimal-subtitle-preference=AVOID \ No newline at end of file