Fixes various issues
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good

Makes it possible to turn off all types of hardware acceleration for all converters
Only encodes anime to hevc if not already hevc
Adds an option to force hevc to hevc encoding, for the anime converter
Adds an option to force encoding of audio, for the anime converter
Fixes a bug causing the codec name to not be parsed
Fixes an exception when trying to sort an empty list
Fixes order of sorting in the anime converter
Adds another signs songs filter
This commit is contained in:
Kristian Knarvik 2024-04-19 13:05:12 +02:00
parent d487df0e78
commit 3c9fa55585
10 changed files with 147 additions and 32 deletions

View File

@ -1,6 +1,7 @@
package net.knarcraft.ffmpegconverter; package net.knarcraft.ffmpegconverter;
import net.knarcraft.ffmpegconverter.config.ConfigHandler; import net.knarcraft.ffmpegconverter.config.ConfigHandler;
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;
@ -39,13 +40,18 @@ public class FFMpegConvert {
private static Converter converter = null; private static Converter converter = null;
private static final ConfigHandler configHandler = new ConfigHandler(); private static final ConfigHandler configHandler = new ConfigHandler();
private static boolean debug = false; 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(); Configuration configuration = configHandler.load();
if (configuration.containsKey("debug")) { if (configuration.containsKey(ConfigKey.DEBUG.toString())) {
debug = configuration.getBoolean("debug"); debug = configuration.getBoolean(ConfigKey.DEBUG.toString());
} }
OutputUtil.setDebug(debug); OutputUtil.setDebug(debug);
if (configuration.containsKey(ConfigKey.USE_HARDWARE_ACCELERATION.toString())) {
useHardwareAcceleration = configuration.getBoolean(ConfigKey.USE_HARDWARE_ACCELERATION.toString());
}
converter = loadConverter(); converter = loadConverter();
if (converter == null) { if (converter == null) {
@ -92,6 +98,15 @@ public class FFMpegConvert {
return debug; 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
*/ */
@ -217,14 +232,17 @@ public class FFMpegConvert {
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("[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] " + "preference REQUIRE/PREFER/NO_PREFERENCE/AVOID/REJECT] [Forced audio index 0-n] " +
"[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: "); "[Forced subtitle index 0-n] [Force video encoding true/false] [Force audio encoding true/false] " +
List<String> input = readInput(7); "[Subtitle name filter]\nYour input: ");
String[] audioLanguage = new String[]{"jpn", "eng", "0"}; List<String> input = readInput(8);
String[] audioLanguage = new String[]{"jpn", "nor", "eng", "0"};
String[] subtitleLanguage = new String[]{"nob", "nor", "eng", "jpn", "0"}; String[] subtitleLanguage = new String[]{"nob", "nor", "eng", "jpn", "0"};
MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID; MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID;
int forcedAudioIndex = 0; int forcedAudioIndex = 0;
int forcedSubtitleIndex = 0; int forcedSubtitleIndex = 0;
String subtitleNameFilter = ""; String subtitleNameFilter = "";
boolean forceVideoEncoding = false;
boolean forceAudioEncoding = false;
if (!input.isEmpty()) { if (!input.isEmpty()) {
audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0)); audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0));
@ -232,25 +250,31 @@ public class FFMpegConvert {
if (input.size() > 1) { if (input.size() > 1) {
subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1)); subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1));
} }
if (input.size() > 3) { if (input.size() > 2) {
subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase()); subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(2).toUpperCase());
} }
try { try {
if (input.size() > 4) { if (input.size() > 3) {
forcedAudioIndex = Integer.parseInt(input.get(4)); forcedAudioIndex = Integer.parseInt(input.get(3));
} }
if (input.size() > 5) { if (input.size() > 4) {
forcedSubtitleIndex = Integer.parseInt(input.get(5)); 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) {
forceVideoEncoding = Boolean.parseBoolean(input.get(5));
}
if (input.size() > 6) { 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, return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, subtitlePreference,
forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter); forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter, forceVideoEncoding, forceAudioEncoding);
} }
/** /**

View File

@ -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;
}
}

View File

@ -1,5 +1,6 @@
package net.knarcraft.ffmpegconverter.converter; package net.knarcraft.ffmpegconverter.converter;
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.converter.module.ConverterModule; 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.mapping.SetDefaultStreamModule;
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule; 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.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;
@ -24,6 +26,7 @@ import net.knarcraft.ffmpegconverter.converter.sorter.SubtitleTitleSorter;
import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference; import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
import net.knarcraft.ffmpegconverter.streams.AudioStream; 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.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;
@ -42,6 +45,8 @@ public class AnimeConverter extends AbstractConverter {
private final int forcedAudioIndex; private final int forcedAudioIndex;
private final int forcedSubtitleIndex; private final int forcedSubtitleIndex;
private final String subtitleNameFilter; private final String subtitleNameFilter;
private final boolean forceVideoEncoding;
private final boolean forceAudioEncoding;
/** /**
* Instantiates a new anime converter * Instantiates a new anime converter
@ -51,13 +56,16 @@ public class AnimeConverter extends AbstractConverter {
* @param audioLanguages <p>List of wanted audio languages in descending order.</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 subtitleLanguages <p>List of wanted subtitle languages in descending order.</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 as default. 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 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 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, @NotNull String[] audioLanguages,
@NotNull String[] subtitleLanguages, @NotNull String[] subtitleLanguages,
@NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex, @NotNull MinimalSubtitlePreference subtitlePreference, int forcedAudioIndex,
int forcedSubtitleIndex, @NotNull String subtitleNameFilter) { int forcedSubtitleIndex, @NotNull String subtitleNameFilter, boolean forceVideoEncoding,
boolean forceAudioEncoding) {
super("mkv"); super("mkv");
this.ffprobePath = ffprobePath; this.ffprobePath = ffprobePath;
this.ffmpegPath = ffmpegPath; this.ffmpegPath = ffmpegPath;
@ -67,6 +75,8 @@ public class AnimeConverter extends AbstractConverter {
this.forcedAudioIndex = forcedAudioIndex; this.forcedAudioIndex = forcedAudioIndex;
this.forcedSubtitleIndex = forcedSubtitleIndex; this.forcedSubtitleIndex = forcedSubtitleIndex;
this.subtitleNameFilter = subtitleNameFilter; this.subtitleNameFilter = subtitleNameFilter;
this.forceVideoEncoding = forceVideoEncoding;
this.forceAudioEncoding = forceAudioEncoding;
} }
@Override @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 //Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
StreamSorter<SubtitleStream> subtitleSorter = new SubtitleTitleSorter(this.subtitleNameFilter) StreamSorter<SubtitleStream> subtitleSorter = new SubtitleTitleSorter(this.subtitleNameFilter)
.append(new MinimalSubtitleSorter(this.subtitlePreference))
.append(new SubtitleLanguageSorter(this.subtitleLanguages)) .append(new SubtitleLanguageSorter(this.subtitleLanguages))
.append(new MinimalSubtitleSorter(this.subtitlePreference))
.append(new ForcedFirstSorter<>(this.forcedSubtitleIndex)); .append(new ForcedFirstSorter<>(this.forcedSubtitleIndex));
List<SubtitleStream> sorted = subtitleSorter.chainSort(probeResult.getSubtitleStreams()); List<SubtitleStream> sorted = subtitleSorter.chainSort(probeResult.getSubtitleStreams());
modules.add(new MapAllModule<>(sorted)); modules.add(new MapAllModule<>(sorted));
modules.add(new SetDefaultStreamModule<>(sorted, 0)); modules.add(new SetDefaultStreamModule<>(sorted, 0));
modules.add(new HardwareDecodeModule()); modules.add(new HardwareDecodeModule());
if (!this.forceAudioEncoding) {
modules.add(new CopyAudioModule()); modules.add(new CopyAudioModule());
}
modules.add(new CopySubtitlesModule()); modules.add(new CopySubtitlesModule());
boolean encodingNecessary = false;
for (VideoStream videoStream : probeResult.getVideoStreams()) {
if (!videoStream.getCodecName().trim().equalsIgnoreCase("hevc")) {
encodingNecessary = true;
break;
}
}
if (encodingNecessary || this.forceVideoEncoding) {
List<String> availableHWAcceleration = getAvailableHardwareEncodingMethods(); List<String> availableHWAcceleration = getAvailableHardwareEncodingMethods();
if (availableHWAcceleration.contains("qsv")) { if (FFMpegConvert.useHardwareAcceleration() && 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 (availableHWAcceleration.contains("cuda")) { } else if (FFMpegConvert.useHardwareAcceleration() && 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"));
modules.add(new SetQualityModule(19, "medium"));
} }
} else {
modules.add(new CopyVideoModule());
}
modules.add(new SetOutputFileModule(outFile)); modules.add(new SetOutputFileModule(outFile));
new ModuleExecutor(command, modules).execute(); new ModuleExecutor(command, modules).execute();

View File

@ -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");
}
}

View File

@ -26,6 +26,10 @@ public class ForcedFirstSorter<K extends StreamObject> extends AbstractSorter<K>
@Override @Override
public @NotNull List<K> sort(@NotNull List<K> input) { public @NotNull List<K> sort(@NotNull List<K> input) {
if (input.isEmpty()) {
return input;
}
int listIndex = 0; int listIndex = 0;
for (int i = 0; i < input.size(); i++) { for (int i = 0; i < input.size(); i++) {
if (input.get(i).getRelativeIndex() == forcedIndex) { if (input.get(i).getRelativeIndex() == forcedIndex) {

View File

@ -2,6 +2,7 @@ package net.knarcraft.ffmpegconverter.handler;
import net.knarcraft.ffmpegconverter.FFMpegConvert; 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.utility.OutputUtil; import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.Configuration; import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.configuration2.PropertiesConfiguration;
@ -43,7 +44,7 @@ public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHar
*/ */
public void save() { public void save() {
PropertiesConfiguration configuration = configHandler.getWritableConfiguration(); PropertiesConfiguration configuration = configHandler.getWritableConfiguration();
configuration.setProperty("encoder.hardware", this.availableHardwareEncodings); configuration.setProperty(ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString(), this.availableHardwareEncodings);
configHandler.writeConfiguration(); configHandler.writeConfiguration();
OutputUtil.printDebug("Saved available hardware encoder handler"); OutputUtil.printDebug("Saved available hardware encoder handler");
} }
@ -61,7 +62,7 @@ public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHar
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
List<String> getEncodings = configuration.getList(String.class, "encoder.hardware"); List<String> getEncodings = configuration.getList(String.class, ConfigKey.HARDWARE_ACCELERATED_ENCODERS.toString());
return new AvailableHardwareEncoderHandler(getEncodings); return new AvailableHardwareEncoderHandler(getEncodings);
} }

View File

@ -22,7 +22,7 @@ public enum StreamTag {
* *
* <p>Applicable for all 3 stream types</p> * <p>Applicable for all 3 stream types</p>
*/ */
CODEC_NAME("codec_name="), CODEC_NAME("codec_name"),
/** /**
* The long name of the codec, useful for displaying information * The long name of the codec, useful for displaying information

View File

@ -57,7 +57,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
!titleLowercase.matches(".*s&s.*") && !titleLowercase.matches(".*s&s.*") &&
!titleLowercase.matches("signs?") && !titleLowercase.matches("signs?") &&
!titleLowercase.matches("songs?") && !titleLowercase.matches("songs?") &&
!titleLowercase.matches(".*signs only.*"); !titleLowercase.matches(".*signs only.*") &&
!titleLowercase.matches(".* signs .*");
} }
@Override @Override

View File

@ -160,7 +160,7 @@ public final class FFMpegHelper {
} }
try { try {
int exitCode = process.waitFor(); int exitCode = process.waitFor();
OutputUtil.println("Process finished."); OutputUtil.println("Process finished with exit code: " + exitCode);
return new ProcessResult(exitCode, output.toString()); return new ProcessResult(exitCode, output.toString());
} catch (InterruptedException e) { } catch (InterruptedException e) {
return new ProcessResult(1, output.toString()); return new ProcessResult(1, output.toString());

View File

@ -1 +1,6 @@
# 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 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