Moves stuff around
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				KnarCraft/FFmpegConvert/master There was a failure building this commit
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	KnarCraft/FFmpegConvert/master There was a failure building this commit
				
			Fixes packages for use with maven Updates pom Fixes broken link in licence Updates Jenkinsfile
This commit is contained in:
		
							
								
								
									
										352
									
								
								src/main/java/ffmpegconverter/Main.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								src/main/java/ffmpegconverter/Main.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,352 @@ | ||||
| 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 final Scanner READER = new Scanner(System.in, "UTF-8"); | ||||
|     private static final BufferedWriter WRITER = new BufferedWriter(new OutputStreamWriter(System.out)); | ||||
|     private static Converter con = null; | ||||
|  | ||||
|     public static void main(String[] args) throws IOException { | ||||
|         //System.out.println(tokenizer("AnimeConverter -audiolang jap,eng -sublang eng,nor,* \"C:\\Users\\Kristian\\Downloads\\Anime\\[Kametsu] ERASED (BD 1080p Hi10 FLAC)\"")); | ||||
|         //parser(tokenizer("AnimeConverter \"C:\\Users\\Kristian\\Downloads\\Anime\\[Kametsu] ERASED (BD 1080p Hi10 FLAC)\"")); | ||||
|         //System.exit(1); | ||||
|  | ||||
|         int choice = getChoice("Which converter do you want do use?\n1. Anime to web mp4\n2. Audio converter\n3. VideoStream converter", 1, 3); | ||||
|  | ||||
|         printl("Input for this converter:"); | ||||
|         switch (choice) { | ||||
|             case 1: | ||||
|                 animeConverter(); | ||||
|                 break; | ||||
|             case 2: | ||||
|                 con = new AudioConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>")); | ||||
|                 break; | ||||
|             case 3: | ||||
|                 con = new VideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>")); | ||||
|                 break; | ||||
|             default: | ||||
|                 System.exit(1); | ||||
|         } | ||||
|  | ||||
|         int recursionSteps = 1; | ||||
|  | ||||
|         printl("<Folder/File> [Recursions]: "); | ||||
|         List<String> input = readInput(2); | ||||
|         while (input.isEmpty()) { | ||||
|             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) { | ||||
|                 printl("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 { | ||||
|                 printl("No valid files found in folder."); | ||||
|             } | ||||
|         } else if (folder.exists()) { | ||||
|             con.convert(folder); | ||||
|         } else { | ||||
|             System.out.println("Path " + folder.getAbsolutePath() + " does not point to any file or folder."); | ||||
|         } | ||||
|         WRITER.close(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Prints a string | ||||
|      * @param input <p>The string to print.</p> | ||||
|      * @throws IOException <p>If the writer fails to write.</p> | ||||
|      */ | ||||
|     private static void print(String input) throws IOException { | ||||
|        WRITER.write(input); | ||||
|        WRITER.flush(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Prints a string and a line break | ||||
|      * @param input <p>The string to print.</p> | ||||
|      * @throws IOException <p>If the writer fails to write.</p> | ||||
|      */ | ||||
|     private static void printl(String input) throws IOException { | ||||
|        WRITER.write(input); | ||||
|        WRITER.newLine(); | ||||
|        WRITER.flush(); | ||||
|     } | ||||
|  | ||||
|     private enum converterArgumentValueType { | ||||
|         BOOLEAN, | ||||
|         COMMA_SEPARATED_LIST, | ||||
|         SINGLE_VALUE, | ||||
|         INT | ||||
|     } | ||||
|  | ||||
|     private static class converterArgument { | ||||
|         private String name; | ||||
|         private boolean valueRequired; | ||||
|         private converterArgumentValueType valueType; | ||||
|         private converterArgument(String name, boolean valueRequired, converterArgumentValueType valueType) { | ||||
|             this.name = name; | ||||
|             this.valueRequired = valueRequired; | ||||
|             this.valueType = valueType; | ||||
|         } | ||||
|  | ||||
|         private boolean testArgumentValue(String value) { | ||||
|             if (value.length() == 0) { | ||||
|                 return !valueRequired; | ||||
|             } | ||||
|             if (valueRequired && value.startsWith("-")) { | ||||
|                 return false; | ||||
|             } | ||||
|             switch (valueType) { | ||||
|                 case BOOLEAN: | ||||
|                     String lower = value.toLowerCase(); | ||||
|                     return lower.equals("true") || lower.equals("false"); | ||||
|                 case COMMA_SEPARATED_LIST: | ||||
|                     return !value.contains(" "); | ||||
|                 case SINGLE_VALUE: | ||||
|                     return !value.contains(" "); | ||||
|                 case INT: | ||||
|                     int ignored = Integer.parseInt(value); | ||||
|                     return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void parser(List<String> tokens) { | ||||
|         String[] types = {"animeconverter", "audioconverter", "videoconverter"}; | ||||
|         converterArgument[] commonArgs = { | ||||
|                 new converterArgument("-recursions", true, converterArgumentValueType.INT) | ||||
|         }; | ||||
|         converterArgument[] animeArgs = { | ||||
|  | ||||
|         }; | ||||
|         converterArgument[] audioArgs = { | ||||
|                 new converterArgument("-outext", true, converterArgumentValueType.SINGLE_VALUE) | ||||
|         }; | ||||
|         converterArgument[] videoArgs = { | ||||
|                 new converterArgument("-outext", true, converterArgumentValueType.SINGLE_VALUE) | ||||
|         }; | ||||
|         String type = tokens.get(0).toLowerCase(); | ||||
|         if (!listContains(types, s -> s.equals(type))) { | ||||
|             throw new IllegalArgumentException("Unknown converter type chosen."); | ||||
|         } | ||||
|         if (tokens.size() < 2) { | ||||
|             throw new IllegalArgumentException("No file/folder path in argument."); | ||||
|         } | ||||
|         for (int i = 1; i < tokens.size() - 1; i++) { | ||||
|             //TODO: Find the type of argument and check the value | ||||
|             //TODO: Find an executable way to represent the chain of commands parsed | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Tokenizes a string | ||||
|      * @param input <p>A string.</p> | ||||
|      * @return <p>A list of tokens.</p> | ||||
|      */ | ||||
|     private static List<String> tokenizer(String input) { | ||||
|         List<String> tokens = new ArrayList<>(); | ||||
|         boolean startedQuote = false; | ||||
|         StringBuilder currentToken = new StringBuilder(); | ||||
|         for (int i = 0; i < input.length(); i++) { | ||||
|             char c = input.charAt(i); | ||||
|             switch (c) { | ||||
|                 case ' ': | ||||
|                     if (!startedQuote) { | ||||
|                         //If not inside "", a space marks the end of a parameter | ||||
|                         if (!currentToken.toString().trim().equals("")) { | ||||
|                             tokens.add(currentToken.toString()); | ||||
|                             currentToken = new StringBuilder(); | ||||
|                         } else { | ||||
|                             currentToken = new StringBuilder(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         currentToken.append(c); | ||||
|                     } | ||||
|                     break; | ||||
|                 case '"': | ||||
|                     if (startedQuote) { | ||||
|                         if (!currentToken.toString().trim().equals("")) { | ||||
|                             tokens.add(currentToken.toString()); | ||||
|                             currentToken = new StringBuilder(); | ||||
|                         } | ||||
|                         startedQuote = false; | ||||
|                     } else { | ||||
|                         startedQuote = true; | ||||
|                         currentToken = new StringBuilder(); | ||||
|                     }   break; | ||||
|                 default: | ||||
|                     currentToken.append(c); | ||||
|                     if (i == input.length() - 1) { | ||||
|                         tokens.add(currentToken.toString()); | ||||
|                     }   break; | ||||
|             } | ||||
|         } | ||||
|         return tokens; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initializes the anime converter | ||||
|      * @throws IOException <p>If reading or writing fails.</p> | ||||
|      */ | ||||
|     private static void animeConverter() throws IOException { | ||||
|         printl("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stereo if necessary true/false] [Prevent signs&songs subtitles true/false]\nYour input: "); | ||||
|         List<String> 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); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets a list from a comma separated string at index in list | ||||
|      * @param list <p>A list of tokens.</p> | ||||
|      * @param index <p>The index of the token containing comma separated entries.</p> | ||||
|      * @return <p>A string list.</p> | ||||
|      */ | ||||
|     private static String[] getList(List<String> 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; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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> | ||||
|      */ | ||||
|     private static List<String> readInput(int max) { | ||||
|         List<String> tokens =  tokenizer(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> | ||||
|      * @throws IOException <p>If reading or writing fails.</p> | ||||
|      */ | ||||
|     private static String getChoice(String prompt) throws IOException { | ||||
|         printl(prompt); | ||||
|         String choice = ""; | ||||
|         while (choice.equals("")) { | ||||
|             printl("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(String prompt, int min, int max) throws IOException { | ||||
|         printl(prompt); | ||||
|         int choice = 0; | ||||
|         while (choice < min || choice > max) { | ||||
|             printl("Your input: "); | ||||
|             try { | ||||
|                 choice = Integer.parseInt(READER.next()); | ||||
|             } catch (NumberFormatException e) { | ||||
|                 printl("Invalid choice. Please try again."); | ||||
|             } finally { | ||||
|                 READER.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 <T>       Anything which can be stored in a list | ||||
|      * @return          True if at least one element fulfills the predicate | ||||
|      */ | ||||
|     private static <T> boolean listContains(T[] list, Predicate<T> 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										130
									
								
								src/main/java/ffmpegconverter/converter/AnimeConverter.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/main/java/ffmpegconverter/converter/AnimeConverter.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| package ffmpegconverter.converter; | ||||
|  | ||||
| import ffmpegconverter.streams.AudioStream; | ||||
| import ffmpegconverter.streams.StreamObject; | ||||
| import ffmpegconverter.streams.SubtitleStream; | ||||
| import ffmpegconverter.streams.VideoStream; | ||||
|  | ||||
| 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; | ||||
|     private boolean debug = false; | ||||
|  | ||||
|     /** | ||||
|      * @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 { | ||||
|         List<StreamObject> streams = probeFile(ffprobePath, file); | ||||
|         if (streams.isEmpty()) { | ||||
|             throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and is not corrupt."); | ||||
|         } | ||||
|         String newPath = fileCollisionPrevention(folder.getAbsolutePath() + File.separator + stripExtension(file) + ".mp4", "mp4"); | ||||
|         printl("Preparing to start process..."); | ||||
|         String[] command = builderCommand(ffmpegPath, file.getName(), streams, newPath); | ||||
|         ProcessBuilder processBuilder = new ProcessBuilder(command); | ||||
|         convertProcess(processBuilder, 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, List<StreamObject> streams, String outFile) { | ||||
|         List<String> command = ffmpegWebVideo(executable, fileName); | ||||
|  | ||||
|         if (this.debug) { | ||||
|             addDebug(command, 50, 120); | ||||
|         } | ||||
|  | ||||
|         List<AudioStream> audioStreams = filterStreamsByType(streams, "audio"); | ||||
|         List<VideoStream> videoStreams = filterStreamsByType(streams, "video"); | ||||
|         List<SubtitleStream> subtitleStreams = filterStreamsByType(streams, "subtitle"); | ||||
|  | ||||
|         audioStreams = filterAudioStreams(audioStreams, audioLang); | ||||
|         subtitleStreams = filterSubtitleStreams(subtitleStreams, subtitleLang, preventSignsAndSongs); | ||||
|  | ||||
|         VideoStream videoStream = null; | ||||
|         AudioStream audioStream = null; | ||||
|         SubtitleStream subtitleStream = null; | ||||
|         if (videoStreams.size() > 0) { | ||||
|             videoStream = videoStreams.get(0); | ||||
|         } | ||||
|         if (audioStreams.size() > 0) { | ||||
|             audioStream = audioStreams.get(0); | ||||
|         } | ||||
|         if (subtitleStreams.size() > 0) { | ||||
|             subtitleStream = subtitleStreams.get(0); | ||||
|         } | ||||
|  | ||||
|         if (videoStream == null) { | ||||
|             throw new IllegalArgumentException("The file does not have any valid video streams."); | ||||
|         } | ||||
|  | ||||
|         if (audioStream != null) { | ||||
|             command.add("-map"); | ||||
|             command.add("0:" + audioStream.getAbsoluteIndex()); | ||||
|             if (toStereo && audioStream.getChannels() > 2) { | ||||
|                 command.add("-af"); | ||||
|                 command.add("pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (subtitleStream != null && subtitleStream.getIsImageSubtitle()) { | ||||
|             command.add("-filter_complex"); | ||||
|             command.add("[0:v:" + videoStream.getAbsoluteIndex() + "][0:" + subtitleStream.getAbsoluteIndex() + "]overlay"); | ||||
|         } else if (subtitleStream != null) { | ||||
|             command.add("-map"); | ||||
|             command.add("0:" + videoStream.getAbsoluteIndex()); | ||||
|             command.add("-vf"); | ||||
|             command.add("subtitles='" + fileName.replace("'", "\'") + "':si=" + | ||||
|                     subtitleStream.getRelativeIndex()); | ||||
|         } else { | ||||
|             command.add("-map"); | ||||
|             command.add("0:" + videoStream.getAbsoluteIndex()); | ||||
|         } | ||||
|  | ||||
|         command.add(outFile); | ||||
|         return command.toArray(new String[0]); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String[] getValidFormats() { | ||||
|         return VIDEO_FORMATS; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										68
									
								
								src/main/java/ffmpegconverter/converter/AudioConverter.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/main/java/ffmpegconverter/converter/AudioConverter.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| package ffmpegconverter.converter; | ||||
|  | ||||
| import ffmpegconverter.streams.AudioStream; | ||||
| import ffmpegconverter.streams.StreamObject; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
|  | ||||
| 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 { | ||||
|         List<StreamObject> streams = probeFile(ffprobePath, file); | ||||
|         if (streams.size() == 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, List<StreamObject> streams, String outFile) { | ||||
|         List<String> command = generalFile(executable, fileName); | ||||
|         List<AudioStream> audioStreams = filterStreamsByType(streams, "audio"); | ||||
|         AudioStream audioStream = null; | ||||
|         if (audioStreams.size() > 0) { | ||||
|             audioStream = audioStreams.get(0); | ||||
|         } | ||||
|         if (audioStreams.size() > 0) { | ||||
|             command.add("-map"); | ||||
|             command.add("0:" + audioStream.getAbsoluteIndex()); | ||||
|         } | ||||
|         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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										441
									
								
								src/main/java/ffmpegconverter/converter/Converter.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										441
									
								
								src/main/java/ffmpegconverter/converter/Converter.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,441 @@ | ||||
| package ffmpegconverter.converter; | ||||
|  | ||||
| import ffmpegconverter.streams.AudioStream; | ||||
| import ffmpegconverter.streams.StreamObject; | ||||
| import ffmpegconverter.streams.SubtitleStream; | ||||
| import ffmpegconverter.streams.VideoStream; | ||||
|  | ||||
| import java.io.BufferedReader; | ||||
| import java.io.BufferedWriter; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.OutputStreamWriter; | ||||
| import java.lang.reflect.Array; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Iterator; | ||||
| 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; | ||||
|      | ||||
|     private static final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); | ||||
|  | ||||
|     private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå"; | ||||
|  | ||||
|     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"}; | ||||
|  | ||||
|     /** | ||||
|      * Gets streams from a file | ||||
|      * @param ffprobePath The path/command to ffprobe | ||||
|      * @param file The file to probe | ||||
|      * @return A list of StreamObjects | ||||
|      * @throws IOException If the process can't be read | ||||
|      */ | ||||
|     static List<StreamObject> 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() | ||||
|         ); | ||||
|         print("Probe command: "); | ||||
|         printl(builderProbe.command().toString()); | ||||
|         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, PROBE_SPLIT_CHARACTER); | ||||
|             if (!read.equals("")) { | ||||
|                 print(read); | ||||
|                 output.append(read); | ||||
|             } | ||||
|         } | ||||
|         return parseStreams(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(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Starts and prints output of a process | ||||
|      * @param process The process to run | ||||
|      * @param folder The folder the process should run in | ||||
|      * @throws IOException If the process can't be read | ||||
|      */ | ||||
|     static void convertProcess(ProcessBuilder process, File folder) throws IOException { | ||||
|         print("Command to be run: "); | ||||
|         printl(process.command().toString()); | ||||
|         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("")) { | ||||
|                 printl(read); | ||||
|             } | ||||
|         } | ||||
|         printl("FFMPEG is finished."); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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<String> ffmpegWebVideo(String executable, String fileName) { | ||||
|         List<String> 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<String> generalFile(String executable, String fileName) { | ||||
|         List<String> command = new ArrayList<>(); | ||||
|         command.add(executable); | ||||
|         command.add("-nostdin"); | ||||
|         command.add("-i"); | ||||
|         command.add(fileName); | ||||
|         return command; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds debugging parameters for only converting parts of a file | ||||
|      * @param command The list containing the command to run | ||||
|      * @param start The offset before converting | ||||
|      * @param length The offset for stopping the conversion | ||||
|      */ | ||||
|     static void addDebug(List<String> command, int start, int length) { | ||||
|         command.add("-ss"); | ||||
|         command.add("" + start); | ||||
|         command.add("-t"); | ||||
|         command.add("" + length); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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<Integer> listIndexes(String[] list, Predicate<String> p) { | ||||
|         List<Integer> indexes = new ArrayList<>(); | ||||
|         for (String str : list) { | ||||
|             if (p.test(str)) { | ||||
|                 indexes.add(Integer.parseInt(stringBetweenSingle(str, "index=", PROBE_SPLIT_CHARACTER))); | ||||
|             } | ||||
|         } | ||||
|         return indexes; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Tests a predicate on a list | ||||
|      * @param list A list | ||||
|      * @param p A predicate | ||||
|      * @param <T> Any type | ||||
|      * @return True if the list have an element for which the predicate is true | ||||
|      */ | ||||
|     private static <T> boolean testPredicate(T[] list, Predicate<T> p) { | ||||
|         for (T o : list) { | ||||
|             if (p.test(o)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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 | ||||
|      */ | ||||
|     private 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)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets filename without extension from File object | ||||
|      * @param file A file object | ||||
|      * @return A filename | ||||
|      */ | ||||
|     static String stripExtension(File file) { | ||||
|         return file.getName().substring(0, file.getName().lastIndexOf('.')); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Removes the extension from a file name | ||||
|      * @param file A filename | ||||
|      * @return A filename without its extension | ||||
|      */ | ||||
|     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 <T>   Any type | ||||
|      * @return      A new array containing all elements from the two arrays | ||||
|      */ | ||||
|     public static <T> 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; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Filters parsed streams into one of the stream types | ||||
|      * @param streams A list of stream objects | ||||
|      * @param codecType The codec type of the streams to select | ||||
|      * @param <G> The correct object type for the streams with the selected codec type | ||||
|      * @return A potentially shorter list of streams | ||||
|      */ | ||||
|     static <G extends StreamObject> List<G> filterStreamsByType(List<StreamObject> streams, String codecType) { | ||||
|         Iterator<StreamObject> i = streams.iterator(); | ||||
|         List<G> newStreams = new ArrayList<>(); | ||||
|         while (i.hasNext()) { | ||||
|             StreamObject next = i.next(); | ||||
|             if (next.getCodecType().equals(codecType)) { | ||||
|                 newStreams.add((G) next); | ||||
|             } | ||||
|         } | ||||
|         return newStreams; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Filters and sorts audio streams according to chosen languages | ||||
|      * @param audioStreams A list of audio streams | ||||
|      * @param audioLanguages A list of languages | ||||
|      * @return A list containing just audio tracks of chosen languages, sorted in order of languages | ||||
|      */ | ||||
|     static List<AudioStream> filterAudioStreams(List<AudioStream> audioStreams, String[] audioLanguages) { | ||||
|         List<AudioStream> filtered = new ArrayList<>(); | ||||
|         for (String language : audioLanguages) { | ||||
|             for (AudioStream stream : audioStreams) { | ||||
|                 if ((stream.getLanguage() != null && stream.getLanguage().equals(language)) || language.equals("*")) { | ||||
|                     filtered.add(stream); | ||||
|                 } | ||||
|             } | ||||
|             //Tries to reduce execution time from n^2 | ||||
|             audioStreams.removeAll(filtered); | ||||
|         } | ||||
|         return filtered; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Filters and sorts subtitle streams according to chosen languages | ||||
|      * @param subtitleStreams A list of subtitle streams | ||||
|      * @param subtitleLanguages A list of languages | ||||
|      * @param preventSignsAndSongs Whether partial subtitles should be avoided | ||||
|      * @return A list containing just subtitles of chosen languages, sorted in order of languages | ||||
|      */ | ||||
|     static List<SubtitleStream> filterSubtitleStreams(List<SubtitleStream> subtitleStreams, String[] subtitleLanguages, | ||||
|                                  boolean preventSignsAndSongs) { | ||||
|         List<SubtitleStream> filtered = new ArrayList<>(); | ||||
|         //Go through languages. Select all subtitles of the language | ||||
|         for (String language : subtitleLanguages) { | ||||
|             for (SubtitleStream stream : subtitleStreams) { | ||||
|                 String streamLanguage = stream.getLanguage(); | ||||
|                 if (((streamLanguage != null && streamLanguage.equals(language)) || language.equals("*")) && | ||||
|                         (!preventSignsAndSongs || stream.getIsFullSubtitle())) { | ||||
|                     filtered.add(stream); | ||||
|                 } | ||||
|             } | ||||
|             //Tries to reduce execution time from n^2 | ||||
|             subtitleStreams.removeAll(filtered); | ||||
|         } | ||||
|         return filtered; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Takes a list of all streams and parses each stream into one of three objects | ||||
|      * @param streams A list of all streams for the current file | ||||
|      * @return A list of StreamObjects | ||||
|      */ | ||||
|     private static List<StreamObject> parseStreams(String[] streams) { | ||||
|         List<StreamObject> parsedStreams = new ArrayList<>(); | ||||
|         int relativeAudioIndex = 0; | ||||
|         int relativeVideoIndex = 0; | ||||
|         int relativeSubtitleIndex = 0; | ||||
|         for (String stream : streams) { | ||||
|             String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER); | ||||
|             if (stream.contains("codec_type=video")) { | ||||
|                 parsedStreams.add(parseVideoStream(streamParts, relativeVideoIndex++)); | ||||
|             } else if (stream.contains("codec_type=audio")) { | ||||
|                 parsedStreams.add(parseAudioStream(streamParts, relativeAudioIndex++)); | ||||
|             } else if (stream.contains("codec_type=subtitle")) { | ||||
|                 parsedStreams.add(parseSubtitleStream(streamParts, relativeSubtitleIndex++)); | ||||
|             } | ||||
|         } | ||||
|         return parsedStreams; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses a list of video stream parameters to a video stream object | ||||
|      * @param streamParts A list of parameters belonging to an video stream | ||||
|      * @param relativeIndex The relative index of the video stream | ||||
|      * @return A SubtitleStream object | ||||
|      * @throws NumberFormatException If codec index contains a non-numeric value | ||||
|      */ | ||||
|     private static VideoStream parseVideoStream(String[] streamParts, int relativeIndex) throws NumberFormatException { | ||||
|         String codec = null; | ||||
|         int absoluteIndex = -1; | ||||
|         for (String streamPart : streamParts) { | ||||
|             if (streamPart.contains("codec_name=")) { | ||||
|                 codec = streamPart.replace("codec_name=", ""); | ||||
|             } else if (streamPart.contains("index=")) { | ||||
|                 absoluteIndex = Integer.parseInt(streamPart.replace("index=", "")); | ||||
|             } | ||||
|         } | ||||
|         return new VideoStream(codec, absoluteIndex, relativeIndex); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses a list of audio stream parameters to an audio stream object | ||||
|      * @param streamParts A list of parameters belonging to an audio stream | ||||
|      * @param relativeIndex The relative index of the audio stream | ||||
|      * @return A SubtitleStream object | ||||
|      * @throws NumberFormatException If codec index contains a non-numeric value | ||||
|      */ | ||||
|     private static AudioStream parseAudioStream(String[] streamParts, int relativeIndex) throws NumberFormatException { | ||||
|         String codec = null; | ||||
|         int absoluteIndex = -1; | ||||
|         String language = null; | ||||
|         int channels = 0; | ||||
|         String title = ""; | ||||
|         for (String streamPart : streamParts) { | ||||
|             if (streamPart.contains("codec_name=")) { | ||||
|                 codec = streamPart.replace("codec_name=", ""); | ||||
|             } else if (streamPart.contains("index=")) { | ||||
|                 absoluteIndex = Integer.parseInt(streamPart.replace("index=", "")); | ||||
|             } else if (streamPart.contains("TAG:language=")) { | ||||
|                 language = streamPart.replace("TAG:language=", ""); | ||||
|             } else if (streamPart.contains("channels=")) { | ||||
|                 channels = Integer.parseInt(streamPart.replace("channels=", "")); | ||||
|             } else if (streamPart.contains("TAG:title=")) { | ||||
|                 title = streamPart.replace("TAG:title=", ""); | ||||
|             } | ||||
|         } | ||||
|         return new AudioStream(codec, absoluteIndex, relativeIndex, language, title, channels); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses a list of subtitle stream parameters to a subtitle stream object | ||||
|      * @param streamParts A list of parameters belonging to a subtitle stream | ||||
|      * @param relativeIndex The relative index of the subtitle | ||||
|      * @return A SubtitleStream object | ||||
|      * @throws NumberFormatException If codec index contains a non-numeric value | ||||
|      */ | ||||
|     private static SubtitleStream parseSubtitleStream(String[] streamParts, int relativeIndex) throws NumberFormatException { | ||||
|         String codecName = null; | ||||
|         int absoluteIndex = -1; | ||||
|         String language = null; | ||||
|         String title = ""; | ||||
|         for (String streamPart : streamParts) { | ||||
|             if (streamPart.contains("codec_name=")) { | ||||
|                 codecName = streamPart.replace("codec_name=", ""); | ||||
|             } else if (streamPart.contains("index=")) { | ||||
|                 absoluteIndex = Integer.parseInt(streamPart.replace("index=", "")); | ||||
|             } else if (streamPart.contains("TAG:language=")) { | ||||
|                 language = streamPart.replace("TAG:language=", ""); | ||||
|             } else if (streamPart.contains("TAG:title=")) { | ||||
|                 title = streamPart.replace("TAG:title=", ""); | ||||
|             } | ||||
|         } | ||||
|         return new SubtitleStream(codecName, absoluteIndex, relativeIndex, language, title); | ||||
|     } | ||||
|      | ||||
|     static void print(String input) throws IOException { | ||||
|         if (!input.equals("")) { | ||||
|             writer.write(input); | ||||
|             writer.flush(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     static void printl(String input) throws IOException { | ||||
|         if (!input.equals("")) { | ||||
|             writer.write(input); | ||||
|         } | ||||
|         writer.newLine(); | ||||
|         writer.flush(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										129
									
								
								src/main/java/ffmpegconverter/converter/VideoConverter.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/main/java/ffmpegconverter/converter/VideoConverter.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| package ffmpegconverter.converter; | ||||
|  | ||||
| import ffmpegconverter.streams.AudioStream; | ||||
| import ffmpegconverter.streams.StreamObject; | ||||
| import ffmpegconverter.streams.VideoStream; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
|  | ||||
| public class VideoConverter extends Converter { | ||||
|     private String newExt; | ||||
|     private boolean debug = false; | ||||
|  | ||||
|     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 { | ||||
|         List<StreamObject> streams = probeFile(ffprobePath, file); | ||||
|         if (streams.size() == 0) { | ||||
|             throw new IllegalArgumentException("The file has no streams"); | ||||
|         } | ||||
|         String newPath = fileCollisionPrevention(folder.getAbsolutePath() + File.separator + stripExtension(file) + "." + newExt, newExt); | ||||
|         convertProcess(new ProcessBuilder(builderCommand(ffmpegPath, file.getName(), streams, newPath, folder)), 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, List<StreamObject> streams, String outFile, File folder) { | ||||
|         List<String> command = generalFile(executable, fileName); | ||||
|  | ||||
|         if (this.debug) { | ||||
|             addDebug(command, 50, 120); | ||||
|         } | ||||
|  | ||||
|         List<AudioStream> audioStreams = filterStreamsByType(streams, "audio"); | ||||
|         List<VideoStream> videoStreams = filterStreamsByType(streams, "video"); | ||||
|  | ||||
|         VideoStream videoStream = null; | ||||
|         AudioStream audioStream = null; | ||||
|         if (videoStreams.size() > 0) { | ||||
|             videoStream = videoStreams.get(0); | ||||
|         } | ||||
|         if (audioStreams.size() > 0) { | ||||
|             audioStream = audioStreams.get(0); | ||||
|         } | ||||
|  | ||||
|         String ext = hasExternalSubtitle(folder.getAbsolutePath(), fileName); | ||||
|         String ext2 = hasExternalImageSubtitle(folder.getAbsolutePath(), fileName); | ||||
|         if (!ext.equals("")) { | ||||
|             command.add("-vf"); | ||||
|             command.add("subtitles=" + stripExtension(fileName) + ext); | ||||
|         } else if (!ext2.equals("")) { | ||||
|             command.add("-i"); | ||||
|             command.add(stripExtension(fileName) + ext2); | ||||
|             if (this.debug) { | ||||
|                 addDebug(command, 50, 120); | ||||
|             } | ||||
|             //TODO: Scale subtitles to video | ||||
|             command.add("-filter_complex"); | ||||
|             command.add("[1:s]scale=width=1920:height=800,crop=w=1920:h=800:x=0:y=out_h[sub];[" + videoStream + ":v][sub]overlay"); | ||||
|             command.add("-profile:v"); | ||||
|             command.add("baseline"); | ||||
|         } | ||||
|  | ||||
|         if (ext2.equals("") || !ext.equals("")) { | ||||
|             if (videoStreams.size() > 0) { | ||||
|                 command.add("-map"); | ||||
|                 command.add("0:" + videoStream); | ||||
|             } | ||||
|             if (audioStreams.size() > 0) { | ||||
|                 command.add("-map"); | ||||
|                 command.add("0:" + audioStream); | ||||
|             } | ||||
|         } | ||||
|         command.add("-af"); | ||||
|         command.add("pan=stereo|FL < 1.0*FL + 0.707*FC + 0.707*BL|FR < 1.0*FR + 0.707*FC + 0.707*BR"); | ||||
|  | ||||
|         command.add(outFile); | ||||
|         return command.toArray(new String[0]); | ||||
|     } | ||||
|  | ||||
|     private String hasExternalImageSubtitle(String directory, String file) { | ||||
|         String path = stripExtension(file); | ||||
|         for (String s : new String[] {".idx", ".sub"}) { | ||||
|             if (new File(directory + File.separator + path + s).exists()) { | ||||
|                 return s; | ||||
|             } | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     private String hasExternalSubtitle(String directory, String file) { | ||||
|         String path = stripExtension(file); | ||||
|         for (String s : new String[] {".srt", ".ass"}) { | ||||
|             if (new File(directory + File.separator + path + s).exists()) { | ||||
|                 return s; | ||||
|             } | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String[] getValidFormats() { | ||||
|         return VIDEO_FORMATS; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void convert(File file) throws IOException { | ||||
|         processFile(file.getParentFile(), file, newExt); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/main/java/ffmpegconverter/streams/AudioStream.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/main/java/ffmpegconverter/streams/AudioStream.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| package ffmpegconverter.streams; | ||||
|  | ||||
| public class AudioStream extends StreamObject { | ||||
|     private String language; //The audio language | ||||
|     private int channels; //Whether mono, stereo, etc | ||||
|     private String title; //Titles exist | ||||
|  | ||||
|     public AudioStream(String codec, int absoluteIndex, int relativeIndex, String language, String title, int channels) { | ||||
|         this.codecType = "audio"; | ||||
|         this.codecName = codec; | ||||
|         this.absoluteIndex = absoluteIndex; | ||||
|         this.language = language; | ||||
|         this.title = title; | ||||
|         this.relativeIndex = relativeIndex; | ||||
|         this.channels = channels; | ||||
|     } | ||||
|  | ||||
|     public String getLanguage() { | ||||
|         return this.language; | ||||
|     } | ||||
|  | ||||
|     public int getChannels() { | ||||
|         return this.channels; | ||||
|     } | ||||
|  | ||||
|     public String getTitle() { | ||||
|         return this.title; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/main/java/ffmpegconverter/streams/StreamObject.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/main/java/ffmpegconverter/streams/StreamObject.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| package ffmpegconverter.streams; | ||||
|  | ||||
| /** | ||||
|  * An object representation of a stream in a media file | ||||
|  */ | ||||
| public abstract class StreamObject { | ||||
|     int absoluteIndex; | ||||
|     int relativeIndex; | ||||
|     String codecName; | ||||
|     String codecType; | ||||
|  | ||||
|     /** | ||||
|      * Gets the type of the stream codec (video/audio/subtitle) | ||||
|      * @return codec type | ||||
|      */ | ||||
|     public String getCodecType() { | ||||
|         return this.codecType; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the name of the stream codec | ||||
|      * @return codec name | ||||
|      */ | ||||
|     public String getCodecName() { | ||||
|         return this.codecName; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the absolute index of a stream object | ||||
|      * @return absolute index | ||||
|      */ | ||||
|     public int getAbsoluteIndex() { | ||||
|         return this.absoluteIndex; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the relative index of a stream object (kth element of codec type) | ||||
|      * @return relative index | ||||
|      */ | ||||
|     public int getRelativeIndex() { | ||||
|         return this.relativeIndex; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/main/java/ffmpegconverter/streams/SubtitleStream.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/main/java/ffmpegconverter/streams/SubtitleStream.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| package ffmpegconverter.streams; | ||||
|  | ||||
| /** | ||||
|  * An object representation of a subtitle stream in a media file | ||||
|  */ | ||||
| public class SubtitleStream extends StreamObject { | ||||
|     final private String language; | ||||
|     final private String title; //Title shown | ||||
|     final private boolean isFullSubtitle; //Songs and signs will be false | ||||
|     final private boolean isImageSubtitle; | ||||
|  | ||||
|     public SubtitleStream(String codecName, int absoluteIndex, int relativeIndex, String language, String title) { | ||||
|         this.codecType = "subtitle"; | ||||
|         this.codecName = codecName; | ||||
|         this.absoluteIndex = absoluteIndex; | ||||
|         this.language = language; | ||||
|         this.title = title; | ||||
|         this.isFullSubtitle = isFullSubtitle(); | ||||
|         this.relativeIndex = relativeIndex; | ||||
|         this.isImageSubtitle = isImageSubtitle(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks whether a subtitle is image based (as opposed to text based) | ||||
|      * @return True if the subtitle is image based | ||||
|      */ | ||||
|     private boolean isImageSubtitle() { | ||||
|         return codecName != null && getCodecName().equals("hdmv_pgs_subtitle"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks whether translates everything (as opposed to just songs and signs) | ||||
|      * @return True if the subtitles translate everything | ||||
|      */ | ||||
|     private boolean isFullSubtitle() { | ||||
|         if (getTitle() == null) { | ||||
|             return false; | ||||
|         } | ||||
|         String titleLowercase = getTitle().toLowerCase(); | ||||
|         return !(titleLowercase.contains("songs and signs") ||  | ||||
|                 titleLowercase.contains("signs and songs") || | ||||
|                 titleLowercase.contains("songs & signs") ||  | ||||
|                 titleLowercase.contains("signs & songs") || | ||||
|                 titleLowercase.contains("signs/song") ||  | ||||
|                 titleLowercase.contains("songs/sign") || | ||||
|                 titleLowercase.contains("[forced]") ||  | ||||
|                 titleLowercase.contains("(forced)")); | ||||
|     } | ||||
|  | ||||
|     public String getLanguage() { | ||||
|         return this.language; | ||||
|     } | ||||
|  | ||||
|     public String getTitle() { | ||||
|         return this.title; | ||||
|     } | ||||
|  | ||||
|     public boolean getIsImageSubtitle() { | ||||
|         return this.isImageSubtitle; | ||||
|     } | ||||
|  | ||||
|     public boolean getIsFullSubtitle() { | ||||
|         return this.isFullSubtitle; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/main/java/ffmpegconverter/streams/VideoStream.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/main/java/ffmpegconverter/streams/VideoStream.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| package ffmpegconverter.streams; | ||||
|  | ||||
| /** | ||||
|  * An object representation of a video stream in a media file | ||||
|  */ | ||||
| public class VideoStream extends StreamObject { | ||||
|     public VideoStream(String codec, int absoluteIndex, int relativeIndex) { | ||||
|         this.codecType = "video"; | ||||
|         this.codecName = codec; | ||||
|         this.absoluteIndex = absoluteIndex; | ||||
|         this.relativeIndex = relativeIndex; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user