diff --git a/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java b/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java index 422775e..caf4c08 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/FFMpegConvert.java @@ -1,6 +1,7 @@ package net.knarcraft.ffmpegconverter; import net.knarcraft.ffmpegconverter.config.ConfigHandler; +import net.knarcraft.ffmpegconverter.config.ConfigKey; import net.knarcraft.ffmpegconverter.converter.AnimeConverter; import net.knarcraft.ffmpegconverter.converter.AudioConverter; import net.knarcraft.ffmpegconverter.converter.Converter; @@ -39,13 +40,18 @@ public class FFMpegConvert { private static Converter converter = null; private static final ConfigHandler configHandler = new ConfigHandler(); private static boolean debug = false; + private static boolean useHardwareAcceleration = false; public static void main(@NotNull String[] arguments) throws IOException { Configuration configuration = configHandler.load(); - if (configuration.containsKey("debug")) { - debug = configuration.getBoolean("debug"); + 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(); if (converter == null) { @@ -92,6 +98,15 @@ public class FFMpegConvert { 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 */ @@ -217,14 +232,17 @@ public class FFMpegConvert { 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] [Subtitle name filter]\nYour input: "); - List input = readInput(7); - String[] audioLanguage = new String[]{"jpn", "eng", "0"}; + "[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; int forcedAudioIndex = 0; int forcedSubtitleIndex = 0; String subtitleNameFilter = ""; + boolean forceVideoEncoding = false; + boolean forceAudioEncoding = false; if (!input.isEmpty()) { audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0)); @@ -232,25 +250,31 @@ public class FFMpegConvert { if (input.size() > 1) { subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1)); } - if (input.size() > 3) { - subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase()); + if (input.size() > 2) { + subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(2).toUpperCase()); } try { - if (input.size() > 4) { - forcedAudioIndex = Integer.parseInt(input.get(4)); + if (input.size() > 3) { + forcedAudioIndex = Integer.parseInt(input.get(3)); } - if (input.size() > 5) { - forcedSubtitleIndex = Integer.parseInt(input.get(5)); + if (input.size() > 4) { + forcedSubtitleIndex = Integer.parseInt(input.get(4)); } } 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() > 6) { - subtitleNameFilter = input.get(6); + forceAudioEncoding = Boolean.parseBoolean(input.get(6)); + } + if (input.size() > 7) { + subtitleNameFilter = input.get(7); } return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, subtitlePreference, - forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter); + forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter, forceVideoEncoding, forceAudioEncoding); } /** diff --git a/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java b/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java new file mode 100644 index 0000000..544a972 --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/config/ConfigKey.java @@ -0,0 +1,37 @@ +package net.knarcraft.ffmpegconverter.config; + +import org.jetbrains.annotations.NotNull; + +/** + * A representation of all configuration keys + */ +public enum ConfigKey { + + /** + * The configuration key for the list of hardware-accelerated encoders available on the system + */ + HARDWARE_ACCELERATED_ENCODERS("encoders-hardware-accelerated"), + + /** + * The configuration key for toggling debug mode + */ + DEBUG("debug"), + + /** + * The configuration key for toggling hardware acceleration + */ + USE_HARDWARE_ACCELERATION("hardware-acceleration"), + ; + + private final String configKey; + + ConfigKey(@NotNull String configKey) { + this.configKey = configKey; + } + + @Override + public String toString() { + return this.configKey; + } + +} diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java index 872cb85..27a2b33 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/AnimeConverter.java @@ -1,5 +1,6 @@ package net.knarcraft.ffmpegconverter.converter; +import net.knarcraft.ffmpegconverter.FFMpegConvert; import net.knarcraft.ffmpegconverter.container.FFMpegCommand; import net.knarcraft.ffmpegconverter.container.StreamProbeResult; import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; @@ -11,6 +12,7 @@ import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule; import net.knarcraft.ffmpegconverter.converter.module.mapping.SetDefaultStreamModule; import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule; import net.knarcraft.ffmpegconverter.converter.module.output.CopySubtitlesModule; +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; @@ -24,6 +26,7 @@ import net.knarcraft.ffmpegconverter.converter.sorter.SubtitleTitleSorter; import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference; import net.knarcraft.ffmpegconverter.streams.AudioStream; import net.knarcraft.ffmpegconverter.streams.SubtitleStream; +import net.knarcraft.ffmpegconverter.streams.VideoStream; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,6 +45,8 @@ public class AnimeConverter extends AbstractConverter { private final int forcedAudioIndex; private final int forcedSubtitleIndex; private final String subtitleNameFilter; + private final boolean forceVideoEncoding; + private final boolean forceAudioEncoding; /** * Instantiates a new anime converter @@ -51,13 +56,16 @@ public class AnimeConverter extends AbstractConverter { * @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. 0-indexed from the first audio stream found

- * @param forcedSubtitleIndex

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

+ * @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, - int forcedSubtitleIndex, @NotNull String subtitleNameFilter) { + int forcedSubtitleIndex, @NotNull String subtitleNameFilter, boolean forceVideoEncoding, + boolean forceAudioEncoding) { super("mkv"); this.ffprobePath = ffprobePath; this.ffmpegPath = ffmpegPath; @@ -67,6 +75,8 @@ public class AnimeConverter extends AbstractConverter { this.forcedAudioIndex = forcedAudioIndex; this.forcedSubtitleIndex = forcedSubtitleIndex; this.subtitleNameFilter = subtitleNameFilter; + this.forceVideoEncoding = forceVideoEncoding; + this.forceAudioEncoding = forceAudioEncoding; } @Override @@ -93,26 +103,42 @@ public class AnimeConverter extends AbstractConverter { //Get the first subtitle stream in accordance with chosen languages and signs and songs prevention StreamSorter subtitleSorter = new SubtitleTitleSorter(this.subtitleNameFilter) - .append(new MinimalSubtitleSorter(this.subtitlePreference)) .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)); modules.add(new HardwareDecodeModule()); - modules.add(new CopyAudioModule()); + if (!this.forceAudioEncoding) { + modules.add(new CopyAudioModule()); + } modules.add(new CopySubtitlesModule()); - List availableHWAcceleration = getAvailableHardwareEncodingMethods(); - if (availableHWAcceleration.contains("qsv")) { - modules.add(new SetVideoCodecModule("hevc_qsv")); - modules.add(new SetQualityModule(17, "veryslow")); - } else if (availableHWAcceleration.contains("cuda")) { - modules.add(new H265HardwareEncodingModule(20)); - } else { - modules.add(new SetVideoCodecModule("hevc")); + boolean encodingNecessary = false; + for (VideoStream videoStream : probeResult.getVideoStreams()) { + if (!videoStream.getCodecName().trim().equalsIgnoreCase("hevc")) { + encodingNecessary = true; + break; + } } + + if (encodingNecessary || this.forceVideoEncoding) { + List availableHWAcceleration = getAvailableHardwareEncodingMethods(); + if (FFMpegConvert.useHardwareAcceleration() && availableHWAcceleration.contains("qsv")) { + modules.add(new SetVideoCodecModule("hevc_qsv")); + modules.add(new SetQualityModule(17, "veryslow")); + } else if (FFMpegConvert.useHardwareAcceleration() && availableHWAcceleration.contains("cuda")) { + modules.add(new H265HardwareEncodingModule(20)); + } else { + modules.add(new SetVideoCodecModule("hevc")); + modules.add(new SetQualityModule(19, "medium")); + } + } else { + modules.add(new CopyVideoModule()); + } + modules.add(new SetOutputFileModule(outFile)); new ModuleExecutor(command, modules).execute(); diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyVideoModule.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyVideoModule.java new file mode 100644 index 0000000..b7a81d1 --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/module/output/CopyVideoModule.java @@ -0,0 +1,17 @@ +package net.knarcraft.ffmpegconverter.converter.module.output; + +import net.knarcraft.ffmpegconverter.container.FFMpegCommand; +import net.knarcraft.ffmpegconverter.converter.module.ConverterModule; +import org.jetbrains.annotations.NotNull; + +/** + * A module for making FFMpeg copy the video codec + */ +public class CopyVideoModule implements ConverterModule { + + @Override + public void addArguments(@NotNull FFMpegCommand command) { + command.addOutputFileOption("-c:v", "copy"); + } + +} diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/ForcedFirstSorter.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/ForcedFirstSorter.java index 39dda6e..4cdda38 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/ForcedFirstSorter.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/sorter/ForcedFirstSorter.java @@ -26,6 +26,10 @@ public class ForcedFirstSorter extends AbstractSorter @Override public @NotNull List sort(@NotNull List input) { + if (input.isEmpty()) { + return input; + } + int listIndex = 0; for (int i = 0; i < input.size(); i++) { if (input.get(i).getRelativeIndex() == forcedIndex) { diff --git a/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java b/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java index 4fcdfb6..9b014b5 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/handler/AvailableHardwareEncoderHandler.java @@ -2,6 +2,7 @@ package net.knarcraft.ffmpegconverter.handler; 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; @@ -43,7 +44,7 @@ public record AvailableHardwareEncoderHandler(@NotNull List availableHar */ public void save() { PropertiesConfiguration configuration = configHandler.getWritableConfiguration(); - configuration.setProperty("encoder.hardware", this.availableHardwareEncodings); + configuration.setProperty(ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString(), this.availableHardwareEncodings); configHandler.writeConfiguration(); OutputUtil.printDebug("Saved available hardware encoder handler"); } @@ -61,7 +62,7 @@ public record AvailableHardwareEncoderHandler(@NotNull List availableHar } catch (IOException e) { throw new RuntimeException(e); } - List getEncodings = configuration.getList(String.class, "encoder.hardware"); + List getEncodings = configuration.getList(String.class, ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString()); return new AvailableHardwareEncoderHandler(getEncodings); } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamTag.java b/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamTag.java index ab21c7f..cb8b73e 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamTag.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/streams/StreamTag.java @@ -22,7 +22,7 @@ public enum StreamTag { * *

Applicable for all 3 stream types

*/ - CODEC_NAME("codec_name="), + CODEC_NAME("codec_name"), /** * The long name of the codec, useful for displaying information diff --git a/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java b/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java index 1872392..cfaeb63 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java @@ -57,7 +57,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject { !titleLowercase.matches(".*s&s.*") && !titleLowercase.matches("signs?") && !titleLowercase.matches("songs?") && - !titleLowercase.matches(".*signs only.*"); + !titleLowercase.matches(".*signs only.*") && + !titleLowercase.matches(".* signs .*"); } @Override diff --git a/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java b/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java index 426bde6..5a62e3a 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java @@ -160,7 +160,7 @@ public final class FFMpegHelper { } try { int exitCode = process.waitFor(); - OutputUtil.println("Process finished."); + OutputUtil.println("Process finished with exit code: " + exitCode); return new ProcessResult(exitCode, output.toString()); } catch (InterruptedException e) { return new ProcessResult(1, output.toString()); diff --git a/src/main/resources/conf/config.properties b/src/main/resources/conf/config.properties index aab92eb..513139f 100644 --- a/src/main/resources/conf/config.properties +++ b/src/main/resources/conf/config.properties @@ -1 +1,6 @@ -debug=false \ No newline at end of file +# Enabling debug mode will only output part of videos, so different settings can be tested more quickly. +# Debug mode also prints more information about probe results, and potentially useful output. +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