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:
Kristian Knarvik 2024-04-17 15:35:09 +02:00
parent f0e75eb440
commit 4ebd29b358
29 changed files with 492 additions and 177 deletions

View File

@ -86,7 +86,7 @@
<transformer <transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries> <manifestEntries>
<Main-Class>net.knarcraft.ffmpegconverter.Main</Main-Class> <Main-Class>net.knarcraft.ffmpegconverter.FFMpegConvert</Main-Class>
</manifestEntries> </manifestEntries>
</transformer> </transformer>
</transformers> </transformers>

View File

@ -1,5 +1,6 @@
package net.knarcraft.ffmpegconverter; package net.knarcraft.ffmpegconverter;
import net.knarcraft.ffmpegconverter.config.ConfigHandler;
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;
@ -15,11 +16,13 @@ import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
import net.knarcraft.ffmpegconverter.utility.FileUtil; import net.knarcraft.ffmpegconverter.utility.FileUtil;
import net.knarcraft.ffmpegconverter.utility.ListUtil; import net.knarcraft.ffmpegconverter.utility.ListUtil;
import net.knarcraft.ffmpegconverter.utility.OutputUtil; import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.Configuration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
@ -28,15 +31,23 @@ import static net.knarcraft.ffmpegconverter.utility.Parser.tokenize;
/** /**
* The main class for starting the software * The main class for starting the software
*/ */
class Main { public class FFMpegConvert {
private static final String FFPROBE_PATH = "ffprobe"; //Can be just ffprobe if it's in the path private static final String FFPROBE_PATH = "ffprobe"; //Can be just ffprobe if it's in the path
private static final String FFMPEG_PATH = "ffmpeg"; //Can be just ffmpeg if it's in the path private static final String FFMPEG_PATH = "ffmpeg"; //Can be just ffmpeg if it's in the path
private static final Scanner READER = new Scanner(System.in, StandardCharsets.UTF_8); private static final Scanner READER = new Scanner(System.in, StandardCharsets.UTF_8);
private static Converter converter = null; private static Converter converter = null;
private static final ConfigHandler configHandler = new ConfigHandler();
private static boolean debug = false;
public static void main(String[] args) throws IOException { public static void main(@NotNull String[] arguments) throws IOException {
Configuration configuration = configHandler.load();
if (configuration.containsKey("debug")) {
debug = configuration.getBoolean("debug");
}
OutputUtil.setDebug(debug);
converter = loadConverter(); converter = loadConverter();
if (converter == null) { if (converter == null) {
System.exit(1); System.exit(1);
return; return;
@ -62,9 +73,29 @@ class Main {
OutputUtil.close(); OutputUtil.close();
} }
/**
* Gets the configuration handler
*
* @return <p>The configuration handler</p>
*/
@NotNull
public static ConfigHandler getConfigHandler() {
return configHandler;
}
/**
* Gets whether debug mode is enabled
*
* @return <p>True if debug mode is enabled</p>
*/
public static boolean isDebugEnabled() {
return debug;
}
/** /**
* 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
*/ */
@Nullable
private static Converter loadConverter() { private static Converter loadConverter() {
int choice = getChoice(""" int choice = getChoice("""
Which converter do you want do use? Which converter do you want do use?
@ -101,7 +132,7 @@ class Main {
* @param recursionSteps <p>The depth to recurse if a folder is given.</p> * @param recursionSteps <p>The depth to recurse if a folder is given.</p>
* @throws IOException <p>If conversion or writing fails.</p> * @throws IOException <p>If conversion or writing fails.</p>
*/ */
private static void convertAllFiles(File fileOrFolder, int recursionSteps) throws IOException { private static void convertAllFiles(@NotNull File fileOrFolder, int recursionSteps) throws IOException {
if (fileOrFolder.isDirectory()) { if (fileOrFolder.isDirectory()) {
File[] files = FileUtil.listFilesRecursive(fileOrFolder, converter.getValidFormats(), recursionSteps); File[] files = FileUtil.listFilesRecursive(fileOrFolder, converter.getValidFormats(), recursionSteps);
if (files != null && files.length > 0) { if (files != null && files.length > 0) {
@ -113,7 +144,7 @@ class Main {
} }
} else if (fileOrFolder.exists()) { } else if (fileOrFolder.exists()) {
String path = fileOrFolder.getPath(); String path = fileOrFolder.getPath();
if (Arrays.stream(converter.getValidFormats()).anyMatch((format) -> format.equalsIgnoreCase( if (converter.getValidFormats().stream().anyMatch((format) -> format.equalsIgnoreCase(
path.substring(path.lastIndexOf('.') + 1)))) { path.substring(path.lastIndexOf('.') + 1)))) {
converter.convert(fileOrFolder); converter.convert(fileOrFolder);
} else { } else {
@ -130,6 +161,7 @@ class Main {
* *
* @return <p>The initialized downscale converter</p> * @return <p>The initialized downscale converter</p>
*/ */
@Nullable
private static Converter generateDownScaleConverter() { private static Converter generateDownScaleConverter() {
OutputUtil.println("(New width e.x. 1920) (New height e.x. 1080)\nYour input: "); OutputUtil.println("(New width e.x. 1920) (New height e.x. 1080)\nYour input: ");
List<String> input = readInput(3); List<String> input = readInput(3);
@ -151,6 +183,7 @@ class Main {
* *
* @return <p>The initialized transcoder</p> * @return <p>The initialized transcoder</p>
*/ */
@Nullable
private static Converter generateMKVToMP4Transcoder() { private static Converter generateMKVToMP4Transcoder() {
OutputUtil.println("[Audio stream index 0-n] [Subtitle stream index 0-n] [Video stream index 0-n]\nYour input: "); OutputUtil.println("[Audio stream index 0-n] [Subtitle stream index 0-n] [Video stream index 0-n]\nYour input: ");
List<String> input = readInput(3); List<String> input = readInput(3);
@ -180,6 +213,7 @@ class Main {
* *
* @return <p>The initialized anime converter</p> * @return <p>The initialized anime converter</p>
*/ */
@Nullable
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] " +
@ -224,6 +258,7 @@ class Main {
* *
* @return <p>The initialized anime converter</p> * @return <p>The initialized anime converter</p>
*/ */
@Nullable
private static Converter generateWebAnimeConverter() { private static Converter generateWebAnimeConverter() {
OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stereo if " + OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stereo if " +
"necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " + "necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " +
@ -274,6 +309,7 @@ class Main {
* @param max <p>The number of tokens expected.</p> * @param max <p>The number of tokens expected.</p>
* @return <p>A list of tokens.</p> * @return <p>A list of tokens.</p>
*/ */
@NotNull
private static List<String> readInput(int max) { private static List<String> readInput(int max) {
List<String> tokens = tokenize(READER.nextLine()); List<String> tokens = tokenize(READER.nextLine());
if (max < tokens.size()) { if (max < tokens.size()) {
@ -289,7 +325,8 @@ class Main {
* @param prompt <p>The prompt shown to the user.</p> * @param prompt <p>The prompt shown to the user.</p>
* @return <p>The non-empty choice given by the user.</p> * @return <p>The non-empty choice given by the user.</p>
*/ */
private static String getChoice(String prompt) { @NotNull
private static String getChoice(@NotNull String prompt) {
OutputUtil.println(prompt); OutputUtil.println(prompt);
String choice = ""; String choice = "";
while (choice.isEmpty()) { while (choice.isEmpty()) {
@ -307,7 +344,7 @@ class Main {
* @param max The maximum allowed value * @param max The maximum allowed value
* @return The value given by the user * @return The value given by the user
*/ */
private static int getChoice(String prompt, int min, int max) { private static int getChoice(@NotNull String prompt, int min, int max) {
OutputUtil.println(prompt); OutputUtil.println(prompt);
int choice = Integer.MIN_VALUE; int choice = Integer.MIN_VALUE;
do { do {

View File

@ -0,0 +1,92 @@
package net.knarcraft.ffmpegconverter.config;
import net.knarcraft.ffmpegconverter.utility.FileHelper;
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* A handler for dealing with configurations
*/
public class ConfigHandler {
private final File configFolder = new File("conf").getAbsoluteFile();
private final Configurations configurations = new Configurations();
private final File settingsFile = new File(configFolder, "config.properties");
private FileBasedConfigurationBuilder<PropertiesConfiguration> builder = configurations.propertiesBuilder(settingsFile);
/**
* Gets a writable configuration used for changing settings
*
* @return <p>A writable properties configuration</p>
*/
@NotNull
public PropertiesConfiguration getWritableConfiguration() {
try {
return builder.getConfiguration();
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
}
/**
* Writes the writable configuration to disk
*/
public void writeConfiguration() {
OutputUtil.printDebug("Preparing to save config");
if (!configFolder.exists() && !configFolder.mkdir()) {
throw new RuntimeException("Unable to create config folder. Make sure to run this .jar file from a " +
"writable directory!");
}
try {
if (!settingsFile.exists() && !settingsFile.createNewFile()) {
OutputUtil.println("Failed to create configuration file.");
}
} catch (IOException e) {
OutputUtil.println("Failed to create configuration file.");
}
try {
builder.save();
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
OutputUtil.printDebug("Saved available hardware encoder handler");
}
/**
* Loads the saved configuration file
*
* @return <p>The loaded configuration</p>
*/
@NotNull
public Configuration load() throws IOException {
Configuration configuration;
if (!settingsFile.exists()) {
configuration = new PropertiesConfiguration();
BufferedReader reader = FileHelper.getBufferedReaderForInternalFile("/conf/config.properties");
Map<String, String> entries = FileHelper.readKeyValuePairs(reader, "=");
for (Map.Entry<String, String> entry : entries.entrySet()) {
configuration.setProperty(entry.getKey(), entry.getValue());
}
} else {
try {
configuration = configurations.properties(settingsFile);
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
}
// Reload contents in the builder
builder = configurations.propertiesBuilder(settingsFile);
return configuration;
}
}

View File

@ -1,12 +1,13 @@
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.handler.AvailableHardwareEncoderHandler; import net.knarcraft.ffmpegconverter.handler.AvailableHardwareEncoderHandler;
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper; import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
import net.knarcraft.ffmpegconverter.utility.FileHelper;
import net.knarcraft.ffmpegconverter.utility.FileUtil; import net.knarcraft.ffmpegconverter.utility.FileUtil;
import net.knarcraft.ffmpegconverter.utility.OutputUtil; import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -20,12 +21,13 @@ import java.util.List;
*/ */
public abstract class AbstractConverter implements Converter { public abstract class AbstractConverter implements Converter {
final boolean debug = false; final boolean debug = FFMpegConvert.isDebugEnabled();
private final String newExtension; private final String newExtension;
protected String ffprobePath; protected String ffprobePath;
protected String ffmpegPath; protected String ffmpegPath;
protected String[] audioFormats; protected List<String> audioFormats;
protected String[] videoFormats; protected List<String> videoFormats;
protected List<String> subtitleFormats;
protected AvailableHardwareEncoderHandler encoderHandler = null; protected AvailableHardwareEncoderHandler encoderHandler = null;
@ -34,31 +36,31 @@ public abstract class AbstractConverter implements Converter {
*/ */
AbstractConverter(@Nullable String newExtension) { AbstractConverter(@Nullable String newExtension) {
this.newExtension = newExtension; this.newExtension = newExtension;
OutputUtil.setDebug(this.debug);
try { try {
audioFormats = FileUtil.readFileLines("audio_formats.txt"); this.audioFormats = FileHelper.readLines(FileHelper.getBufferedReaderForInternalFile("/audio_formats.txt"));
videoFormats = FileUtil.readFileLines("video_formats.txt"); this.videoFormats = FileHelper.readLines(FileHelper.getBufferedReaderForInternalFile("/video_formats.txt"));
this.subtitleFormats = FileHelper.readLines(FileHelper.getBufferedReaderForInternalFile("/subtitle_formats.txt"));
} catch (IOException e) { } catch (IOException e) {
System.out.println("Unable to read audio and/or video formats from internal files."); OutputUtil.println("Unable to read audio and/or video formats from internal files.");
System.exit(1); System.exit(1);
} }
} }
@Override @Override
public void convert(@NotNull File file) throws IOException { public void convert(@NotNull File file) throws IOException {
StreamProbeResult probeResult = FFMpegHelper.probeFile(ffprobePath, file); StreamProbeResult probeResult = FFMpegHelper.probeFile(this.ffprobePath, file, this.subtitleFormats);
if (probeResult.parsedStreams().isEmpty()) { if (probeResult.parsedStreams().isEmpty()) {
throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" + throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" +
" is not corrupt."); " is not corrupt.");
} }
String outExtension = newExtension != null ? newExtension : FileUtil.getExtension(file.getName()); String outExtension = this.newExtension != null ? this.newExtension : FileUtil.getExtension(file.getName());
String newPath = FileUtil.getNonCollidingPath(file.getParentFile(), file, outExtension); String newPath = FileUtil.getNonCollidingPath(file.getParentFile(), file, outExtension);
OutputUtil.println(); OutputUtil.println();
OutputUtil.println("Preparing to start process..."); OutputUtil.println("Preparing to start process...");
OutputUtil.println("Converting " + file); OutputUtil.println("Converting " + file);
FFMpegCommand ffMpegCommand = generateConversionCommand(ffmpegPath, probeResult, newPath); FFMpegCommand ffMpegCommand = generateConversionCommand(this.ffmpegPath, probeResult, newPath);
// If the command is null, that means the file does not need conversion // If the command is null, that means the file does not need conversion
if (ffMpegCommand == null) { if (ffMpegCommand == null) {
return; return;
@ -73,11 +75,7 @@ public abstract class AbstractConverter implements Converter {
ProcessBuilder processBuilder = new ProcessBuilder(command); ProcessBuilder processBuilder = new ProcessBuilder(command);
int exitCode = FFMpegHelper.runProcess(processBuilder, file.getParentFile(), "\n", true).exitCode(); int exitCode = FFMpegHelper.runProcess(processBuilder, file.getParentFile(), "\n", true).exitCode();
if (exitCode != 0) { if (exitCode != 0) {
try {
handleError(ffMpegCommand, file, newPath); handleError(ffMpegCommand, file, newPath);
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
} }
} }
@ -88,10 +86,9 @@ public abstract class AbstractConverter implements Converter {
* @param file <p>The file that was to be converted</p> * @param file <p>The file that was to be converted</p>
* @param newPath <p>The path of the output file</p> * @param newPath <p>The path of the output file</p>
* @throws IOException <p>If unable to produce output</p> * @throws IOException <p>If unable to produce output</p>
* @throws ConfigurationException <p>If unable </p>
*/ */
private void handleError(@NotNull FFMpegCommand ffMpegCommand, @NotNull File file, private void handleError(@NotNull FFMpegCommand ffMpegCommand, @NotNull File file,
@NotNull String newPath) throws IOException, ConfigurationException { @NotNull String newPath) throws IOException {
File outputFile = new File(newPath); File outputFile = new File(newPath);
if (outputFile.exists() && !outputFile.delete()) { if (outputFile.exists() && !outputFile.delete()) {
OutputUtil.println("Failed to remove failed output file. Please remove it manually"); OutputUtil.println("Failed to remove failed output file. Please remove it manually");
@ -118,12 +115,17 @@ public abstract class AbstractConverter implements Converter {
* *
* @return <p>Available hardware encoding methods</p> * @return <p>Available hardware encoding methods</p>
*/ */
@NotNull
protected List<String> getAvailableHardwareEncodingMethods() { protected List<String> getAvailableHardwareEncodingMethods() {
try {
if (encoderHandler == null) { if (encoderHandler == null) {
encoderHandler = AvailableHardwareEncoderHandler.load(); encoderHandler = AvailableHardwareEncoderHandler.load();
if (encoderHandler.availableHardwareEncodings().isEmpty()) { if (encoderHandler.availableHardwareEncodings().isEmpty()) {
List<String> hardwareEncoding = new ArrayList<>(FFMpegHelper.getHWAcceleration(ffmpegPath)); List<String> hardwareEncoding;
try {
hardwareEncoding = new ArrayList<>(FFMpegHelper.getHWAcceleration(ffmpegPath));
} catch (IOException e) {
throw new RuntimeException(e);
}
hardwareEncoding.remove(0); hardwareEncoding.remove(0);
encoderHandler = new AvailableHardwareEncoderHandler(hardwareEncoding); encoderHandler = new AvailableHardwareEncoderHandler(hardwareEncoding);
encoderHandler.save(); encoderHandler.save();
@ -131,10 +133,6 @@ public abstract class AbstractConverter implements Converter {
} }
return encoderHandler.availableHardwareEncodings(); return encoderHandler.availableHardwareEncodings();
} catch (ConfigurationException | IOException exception) {
OutputUtil.println("Unable to get available hardware encoders: " + exception.getMessage());
return new ArrayList<>();
}
} }
} }

View File

@ -26,6 +26,7 @@ import net.knarcraft.ffmpegconverter.streams.AudioStream;
import net.knarcraft.ffmpegconverter.streams.SubtitleStream; import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -69,6 +70,7 @@ public class AnimeConverter extends AbstractConverter {
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, public FFMpegCommand generateConversionCommand(@NotNull String executable,
@NotNull StreamProbeResult probeResult, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
@ -118,7 +120,8 @@ public class AnimeConverter extends AbstractConverter {
} }
@Override @Override
public @NotNull String[] getValidFormats() { @NotNull
public List<String> getValidFormats() {
return this.videoFormats; return this.videoFormats;
} }

View File

@ -9,6 +9,7 @@ import net.knarcraft.ffmpegconverter.converter.module.mapping.NthAudioStreamModu
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -25,13 +26,14 @@ public class AudioConverter extends AbstractConverter {
* @param ffmpegPath <p>Path/command to ffmpeg.</p> * @param ffmpegPath <p>Path/command to ffmpeg.</p>
* @param newExtension <p>The extension of the new file.</p> * @param newExtension <p>The extension of the new file.</p>
*/ */
public AudioConverter(String ffprobePath, String ffmpegPath, String newExtension) { public AudioConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, @NotNull String newExtension) {
super(newExtension); super(newExtension);
this.ffprobePath = ffprobePath; this.ffprobePath = ffprobePath;
this.ffmpegPath = ffmpegPath; this.ffmpegPath = ffmpegPath;
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
@ -50,7 +52,8 @@ public class AudioConverter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
public List<String> getValidFormats() {
return audioFormats; return audioFormats;
} }

View File

@ -3,9 +3,11 @@ package net.knarcraft.ffmpegconverter.converter;
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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List;
/** /**
* This interface describes a file converter * This interface describes a file converter
@ -18,7 +20,7 @@ public interface Converter {
* @return <p>A list of valid input formats</p> * @return <p>A list of valid input formats</p>
*/ */
@NotNull @NotNull
String[] getValidFormats(); List<String> getValidFormats();
/** /**
* Converts the given file * Converts the given file
@ -36,6 +38,7 @@ public interface Converter {
* @param outFile <p>The output file</p> * @param outFile <p>The output file</p>
* @return <p>A list of commands</p> * @return <p>A list of commands</p>
*/ */
@Nullable
FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile); @NotNull String outFile);

View File

@ -16,6 +16,7 @@ import net.knarcraft.ffmpegconverter.streams.StreamObject;
import net.knarcraft.ffmpegconverter.streams.VideoStream; 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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -36,7 +37,7 @@ public class DownScaleConverter extends AbstractConverter {
* @param newWidth <p>The new width of the video</p> * @param newWidth <p>The new width of the video</p>
* @param newHeight <p>The new height of the video</p> * @param newHeight <p>The new height of the video</p>
*/ */
public DownScaleConverter(String ffprobePath, String ffmpegPath, int newWidth, int newHeight) { public DownScaleConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath, int newWidth, int newHeight) {
super(null); super(null);
this.ffprobePath = ffprobePath; this.ffprobePath = ffprobePath;
this.ffmpegPath = ffmpegPath; this.ffmpegPath = ffmpegPath;
@ -45,6 +46,7 @@ public class DownScaleConverter extends AbstractConverter {
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
List<StreamObject> streams = probeResult.parsedStreams(); List<StreamObject> streams = probeResult.parsedStreams();
@ -78,7 +80,8 @@ public class DownScaleConverter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
public List<String> getValidFormats() {
return videoFormats; return videoFormats;
} }

View File

@ -13,6 +13,7 @@ import net.knarcraft.ffmpegconverter.converter.module.output.CopyAllModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -46,11 +47,13 @@ public class MKVToMP4Transcoder extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
return new String[]{"mkv"}; public List<String> getValidFormats() {
return List.of("mkv");
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());

View File

@ -15,6 +15,7 @@ import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule
import net.knarcraft.ffmpegconverter.streams.StreamObject; import net.knarcraft.ffmpegconverter.streams.StreamObject;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -37,11 +38,13 @@ public class MkvH264Converter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
return new String[]{"mkv"}; public List<String> getValidFormats() {
return List.of("mkv");
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());

View File

@ -16,6 +16,7 @@ import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
import net.knarcraft.ffmpegconverter.streams.VideoStream; 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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -38,11 +39,13 @@ public class MkvH265ReducedConverter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
return new String[]{"mkv"}; public List<String> getValidFormats() {
return List.of("mkv");
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());

View File

@ -12,6 +12,7 @@ import net.knarcraft.ffmpegconverter.converter.module.output.MovTextModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule; import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -34,11 +35,13 @@ public class SubtitleEmbed extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
return new String[]{"mp4"}; public List<String> getValidFormats() {
return List.of("mp4");
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());

View File

@ -12,6 +12,7 @@ import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule
import net.knarcraft.ffmpegconverter.streams.StreamObject; import net.knarcraft.ffmpegconverter.streams.StreamObject;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -35,6 +36,7 @@ public class VideoConverter extends AbstractConverter {
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
@ -57,7 +59,8 @@ public class VideoConverter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
public List<String> getValidFormats() {
return videoFormats; return videoFormats;
} }

View File

@ -21,6 +21,7 @@ import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
import net.knarcraft.ffmpegconverter.streams.VideoStream; 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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -69,6 +70,7 @@ public class WebAnimeConverter extends AbstractConverter {
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles());
@ -114,7 +116,8 @@ public class WebAnimeConverter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
public List<String> getValidFormats() {
return this.videoFormats; return this.videoFormats;
} }

View File

@ -14,6 +14,7 @@ import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
import net.knarcraft.ffmpegconverter.streams.VideoStream; 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 java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -39,11 +40,13 @@ public class WebVideoConverter extends AbstractConverter {
} }
@Override @Override
public String[] getValidFormats() { @NotNull
public List<String> getValidFormats() {
return videoFormats; return videoFormats;
} }
@Override @Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult, public FFMpegCommand generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) { @NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles()); FFMpegCommand command = FFMpegHelper.getFFMpegWebVideoCommand(executable, probeResult.parsedFiles());

View File

@ -1,14 +1,12 @@
package net.knarcraft.ffmpegconverter.handler; package net.knarcraft.ffmpegconverter.handler;
import net.knarcraft.ffmpegconverter.FFMpegConvert;
import net.knarcraft.ffmpegconverter.config.ConfigHandler;
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;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -18,8 +16,7 @@ import java.util.List;
*/ */
public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHardwareEncodings) { public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHardwareEncodings) {
private static final File configFolder = new File("conf").getAbsoluteFile(); private static final ConfigHandler configHandler = FFMpegConvert.getConfigHandler();
private static final Configurations configurations = new Configurations();
/** /**
* Gets all hardware encodings * Gets all hardware encodings
@ -42,30 +39,12 @@ public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHar
} }
/** /**
* Saves settings fro this available hardware encoder handler * Saves settings for this available hardware encoder handler
*
* @throws ConfigurationException <p>If the configuration file cannot be saved</p>
*/ */
public void save() throws ConfigurationException { public void save() {
OutputUtil.printDebug("Preparing to save config"); PropertiesConfiguration configuration = configHandler.getWritableConfiguration();
if (!configFolder.exists() && !configFolder.mkdir()) { configuration.setProperty("encoder.hardware", this.availableHardwareEncodings);
throw new RuntimeException("Unable to create config folder. Make sure to run this .jar file from a " + configHandler.writeConfiguration();
"writable directory!");
}
File settingsFile = new File(configFolder, "config.properties");
try {
if (!settingsFile.exists() && !settingsFile.createNewFile()) {
OutputUtil.println("Failed to create configuration file.");
}
} catch (IOException e) {
OutputUtil.println("Failed to create configuration file.");
}
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
configurations.propertiesBuilder(settingsFile);
PropertiesConfiguration config = builder.getConfiguration();
config.setProperty("encoder.hardware", this.availableHardwareEncodings);
builder.save();
OutputUtil.printDebug("Saved available hardware encoder handler"); OutputUtil.printDebug("Saved available hardware encoder handler");
} }
@ -73,14 +52,15 @@ public record AvailableHardwareEncoderHandler(@NotNull List<String> availableHar
* Loads saved settings for an available hardware encoder handler * Loads saved settings for an available hardware encoder handler
* *
* @return <p>The loaded available hardware encoder handler, or a new one if no data has been saved</p> * @return <p>The loaded available hardware encoder handler, or a new one if no data has been saved</p>
* @throws ConfigurationException <p>If the configuration file cannot be loaded</p>
*/ */
public static AvailableHardwareEncoderHandler load() throws ConfigurationException { @NotNull
File settingsFile = new File(configFolder, "config.properties"); public static AvailableHardwareEncoderHandler load() {
if (!settingsFile.exists()) { Configuration configuration;
return new AvailableHardwareEncoderHandler(new ArrayList<>()); try {
configuration = configHandler.load();
} catch (IOException e) {
throw new RuntimeException(e);
} }
Configuration configuration = configurations.properties(settingsFile);
List<String> getEncodings = configuration.getList(String.class, "encoder.hardware"); List<String> getEncodings = configuration.getList(String.class, "encoder.hardware");
return new AvailableHardwareEncoderHandler(getEncodings); return new AvailableHardwareEncoderHandler(getEncodings);
} }

View File

@ -1,6 +1,7 @@
package net.knarcraft.ffmpegconverter.parser; package net.knarcraft.ffmpegconverter.parser;
import net.knarcraft.ffmpegconverter.utility.ListUtil; import net.knarcraft.ffmpegconverter.utility.ListUtil;
import org.jetbrains.annotations.NotNull;
/** /**
* A class representing a command argument * A class representing a command argument
@ -20,7 +21,8 @@ public class ConverterArgument {
* @param valueRequired <p>Whether the argument must be followed by a valid value.</p> * @param valueRequired <p>Whether the argument must be followed by a valid value.</p>
* @param valueType <p>The type of value the argument requires.</p> * @param valueType <p>The type of value the argument requires.</p>
*/ */
public ConverterArgument(String name, char shorthand, boolean valueRequired, ConverterArgumentValueType valueType) { public ConverterArgument(@NotNull String name, char shorthand, boolean valueRequired,
@NotNull ConverterArgumentValueType valueType) {
this.name = name; this.name = name;
this.shorthand = shorthand; this.shorthand = shorthand;
this.valueRequired = valueRequired; this.valueRequired = valueRequired;
@ -60,7 +62,7 @@ public class ConverterArgument {
* @param value <p>The value to test.</p> * @param value <p>The value to test.</p>
* @return <p>True if the argument is valid. False otherwise.</p> * @return <p>True if the argument is valid. False otherwise.</p>
*/ */
public boolean testArgumentValue(String value) { public boolean testArgumentValue(@NotNull String value) {
if (value.isEmpty()) { if (value.isEmpty()) {
return !valueRequired; return !valueRequired;
} }

View File

@ -36,7 +36,8 @@ public abstract class AbstractStream implements StreamObject {
} }
@Override @Override
public @NotNull String getCodecName() { @NotNull
public String getCodecName() {
return this.codecName; return this.codecName;
} }
@ -51,7 +52,8 @@ public abstract class AbstractStream implements StreamObject {
} }
@Override @Override
public @NotNull String getLanguage() { @NotNull
public String getLanguage() {
return this.language; return this.language;
} }

View File

@ -27,7 +27,6 @@ import java.util.Map;
public final class FFMpegHelper { public final class FFMpegHelper {
private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå"; private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå";
private static String[] subtitleFormats = null;
private FFMpegHelper() { private FFMpegHelper() {
@ -70,14 +69,16 @@ public final class FFMpegHelper {
/** /**
* Gets streams from a file * Gets streams from a file
* *
* @param ffprobePath <p>The path/command to ffprobe.</p> * @param ffprobePath <p>The path/command to ffprobe</p>
* @param file <p>The file to probe.</p> * @param file <p>The file to probe</p>
* @return <p>A list of StreamObjects.</p> * @param subtitleFormats <p>The extensions to accept for external subtitles</p>
* @throws IOException <p>If the process can't be readProcess.</p> * @return <p>A list of StreamObjects</p>
* @throws IOException <p>If the process can't be readProcess</p>
*/ */
@NotNull @NotNull
public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file) throws IOException { public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file,
return parseStreams(ffprobePath, probeForStreams(ffprobePath, 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> * @param fileName <p>The filename to escape.</p>
* @return <p>A filename with known special characters escaped.</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("\\\\", "\\\\\\\\\\\\\\\\") return fileName.replaceAll("\\\\", "\\\\\\\\\\\\\\\\")
.replaceAll("'", "'\\\\\\\\\\\\\''") .replaceAll("'", "'\\\\\\\\\\\\\''")
.replaceAll("%", "\\\\\\\\\\\\%") .replaceAll("%", "\\\\\\\\\\\\%")
@ -260,7 +262,8 @@ public final class FFMpegHelper {
* @return <p>A list of streams.</p> * @return <p>A list of streams.</p>
* @throws IOException <p>If something goes wrong while probing.</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); FFMpegCommand probeCommand = new FFMpegCommand(ffprobePath);
probeCommand.addGlobalOption("-v", "error", "-show_streams"); probeCommand.addGlobalOption("-v", "error", "-show_streams");
probeCommand.addInputFile(file.toString()); 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 * Takes a list of all streams and parses each stream into one of three objects
* *
* @param ffprobePath <p>The path to the ffprobe executable</p>
* @param streams <p>A list of all streams for the current file.</p> * @param streams <p>A list of all streams for the current file.</p>
* @param file <p>The file currently being converted.</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> * @return <p>A list of StreamObjects.</p>
*/ */
@NotNull @NotNull
private static StreamProbeResult parseStreams(@NotNull String ffprobePath, @NotNull List<String> streams, 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<>(); List<StreamObject> parsedStreams = new ArrayList<>();
int relativeAudioIndex = 0; int relativeAudioIndex = 0;
int relativeVideoIndex = 0; int relativeVideoIndex = 0;
@ -306,7 +311,7 @@ public final class FFMpegHelper {
} }
} }
StreamProbeResult probeResult = new StreamProbeResult(List.of(file), parsedStreams); 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; 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 * Tries to find any external subtitles adjacent to the first input file, and appends it to the given probe result
* *
* @param streamProbeResult <p>The stream probe result to append to</p>
* @param ffprobePath <p>The path/command to ffprobe</p> * @param ffprobePath <p>The path/command to ffprobe</p>
* @param directory <p>The directory containing the file</p> * @param directory <p>The directory containing the file</p>
* @param convertingFile <p>The first/main file to be converted</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, private static void getExternalSubtitles(@NotNull StreamProbeResult streamProbeResult,
@NotNull String ffprobePath, @NotNull File directory, @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 //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); File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
// TODO: Generalize this for external audio tracks // TODO: Generalize this for external audio tracks
@ -382,7 +386,8 @@ public final class FFMpegHelper {
* @return <p>The output from the readProcess.</p> * @return <p>The output from the readProcess.</p>
* @throws IOException <p>On reader failure.</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; String line;
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
while (reader.ready() && (line = reader.readLine()) != null && !line.isEmpty() && !line.equals("\n")) { 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> * @return <p>The available hardware acceleration methods</p>
* @throws IOException <p>If the process fails</p> * @throws IOException <p>If the process fails</p>
*/ */
@NotNull
public static List<String> getHWAcceleration(@NotNull String ffmpegPath) throws IOException { public static List<String> getHWAcceleration(@NotNull String ffmpegPath) throws IOException {
FFMpegCommand probeCommand = new FFMpegCommand(ffmpegPath); FFMpegCommand probeCommand = new FFMpegCommand(ffmpegPath);
probeCommand.addGlobalOption("-v", "error", "-hwaccels"); 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; 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.File;
import java.io.IOException; import java.util.List;
import java.io.InputStream;
import java.io.InputStreamReader;
/** /**
* A class which helps with file handling * 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> * @param outExtension <p>The extension of the output file.</p>
* @return <p>A file name with the new extension and without any collisions.</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 + return FileUtil.getNonCollidingFilename(folder.getAbsolutePath() + File.separator +
FileUtil.stripExtension(file.getName()) + "." + outExtension, outExtension); FileUtil.stripExtension(file.getName()) + "." + outExtension, outExtension);
} }
@ -35,7 +36,8 @@ public final class FileUtil {
* @param maxRecursions <p>Maximum number of recursions</p> * @param maxRecursions <p>Maximum number of recursions</p>
* @return A list of files * @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 //Return if the target depth has been reached
if (maxRecursions == 0) { if (maxRecursions == 0) {
return null; 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 //Get a list of all files which are folders and has one of the extensions specified
File[] foundFiles = folder.listFiles((file) -> file.isFile() && File[] foundFiles = folder.listFiles((file) -> file.isFile() &&
ListUtil.listContains(extensions, (item) -> file.getName().toLowerCase().endsWith(item))); ListUtil.listContains(extensions, (item) -> file.getName().toLowerCase().endsWith(item)));
//Return if recursion is finished //Return if recursion is finished
if (maxRecursions == 1) { if (maxRecursions == 1) {
return foundFiles; return foundFiles;
@ -68,36 +71,6 @@ public final class FileUtil {
return foundFiles; 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 * 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> * @param extension <p>The extension of the target file.</p>
* @return <p>A filename guaranteed not to collide with other files.</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); File newFile = new File(targetPath);
String fileName = stripExtension(targetPath).replaceAll("\\([0-9]+\\)$", ""); String fileName = stripExtension(targetPath).replaceAll("\\([0-9]+\\)$", "");
int i = 1; int i = 1;
@ -121,7 +95,8 @@ public final class FileUtil {
* @param file <p>The filename to check</p> * @param file <p>The filename to check</p>
* @return <p>The file's extension</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(".")) { if (file.contains(".")) {
return file.substring(file.lastIndexOf('.') + 1); return file.substring(file.lastIndexOf('.') + 1);
} else { } else {
@ -135,7 +110,8 @@ public final class FileUtil {
* @param file <p>A filename.</p> * @param file <p>A filename.</p>
* @return <p>A filename without its extension.</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('.')); return file.substring(0, file.lastIndexOf('.'));
} }

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
39
3gp 3gp
aa aa
aac aac

View File

@ -0,0 +1 @@
debug=false

View File

@ -1,4 +1,3 @@
5
idx idx
sub sub
srt srt

View File

@ -1,4 +1,3 @@
31
avi avi
mpg mpg
mpeg mpeg

View File

@ -13,7 +13,7 @@ import static org.junit.Assert.assertFalse;
public class ListUtilTest { public class ListUtilTest {
private static List<Integer> matchesList; private static List<Integer> matchesList;
private static Integer[] containsList; private static List<Integer> containsList;
@BeforeClass @BeforeClass
public static void setUp() { public static void setUp() {
@ -28,7 +28,7 @@ public class ListUtilTest {
matchesList.add(19); matchesList.add(19);
matchesList.add(21); matchesList.add(21);
matchesList.add(23); matchesList.add(23);
containsList = new Integer[]{1, 3, 5, 7, 234, 23, 45}; containsList = List.of(1, 3, 5, 7, 234, 23, 45);
} }
@Test @Test