diff --git a/.idea/FFmpegConvert1.iml b/.idea/FFmpegConvert1.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/.idea/FFmpegConvert1.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f091b54 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/modules.xml b/.idea/modules.xml similarity index 52% rename from modules.xml rename to .idea/modules.xml index 96cef43..20f6263 100644 --- a/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MANIFEST.MF b/META-INF/MANIFEST.MF similarity index 94% rename from MANIFEST.MF rename to META-INF/MANIFEST.MF index cf70b04..079a349 100644 --- a/MANIFEST.MF +++ b/META-INF/MANIFEST.MF @@ -1,3 +1,3 @@ -Manifest-Version: 1.0 -Main-Class: ffmpegconverter.Main - +Manifest-Version: 1.0 +Main-Class: ffmpegconverter.Main + diff --git a/misc.xml b/misc.xml deleted file mode 100644 index bb91d91..0000000 --- a/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/ffmpegconverter/Main.java b/src/ffmpegconverter/Main.java new file mode 100644 index 0000000..9e7beb9 --- /dev/null +++ b/src/ffmpegconverter/Main.java @@ -0,0 +1,190 @@ +package ffmpegconverter; + +import ffmpegconverter.converter.AnimeConverter; +import ffmpegconverter.converter.AudioConverter; +import ffmpegconverter.converter.Converter; +import ffmpegconverter.converter.VideoConverter; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.function.Predicate; + +/** + * Converts a files or files in a folder to a web playable mp4. + */ +public class Main { + 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 Scanner in = new Scanner(System.in); + private static Converter con = null; + + public static void main(String[] args) throws IOException { + int choice = getChoice("Which converter do you want do use?\n1. Anime to web mp4\n2. Audio converter\n3. Video converter", 1, 3); + + System.out.println("Input for this converter:"); + switch (choice) { + case 1: + animeConverter(); + break; + case 2: + con = new AudioConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("")); + break; + case 3: + con = new VideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("")); + break; + default: + System.exit(1); + } + + int recursionSteps = 1; + + System.out.println(" [Recursions]"); + List input = readInput(2); + while (input.size() == 0) { + System.out.print("File path required."); + input = readInput(2); + } + File folder = new File(input.get(0)); + if (input.size() > 1) { + try { + recursionSteps = Integer.parseInt(input.get(1)); + } catch (NumberFormatException e) { + System.out.println("Recursion steps is invalid and will be ignored."); + } + } + + if (folder.isDirectory()) { + File[] files = listFilesRec(folder, con.getValidFormats(), recursionSteps); + if (files != null && files.length > 0) { + for (File file : files) { + con.convert(file); + } + } else { + System.out.println("No valid files found in folder."); + } + } else { + con.convert(folder); + } + } + + private static void animeConverter() { + System.out.println("[Audio languages jap,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stareo if necessary true/false] [Prevent signs&songs subtitles true/false]\nYour input: "); + List input = readInput(4); + String[] audioLang = new String[]{"jpn"}; + String[] subtitleLang = new String[]{"eng"}; + boolean toStereo = true; + boolean preventSigns = true; + if (input.size() > 0 && getList(input, 0) != null) { + audioLang = getList(input, 0); + } + if (input.size() > 1 && getList(input, 1) != null) { + subtitleLang = getList(input, 1); + } + if (input.size() > 2) { + toStereo = Boolean.parseBoolean(input.get(2)); + } + if (input.size() > 3) { + preventSigns = Boolean.parseBoolean(input.get(3)); + } + con = new AnimeConverter(FFPROBE_PATH, FFMPEG_PATH, audioLang, subtitleLang, toStereo, preventSigns); + } + + private static String[] getList(List list, int index) { + String[] result = null; + if (list.size() > index) { + if (list.get(index).contains(",")) { + result = list.get(index).split(","); + } else { + result = new String[]{list.get(index)}; + } + } + return result; + } + + private static List readInput(int max) { + List input = new ArrayList<>(); + String rx = "[^\"\\s]+|\"(\\\\.|[^\\\\\"])*\""; + for (int i = 0; i < max; i++) { + String line = in.findInLine(rx); + if (line != null) { + if (line.startsWith("\"") && line.endsWith("\"")) { + input.add(line.substring(1, line.length() - 1)); + } else { + input.add(line); + } + } + } + in.nextLine(); + return input; + } + + private static String getChoice(String prompt) { + System.out.println(prompt); + String choice = ""; + while (choice.equals("")) { + System.out.println("Your input: "); + choice = in.nextLine(); + } + return choice; + } + + private static int getChoice(String prompt, int min, int max) { + System.out.println(prompt); + int choice = 0; + while (choice < min || choice > max) { + System.out.println("Your input: "); + try { + choice = Integer.parseInt(in.next()); + } catch (NumberFormatException e) { + System.out.println("Invalid choice. Please try again."); + } finally { + in.nextLine(); + } + } + return choice; + } + + /** + * Tests if any element in a list fulfills a condition. + * + * @param list The list to test against + * @param predicate A predicate to use on every element in the list + * @param Anything which can be stored in a list + * @return + */ + private static boolean listContains(T[] list, Predicate predicate) { + for (T item : list) { + if (predicate.test(item)) { + return true; + } + } + return false; + } + + /** + * Recursively lists all files in a folder + * + * @param folder The folder to start from + * @param maxRec Maximum number of recursions + * @return A list of files + */ + private static File[] listFilesRec(File folder, String[] extensions, int maxRec) { + if (maxRec == 0) { return null; } + File[] listOfFiles = folder.listFiles((file) -> file.isFile() && listContains(extensions, (item) -> file.getName().endsWith(item))); + if (listOfFiles == null) { return null; } + if (maxRec > 1) { + File[] listOfFolders = folder.listFiles((dir, name) -> new File(dir, name).isDirectory()); + if (listOfFolders != null) { + for (File file : listOfFolders) { + File[] nextLevel = listFilesRec(file, extensions, maxRec - 1); + if (nextLevel != null) { + listOfFiles = Converter.concatenate(listOfFiles, nextLevel); + } + } + } + } + return listOfFiles; + } +} diff --git a/src/ffmpegconverter/converter/AnimeConverter.java b/src/ffmpegconverter/converter/AnimeConverter.java new file mode 100644 index 0000000..1834de5 --- /dev/null +++ b/src/ffmpegconverter/converter/AnimeConverter.java @@ -0,0 +1,126 @@ +package ffmpegconverter.converter; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class AnimeConverter extends Converter { + private String[] audioLang; + private String[] subtitleLang; + private boolean toStereo; + private boolean preventSignsAndSongs; + + /** + * @param ffprobePath Path/command to ffprobe + * @param ffmpegPath Path/command to ffmpeg + * @param audioLang List of wanted audio languages in descending order + * @param subtitleLang List of wanted subtitle languages in descending order + * @param toStereo Convert video with several audio channels to stereo + * @param preventSignsAndSongs Prevent subtitles only converting signs and songs (not speech) + */ + public AnimeConverter(String ffprobePath, String ffmpegPath, String[] audioLang, String[] subtitleLang, boolean toStereo, boolean preventSignsAndSongs) { + this.ffprobePath = ffprobePath; + this.ffmpegPath = ffmpegPath; + this.audioLang = audioLang; + this.subtitleLang = subtitleLang; + this.toStereo = toStereo; + this.preventSignsAndSongs = preventSignsAndSongs; + } + + public void convert(File file) throws IOException { + processFile(file.getParentFile(), file); + } + + /** + * Reads streams from a file, and converts it to an mp4. + * + * @param folder The folder of the file to process + * @param file The file to process + * @throws IOException If the BufferedReader fails + */ + private void processFile(File folder, File file) throws IOException { + String[] streams = probeFile(ffprobePath, file); + if (streams.length == 0) { + throw new IllegalArgumentException("The file has no streams"); + } + String noExt = file.getName().substring(0, file.getName().lastIndexOf('.')); + String newPath = noExt + ".mp4"; + boolean isMP4 = newPath.equals(file.getName()); + if (isMP4) { + newPath = noExt + ".tmp.mp4"; + } + convertProcess(new ProcessBuilder(builderCommand(ffmpegPath, file.getName(), streams, newPath)), folder); + if (isMP4) { + File oldFile = new File(newPath); + if (!oldFile.renameTo(new File(noExt + ".mp4"))) { + System.out.println("Failed to move converted file."); + } + } + } + + /** + * Generates a command for a ProcessBuilder. + * + * @param executable The executable file for ffmpeg + * @param fileName The input file + * @param streams A list of ffprobe streams + * @param outFile The output file + * @return A list of commands + */ + private String[] builderCommand(String executable, String fileName, String[] streams, String outFile) { + List command = ffmpegWebVideo(executable, fileName); + for (String lang : audioLang) { + List audioStreams = listAudio(streams, lang); + if (audioStreams.size() > 0) { + command.add("-map"); + command.add("0:" + audioStreams.get(0)); + String[] channels = stringBetween(streams[audioStreams.get(0)], "channels=", " "); + if (toStereo && channels.length > 0 && Integer.parseInt(channels[0]) > 2) { + command.add("-af"); + command.add("pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR"); + } + break; + } else { + audioStreams = listAudio(streams); + if (audioStreams.size() > 0) { + command.add("-map"); + command.add("0:" + audioStreams.get(0)); + } + } + } + addSubtitles(streams, listVideo(streams), command, fileName); + command.add(outFile); + return command.toArray(new String[0]); + } + + private void addSubtitles(String[] streams, List videoStreams, List command, String fileName) { + for (String lang : subtitleLang) { + List subtitleStreams = listSubtitles(streams, lang); + if (subtitleStreams.size() > 0 && videoStreams.size() > 0 && isImageSub(streams, subtitleStreams.get(0))) { + command.add("-filter_complex"); + command.add("[0:v:" + listVideo(streams).get(0) + "][0:" + subtitleStreams.get(0) + "]overlay"); + break; + } else if (subtitleStreams.size() > 0) { + if (videoStreams.size() > 0) { + command.add("-map"); + command.add("0:" + listVideo(streams).get(0)); + } + if (preventSignsAndSongs) { + subtitleStreams = listSubtitlesRelative(streams, lang, new String[]{"title=Signs"}); + } else { + subtitleStreams = listSubtitlesRelative(streams, lang); + } + if (subtitleStreams.size() > 0) { + command.add("-vf"); + command.add("subtitles='" + fileName.replace("'", "\'") + "':si=" + subtitleStreams.get(0)); + } + break; + } + } + } + + @Override + public String[] getValidFormats() { + return VIDEO_FORMATS; + } +} diff --git a/src/ffmpegconverter/converter/AudioConverter.java b/src/ffmpegconverter/converter/AudioConverter.java new file mode 100644 index 0000000..d13e970 --- /dev/null +++ b/src/ffmpegconverter/converter/AudioConverter.java @@ -0,0 +1,180 @@ +package ffmpegconverter.converter; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +public class AudioConverter extends Converter { + private String newExt; + + public AudioConverter(String ffprobePath, String ffmpegPath, String newExt) { + this.ffprobePath = ffprobePath; + this.ffmpegPath = ffmpegPath; + this.newExt = newExt; + } + + /** + * Reads streams from a file, and converts it to an mp4. + * + * @param folder The folder of the file to process + * @param file The file to process + * @throws IOException If the BufferedReader fails + */ + private void processFile(File folder, File file, String newExt) throws IOException { + String[] streams = probeFile(ffprobePath, file); + if (streams.length == 0) { + throw new IllegalArgumentException("The file has no streams"); + } + String newPath = stripExtension(file) + "." + newExt; + convertProcess(new ProcessBuilder(builderCommand(ffmpegPath, file.getName(), streams, newPath)), folder); + } + + /** + * Generates a command for a ProcessBuilder. + * + * @param executable The executable file for ffmpeg + * @param fileName The input file + * @param streams A list of ffprobe streams + * @param outFile The output file + * @return A list of commands + */ + private String[] builderCommand(String executable, String fileName, String[] streams, String outFile) { + List command = generalFile(executable, fileName); + List audioStreams = listAudio(streams); + if (audioStreams.size() > 0) { + command.add("-map"); + command.add("0:" + audioStreams.get(0)); + } + command.add(outFile); + return command.toArray(new String[0]); + } + + @Override + public String[] getValidFormats() { + return AUDIO_FORMATS; + } + + @Override + public void convert(File file) throws IOException { + processFile(file.getParentFile(), file, newExt); + } + public class StringList implements List { + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public Iterator iterator() { + return null; + } + + @Override + public Object[] toArray() { + return new Object[0]; + } + + @Override + public T[] toArray(T[] a) { + return null; + } + + @Override + public boolean add(String string) { + return false; + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public boolean addAll(Collection c) { + return false; + } + + @Override + public boolean addAll(int index, Collection c) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public void clear() { + + } + + @Override + public String get(int index) { + return null; + } + + @Override + public String set(int index, String element) { + return null; + } + + @Override + public void add(int index, String element) { + + } + + @Override + public String remove(int index) { + return null; + } + + @Override + public int indexOf(Object o) { + return 0; + } + + @Override + public int lastIndexOf(Object o) { + return 0; + } + + @Override + public ListIterator listIterator() { + return null; + } + + @Override + public ListIterator listIterator(int index) { + return null; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return null; + } + } +} \ No newline at end of file diff --git a/src/ffmpegconverter/converter/Converter.java b/src/ffmpegconverter/converter/Converter.java new file mode 100644 index 0000000..1a7ffe8 --- /dev/null +++ b/src/ffmpegconverter/converter/Converter.java @@ -0,0 +1,302 @@ +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 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; + } + + /** + * 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"); + } + + /** + * 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. + * + * @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 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(), string.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('.')); + } + + /** + * 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; + } +} diff --git a/src/ffmpegconverter/converter/VideoConverter.java b/src/ffmpegconverter/converter/VideoConverter.java new file mode 100644 index 0000000..54bd95d --- /dev/null +++ b/src/ffmpegconverter/converter/VideoConverter.java @@ -0,0 +1,66 @@ +package ffmpegconverter.converter; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class VideoConverter extends Converter { + private String newExt; + + public VideoConverter(String ffprobePath, String ffmpegPath, String newExt) { + this.ffprobePath = ffprobePath; + this.ffmpegPath = ffmpegPath; + this.newExt = newExt; + } + + /** + * Reads streams from a file, and converts it to an mp4. + * + * @param folder The folder of the file to process + * @param file The file to process + * @throws IOException If the BufferedReader fails + */ + private void processFile(File folder, File file, String newExt) throws IOException { + String[] streams = probeFile(ffprobePath, file); + if (streams.length == 0) { + throw new IllegalArgumentException("The file has no streams"); + } + String newPath = stripExtension(file) + Math.random() + "." + newExt; + convertProcess(new ProcessBuilder(builderCommand(ffmpegPath, file.getName(), streams, newPath)), folder); + } + + /** + * Generates a command for a ProcessBuilder. + * + * @param executable The executable file for ffmpeg + * @param fileName The input file + * @param streams A list of ffprobe streams + * @param outFile The output file + * @return A list of commands + */ + private String[] builderCommand(String executable, String fileName, String[] streams, String outFile) { + List command = generalFile(executable, fileName); + List videoStreams = listVideo(streams); + if (videoStreams.size() > 0) { + command.add("-map"); + command.add("0:" + videoStreams.get(0)); + } + List audioStreams = listAudio(streams); + if (audioStreams.size() > 0) { + command.add("-map"); + command.add("0:" + audioStreams.get(0)); + } + command.add(outFile); + return command.toArray(new String[0]); + } + + @Override + public String[] getValidFormats() { + return VIDEO_FORMATS; + } + + @Override + public void convert(File file) throws IOException { + processFile(file.getParentFile(), file, newExt); + } +} \ No newline at end of file diff --git a/uiDesigner.xml b/uiDesigner.xml deleted file mode 100644 index e96534f..0000000 --- a/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file