Improves configuration handling and stuff
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good

This commit is contained in:
Kristian Knarvik 2024-05-19 10:16:51 +02:00
parent ac25ca1986
commit 380a1b800a
22 changed files with 598 additions and 148 deletions

View File

@ -1,7 +1,6 @@
package net.knarcraft.ffmpegconverter; package net.knarcraft.ffmpegconverter;
import net.knarcraft.ffmpegconverter.config.ConfigHandler; import net.knarcraft.ffmpegconverter.config.Configuration;
import net.knarcraft.ffmpegconverter.config.ConfigKey;
import net.knarcraft.ffmpegconverter.converter.AnimeConverter; 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;
@ -15,9 +14,7 @@ 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.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.OutputUtil; import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.Configuration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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 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 final Scanner READER = new Scanner(System.in, StandardCharsets.UTF_8);
private static Converter converter = null; private static Converter converter = null;
private static final ConfigHandler configHandler = new ConfigHandler(); private static final Configuration configuration = new Configuration();
private static boolean debug = false;
private static boolean useHardwareAcceleration = false;
public static void main(@NotNull String[] arguments) throws IOException { public static void main(@NotNull String[] arguments) throws IOException {
Configuration configuration = configHandler.load(); OutputUtil.setDebug(configuration.isDebugEnabled());
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());
}
converter = loadConverter(); converter = loadConverter();
@ -85,27 +73,10 @@ public class FFMpegConvert {
* @return <p>The configuration handler</p> * @return <p>The configuration handler</p>
*/ */
@NotNull @NotNull
public static ConfigHandler getConfigHandler() { public static Configuration getConfiguration() {
return configHandler; return configuration;
} }
/**
* Gets whether debug mode is enabled
*
* @return <p>True if debug mode is enabled</p>
*/
public static boolean isDebugEnabled() {
return debug;
}
/**
* Gets whether hardware accelerated encoding is enabled
*
* @return <p>True if hardware accelerated encoding is enabled</p>
*/
public static boolean useHardwareAcceleration() {
return useHardwareAcceleration;
}
/** /**
* Asks the user which converter they want, and assigns a converter instance to the converter variable * Asks the user which converter they want, and assigns a converter instance to the converter variable
@ -230,51 +201,37 @@ public class FFMpegConvert {
*/ */
@Nullable @Nullable
private static Converter generateAnimeConverter() { private static Converter generateAnimeConverter() {
OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Minimal subtitle " + OutputUtil.println("[Forced audio index 0-n] [Forced subtitle index 0-n] [Force video encoding true/false] " +
"preference REQUIRE/PREFER/NO_PREFERENCE/AVOID/REJECT] [Forced audio index 0-n] " + "[Force audio encoding true/false] [Subtitle name filter]\nYour input: ");
"[Forced subtitle index 0-n] [Force video encoding true/false] [Force audio encoding true/false] " + List<String> input = readInput(5);
"[Subtitle name filter]\nYour input: ");
List<String> input = readInput(8);
String[] audioLanguage = new String[]{"jpn", "nor", "eng", "0"};
String[] subtitleLanguage = new String[]{"nob", "nor", "eng", "jpn", "0"};
MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID;
int forcedAudioIndex = 0; int forcedAudioIndex = 0;
int forcedSubtitleIndex = 0; int forcedSubtitleIndex = 0;
String subtitleNameFilter = ""; String subtitleNameFilter = "";
boolean forceVideoEncoding = false; boolean forceVideoEncoding = false;
boolean forceAudioEncoding = false; boolean forceAudioEncoding = false;
try {
if (!input.isEmpty()) { if (!input.isEmpty()) {
audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0)); forcedAudioIndex = Integer.parseInt(input.get(0));
} }
if (input.size() > 1) { if (input.size() > 1) {
subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1)); forcedSubtitleIndex = Integer.parseInt(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.size() > 4) {
forcedSubtitleIndex = Integer.parseInt(input.get(4));
} }
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
OutputUtil.println("Forced audio or subtitle index is not a number"); OutputUtil.println("Forced audio or subtitle index is not a number");
return null; return null;
} }
if (input.size() > 5) { if (input.size() > 2) {
forceVideoEncoding = Boolean.parseBoolean(input.get(5)); forceVideoEncoding = Boolean.parseBoolean(input.get(2));
} }
if (input.size() > 6) { if (input.size() > 3) {
forceAudioEncoding = Boolean.parseBoolean(input.get(6)); forceAudioEncoding = Boolean.parseBoolean(input.get(3));
} }
if (input.size() > 7) { if (input.size() > 4) {
subtitleNameFilter = input.get(7); subtitleNameFilter = input.get(4);
} }
return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, subtitlePreference, return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter,
forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter, forceVideoEncoding, forceAudioEncoding); forceVideoEncoding, forceAudioEncoding);
} }
/** /**
@ -284,12 +241,9 @@ public class FFMpegConvert {
*/ */
@Nullable @Nullable
private static Converter generateWebAnimeConverter() { private static Converter generateWebAnimeConverter() {
OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stereo if " + OutputUtil.println("[Convert to stereo if necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " +
"necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " +
"[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: "); "[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: ");
List<String> input = readInput(7); List<String> input = readInput(5);
String[] audioLanguage = new String[]{"jpn", "0"};
String[] subtitleLanguage = new String[]{"eng", "0"};
boolean toStereo = true; boolean toStereo = true;
MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID; MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID;
int forcedAudioIndex = 0; int forcedAudioIndex = 0;
@ -297,32 +251,26 @@ public class FFMpegConvert {
String subtitleNameFilter = ""; String subtitleNameFilter = "";
if (!input.isEmpty()) { if (!input.isEmpty()) {
audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0)); toStereo = Boolean.parseBoolean(input.get(0));
} }
if (input.size() > 1) { if (input.size() > 1) {
subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1)); subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(1).toUpperCase());
}
if (input.size() > 2) {
toStereo = Boolean.parseBoolean(input.get(2));
}
if (input.size() > 3) {
subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase());
} }
try { try {
if (input.size() > 4) { if (input.size() > 2) {
forcedAudioIndex = Integer.parseInt(input.get(4)); forcedAudioIndex = Integer.parseInt(input.get(2));
} }
if (input.size() > 5) { if (input.size() > 3) {
forcedSubtitleIndex = Integer.parseInt(input.get(5)); forcedSubtitleIndex = Integer.parseInt(input.get(3));
} }
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
OutputUtil.println("Forced audio or subtitle index is not a number"); OutputUtil.println("Forced audio or subtitle index is not a number");
return null; return null;
} }
if (input.size() > 6) { if (input.size() > 4) {
subtitleNameFilter = input.get(6); 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); subtitlePreference, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter);
} }

View File

@ -8,10 +8,12 @@ import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations; import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.commons.configuration2.ex.ConfigurationException;
import org.jetbrains.annotations.NotNull; 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;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -23,6 +25,18 @@ public class ConfigHandler {
private final Configurations configurations = new Configurations(); private final Configurations configurations = new Configurations();
private final File settingsFile = new File(configFolder, "config.properties"); private final File settingsFile = new File(configFolder, "config.properties");
private FileBasedConfigurationBuilder<PropertiesConfiguration> builder = configurations.propertiesBuilder(settingsFile); private FileBasedConfigurationBuilder<PropertiesConfiguration> builder = configurations.propertiesBuilder(settingsFile);
private final Map<ConfigKey<?>, Object> optionValues = new HashMap<>();
/**
* Gets the current value of a configuration option
*
* @param key <p>The configuration key to get the value of</p>
* @return <p>The current value</p>
*/
@Nullable
public Object getValue(@NotNull ConfigKey<?> key) {
return optionValues.get(key);
}
/** /**
* Gets a writable configuration used for changing settings * Gets a writable configuration used for changing settings
@ -64,11 +78,9 @@ public class ConfigHandler {
/** /**
* Loads the saved configuration file * Loads the saved configuration file
*
* @return <p>The loaded configuration</p>
*/ */
@NotNull public void load() throws IOException {
public Configuration load() throws IOException { optionValues.clear();
Configuration configuration; Configuration configuration;
if (!settingsFile.exists()) { if (!settingsFile.exists()) {
configuration = new PropertiesConfiguration(); configuration = new PropertiesConfiguration();
@ -86,7 +98,14 @@ public class ConfigHandler {
} }
// Reload contents in the builder // Reload contents in the builder
builder = configurations.propertiesBuilder(settingsFile); 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());
}
}
} }
} }

View File

@ -2,31 +2,106 @@ package net.knarcraft.ffmpegconverter.config;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/** /**
* A representation of all configuration keys * A representation of all configuration keys
*/ */
public enum ConfigKey { public class ConfigKey<T> {
private static final Set<ConfigKey<?>> allKeys = new HashSet<>();
/** /**
* The configuration key for the list of hardware-accelerated encoders available on the system * The configuration key for the list of hardware-accelerated encoders available on the system
*/ */
HARDWARE_ACCELERATED_ENCODERS("encoders-hardware-accelerated"), public static final ConfigKey<String> HARDWARE_ACCELERATED_ENCODERS = new ConfigKey<>("encoders-hardware-accelerated", String.class, "qsv,cuda,dxva2,d3d11va,opencl,vulkan");
/** /**
* The configuration key for toggling debug mode * The configuration key for toggling debug mode
*/ */
DEBUG("debug"), public static final ConfigKey<Boolean> 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<Boolean> USE_HARDWARE_ENCODING = createKey("hardware-acceleration-encode", Boolean.class, false);
;
/**
* The configuration key for toggling hardware decoding
*/
public static final ConfigKey<Boolean> USE_HARDWARE_DECODING = createKey("hardware-acceleration-decode", Boolean.class, true);
/**
* The configuration key for toggling forced flac encoding
*/
public static final ConfigKey<Boolean> ENCODE_FLAC_ALWAYS = createKey("encode-flac-always", Boolean.class, false);
/**
* The configuration key for anime audio languages
*/
public static final ConfigKey<String> AUDIO_LANGUAGES_ANIME = createKey("audio-languages-anime", String.class, "jpn,eng,*");
/**
* The configuration key for anime subtitle languages
*/
public static final ConfigKey<String> SUBTITLE_LANGUAGES_ANIME = createKey("subtitle-languages-anime", String.class, "eng,*");
/**
* The configuration key for the minimal subtitle preference
*/
public static final ConfigKey<String> MINIMAL_SUBTITLE_PREFERENCE = createKey("minimal-subtitle-preference", String.class, "AVOID");
private final String configKey; private final String configKey;
private final T defaultValue;
private final Class<T> type;
ConfigKey(@NotNull String configKey) { /**
* Instantiates a new config key
*
* @param configKey <p>The storage key in the configuration file</p>
* @param type <p>The type of this option's value</p>
* @param defaultValue <p>The default value of this option</p>
*/
private ConfigKey(@NotNull String configKey, @NotNull Class<T> type, @NotNull T defaultValue) {
this.configKey = configKey; this.configKey = configKey;
this.type = type;
this.defaultValue = defaultValue;
}
/**
* Creates a new config key
*
* @param configKey <p>The storage key in the configuration file</p>
* @param type <p>The type of this option's value</p>
* @param defaultValue <p>The default value of this option</p>
* @param <F> <p>The type of key to create</p>
* @return <p>The new config key</p>
*/
private static <F> ConfigKey<F> createKey(@NotNull String configKey, @NotNull Class<F> type, @NotNull F defaultValue) {
ConfigKey<F> key = new ConfigKey<>(configKey, type, defaultValue);
allKeys.add(key);
return key;
}
/**
* Gets the type of this option's value
*
* @return <p>The type of the value</p>
*/
@NotNull
public Class<T> getType() {
return this.type;
}
/**
* Gets the default value for the option represented by this key
*
* @return <p>The default value</p>
*/
@NotNull
public T getDefaultValue() {
return this.defaultValue;
} }
@Override @Override
@ -34,4 +109,14 @@ public enum ConfigKey {
return this.configKey; return this.configKey;
} }
/**
* Gets all configuration keys
*
* @return <p>All configuration keys</p>
*/
@NotNull
public static ConfigKey<?>[] values() {
return allKeys.toArray(new ConfigKey[0]);
}
} }

View File

@ -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<String> hardwareEncoders;
private boolean alwaysEncodeFlac;
private List<String> animeAudioLanguages;
private List<String> 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 <p>The underlying config handler</p>
*/
@NotNull
public ConfigHandler getConfigHandler() {
return this.configHandler;
}
/**
* Gets whether debug mode is enabled
*
* @return <p>True if debug mode is enabled</p>
*/
public boolean isDebugEnabled() {
return this.debug;
}
/**
* Gets whether hardware accelerated encoding is enabled
*
* @return <p>True if hardware accelerated encoding is enabled</p>
*/
public boolean useHardwareEncoding() {
return this.useHardwareEncoding;
}
/**
* Gets whether hardware accelerated decoding is enabled
*
* @return <p>True if hardware accelerated decoding is enabled</p>
*/
public boolean useHardwareDecoding() {
return this.useHardwareDecoding;
}
/**
* Gets whether FLAC audio tracks should always be re-encoded
*
* @return <p>Whether FLAC tracks should always be re-encoded</p>
*/
public boolean alwaysEncodeFlac() {
return this.alwaysEncodeFlac;
}
/**
* Gets all hardware encoders defined in the configuration
*
* @return <p>The specified hardware encoders</p>
*/
@NotNull
public List<String> hardwareEncoders() {
return this.hardwareEncoders;
}
/**
* Gets the audio language priorities for usage when converting anime
*
* @return <p>The anime audio language priorities</p>
*/
@NotNull
public List<String> getAnimeAudioLanguages() {
return this.animeAudioLanguages;
}
/**
* Gets the subtitle language priorities for usage when converting anime
*
* @return <p>The anime subtitle language priorities</p>
*/
@NotNull
public List<String> getAnimeSubtitleLanguages() {
return this.animeSubtitleLanguages;
}
/**
* Gets the preference for minimal subtitles
*
* @return <p>The minimal subtitle preference</p>
*/
@NotNull
public MinimalSubtitlePreference getMinimalSubtitlePreference() {
return this.minimalSubtitlePreference;
}
}

View File

@ -4,6 +4,7 @@ import net.knarcraft.ffmpegconverter.FFMpegConvert;
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.handler.AvailableHardwareEncoderHandler; import net.knarcraft.ffmpegconverter.handler.AvailableHardwareEncoderHandler;
import net.knarcraft.ffmpegconverter.streams.StreamObject;
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
import net.knarcraft.ffmpegconverter.utility.FileHelper; import net.knarcraft.ffmpegconverter.utility.FileHelper;
import net.knarcraft.ffmpegconverter.utility.FileUtil; import net.knarcraft.ffmpegconverter.utility.FileUtil;
@ -21,7 +22,7 @@ import java.util.List;
*/ */
public abstract class AbstractConverter implements Converter { public abstract class AbstractConverter implements Converter {
final boolean debug = FFMpegConvert.isDebugEnabled(); final boolean debug = FFMpegConvert.getConfiguration().isDebugEnabled();
private final String newExtension; private final String newExtension;
protected String ffprobePath; protected String ffprobePath;
protected String ffmpegPath; protected String ffmpegPath;
@ -136,4 +137,18 @@ public abstract class AbstractConverter implements Converter {
return encoderHandler.availableHardwareEncodings(); return encoderHandler.availableHardwareEncodings();
} }
/**
* Sets the output indexes for the given streams
*
* <p>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.</p>
*
* @param streams <p>The streams to set the output indexes for</p>
*/
protected <K extends StreamObject> void setOutputIndexes(@NotNull List<K> streams) {
for (int i = 0; i < streams.size(); i++) {
streams.get(i).setOutputIndex(i);
}
}
} }

View File

@ -1,6 +1,7 @@
package net.knarcraft.ffmpegconverter.converter; package net.knarcraft.ffmpegconverter.converter;
import net.knarcraft.ffmpegconverter.FFMpegConvert; import net.knarcraft.ffmpegconverter.FFMpegConvert;
import net.knarcraft.ffmpegconverter.config.Configuration;
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.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.FastStartModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetQualityModule; 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.module.output.SetVideoCodecModule;
import net.knarcraft.ffmpegconverter.converter.sorter.AudioLanguageSorter; import net.knarcraft.ffmpegconverter.converter.sorter.AudioLanguageSorter;
import net.knarcraft.ffmpegconverter.converter.sorter.ForcedFirstSorter; 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.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 net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
@ -40,8 +44,8 @@ import java.util.List;
*/ */
public class AnimeConverter extends AbstractConverter { public class AnimeConverter extends AbstractConverter {
private final String[] audioLanguages; private final List<String> audioLanguages;
private final String[] subtitleLanguages; private final List<String> subtitleLanguages;
private final MinimalSubtitlePreference subtitlePreference; private final MinimalSubtitlePreference subtitlePreference;
private final int forcedAudioIndex; private final int forcedAudioIndex;
private final int forcedSubtitleIndex; private final int forcedSubtitleIndex;
@ -54,25 +58,21 @@ public class AnimeConverter extends AbstractConverter {
* *
* @param ffprobePath <p>Path/command to ffprobe.</p> * @param ffprobePath <p>Path/command to ffprobe.</p>
* @param ffmpegPath <p>Path/command to ffmpeg.</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 subtitlePreference <p>How minimal subtitles should be prioritized</p>
* @param forcedAudioIndex <p>A specific audio stream to force as default. 0-indexed from the first audio stream found</p> * @param forcedAudioIndex <p>A specific audio stream to force as default. 0-indexed from the first audio stream found</p>
* @param forcedSubtitleIndex <p>A specific subtitle stream to force as default. 0-indexed for the first subtitle stream found</p> * @param forcedSubtitleIndex <p>A specific subtitle stream to force as default. 0-indexed for the first subtitle stream found</p>
* @param forceVideoEncoding <p>Whether to enforce encoding on the video, even if already hevc</p> * @param forceVideoEncoding <p>Whether to enforce encoding on the video, even if already hevc</p>
* @param forceAudioEncoding <p>Whether to convert audio to web-playable, even though there should be no need</p> * @param forceAudioEncoding <p>Whether to convert audio to web-playable, even though there should be no need</p>
*/ */
public AnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, @NotNull String[] audioLanguages, public AnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, int forcedAudioIndex,
@NotNull String[] subtitleLanguages,
@NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex,
int forcedSubtitleIndex, @NotNull String subtitleNameFilter, boolean forceVideoEncoding, int forcedSubtitleIndex, @NotNull String subtitleNameFilter, boolean forceVideoEncoding,
boolean forceAudioEncoding) { boolean forceAudioEncoding) {
super("mkv"); super("mkv");
Configuration configuration = FFMpegConvert.getConfiguration();
this.ffprobePath = ffprobePath; this.ffprobePath = ffprobePath;
this.ffmpegPath = ffmpegPath; this.ffmpegPath = ffmpegPath;
this.audioLanguages = audioLanguages; this.audioLanguages = configuration.getAnimeAudioLanguages();
this.subtitleLanguages = subtitleLanguages; this.subtitleLanguages = configuration.getAnimeSubtitleLanguages();
this.subtitlePreference = subtitlePreference; this.subtitlePreference = configuration.getMinimalSubtitlePreference();
this.forcedAudioIndex = forcedAudioIndex; this.forcedAudioIndex = forcedAudioIndex;
this.forcedSubtitleIndex = forcedSubtitleIndex; this.forcedSubtitleIndex = forcedSubtitleIndex;
this.subtitleNameFilter = subtitleNameFilter; this.subtitleNameFilter = subtitleNameFilter;
@ -85,6 +85,7 @@ public class AnimeConverter extends AbstractConverter {
public FFMpegCommand generateConversionCommand(@NotNull String executable, public FFMpegCommand generateConversionCommand(@NotNull String executable,
@NotNull StreamProbeResult probeResult, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
Configuration configuration = FFMpegConvert.getConfiguration();
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
List<ConverterModule> modules = new ArrayList<>(); List<ConverterModule> modules = new ArrayList<>();
if (this.debug) { if (this.debug) {
@ -92,16 +93,20 @@ public class AnimeConverter extends AbstractConverter {
} }
modules.add(new FastStartModule()); modules.add(new FastStartModule());
//Get the first video stream //Map all video streams
modules.add(new MapAllModule<>(probeResult.getVideoStreams())); List<VideoStream> videoStreams = probeResult.getVideoStreams();
modules.add(new MapAllModule<>(videoStreams));
setOutputIndexes(videoStreams);
//Get the first audio stream in accordance with chosen languages //Get the first audio stream in accordance with chosen languages
StreamSorter<AudioStream> audioSorter = new AudioLanguageSorter(this.audioLanguages) StreamSorter<AudioStream> audioSorter = new AudioLanguageSorter(this.audioLanguages)
.append(new ForcedFirstSorter<>(this.forcedAudioIndex)) .append(new ForcedFirstSorter<>(this.forcedAudioIndex))
.append(new SpecialAudioSorter(MinimalSubtitlePreference.REJECT)); .append(new SpecialAudioSorter(MinimalSubtitlePreference.REJECT));
List<AudioStream> sortedAudio = audioSorter.chainSort(probeResult.getAudioStreams()); List<AudioStream> sortedAudio = audioSorter.chainSort(probeResult.getAudioStreams());
setOutputIndexes(sortedAudio);
modules.add(new MapAllModule<>(sortedAudio)); modules.add(new MapAllModule<>(sortedAudio));
modules.add(new SetDefaultStreamModule<>(sortedAudio, 0)); 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 //Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
StreamSorter<SubtitleStream> subtitleSorter = new SubtitleTitleSorter( StreamSorter<SubtitleStream> subtitleSorter = new SubtitleTitleSorter(
@ -109,13 +114,24 @@ public class AnimeConverter extends AbstractConverter {
.append(new SubtitleLanguageSorter(this.subtitleLanguages)) .append(new SubtitleLanguageSorter(this.subtitleLanguages))
.append(new MinimalSubtitleSorter(this.subtitlePreference)) .append(new MinimalSubtitleSorter(this.subtitlePreference))
.append(new ForcedFirstSorter<>(this.forcedSubtitleIndex)); .append(new ForcedFirstSorter<>(this.forcedSubtitleIndex));
List<SubtitleStream> sorted = subtitleSorter.chainSort(probeResult.getSubtitleStreams()); List<SubtitleStream> sortedSubtitles = subtitleSorter.chainSort(probeResult.getSubtitleStreams());
modules.add(new MapAllModule<>(sorted)); setOutputIndexes(sortedSubtitles);
modules.add(new SetDefaultStreamModule<>(sorted, 0)); 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()));
if (configuration.useHardwareDecoding()) {
modules.add(new HardwareDecodeModule()); modules.add(new HardwareDecodeModule());
if (!this.forceAudioEncoding) { }
modules.add(new CopyAudioModule());
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()); modules.add(new CopySubtitlesModule());
@ -129,10 +145,10 @@ public class AnimeConverter extends AbstractConverter {
if (encodingNecessary || this.forceVideoEncoding) { if (encodingNecessary || this.forceVideoEncoding) {
List<String> availableHWAcceleration = getAvailableHardwareEncodingMethods(); List<String> availableHWAcceleration = getAvailableHardwareEncodingMethods();
if (FFMpegConvert.useHardwareAcceleration() && availableHWAcceleration.contains("qsv")) { if (configuration.useHardwareEncoding() && availableHWAcceleration.contains("qsv")) {
modules.add(new SetVideoCodecModule("hevc_qsv")); modules.add(new SetVideoCodecModule("hevc_qsv"));
modules.add(new SetQualityModule(17, "veryslow")); 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)); modules.add(new H265HardwareEncodingModule(20));
} else { } else {
modules.add(new SetVideoCodecModule("hevc")); modules.add(new SetVideoCodecModule("hevc"));

View File

@ -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.CopySubtitlesModule;
import net.knarcraft.ffmpegconverter.converter.module.output.FastStartModule; import net.knarcraft.ffmpegconverter.converter.module.output.FastStartModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
import net.knarcraft.ffmpegconverter.streams.AudioStream;
import net.knarcraft.ffmpegconverter.streams.StreamObject; import net.knarcraft.ffmpegconverter.streams.StreamObject;
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -64,9 +65,11 @@ public class MkvH264Converter extends AbstractConverter {
} }
// Map audio if present // Map audio if present
if (!probeResult.getAudioStreams().isEmpty()) { List<AudioStream> audioStreams = probeResult.getAudioStreams();
modules.add(new MapAllModule<>(probeResult.getAudioStreams())); if (!audioStreams.isEmpty()) {
modules.add(new CopyAudioModule()); modules.add(new MapAllModule<>(audioStreams));
setOutputIndexes(audioStreams);
modules.add(new CopyAudioModule(audioStreams));
} }
// Map subtitles if present // Map subtitles if present

View File

@ -69,7 +69,8 @@ public class MkvH265ReducedConverter extends AbstractConverter {
List<AudioStream> audioStreams = probeResult.getAudioStreams(); List<AudioStream> audioStreams = probeResult.getAudioStreams();
if (!audioStreams.isEmpty()) { if (!audioStreams.isEmpty()) {
modules.add(new MapAllModule<>(audioStreams)); modules.add(new MapAllModule<>(audioStreams));
modules.add(new CopyAudioModule()); setOutputIndexes(audioStreams);
modules.add(new CopyAudioModule(audioStreams));
} }
// Map subtitles if present // Map subtitles if present

View File

@ -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.hardwarecoding.HardwareDecodeModule;
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule; import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule; 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.MovTextModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
import net.knarcraft.ffmpegconverter.streams.AudioStream;
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -53,8 +55,10 @@ public class SubtitleEmbed extends AbstractConverter {
modules.add(new MapAllModule<>(probeResult.parsedStreams())); modules.add(new MapAllModule<>(probeResult.parsedStreams()));
modules.add(new HardwareDecodeModule()); modules.add(new HardwareDecodeModule());
modules.add(new CopyAudioModule()); List<AudioStream> audioStreams = probeResult.getAudioStreams();
modules.add(new CopyAudioModule()); setOutputIndexes(audioStreams);
modules.add(new CopyAudioModule(audioStreams));
modules.add(new CopyVideoModule());
modules.add(new MovTextModule()); modules.add(new MovTextModule());
modules.add(new SetOutputFileModule(outFile)); modules.add(new SetOutputFileModule(outFile));

View File

@ -1,5 +1,7 @@
package net.knarcraft.ffmpegconverter.converter; 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.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.ConverterModule;
@ -33,8 +35,8 @@ import static net.knarcraft.ffmpegconverter.utility.FFMpegHelper.getNthSteam;
*/ */
public class WebAnimeConverter extends AbstractConverter { public class WebAnimeConverter extends AbstractConverter {
private final String[] audioLanguages; private final List<String> audioLanguages;
private final String[] subtitleLanguages; private final List<String> subtitleLanguages;
private final boolean toStereo; private final boolean toStereo;
private final MinimalSubtitlePreference subtitlePreference; private final MinimalSubtitlePreference subtitlePreference;
private final int forcedAudioIndex; private final int forcedAudioIndex;
@ -46,22 +48,20 @@ public class WebAnimeConverter extends AbstractConverter {
* *
* @param ffprobePath <p>Path/command to ffprobe.</p> * @param ffprobePath <p>Path/command to ffprobe.</p>
* @param ffmpegPath <p>Path/command to ffmpeg.</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 toStereo <p>Convert video with several audio channels to stereo.</p>
* @param subtitlePreference <p>How minimal subtitles should be prioritized</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 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> * @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, public WebAnimeConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, boolean toStereo,
@NotNull String[] subtitleLanguages, boolean toStereo,
@NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex, @NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex,
int forcedSubtitleIndex, @NotNull String subtitleNameFilter) { int forcedSubtitleIndex, @NotNull String subtitleNameFilter) {
super("mp4"); super("mp4");
Configuration configuration = FFMpegConvert.getConfiguration();
this.ffprobePath = ffprobePath; this.ffprobePath = ffprobePath;
this.ffmpegPath = ffmpegPath; this.ffmpegPath = ffmpegPath;
this.audioLanguages = audioLanguages; this.audioLanguages = configuration.getAnimeAudioLanguages();
this.subtitleLanguages = subtitleLanguages; this.subtitleLanguages = configuration.getAnimeSubtitleLanguages();
this.toStereo = toStereo; this.toStereo = toStereo;
this.subtitlePreference = subtitlePreference; this.subtitlePreference = subtitlePreference;
this.forcedAudioIndex = forcedAudioIndex; this.forcedAudioIndex = forcedAudioIndex;

View File

@ -2,16 +2,56 @@ package net.knarcraft.ffmpegconverter.converter.module.output;
import net.knarcraft.ffmpegconverter.container.FFMpegCommand; import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; 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 org.jetbrains.annotations.NotNull;
import java.util.List;
/** /**
* A module for making FFMpeg copy the audio codec * A module for making FFMpeg copy the audio codec
*/ */
public class CopyAudioModule implements ConverterModule { public class CopyAudioModule implements ConverterModule {
private final List<AudioStream> streams;
/**
* Instantiates a new copy audio module
*
* @param streams <p>The streams to specify the copy flag for, or null to not use a per-stream selector</p>
*/
public CopyAudioModule(@NotNull List<AudioStream> streams) {
this.streams = streams;
}
/**
* Instantiates a new copy audio module
*
* @param stream <p>The stream to specify the copy flag for</p>
*/
public CopyAudioModule(@NotNull AudioStream stream) {
this.streams = List.of(stream);
}
/**
* Instantiates a new copy audio module
*/
public CopyAudioModule() {
this.streams = null;
}
@Override @Override
public void addArguments(@NotNull FFMpegCommand command) { public void addArguments(@NotNull FFMpegCommand command) {
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"); command.addOutputFileOption("-c:a", "copy");
} }
}
} }

View File

@ -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 <K> <p>The type of stream to set language for</p>
*/
public class SetStreamLanguageModule<K extends StreamObject> implements ConverterModule {
private final List<K> streams;
/**
* Instantiates a new set stream language module
*
* @param streams <p>The streams to set language for</p>
*/
public SetStreamLanguageModule(@NotNull List<K> 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()));
}
}
}
}

View File

@ -71,7 +71,7 @@ public abstract class AbstractSorter<L extends StreamObject> implements StreamSo
*/ */
@NotNull @NotNull
protected <G extends StreamObject> List<G> sortStreamsByLanguage(@NotNull List<G> streams, protected <G extends StreamObject> List<G> sortStreamsByLanguage(@NotNull List<G> streams,
@NotNull String[] languages) { @NotNull List<String> languages) {
List<G> sorted = new ArrayList<>(); List<G> sorted = new ArrayList<>();
for (String language : languages) { for (String language : languages) {
for (G stream : streams) { for (G stream : streams) {

View File

@ -10,14 +10,14 @@ import java.util.List;
*/ */
public class AudioLanguageSorter extends AbstractSorter<AudioStream> { public class AudioLanguageSorter extends AbstractSorter<AudioStream> {
private final String[] languageOrder; private final List<String> languageOrder;
/** /**
* Instantiates a new audio language sorter * Instantiates a new audio language sorter
* *
* @param languageOrder <p>The order of preference for audio languages</p> * @param languageOrder <p>The order of preference for audio languages</p>
*/ */
public AudioLanguageSorter(@NotNull String[] languageOrder) { public AudioLanguageSorter(@NotNull List<String> languageOrder) {
this.languageOrder = languageOrder; this.languageOrder = languageOrder;
} }

View File

@ -11,14 +11,14 @@ import java.util.List;
*/ */
public class SubtitleLanguageSorter extends AbstractSorter<SubtitleStream> { public class SubtitleLanguageSorter extends AbstractSorter<SubtitleStream> {
private final String[] languageOrder; private final List<String> languageOrder;
/** /**
* Instantiates a new subtitle language sorter * Instantiates a new subtitle language sorter
* *
* @param languageOrder <p>The order of preference for subtitle languages</p> * @param languageOrder <p>The order of preference for subtitle languages</p>
*/ */
public SubtitleLanguageSorter(@NotNull String[] languageOrder) { public SubtitleLanguageSorter(@NotNull List<String> languageOrder) {
this.languageOrder = languageOrder; this.languageOrder = languageOrder;
} }

View File

@ -45,7 +45,7 @@ public class SubtitleTitleSorter extends AbstractSorter<SubtitleStream> {
boolean isRegEx = isValidRegularExpression(filter) && hasSpecialRegexCharacters(filter); boolean isRegEx = isValidRegularExpression(filter) && hasSpecialRegexCharacters(filter);
if (FFMpegConvert.isDebugEnabled()) { if (FFMpegConvert.getConfiguration().isDebugEnabled()) {
System.out.println("Filtering subtitles by filter " + filter + ". RegEx is " + isRegEx); System.out.println("Filtering subtitles by filter " + filter + ". RegEx is " + isRegEx);
} }

View File

@ -4,7 +4,6 @@ import net.knarcraft.ffmpegconverter.FFMpegConvert;
import net.knarcraft.ffmpegconverter.config.ConfigHandler; import net.knarcraft.ffmpegconverter.config.ConfigHandler;
import net.knarcraft.ffmpegconverter.config.ConfigKey; import net.knarcraft.ffmpegconverter.config.ConfigKey;
import net.knarcraft.ffmpegconverter.utility.OutputUtil; import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.configuration2.PropertiesConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -17,7 +16,7 @@ import java.util.List;
*/ */
public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHardwareEncodings) { public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHardwareEncodings) {
private static final ConfigHandler configHandler = FFMpegConvert.getConfigHandler(); private static final ConfigHandler configHandler = FFMpegConvert.getConfiguration().getConfigHandler();
/** /**
* Gets all hardware encodings * Gets all hardware encodings
@ -44,7 +43,7 @@ public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHar
*/ */
public void save() { public void save() {
PropertiesConfiguration configuration = configHandler.getWritableConfiguration(); 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(); configHandler.writeConfiguration();
OutputUtil.printDebug("Saved available hardware encoder handler"); OutputUtil.printDebug("Saved available hardware encoder handler");
} }
@ -56,14 +55,12 @@ public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHar
*/ */
@NotNull @NotNull
public static AvailableHardwareEncoderHandler load() { public static AvailableHardwareEncoderHandler load() {
Configuration configuration;
try { try {
configuration = configHandler.load(); configHandler.load();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
List<String> getEncodings = configuration.getList(String.class, ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString()); return new AvailableHardwareEncoderHandler(FFMpegConvert.getConfiguration().hardwareEncoders());
return new AvailableHardwareEncoderHandler(getEncodings);
} }
} }

View File

@ -17,6 +17,7 @@ public abstract class AbstractStream implements StreamObject {
protected final String language; protected final String language;
protected final boolean isDefault; protected final boolean isDefault;
protected final String title; protected final String title;
protected int outputIndex;
/** /**
* Instantiates a new abstract stream * Instantiates a new abstract stream
@ -28,11 +29,12 @@ public abstract class AbstractStream implements StreamObject {
protected AbstractStream(@NotNull Map<StreamTag, String> streamInfo, int inputIndex, int relativeIndex) { protected AbstractStream(@NotNull Map<StreamTag, String> streamInfo, int inputIndex, int relativeIndex) {
this.codecName = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_NAME), ""); this.codecName = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_NAME), "");
this.absoluteIndex = ValueParsingHelper.parseInt(streamInfo.get(StreamTag.INDEX), -1); 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.isDefault = ValueParsingHelper.parseBoolean(streamInfo.get(StreamTag.DISPOSITION_DEFAULT), false);
this.title = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_TITLE), ""); this.title = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_TITLE), "");
this.inputIndex = inputIndex; this.inputIndex = inputIndex;
this.relativeIndex = relativeIndex; this.relativeIndex = relativeIndex;
this.outputIndex = -1;
} }
@Override @Override
@ -73,4 +75,40 @@ public abstract class AbstractStream implements StreamObject {
return this.inputIndex; 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
*
* <p>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.</p>
*
* @param streamInfo <p>All info about the stream</p>
* @return <p>The actual language</p>
*/
@NotNull
private String parseLanguage(@NotNull Map<StreamTag, String> 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;
}
} }

View File

@ -5,6 +5,7 @@ import org.jetbrains.annotations.NotNull;
/** /**
* An object describing a generic video file stream * An object describing a generic video file stream
*/ */
@SuppressWarnings("unused")
public interface StreamObject { public interface StreamObject {
/** /**
@ -68,4 +69,21 @@ public interface StreamObject {
*/ */
char streamTypeCharacter(); char streamTypeCharacter();
/**
* Gets the index of this stream in the output file
*
* @return <p>The index of this stream in the output file, or -1 if not set</p>
*/
int getOutputIndex();
/**
* Sets the index of this stream in the output file
*
* <p>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</p>
*
* @param newIndex <p>The new output index</p>
*/
void setOutputIndex(int newIndex);
} }

View File

@ -51,7 +51,7 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
*/ */
private boolean checkIfIsFullSubtitle() { private boolean checkIfIsFullSubtitle() {
String titleLowercase = getTitle().toLowerCase().trim(); 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(".*songs?[ &/a-z]+si(gn|ng)s?.*") &&
!titleLowercase.matches(".*forced.*") && !titleLowercase.matches(".*forced.*") &&
!titleLowercase.matches(".*s&s.*") && !titleLowercase.matches(".*s&s.*") &&
@ -68,4 +68,10 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
return 's'; return 's';
} }
@Override
@NotNull
public String toString() {
return super.toString() + " | Is full: " + this.isFullSubtitle;
}
} }

View File

@ -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 <p>The raw string list value</p>
* @return <p>The value as a string list, or null if not compatible</p>
*/
public static @NotNull List<String> 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<String> 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
*
* <p>This will throw an exception if used for a non-boolean value</p>
*
* @param value <p>The object value to get</p>
* @return <p>The value of the given object as a boolean</p>
* @throws ClassCastException <p>If the given value is not a boolean</p>
*/
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();
}
}
}

View File

@ -3,4 +3,16 @@
debug=false debug=false
# Enabling hardware acceleration will try to use hardware acceleration for converters where it's available. Note that # 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. # software encoders generally produce a lower file-size relative to the output quality.
hardware-acceleration=false 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