Makes it possible to enable debug mode
Some checks failed
KnarCraft/FFmpegConvert/pipeline/head There was a failure building this commit

Debug mode is now enabled if the property `debug = true` is set in `conf/config.properties`
Changes code for reading internal configurations
Changes many primitive lists to List<>
Adds some missing annotations
Renames the main class
This commit is contained in:
2024-04-17 15:35:09 +02:00
parent f0e75eb440
commit 4ebd29b358
29 changed files with 492 additions and 177 deletions

View File

@@ -27,7 +27,6 @@ import java.util.Map;
public final class FFMpegHelper {
private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå";
private static String[] subtitleFormats = null;
private FFMpegHelper() {
@@ -70,14 +69,16 @@ public final class FFMpegHelper {
/**
* Gets streams from a file
*
* @param ffprobePath <p>The path/command to ffprobe.</p>
* @param file <p>The file to probe.</p>
* @return <p>A list of StreamObjects.</p>
* @throws IOException <p>If the process can't be readProcess.</p>
* @param ffprobePath <p>The path/command to ffprobe</p>
* @param file <p>The file to probe</p>
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
* @return <p>A list of StreamObjects</p>
* @throws IOException <p>If the process can't be readProcess</p>
*/
@NotNull
public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file) throws IOException {
return parseStreams(ffprobePath, probeForStreams(ffprobePath, file), file);
public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file,
@NotNull List<String> subtitleFormats) throws IOException {
return parseStreams(ffprobePath, probeForStreams(ffprobePath, file), file, subtitleFormats);
}
/**
@@ -223,7 +224,8 @@ public final class FFMpegHelper {
* @param fileName <p>The filename to escape.</p>
* @return <p>A filename with known special characters escaped.</p>
*/
public static String escapeSpecialCharactersInFileName(String fileName) {
@NotNull
public static String escapeSpecialCharactersInFileName(@NotNull String fileName) {
return fileName.replaceAll("\\\\", "\\\\\\\\\\\\\\\\")
.replaceAll("'", "'\\\\\\\\\\\\\''")
.replaceAll("%", "\\\\\\\\\\\\%")
@@ -260,7 +262,8 @@ public final class FFMpegHelper {
* @return <p>A list of streams.</p>
* @throws IOException <p>If something goes wrong while probing.</p>
*/
private static List<String> probeForStreams(String ffprobePath, File file) throws IOException {
@NotNull
private static List<String> probeForStreams(@NotNull String ffprobePath, @NotNull File file) throws IOException {
FFMpegCommand probeCommand = new FFMpegCommand(ffprobePath);
probeCommand.addGlobalOption("-v", "error", "-show_streams");
probeCommand.addInputFile(file.toString());
@@ -276,13 +279,15 @@ public final class FFMpegHelper {
/**
* Takes a list of all streams and parses each stream into one of three objects
*
* @param streams <p>A list of all streams for the current file.</p>
* @param file <p>The file currently being converted.</p>
* @param ffprobePath <p>The path to the ffprobe executable</p>
* @param streams <p>A list of all streams for the current file.</p>
* @param file <p>The file currently being converted.</p>
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
* @return <p>A list of StreamObjects.</p>
*/
@NotNull
private static StreamProbeResult parseStreams(@NotNull String ffprobePath, @NotNull List<String> streams,
@NotNull File file) throws IOException {
@NotNull File file, @NotNull List<String> subtitleFormats) throws IOException {
List<StreamObject> parsedStreams = new ArrayList<>();
int relativeAudioIndex = 0;
int relativeVideoIndex = 0;
@@ -306,7 +311,7 @@ public final class FFMpegHelper {
}
}
StreamProbeResult probeResult = new StreamProbeResult(List.of(file), parsedStreams);
getExternalSubtitles(probeResult, ffprobePath, file.getParentFile(), file.getName());
getExternalSubtitles(probeResult, ffprobePath, file.getParentFile(), file.getName(), subtitleFormats);
return probeResult;
}
@@ -335,17 +340,16 @@ public final class FFMpegHelper {
/**
* Tries to find any external subtitles adjacent to the first input file, and appends it to the given probe result
*
* @param ffprobePath <p>The path/command to ffprobe</p>
* @param directory <p>The directory containing the file</p>
* @param convertingFile <p>The first/main file to be converted</p>
* @param streamProbeResult <p>The stream probe result to append to</p>
* @param ffprobePath <p>The path/command to ffprobe</p>
* @param directory <p>The directory containing the file</p>
* @param convertingFile <p>The first/main file to be converted</p>
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
*/
private static void getExternalSubtitles(@NotNull StreamProbeResult streamProbeResult,
@NotNull String ffprobePath, @NotNull File directory,
@NotNull String convertingFile) throws IOException {
@NotNull String convertingFile, @NotNull List<String> subtitleFormats) throws IOException {
//Find all files in the same directory with external subtitle formats
if (subtitleFormats == null) {
subtitleFormats = FileUtil.readFileLines("subtitle_formats.txt");
}
File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
// TODO: Generalize this for external audio tracks
@@ -382,7 +386,8 @@ public final class FFMpegHelper {
* @return <p>The output from the readProcess.</p>
* @throws IOException <p>On reader failure.</p>
*/
private static String readProcess(BufferedReader reader, String spacer) throws IOException {
@NotNull
private static String readProcess(@NotNull BufferedReader reader, @NotNull String spacer) throws IOException {
String line;
StringBuilder text = new StringBuilder();
while (reader.ready() && (line = reader.readLine()) != null && !line.isEmpty() && !line.equals("\n")) {
@@ -398,6 +403,7 @@ public final class FFMpegHelper {
* @return <p>The available hardware acceleration methods</p>
* @throws IOException <p>If the process fails</p>
*/
@NotNull
public static List<String> getHWAcceleration(@NotNull String ffmpegPath) throws IOException {
FFMpegCommand probeCommand = new FFMpegCommand(ffmpegPath);
probeCommand.addGlobalOption("-v", "error", "-hwaccels");

View File

@@ -0,0 +1,178 @@
package net.knarcraft.ffmpegconverter.utility;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A helper class for dealing with files
*/
@SuppressWarnings("unused")
public final class FileHelper {
private FileHelper() {
}
/**
* Gets a buffered reader for an internal file
*
* @param file <p>The name of the file to get a buffered reader for (start with a '/'). The file should reside in
* the resources directory.</p>
* @return <p>A buffered read for reading the file</p>
* @throws FileNotFoundException <p>If unable to get an input stream for the given file</p>
*/
@NotNull
public static BufferedReader getBufferedReaderForInternalFile(@NotNull String file) throws FileNotFoundException {
InputStream inputStream = getInputStreamForInternalFile(file);
if (inputStream == null) {
throw new FileNotFoundException("Unable to read the given file");
}
return getBufferedReaderFromInputStream(inputStream);
}
/**
* Gets an input stream from a string pointing to an internal file
*
* <p>This is used for getting an input stream for reading a file contained within the compiled .jar file. The file
* should be in the resources directory, and the file path should start with a forward slash ("/") character.</p>
*
* @param file <p>The file to read</p>
* @return <p>An input stream for the file</p>
*/
@Nullable
public static InputStream getInputStreamForInternalFile(@NotNull String file) {
return FileHelper.class.getResourceAsStream(file);
}
/**
* Gets a buffered reader from a string pointing to a file
*
* @param file <p>The file to read</p>
* @return <p>A buffered reader reading the file</p>
* @throws FileNotFoundException <p>If the given file does not exist</p>
*/
@NotNull
public static BufferedReader getBufferedReaderFromString(@NotNull String file) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream(file);
return getBufferedReaderFromInputStream(fileInputStream);
}
/**
* Gets a buffered reader given an input stream
*
* @param inputStream <p>The input stream to read</p>
* @return <p>A buffered reader reading the input stream</p>
*/
@NotNull
public static BufferedReader getBufferedReaderFromInputStream(@NotNull InputStream inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return new BufferedReader(inputStreamReader);
}
/**
* Gets a buffered writer from a string pointing to a file
*
* @param file <p>The file to write to</p>
* @return <p>A buffered writer writing to the file</p>
* @throws FileNotFoundException <p>If the file does not exist</p>
*/
@NotNull
public static BufferedWriter getBufferedWriterFromString(@NotNull String file) throws FileNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
return new BufferedWriter(outputStreamWriter);
}
/**
* Reads key/value pairs from a buffered reader
*
* @param bufferedReader <p>The buffered reader to read</p>
* @param separator <p>The separator separating a key from a value</p>
* @return <p>A map containing the read pairs</p>
* @throws IOException <p>If unable to read from the stream</p>
*/
@NotNull
public static Map<String, String> readKeyValuePairs(@NotNull BufferedReader bufferedReader,
@NotNull String separator) throws IOException {
Map<String, String> readPairs = new HashMap<>();
List<String> lines = readLines(bufferedReader);
for (String line : lines) {
int separatorIndex = line.indexOf(separator);
if (separatorIndex == -1) {
continue;
}
//Read the line
String key = line.substring(0, separatorIndex);
String value = line.substring(separatorIndex + 1);
readPairs.put(key, value);
}
return readPairs;
}
/**
* Reads a list from a buffered reader
*
* @param bufferedReader <p>The buffered reader to read</p>
* @return <p>A list of the read strings</p>
* @throws IOException <p>If unable to read from the stream</p>
*/
@NotNull
public static List<String> readLines(@NotNull BufferedReader bufferedReader) throws IOException {
List<String> readLines = new ArrayList<>();
String line = bufferedReader.readLine();
boolean firstLine = true;
while (line != null) {
//Strip UTF BOM from the first line
if (firstLine) {
line = removeUTF8BOM(line);
firstLine = false;
}
//Split at first separator
if (line.isEmpty()) {
line = bufferedReader.readLine();
continue;
}
readLines.add(line);
line = bufferedReader.readLine();
}
bufferedReader.close();
return readLines;
}
/**
* Removes the UTF-8 Byte Order Mark if present
*
* @param string <p>The string to remove the BOM from</p>
* @return <p>A string guaranteed without a BOM</p>
*/
private static @NotNull String removeUTF8BOM(@NotNull String string) {
String UTF8_BOM = "\uFEFF";
if (string.startsWith(UTF8_BOM)) {
string = string.substring(1);
}
return string;
}
}

View File

@@ -1,10 +1,10 @@
package net.knarcraft.ffmpegconverter.utility;
import java.io.BufferedReader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
/**
* A class which helps with file handling
@@ -23,7 +23,8 @@ public final class FileUtil {
* @param outExtension <p>The extension of the output file.</p>
* @return <p>A file name with the new extension and without any collisions.</p>
*/
public static String getNonCollidingPath(File folder, File file, String outExtension) {
@NotNull
public static String getNonCollidingPath(@NotNull File folder, @NotNull File file, @NotNull String outExtension) {
return FileUtil.getNonCollidingFilename(folder.getAbsolutePath() + File.separator +
FileUtil.stripExtension(file.getName()) + "." + outExtension, outExtension);
}
@@ -35,7 +36,8 @@ public final class FileUtil {
* @param maxRecursions <p>Maximum number of recursions</p>
* @return A list of files
*/
public static File[] listFilesRecursive(File folder, String[] extensions, int maxRecursions) {
@Nullable
public static File[] listFilesRecursive(@NotNull File folder, @NotNull List<String> extensions, int maxRecursions) {
//Return if the target depth has been reached
if (maxRecursions == 0) {
return null;
@@ -43,6 +45,7 @@ public final class FileUtil {
//Get a list of all files which are folders and has one of the extensions specified
File[] foundFiles = folder.listFiles((file) -> file.isFile() &&
ListUtil.listContains(extensions, (item) -> file.getName().toLowerCase().endsWith(item)));
//Return if recursion is finished
if (maxRecursions == 1) {
return foundFiles;
@@ -68,36 +71,6 @@ public final class FileUtil {
return foundFiles;
}
/**
* Reads a file's contents to a string list
*
* <p>The file must contain the number of lines to read in the first line.</p>
*
* @param fileName <p>The file to read.</p>
* @return <p>A string list where each element is one line of the file.</p>
* @throws IOException <p>If the file cannot be read.</p>
*/
public static String[] readFileLines(String fileName) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(getResourceAsStream(fileName)));
int numberOfLines = Integer.parseInt(reader.readLine());
String[] lines = new String[numberOfLines];
for (int i = 0; i < lines.length; i++) {
lines[i] = reader.readLine();
}
return lines;
}
/**
* Gets a resource as an InputStream
*
* @param resourceName <p>The name of the resource you want to read.</p>
* @return <p>An input stream which can be used to access the resource.</p>
*/
private static InputStream getResourceAsStream(String resourceName) {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
return classloader.getResourceAsStream(resourceName);
}
/**
* Adds parentheses with an integer if the output file already exists
*
@@ -105,7 +78,8 @@ public final class FileUtil {
* @param extension <p>The extension of the target file.</p>
* @return <p>A filename guaranteed not to collide with other files.</p>
*/
private static String getNonCollidingFilename(String targetPath, String extension) {
@NotNull
private static String getNonCollidingFilename(@NotNull String targetPath, @NotNull String extension) {
File newFile = new File(targetPath);
String fileName = stripExtension(targetPath).replaceAll("\\([0-9]+\\)$", "");
int i = 1;
@@ -121,7 +95,8 @@ public final class FileUtil {
* @param file <p>The filename to check</p>
* @return <p>The file's extension</p>
*/
public static String getExtension(String file) {
@NotNull
public static String getExtension(@NotNull String file) {
if (file.contains(".")) {
return file.substring(file.lastIndexOf('.') + 1);
} else {
@@ -135,7 +110,8 @@ public final class FileUtil {
* @param file <p>A filename.</p>
* @return <p>A filename without its extension.</p>
*/
public static String stripExtension(String file) {
@NotNull
public static String stripExtension(@NotNull String file) {
return file.substring(0, file.lastIndexOf('.'));
}

View File

@@ -1,5 +1,7 @@
package net.knarcraft.ffmpegconverter.utility;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
@@ -22,7 +24,8 @@ public final class ListUtil {
* @param <T> <p>The type of the two lists.</p>
* @return <p>A new array containing all elements from the two arrays.</p>
*/
static <T> T[] concatenate(T[] listA, T[] listB) {
@NotNull
public static <T> T[] concatenate(@NotNull T[] listA, @NotNull T[] listB) {
int listALength = listA.length;
int listBLength = listB.length;
@SuppressWarnings("unchecked")
@@ -40,7 +43,7 @@ public final class ListUtil {
* @param <T> <p>The type of the list.</p>
* @return <p>A new list containing all matching elements.</p>
*/
static <T> List<T> getMatching(List<T> list, Predicate<T> predicate) {
public static <T> List<T> getMatching(List<T> list, Predicate<T> predicate) {
List<T> matching = new ArrayList<>(list);
matching.removeIf(predicate.negate());
return matching;
@@ -54,7 +57,7 @@ public final class ListUtil {
* @param <T> Anything which can be stored in a list
* @return True if at least one element fulfills the predicate
*/
static <T> boolean listContains(T[] list, Predicate<T> predicate) {
public static <T> boolean listContains(@NotNull List<T> list, @NotNull Predicate<T> predicate) {
for (T item : list) {
if (predicate.test(item)) {
return true;
@@ -69,7 +72,8 @@ public final class ListUtil {
* @param string <p>A string which may include commas.</p>
* @return <p>A string list.</p>
*/
public static String[] getListFromCommaSeparatedString(String string) {
@NotNull
public static String[] getListFromCommaSeparatedString(@NotNull String string) {
String[] result;
if (string.contains(",")) {
result = string.split(",");

View File

@@ -1,5 +1,7 @@
package net.knarcraft.ffmpegconverter.utility;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -30,7 +32,7 @@ public final class OutputUtil {
*
* @param input <p>The text to print.</p>
*/
public static void println(String input) {
public static void println(@NotNull String input) {
if (!input.isEmpty()) {
try {
writer.write(input);
@@ -46,7 +48,7 @@ public final class OutputUtil {
*
* @param input <p>The string to print.</p>
*/
public static void print(String input) {
public static void print(@NotNull String input) {
try {
writer.write(input);
writer.flush();
@@ -82,7 +84,7 @@ public final class OutputUtil {
*
* @param message <p>The debug message to show.</p>
*/
public static void printDebug(String message) {
public static void printDebug(@NotNull String message) {
if (debug) {
println(message);
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.ffmpegconverter.utility;
import net.knarcraft.ffmpegconverter.parser.ConverterArgument;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
@@ -23,7 +24,8 @@ public final class Parser {
* @param validArguments <p>All arguments which are considered valid.</p>
* @return <p>A map with all parsed arguments.</p>
*/
static Map<String, String> parse(String input, List<ConverterArgument> validArguments) {
@NotNull
static Map<String, String> parse(@NotNull String input, @NotNull List<ConverterArgument> validArguments) {
return parse(tokenize(input), validArguments);
}
@@ -34,7 +36,8 @@ public final class Parser {
* @param validArguments <p>A list of arguments which are considered valid.</p>
* @return <p>A map with all parsed arguments.</p>
*/
private static Map<String, String> parse(List<String> tokens, List<ConverterArgument> validArguments) {
@NotNull
private static Map<String, String> parse(@NotNull List<String> tokens, @NotNull List<ConverterArgument> validArguments) {
Map<String, String> parsedArguments = new HashMap<>();
while (!tokens.isEmpty()) {
@@ -50,7 +53,8 @@ public final class Parser {
* @param converterArguments <p>A list of all the valid arguments in existence.</p>
* @param parsedArguments <p>The map to store the parsed argument to.</p>
*/
private static void parseArgument(List<String> tokens, List<ConverterArgument> converterArguments, Map<String, String> parsedArguments) {
private static void parseArgument(@NotNull List<String> tokens, @NotNull List<ConverterArgument> converterArguments,
@NotNull Map<String, String> parsedArguments) {
String currentToken = tokens.remove(0);
List<ConverterArgument> foundArguments;
@@ -79,7 +83,8 @@ public final class Parser {
* @param foundArgument <p>The found argument to store.</p>
* @param parsedArguments <p>The map to store parsed arguments to.</p>
*/
private static void storeArgumentValue(List<String> tokens, ConverterArgument foundArgument, Map<String, String> parsedArguments) {
private static void storeArgumentValue(@NotNull List<String> tokens, @NotNull ConverterArgument foundArgument,
@NotNull Map<String, String> parsedArguments) {
String argumentValue;
if (tokens.isEmpty()) {
argumentValue = "";
@@ -114,7 +119,8 @@ public final class Parser {
* @param input <p>A string.</p>
* @return <p>A list of tokens.</p>
*/
public static List<String> tokenize(String input) {
@NotNull
public static List<String> tokenize(@NotNull String input) {
List<String> tokens = new ArrayList<>();
boolean startedQuote = false;
StringBuilder currentToken = new StringBuilder();
@@ -157,8 +163,8 @@ public final class Parser {
* @param index <p>The index of the read character.</p>
* @param tokens <p>The list of processed tokens.</p>
*/
private static void tokenizeNormalCharacter(StringBuilder currentToken, char character, int inputLength, int index,
List<String> tokens) {
private static void tokenizeNormalCharacter(@NotNull StringBuilder currentToken, char character, int inputLength,
int index, @NotNull List<String> tokens) {
currentToken.append(character);
if (index == inputLength - 1) {
tokens.add(currentToken.toString());
@@ -173,7 +179,8 @@ public final class Parser {
* @param tokens <p>The list of processed tokens.</p>
* @return <p>True if the token is finished.</p>
*/
private static boolean tokenizeSpace(boolean startedQuote, StringBuilder currentToken, List<String> tokens) {
private static boolean tokenizeSpace(boolean startedQuote, @NotNull StringBuilder currentToken,
@NotNull List<String> tokens) {
if (!startedQuote) {
//If not inside "", a space marks the end of a parameter
if (isNotEmpty(currentToken)) {
@@ -192,7 +199,7 @@ public final class Parser {
* @param builder <p>The string builder to check.</p>
* @return <p>True if the string builder is non empty.</p>
*/
private static boolean isNotEmpty(StringBuilder builder) {
private static boolean isNotEmpty(@NotNull StringBuilder builder) {
return !builder.toString().trim().isEmpty();
}