package ffmpegconverter.converter; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; /** * Implements all methods which can be usefull for any implementation of a converter. */ public abstract class Converter { String ffprobePath; String ffmpegPath; public abstract String[] getValidFormats(); public abstract void convert(File file) throws IOException; final String[] AUDIO_FORMATS = new String[] {".3gp", ".aa", ".aac", ".aax", ".act", ".aiff", ".amr", ".ape", ".au", ".awb", ".dct", ".dss", ".dvf", ".flac", ".gsm", ".iklax", ".ivs", ".m4a", ".m4b", ".m4p", ".mmf", ".mp3", ".mpc", ".msv", ".ogg", ".oga", ".mogg", ".opus", ".ra", ".rm", ".raw", ".sln", ".tta", ".vox", ".wav", ".wma", ".wv", ".webm", ".8svx"}; final String[] VIDEO_FORMATS = new String[] {".avi", ".mpg", ".mpeg", ".mkv", ".wmv", ".flv", ".webm", ".3gp", ".rmvb", ".3gpp", ".mts", ".m4v", ".mov", ".rm", ".asf", ".mp4", ".vob", ".ogv", ".drc", ".qt", ".yuv", ".asm", ".m4p", ".mp2", ".mpe", ".mpv", ".m2v", ".svi", ".3g2", ".roq", ".nsv"}; static String[] probeFile(String ffprobePath, File file) throws IOException { ProcessBuilder builderProbe = new ProcessBuilder( ffprobePath, "-v", "error", "-show_entries", "stream_tags=language,title:stream=index,codec_name,codec_type,channels", file.toString() ); System.out.println(builderProbe.command()); builderProbe.redirectErrorStream(true); Process processProbe = builderProbe.start(); BufferedReader readerProbe = new BufferedReader(new InputStreamReader(processProbe.getInputStream())); StringBuilder output = new StringBuilder(); while (processProbe.isAlive()) { String read = read(readerProbe, " "); if (!read.equals("")) { System.out.print(read); output.append(read); } } return stringBetween(output.toString(), "[STREAM]", "[/STREAM]"); } static String fileCollisionPrevention(String targetPath, String extension) { File file = new File(targetPath); int i = 1; while (file.exists()) { file = new File(stripExtension(file) + "(" + i + ")" + "." + extension); } return file.toString(); } static void convertProcess(ProcessBuilder process, File folder) throws IOException { System.out.println(process.command()); process.directory(folder); process.redirectErrorStream(true); Process processConvert = process.start(); BufferedReader readerConvert = new BufferedReader(new InputStreamReader(processConvert.getInputStream())); while (processConvert.isAlive()) { String read = read(readerConvert, "\n"); if (!read.equals("")) { System.out.println(read); } } } /** * Reads from a process reader. * * @param reader The reader of a process * @return The output from the read * @throws IOException On reader failure */ private static String read(BufferedReader reader, String spacer) throws IOException { String line; StringBuilder text = new StringBuilder(); while (reader.ready() && (line = reader.readLine()) != null && !line.equals("") && !line.equals("\n")) { text.append(line).append(spacer); } return text.toString().trim(); } /** * @return A base list of ffmpeg commands for converting a video for web */ static List ffmpegWebVideo(String executable, String fileName) { List command = generalFile(executable, fileName); command.add("-vcodec"); command.add("h264"); command.add("-pix_fmt"); command.add("yuv420p"); command.add("-ar"); command.add("48000"); command.add("-movflags"); command.add("+faststart"); return command; } /** * @return A base list of ffmpeg commands for converting a file */ static List generalFile(String executable, String fileName) { List command = new ArrayList<>(); command.add(executable); command.add("-nostdin"); command.add("-i"); command.add(fileName); return command; } static void addDebug(List command, int start, int length) { command.add("-ss"); command.add("" + start); command.add("-t"); command.add("" + length); } //TODO: Create a new object for subtitle containing language, relative index, absolute index and codec. Also signs & songs. /** * Checks for the occurrence of otf attachments signifying the existence of image based subtitles. * * @param list A list of ffprobe indexes * @return True if otf attachments were found. */ static boolean isImageSub(String[] list, int index) { return list[index].contains("codec_name=hdmv_pgs_subtitle"); //Filename .sup } /** * Lists all video streams. * * @param list A list of ffprobe indexes * @return An integer list containing just the wanted indexes */ static List listVideo(String[] list) { return listIndexes(list, (string) -> string.contains("codec_type=video")); } /** * Lists all audio streams. * * @param list A list of ffprobe indexes * @return An integer list containing just the wanted indexes */ static List listAudio(String[] list) { return listIndexes(list, (string) -> string.contains("codec_type=audio")); } /** * Lists all audio streams of a certain language. * * @param list A list of ffprobe indexes * @param lang The wanted language * @return An integer list containing just the wanted indexes */ static List listAudio(String[] list, String lang) { return listIndexes(list, (string) -> string.contains("codec_type=audio") && string.contains("language=" + lang)); } /** * Lists all subtitle streams of a certain language relatively to the number of subtitle streams. * 0-based. * Filters out all indexes containing anything in illegal * * @param list A list of ffprobe indexes * @param lang The wanted language * @param illegal A list of strings not to allow (Songs & Signs for example) * @return An integer list containing just the wanted indexes */ static List listSubtitles(String[] list, String lang, String[] illegal) { List subtitles = listIndexes(list, (string) -> string.contains("codec_type=subtitle") && string.contains("language=" + lang)); List notWanted = listIndexes(list, (string) -> { for (String s : illegal) { if (string.contains(s)) { return true; } } return false; }); subtitles.removeAll(notWanted); return subtitles; } /** * Lists all subtitle streams of a certain language. * * @param list A list of ffprobe indexes * @param lang The wanted language * @return An integer list containing just the wanted indexes */ static List listSubtitles(String[] list, String lang) { return listIndexes(list, (string) -> string.contains("codec_type=subtitle") && string.contains("language=" + lang)); } /** * Lists all subtitle indexes for video. * * @param list A list of ffprobe indexes * @return An integer list containing just the wanted indexes */ static List listSubtitles(String[] list) { return listIndexes(list, (string) -> string.contains("codec_type=subtitle")); } /** * Lists all subtitle streams of a certain language relatively to the number of subtitle streams. * 0-based. * Filters out all indexes containing anything in illegal * * @param list A list of ffprobe indexes * @param lang The wanted language * @param illegal A list of strings not to allow (Songs & Signs for example) * @return An integer list containing just the wanted indexes */ static List listSubtitlesRelative(String[] list, String lang, String[] illegal) { List relative = listSubtitlesRelative(list, lang); List subtitles = listSubtitles(list, lang); List notWanted = new ArrayList<>(); for (int i = 0; i < relative.size(); i++) { for (String ill : illegal) { if (list[subtitles.get(i)].contains(ill)) { notWanted.add(i); } } } relative.removeAll(notWanted); return relative; } /** * Lists all subtitle streams of a certain language relatively to the number of subtitle streams. * 0-based. * * @param list A list of ffprobe indexes * @param lang The wanted language * @return An integer list containing just the wanted indexes */ static List listSubtitlesRelative(String[] list, String lang) { list = subList(list, (string) -> string.contains("codec_type=subtitle")); List wanted = new ArrayList<>(); for (int i = 0; i < list.length; i++) { if (list[i].contains("language=" + lang)) { wanted.add(i); } } return wanted; } /** * Lists all subtitle streams relatively to the number of subtitle streams. * 0-based. * * @param list A list of ffprobe indexes * @return An integer list containing just the wanted indexes */ static List listSubtitlesRelative(String[] list) { list = subList(list, (string) -> string.contains("codec_type=subtitle")); List wanted = new ArrayList<>(); for (int i = 0; i < list.length; i++) { wanted.add(i); } return wanted; } /** * Lists all indexes fulfilling a predicate. * * @param list A list of ffprobe indexes * @return An integer list containing just the wanted indexes */ private static List listIndexes(String[] list, Predicate p) { List indexes = new ArrayList<>(); for (String str : list) { if (p.test(str)) { indexes.add(Integer.parseInt(stringBetweenSingle(str, "index=", " "))); } } return indexes; } /** * Returns a new list of the string for which the predicate is true. * * @param list A list of ffprobe indexes * @param p The predicate to test * @return A sublist of the original list */ private static String[] subList(String[] list, Predicate p) { List indexes = new ArrayList<>(); for (String str : list) { if (p.test(str)) { indexes.add(str); } } return indexes.toArray(new String[]{}); } /** * Finds all substrings between two substrings in a string. * * @param string The string containing the substrings * @param start The substring before the wanted substring * @param end The substring after the wanted substring * @return A list of all occurrences of the substring */ static String[] stringBetween(String string, String start, String end) { int startPos = string.indexOf(start) + start.length(); if (!string.contains(start) || string.indexOf(end, startPos) < startPos) { return new String[]{}; } int endPos = string.indexOf(end, startPos); String outString = string.substring(startPos, endPos).trim(); String nextString = string.substring(endPos + end.length()); return concatenate(new String[]{outString}, stringBetween(nextString, start, end)); } /** * Finds a substring between two substrings in a string. * * @param string The string containing the substrings * @param start The substring before the wanted substring * @param end The substring after the wanted substring * @return The wanted substring. */ private static String stringBetweenSingle(String string, String start, String end) { int startPos = string.indexOf(start) + start.length(); if (!string.contains(start) || string.indexOf(end, startPos) < startPos) { return ""; } return string.substring(startPos, string.indexOf(end, startPos)); } static String stripExtension(File file) { return file.getName().substring(0, file.getName().lastIndexOf('.')); } static String stripExtension(String file) { return file.substring(0, file.lastIndexOf('.')); } /** * Combines two arrays to one * * @param a The first array * @param b The second array * @param Any type * @return A new array containing all elements from the two arrays */ public static T[] concatenate(T[] a, T[] b) { int aLen = a.length; int bLen = b.length; @SuppressWarnings("unchecked") T[] c = (T[]) Array.newInstance(a.getClass().getComponentType(), aLen + bLen); System.arraycopy(a, 0, c, 0, aLen); System.arraycopy(b, 0, c, aLen, bLen); return c; } }