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;
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 <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
*/
@ -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<String> 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<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 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);
}
/**

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;
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 <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. 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 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 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,
@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<SubtitleStream> 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<SubtitleStream> 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<String> 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<String> 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();

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
public @NotNull List<K> sort(@NotNull List<K> input) {
if (input.isEmpty()) {
return input;
}
int listIndex = 0;
for (int i = 0; i < input.size(); i++) {
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.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<String> 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<String> availableHar
} catch (IOException 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);
}

View File

@ -22,7 +22,7 @@ public enum StreamTag {
*
* <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

View File

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

View File

@ -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());

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
# 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