EpicKnarvik97 4ebd29b358
Some checks failed
KnarCraft/FFmpegConvert/pipeline/head There was a failure building this commit
Makes it possible to enable debug mode
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
2024-04-17 15:35:09 +02:00

364 lines
14 KiB
Java

package net.knarcraft.ffmpegconverter;
import net.knarcraft.ffmpegconverter.config.ConfigHandler;
import net.knarcraft.ffmpegconverter.converter.AnimeConverter;
import net.knarcraft.ffmpegconverter.converter.AudioConverter;
import net.knarcraft.ffmpegconverter.converter.Converter;
import net.knarcraft.ffmpegconverter.converter.DownScaleConverter;
import net.knarcraft.ffmpegconverter.converter.MKVToMP4Transcoder;
import net.knarcraft.ffmpegconverter.converter.MkvH264Converter;
import net.knarcraft.ffmpegconverter.converter.MkvH265ReducedConverter;
import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed;
import net.knarcraft.ffmpegconverter.converter.VideoConverter;
import net.knarcraft.ffmpegconverter.converter.WebAnimeConverter;
import net.knarcraft.ffmpegconverter.converter.WebVideoConverter;
import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
import net.knarcraft.ffmpegconverter.utility.FileUtil;
import net.knarcraft.ffmpegconverter.utility.ListUtil;
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.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Scanner;
import static net.knarcraft.ffmpegconverter.utility.Parser.tokenize;
/**
* The main class for starting the software
*/
public class FFMpegConvert {
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 Scanner READER = new Scanner(System.in, StandardCharsets.UTF_8);
private static Converter converter = null;
private static final ConfigHandler configHandler = new ConfigHandler();
private static boolean debug = false;
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();
if (converter == null) {
System.exit(1);
return;
}
List<String> input;
do {
OutputUtil.println("<Folder/File> [Recursions]:");
input = readInput(2);
} while (input.isEmpty());
File folder = new File(input.get(0));
int recursionSteps = 1;
if (input.size() > 1) {
try {
recursionSteps = Integer.parseInt(input.get(1));
} catch (NumberFormatException e) {
OutputUtil.println("Recursion steps is invalid and will be ignored.");
}
}
convertAllFiles(folder, recursionSteps);
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
*/
@Nullable
private static Converter loadConverter() {
int choice = getChoice("""
Which converter do you want do use?
1. Anime to web mp4
2. Audio converter
3. Video converter
4. Web video converter
5. MKV to h264 converter
6. MKV to h265 reduced converter
7. MKV to MP4 transcoder
8. DownScaleConverter
9. mp4 Subtitle Embed
10. Anime to h265 all streams""", 1, 10);
return switch (choice) {
case 1 -> generateWebAnimeConverter();
case 2 -> new AudioConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
case 3 -> new VideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
case 4 -> new WebVideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
case 5 -> new MkvH264Converter(FFPROBE_PATH, FFMPEG_PATH);
case 6 -> new MkvH265ReducedConverter(FFPROBE_PATH, FFMPEG_PATH);
case 7 -> generateMKVToMP4Transcoder();
case 8 -> generateDownScaleConverter();
case 9 -> new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH);
case 10 -> generateAnimeConverter();
default -> null;
};
}
/**
* Converts the file(s) as specified
*
* @param fileOrFolder <p>A file or a folder.</p>
* @param recursionSteps <p>The depth to recurse if a folder is given.</p>
* @throws IOException <p>If conversion or writing fails.</p>
*/
private static void convertAllFiles(@NotNull File fileOrFolder, int recursionSteps) throws IOException {
if (fileOrFolder.isDirectory()) {
File[] files = FileUtil.listFilesRecursive(fileOrFolder, converter.getValidFormats(), recursionSteps);
if (files != null && files.length > 0) {
for (File file : files) {
converter.convert(file);
}
} else {
OutputUtil.println("No valid files found in folder.");
}
} else if (fileOrFolder.exists()) {
String path = fileOrFolder.getPath();
if (converter.getValidFormats().stream().anyMatch((format) -> format.equalsIgnoreCase(
path.substring(path.lastIndexOf('.') + 1)))) {
converter.convert(fileOrFolder);
} else {
OutputUtil.println("The specified file " + fileOrFolder.getAbsolutePath() + " is not supported for " +
"the selected converter.");
}
} else {
OutputUtil.println("Path " + fileOrFolder.getAbsolutePath() + " does not point to any file or folder.");
}
}
/**
* Initializes and returns the downscale converter
*
* @return <p>The initialized downscale converter</p>
*/
@Nullable
private static Converter generateDownScaleConverter() {
OutputUtil.println("(New width e.x. 1920) (New height e.x. 1080)\nYour input: ");
List<String> input = readInput(3);
int newWidth;
int newHeight;
try {
newWidth = Integer.parseInt(input.get(0));
newHeight = Integer.parseInt(input.get(1));
return new DownScaleConverter(FFPROBE_PATH, FFMPEG_PATH, newWidth, newHeight);
} catch (NumberFormatException exception) {
OutputUtil.println("Width or height is not a number");
return null;
}
}
/**
* Initializes and returns the MKV to MP4 transcoder
*
* @return <p>The initialized transcoder</p>
*/
@Nullable
private static Converter generateMKVToMP4Transcoder() {
OutputUtil.println("[Audio stream index 0-n] [Subtitle stream index 0-n] [Video stream index 0-n]\nYour input: ");
List<String> input = readInput(3);
int audioStreamIndex = -1;
int subtitleStreamIndex = -1;
int videoStreamIndex = -1;
try {
if (!input.isEmpty()) {
audioStreamIndex = Integer.parseInt(input.get(0));
}
if (input.size() > 1) {
subtitleStreamIndex = Integer.parseInt(input.get(1));
}
if (input.size() > 2) {
videoStreamIndex = Integer.parseInt(input.get(2));
}
return new MKVToMP4Transcoder(FFPROBE_PATH, FFMPEG_PATH, audioStreamIndex, subtitleStreamIndex, videoStreamIndex);
} catch (NumberFormatException exception) {
OutputUtil.println("Audio, Subtitle or Video stream index is not a number");
return null;
}
}
/**
* Initializes and returns the anime converter
*
* @return <p>The initialized anime converter</p>
*/
@Nullable
private static Converter generateAnimeConverter() {
OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Minimal subtitle " +
"preference REQUIRE/PREFER/NO_PREFERENCE/AVOID/REJECT] [Forced audio index 0-n] " +
"[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: ");
List<String> input = readInput(7);
String[] audioLanguage = new String[]{"jpn", "eng", "0"};
String[] subtitleLanguage = new String[]{"nob", "nor", "eng", "jpn", "0"};
MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID;
int forcedAudioIndex = 0;
int forcedSubtitleIndex = 0;
String subtitleNameFilter = "";
if (!input.isEmpty()) {
audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0));
}
if (input.size() > 1) {
subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1));
}
if (input.size() > 3) {
subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase());
}
try {
if (input.size() > 4) {
forcedAudioIndex = Integer.parseInt(input.get(4));
}
if (input.size() > 5) {
forcedSubtitleIndex = Integer.parseInt(input.get(5));
}
} catch (NumberFormatException exception) {
OutputUtil.println("Forced audio or subtitle index is not a number");
return null;
}
if (input.size() > 6) {
subtitleNameFilter = input.get(6);
}
return new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, subtitlePreference,
forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter);
}
/**
* Initializes and returns the web anime converter
*
* @return <p>The initialized anime converter</p>
*/
@Nullable
private static Converter generateWebAnimeConverter() {
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] " +
"[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: ");
List<String> input = readInput(7);
String[] audioLanguage = new String[]{"jpn", "0"};
String[] subtitleLanguage = new String[]{"eng", "0"};
boolean toStereo = true;
MinimalSubtitlePreference subtitlePreference = MinimalSubtitlePreference.AVOID;
int forcedAudioIndex = 0;
int forcedSubtitleIndex = 0;
String subtitleNameFilter = "";
if (!input.isEmpty()) {
audioLanguage = ListUtil.getListFromCommaSeparatedString(input.get(0));
}
if (input.size() > 1) {
subtitleLanguage = ListUtil.getListFromCommaSeparatedString(input.get(1));
}
if (input.size() > 2) {
toStereo = Boolean.parseBoolean(input.get(2));
}
if (input.size() > 3) {
subtitlePreference = MinimalSubtitlePreference.valueOf(input.get(3).toUpperCase());
}
try {
if (input.size() > 4) {
forcedAudioIndex = Integer.parseInt(input.get(4));
}
if (input.size() > 5) {
forcedSubtitleIndex = Integer.parseInt(input.get(5));
}
} catch (NumberFormatException exception) {
OutputUtil.println("Forced audio or subtitle index is not a number");
return null;
}
if (input.size() > 6) {
subtitleNameFilter = input.get(6);
}
return new WebAnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguage, subtitleLanguage, toStereo,
subtitlePreference, forcedAudioIndex, forcedSubtitleIndex, subtitleNameFilter);
}
/**
* Reads a number of tokens from the user input
*
* @param max <p>The number of tokens expected.</p>
* @return <p>A list of tokens.</p>
*/
@NotNull
private static List<String> readInput(int max) {
List<String> tokens = tokenize(READER.nextLine());
if (max < tokens.size()) {
throw new IllegalArgumentException("Input contains " + tokens.size() +
" arguments, but the input only supports " + max + " arguments.");
}
return tokens;
}
/**
* Gets the user's choice
*
* @param prompt <p>The prompt shown to the user.</p>
* @return <p>The non-empty choice given by the user.</p>
*/
@NotNull
private static String getChoice(@NotNull String prompt) {
OutputUtil.println(prompt);
String choice = "";
while (choice.isEmpty()) {
OutputUtil.println("Your input: ");
choice = READER.nextLine();
}
return choice;
}
/**
* Gets an integer from the user
*
* @param prompt The prompt to give the user
* @param min The minimum allowed value
* @param max The maximum allowed value
* @return The value given by the user
*/
private static int getChoice(@NotNull String prompt, int min, int max) {
OutputUtil.println(prompt);
int choice = Integer.MIN_VALUE;
do {
OutputUtil.println("Your input: ");
try {
choice = Integer.parseInt(READER.next());
} catch (NumberFormatException e) {
OutputUtil.println("Invalid choice. Please try again.");
} finally {
READER.nextLine();
}
} while (choice < min || choice > max);
return choice;
}
}