Makes a new utility class containing most FFmpeg related methods
This commit is contained in:
parent
0375aea333
commit
c6f57835f5
@ -0,0 +1,294 @@
|
||||
package net.knarcraft.ffmpegconverter.utility;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class which helps with ffmpeg probing and converting
|
||||
*/
|
||||
public final class FFMpegHelper {
|
||||
|
||||
private FFMpegHelper() {
|
||||
}
|
||||
|
||||
private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå";
|
||||
|
||||
/**
|
||||
* Gets streams from a file
|
||||
* @param ffprobePath <p>The path/command to ffprobe.</p>
|
||||
* @param file <p>The file to probe.</p>
|
||||
* @return <p>A list of StreamObjects.</p>
|
||||
* @throws IOException <p>If the process can't be readProcess.</p>
|
||||
*/
|
||||
public static List<StreamObject> probeFile(String ffprobePath, File file) throws IOException {
|
||||
return parseStreams(ffprobePath, probeForStreams(ffprobePath, file), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all streams in a file
|
||||
* @param ffprobePath <p>The path/command to ffprobe.</p>
|
||||
* @param file <p>The file to probe.</p>
|
||||
* @return <p>A list of streams.</p>
|
||||
* @throws IOException <p>If something goes wrong while probing.</p>
|
||||
*/
|
||||
private static String[] probeForStreams(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,codec_type,width,height",
|
||||
file.toString()
|
||||
);
|
||||
OutputUtil.println();
|
||||
OutputUtil.print("Probe command: ");
|
||||
OutputUtil.println(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 = readProcess(readerProbe, PROBE_SPLIT_CHARACTER);
|
||||
if (!read.equals("")) {
|
||||
OutputUtil.printDebug(read);
|
||||
output.append(read);
|
||||
}
|
||||
}
|
||||
return StringUtil.stringBetween(output.toString(), "[STREAM]", "[/STREAM]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a list of all streams and parses each stream into one of three objects
|
||||
* @param streams <p>A list of all streams for the current file.</p>
|
||||
* @param file <p>The file currently being converted.</p>
|
||||
* @return <p>A list of StreamObjects.</p>
|
||||
*/
|
||||
private static List<StreamObject> parseStreams(String ffprobePath, String[] streams, File file) throws IOException {
|
||||
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++, file.getName()));
|
||||
}
|
||||
}
|
||||
List<StreamObject> externalSubtitles = getExternalSubtitles(ffprobePath, file.getParentFile(), file.getName());
|
||||
parsedStreams.addAll(externalSubtitles);
|
||||
return parsedStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there exists an external image subtitle with the same filename as the file
|
||||
* @param ffprobePath <p>The path/command to ffprobe.</p>
|
||||
* @param directory <p>The directory containing the file.</p>
|
||||
* @param convertingFile <p>The file to be converted.</p>
|
||||
* @return <p>The extension of the subtitle or empty if no subtitle was found.</p>
|
||||
*/
|
||||
private static List<StreamObject> getExternalSubtitles(String ffprobePath, File directory, String convertingFile)
|
||||
throws IOException {
|
||||
List<StreamObject> parsedStreams = new ArrayList<>();
|
||||
String[] subtitleFormats = FileUtil.readFileLines("subtitle_formats.txt");
|
||||
File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
|
||||
|
||||
if (subtitleFiles == null) {
|
||||
return parsedStreams;
|
||||
}
|
||||
|
||||
String fileTitle = FileUtil.stripExtension(convertingFile);
|
||||
List<File> subtitleFilesList = new ArrayList<>(Arrays.asList(subtitleFiles));
|
||||
//Finds the files which are subtitles belonging to the file
|
||||
subtitleFilesList = ListUtil.getMatching(subtitleFilesList,
|
||||
(subtitleFile) -> subtitleFile.getName().contains(fileTitle));
|
||||
for (File subtitleFile : subtitleFilesList) {
|
||||
String[] streams = probeForStreams(ffprobePath, subtitleFile);
|
||||
for (String stream : streams) {
|
||||
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
||||
parsedStreams.add(parseSubtitleStream(streamParts, 0, subtitleFile.getName()));
|
||||
}
|
||||
}
|
||||
return parsedStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts and prints output of a process
|
||||
* @param process <p>The process to run.</p>
|
||||
* @param folder <p>The folder the process should run in.</p>
|
||||
* @throws IOException <p>If the process can't be readProcess.</p>
|
||||
*/
|
||||
public static void convertProcess(ProcessBuilder process, File folder) throws IOException {
|
||||
OutputUtil.print("Command to be run: ");
|
||||
OutputUtil.println(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 = readProcess(readerConvert, "\n");
|
||||
if (!read.equals("")) {
|
||||
OutputUtil.println(read);
|
||||
}
|
||||
}
|
||||
OutputUtil.println("Process is finished.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from a process reader
|
||||
* @param reader <p>The reader of a process.</p>
|
||||
* @return <p>The output from the readProcess.</p>
|
||||
* @throws IOException <p>On reader failure.</p>
|
||||
*/
|
||||
private static String readProcess(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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list containing all required arguments for converting a video to a web playable video
|
||||
* @param executable <p>The executable to use (ffmpeg/ffprobe).</p>
|
||||
* @param fileName <p>The name of the file to execute on.</p>
|
||||
* @return <p>A base list of ffmpeg commands for converting a video for web</p>
|
||||
*/
|
||||
public static List<String> getFFMpegWebVideoCommand(String executable, String fileName) {
|
||||
List<String> command = getFFMpegGeneralFileCommand(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list containing command line arguments for a general file
|
||||
* @param executable <p>The executable to use (ffmpeg/ffprobe).</p>
|
||||
* @param fileName <p>The name of the file to execute on.</p>
|
||||
* @return <p>A base list of ffmpeg commands for converting a file.</p>
|
||||
*/
|
||||
public static List<String> getFFMpegGeneralFileCommand(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 <p>The list containing the command to run.</p>
|
||||
* @param start <p>The offset before converting.</p>
|
||||
* @param length <p>The offset for stopping the conversion.</p>
|
||||
*/
|
||||
public static void addDebugArguments(List<String> command, int start, int length) {
|
||||
command.add("-ss");
|
||||
command.add("" + start);
|
||||
command.add("-t");
|
||||
command.add("" + length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of video stream parameters to a video stream object
|
||||
* @param streamParts <p>A list of parameters belonging to an video stream.</p>
|
||||
* @param relativeIndex <p>The relative index of the video stream.</p>
|
||||
* @return <p>A SubtitleStream object.</p>
|
||||
* @throws NumberFormatException <p>If codec index contains a non-numeric value.</p>
|
||||
*/
|
||||
private static VideoStream parseVideoStream(String[] streamParts, int relativeIndex) throws NumberFormatException {
|
||||
String codec = null;
|
||||
int absoluteIndex = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
for (String streamPart : streamParts) {
|
||||
if (streamPart.startsWith("codec_name=")) {
|
||||
codec = streamPart.replace("codec_name=", "");
|
||||
} else if (streamPart.startsWith("index=")) {
|
||||
absoluteIndex = Integer.parseInt(streamPart.replace("index=", ""));
|
||||
} else if (streamPart.startsWith("width=")) {
|
||||
width = Integer.parseInt(streamPart.replace("width=", ""));
|
||||
} else if (streamPart.startsWith("height=")) {
|
||||
height = Integer.parseInt(streamPart.replace("height=", ""));
|
||||
}
|
||||
}
|
||||
return new VideoStream(codec, absoluteIndex, relativeIndex, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of audio stream parameters to an audio stream object
|
||||
* @param streamParts <p>A list of parameters belonging to an audio stream.</p>
|
||||
* @param relativeIndex <p>The relative index of the audio stream.</p>
|
||||
* @return <p>A SubtitleStream object.</p>
|
||||
* @throws NumberFormatException <p>If codec index contains a non-numeric value.</p>
|
||||
*/
|
||||
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.startsWith("codec_name=")) {
|
||||
codec = streamPart.replace("codec_name=", "");
|
||||
} else if (streamPart.startsWith("index=")) {
|
||||
absoluteIndex = Integer.parseInt(streamPart.replace("index=", ""));
|
||||
} else if (streamPart.startsWith("TAG:language=")) {
|
||||
language = streamPart.replace("TAG:language=", "");
|
||||
} else if (streamPart.startsWith("channels=")) {
|
||||
channels = Integer.parseInt(streamPart.replace("channels=", ""));
|
||||
} else if (streamPart.startsWith("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 <p>A list of parameters belonging to a subtitle stream.</p>
|
||||
* @param relativeIndex <p>The relative index of the subtitle.</p>
|
||||
* @param file <p>The file currently being converted.</p>
|
||||
* @return <p>A SubtitleStream object.</p>
|
||||
* @throws NumberFormatException <p>If codec index contains a non-numeric value.</p>
|
||||
*/
|
||||
private static SubtitleStream parseSubtitleStream(String[] streamParts, int relativeIndex, String file)
|
||||
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.startsWith("index=")) {
|
||||
absoluteIndex = Integer.parseInt(streamPart.replace("index=", ""));
|
||||
} else if (streamPart.startsWith("TAG:language=")) {
|
||||
language = streamPart.replace("TAG:language=", "");
|
||||
} else if (streamPart.startsWith("TAG:title=")) {
|
||||
title = streamPart.replace("TAG:title=", "");
|
||||
}
|
||||
}
|
||||
return new SubtitleStream(codecName, absoluteIndex, relativeIndex, language, title, file);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user