Adds a new type of anime converter and stuff
Some checks failed
KnarCraft/FFmpegConvert/pipeline/head There was a failure building this commit

Adds a new type of anime converter that retains all streams, but converts the video to hevc, and re-orders the streams
Adds support for several types of encoding hardware acceleration that are automatically disabled if not available on the system.
Adds automatic hardware decoding acceleration.
Automatically removes empty files if ffmpeg fails.
This commit is contained in:
2024-04-17 04:39:42 +02:00
parent 461c7552b3
commit f0e75eb440
24 changed files with 647 additions and 193 deletions

View File

@@ -1,6 +1,7 @@
package net.knarcraft.ffmpegconverter.utility;
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
import net.knarcraft.ffmpegconverter.container.ProcessResult;
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
import net.knarcraft.ffmpegconverter.streams.AudioStream;
import net.knarcraft.ffmpegconverter.streams.StreamObject;
@@ -57,6 +58,13 @@ public final class FFMpegHelper {
Instead of storing the ffmpeg command as a list of strings, it should be stored as an object with different list
for input arguments and output arguments. That way, it would be much easier to add input-related arguments later
in the process.
ffmpeg -codecs:
Used for checking which codecs are available.
ffmpeg -h encoder=h264_nvenc:
Used to see available encoder presets/profiles/info
*/
/**
@@ -110,18 +118,6 @@ public final class FFMpegHelper {
return command;
}
/**
* Adds debugging parameters for only converting parts of a file
*
* @param command <p>The command to add to</p>
* @param start <p>The time to start at</p>
* @param duration <p>The duration of video to output</p>
*/
public static void addDebugArguments(@NotNull FFMpegCommand command, int start, int duration) {
command.addInputFileOption("-ss", String.valueOf(start));
command.addOutputFileOption("-t", String.valueOf(duration));
}
/**
* Starts and prints output of a process
*
@@ -129,17 +125,20 @@ public final class FFMpegHelper {
* @param folder <p>The folder the process should run in</p>
* @param spacer <p>The character(s) to use between each new line read</p>
* @param write <p>Whether to write the output directly instead of storing it</p>
* @return <p>The result of running the process</p>
* @throws IOException <p>If the process can't be readProcess</p>
*/
@NotNull
public static String runProcess(@NotNull ProcessBuilder processBuilder, @NotNull File folder,
@NotNull String spacer, boolean write) throws IOException {
public static ProcessResult runProcess(@NotNull ProcessBuilder processBuilder, @Nullable File folder,
@NotNull String spacer, boolean write) throws IOException {
//Give the user information about what's about to happen
OutputUtil.print("Command to be run: ");
OutputUtil.println(processBuilder.command().toString());
//Set directory and error stream
processBuilder.directory(folder);
if (folder != null) {
processBuilder.directory(folder);
}
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
@@ -158,8 +157,13 @@ public final class FFMpegHelper {
output.append(read);
}
}
OutputUtil.println("Process finished.");
return output.toString();
try {
int exitCode = process.waitFor();
OutputUtil.println("Process finished.");
return new ProcessResult(exitCode, output.toString());
} catch (InterruptedException e) {
return new ProcessResult(1, output.toString());
}
}
/**
@@ -189,16 +193,6 @@ public final class FFMpegHelper {
command.addOutputFileOption("-crf", String.valueOf(quality));
}
/**
* Adds arguments for using hardware acceleration during h264 decoding
*
* @param command <p>The command to add the arguments to</p>
*/
public static void addH26xHardwareDecoding(@NotNull FFMpegCommand command) {
command.addInputFileOption("-hwaccel", "cuda");
command.addInputFileOption("-hwaccel_output_format", "cuda");
}
/**
* Maps all streams in the given list to the output in the given command
*
@@ -266,14 +260,17 @@ public final class FFMpegHelper {
* @return <p>A list of streams.</p>
* @throws IOException <p>If something goes wrong while probing.</p>
*/
private static String[] probeForStreams(String ffprobePath, File file) throws IOException {
private static List<String> probeForStreams(String ffprobePath, File file) throws IOException {
FFMpegCommand probeCommand = new FFMpegCommand(ffprobePath);
probeCommand.addGlobalOption("-v", "error", "-show_streams");
probeCommand.addInputFile(file.toString());
ProcessBuilder processBuilder = new ProcessBuilder(probeCommand.getResult());
String result = runProcess(processBuilder, file.getParentFile(), PROBE_SPLIT_CHARACTER, false);
return StringUtil.stringBetween(result, "[STREAM]", "[/STREAM]");
ProcessResult result = runProcess(processBuilder, file.getParentFile(), PROBE_SPLIT_CHARACTER, false);
if (result.exitCode() != 0) {
throw new IllegalArgumentException("File probe failed with code " + result.exitCode());
}
return StringUtil.stringBetween(result.output(), "[STREAM]", "[/STREAM]");
}
/**
@@ -284,7 +281,7 @@ public final class FFMpegHelper {
* @return <p>A list of StreamObjects.</p>
*/
@NotNull
private static StreamProbeResult parseStreams(@NotNull String ffprobePath, @NotNull String[] streams,
private static StreamProbeResult parseStreams(@NotNull String ffprobePath, @NotNull List<String> streams,
@NotNull File file) throws IOException {
List<StreamObject> parsedStreams = new ArrayList<>();
int relativeAudioIndex = 0;
@@ -368,7 +365,7 @@ public final class FFMpegHelper {
int inputIndex = streamProbeResult.parsedFiles().size();
streamProbeResult.parsedFiles().add(subtitleFile);
//Probe the files and add them to the result list
String[] streams = probeForStreams(ffprobePath, subtitleFile);
List<String> streams = probeForStreams(ffprobePath, subtitleFile);
int relativeIndex = 0;
for (String stream : streams) {
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
@@ -394,4 +391,20 @@ public final class FFMpegHelper {
return text.toString().trim();
}
/**
* Gets available hardware acceleration types
*
* @param ffmpegPath <p>The path to ffmpeg's executable</p>
* @return <p>The available hardware acceleration methods</p>
* @throws IOException <p>If the process fails</p>
*/
public static List<String> getHWAcceleration(@NotNull String ffmpegPath) throws IOException {
FFMpegCommand probeCommand = new FFMpegCommand(ffmpegPath);
probeCommand.addGlobalOption("-v", "error", "-hwaccels");
ProcessBuilder processBuilder = new ProcessBuilder(probeCommand.getResult());
ProcessResult result = runProcess(processBuilder, null, PROBE_SPLIT_CHARACTER, false);
return List.of(result.output().split(PROBE_SPLIT_CHARACTER));
}
}

View File

@@ -29,11 +29,14 @@ public final class OutputUtil {
* Prints something and a newline to the commandline efficiently
*
* @param input <p>The text to print.</p>
* @throws IOException <p>If a write is not possible.</p>
*/
public static void println(String input) throws IOException {
public static void println(String input) {
if (!input.isEmpty()) {
writer.write(input);
try {
writer.write(input);
} catch (IOException e) {
System.out.print(input);
}
}
println();
}
@@ -42,22 +45,27 @@ public final class OutputUtil {
* Prints a string
*
* @param input <p>The string to print.</p>
* @throws IOException <p>If the writer fails to write.</p>
*/
public static void print(String input) throws IOException {
writer.write(input);
writer.flush();
public static void print(String input) {
try {
writer.write(input);
writer.flush();
} catch (IOException e) {
System.out.print(input);
}
}
/**
* Prints a newline
*
* @throws IOException <p>If a write is not possible.</p>
*/
public static void println() throws IOException {
writer.newLine();
writer.flush();
public static void println() {
try {
writer.newLine();
writer.flush();
} catch (IOException e) {
System.out.println();
}
}
/**
@@ -73,11 +81,10 @@ public final class OutputUtil {
* Prints a message if debug messages should be shown
*
* @param message <p>The debug message to show.</p>
* @throws IOException <p>If a write is not possible.</p>
*/
public static void printDebug(String message) throws IOException {
public static void printDebug(String message) {
if (debug) {
print(message);
println(message);
}
}

View File

@@ -1,5 +1,10 @@
package net.knarcraft.ffmpegconverter.utility;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* A class which helps with operations on strings
*/
@@ -12,23 +17,26 @@ final class StringUtil {
/**
* Finds all substrings between two substrings in a string
*
* @param string <p>The string containing the substrings.</p>
* @param start <p>The substring before the wanted substring.</p>
* @param end <p>The substring after the wanted substring.</p>
* @param input <p>The string containing the substrings.</p>
* @param start <p>The substring before the wanted substring.</p>
* @param end <p>The substring after the wanted substring.</p>
* @return <p>A list of all occurrences of the substring.</p>
*/
static String[] stringBetween(String string, String start, String end) {
int startPosition = string.indexOf(start) + start.length();
//Return if the string is not found
if (!string.contains(start) || string.indexOf(end, startPosition) < startPosition) {
return new String[]{};
public static @NotNull List<String> stringBetween(@NotNull String input, @NotNull String start,
@NotNull String end) {
List<String> output = new ArrayList<>();
String inputString = input;
while (true) {
int startPosition = inputString.indexOf(start) + start.length();
//Return if the string is not found
if (!inputString.contains(start) || inputString.indexOf(end, startPosition) < startPosition) {
return output;
}
int endPosition = inputString.indexOf(end, startPosition);
//Get the string between the start and end string
output.add(inputString.substring(startPosition, endPosition).trim());
inputString = inputString.substring(endPosition + end.length());
}
int endPosition = string.indexOf(end, startPosition);
//Get the string between the start and end string
String outString = string.substring(startPosition, endPosition).trim();
String nextString = string.substring(endPosition + end.length());
//Add other occurrences recursively
return ListUtil.concatenate(new String[]{outString}, stringBetween(nextString, start, end));
}
}