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 input; do { OutputUtil.println(" [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

The configuration handler

*/ @NotNull public static ConfigHandler getConfigHandler() { return configHandler; } /** * Gets whether debug mode is enabled * * @return

True if debug mode is enabled

*/ 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("")); case 3 -> new VideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("")); case 4 -> new WebVideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("")); 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

A file or a folder.

* @param recursionSteps

The depth to recurse if a folder is given.

* @throws IOException

If conversion or writing fails.

*/ 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

The initialized downscale converter

*/ @Nullable private static Converter generateDownScaleConverter() { OutputUtil.println("(New width e.x. 1920) (New height e.x. 1080)\nYour input: "); List 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

The initialized transcoder

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

The initialized anime converter

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

The initialized anime converter

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

The number of tokens expected.

* @return

A list of tokens.

*/ @NotNull private static List readInput(int max) { List 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

The prompt shown to the user.

* @return

The non-empty choice given by the user.

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