367 lines
14 KiB
Java

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