diff --git a/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java b/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java new file mode 100644 index 0000000..ffee661 --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java @@ -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
The path/command to ffprobe.
+ * @param fileThe file to probe.
+ * @returnA list of StreamObjects.
+ * @throws IOExceptionIf the process can't be readProcess.
+ */ + public static ListThe path/command to ffprobe.
+ * @param fileThe file to probe.
+ * @returnA list of streams.
+ * @throws IOExceptionIf something goes wrong while probing.
+ */ + 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 streamsA list of all streams for the current file.
+ * @param fileThe file currently being converted.
+ * @returnA list of StreamObjects.
+ */ + private static ListThe path/command to ffprobe.
+ * @param directoryThe directory containing the file.
+ * @param convertingFileThe file to be converted.
+ * @returnThe extension of the subtitle or empty if no subtitle was found.
+ */ + private static ListThe process to run.
+ * @param folderThe folder the process should run in.
+ * @throws IOExceptionIf the process can't be readProcess.
+ */ + 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 readerThe reader of a process.
+ * @returnThe output from the readProcess.
+ * @throws IOExceptionOn reader failure.
+ */ + 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 executableThe executable to use (ffmpeg/ffprobe).
+ * @param fileNameThe name of the file to execute on.
+ * @returnA base list of ffmpeg commands for converting a video for web
+ */ + public static ListThe executable to use (ffmpeg/ffprobe).
+ * @param fileNameThe name of the file to execute on.
+ * @returnA base list of ffmpeg commands for converting a file.
+ */ + public static ListThe list containing the command to run.
+ * @param startThe offset before converting.
+ * @param lengthThe offset for stopping the conversion.
+ */ + public static void addDebugArguments(ListA list of parameters belonging to an video stream.
+ * @param relativeIndexThe relative index of the video stream.
+ * @returnA SubtitleStream object.
+ * @throws NumberFormatExceptionIf codec index contains a non-numeric value.
+ */ + 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 streamPartsA list of parameters belonging to an audio stream.
+ * @param relativeIndexThe relative index of the audio stream.
+ * @returnA SubtitleStream object.
+ * @throws NumberFormatExceptionIf 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.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 streamPartsA list of parameters belonging to a subtitle stream.
+ * @param relativeIndexThe relative index of the subtitle.
+ * @param fileThe file currently being converted.
+ * @returnA SubtitleStream object.
+ * @throws NumberFormatExceptionIf codec index contains a non-numeric value.
+ */ + 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); + } +}