Makes getting any stream info much easier
Adds some methods for parsing Strings as other objects without resulting in exceptions. Adds a class for representing all possible stream info tags. Makes streams parse data themselves, after receiving all tags set for the stream. Changes Java version to Java 16
This commit is contained in:
@ -2,8 +2,11 @@ package net.knarcraft.ffmpegconverter.utility;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamTag;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@ -11,7 +14,9 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class which helps with ffmpeg probing and converting
|
||||
@ -24,6 +29,26 @@ public final class FFMpegHelper {
|
||||
private FFMpegHelper() {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Developer notes:
|
||||
Setting the default track:
|
||||
-disposition:s:0 +default
|
||||
Where s,a,v is the type specifier, and the number is the relative index
|
||||
|
||||
To unset default:
|
||||
-disposition:s:0 -default
|
||||
|
||||
-map command for reordering:
|
||||
First number is the index of the input file, which is 0, unless more files are given as input
|
||||
Optionally, a,v,s can be used to select the type of stream to map
|
||||
Lastly, the number is either the global index of the stream, or the relative, if a type has been specified
|
||||
If only the first number is given, all streams from that file are selected
|
||||
The output file will contain all mapped streams in the order they are mapped
|
||||
|
||||
Plan: Sort all streams by set criteria. Map all selected streams by looping using their relative index for
|
||||
selection.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets streams from a file
|
||||
@ -259,8 +284,7 @@ public final class FFMpegHelper {
|
||||
ffprobePath,
|
||||
"-v",
|
||||
"error",
|
||||
"-show_entries",
|
||||
"stream_tags=language,title:stream=index,codec_name,codec_type,channels,codec_type,width,height",
|
||||
"-show_streams",
|
||||
file.toString()
|
||||
);
|
||||
String result = runProcess(processBuilder, file.getParentFile(), PROBE_SPLIT_CHARACTER, false);
|
||||
@ -279,14 +303,22 @@ public final class FFMpegHelper {
|
||||
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(), true));
|
||||
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
||||
|
||||
String codecType = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_TYPE), "");
|
||||
switch (codecType) {
|
||||
case "video":
|
||||
parsedStreams.add(new VideoStream(streamInfo, relativeVideoIndex++));
|
||||
break;
|
||||
case "audio":
|
||||
parsedStreams.add(new AudioStream(streamInfo, relativeAudioIndex++));
|
||||
break;
|
||||
case "subtitle":
|
||||
parsedStreams.add(new SubtitleStream(streamInfo, relativeSubtitleIndex++, file.getName(), true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<StreamObject> externalSubtitles = getExternalSubtitles(ffprobePath, file.getParentFile(), file.getName());
|
||||
@ -294,6 +326,28 @@ public final class FFMpegHelper {
|
||||
return parsedStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets stream info from the given raw stream info lines
|
||||
*
|
||||
* @param streamParts <p>The stream info lines to parse</p>
|
||||
* @return <p>The stream tag map parsed</p>
|
||||
*/
|
||||
@NotNull
|
||||
private static Map<StreamTag, String> getStreamInfo(@Nullable String[] streamParts) {
|
||||
Map<StreamTag, String> streamInfo = new HashMap<>();
|
||||
for (String part : streamParts) {
|
||||
if (part == null || !part.contains("=")) {
|
||||
continue;
|
||||
}
|
||||
String[] keyValue = part.split("=");
|
||||
StreamTag tag = StreamTag.getFromString(keyValue[0]);
|
||||
if (tag != null) {
|
||||
streamInfo.put(tag, keyValue[1]);
|
||||
}
|
||||
}
|
||||
return streamInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there exists an external image subtitle with the same filename as the file
|
||||
*
|
||||
@ -302,8 +356,9 @@ public final class FFMpegHelper {
|
||||
* @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 {
|
||||
@NotNull
|
||||
private static List<StreamObject> getExternalSubtitles(@NotNull String ffprobePath, @NotNull File directory,
|
||||
@NotNull String convertingFile) throws IOException {
|
||||
List<StreamObject> parsedStreams = new ArrayList<>();
|
||||
//Find all files in the same directory with external subtitle formats
|
||||
if (subtitleFormats == null) {
|
||||
@ -328,7 +383,8 @@ public final class FFMpegHelper {
|
||||
String[] streams = probeForStreams(ffprobePath, subtitleFile);
|
||||
for (String stream : streams) {
|
||||
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
||||
parsedStreams.add(parseSubtitleStream(streamParts, 0, subtitleFile.getName(), false));
|
||||
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
||||
parsedStreams.add(new SubtitleStream(streamInfo, 0, subtitleFile.getName(), false));
|
||||
}
|
||||
}
|
||||
return parsedStreams;
|
||||
@ -350,91 +406,4 @@ public final class FFMpegHelper {
|
||||
return text.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* @param isInternalStream <p>Whether the subtitle stream is in the main video file</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,
|
||||
boolean isInternalStream) throws NumberFormatException {
|
||||
String codecName = null;
|
||||
int absoluteIndex = -1;
|
||||
String language = null;
|
||||
String title = "";
|
||||
for (String streamPart : streamParts) {
|
||||
if (streamPart.startsWith("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, isInternalStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
package net.knarcraft.ffmpegconverter.utility;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A helper class for parsing values without causing errors
|
||||
*/
|
||||
public final class ValueParsingHelper {
|
||||
|
||||
private ValueParsingHelper() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer from a string
|
||||
*
|
||||
* @param input <p>The input given</p>
|
||||
* @param defaultValue <p>The default value to return if no integer could be parsed</p>
|
||||
* @return <p>The parsed integer, or the given default value</p>
|
||||
*/
|
||||
public static int parseInt(@Nullable String input, int defaultValue) {
|
||||
if (input == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(input);
|
||||
} catch (NumberFormatException exception) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string
|
||||
*
|
||||
* @param input <p>The input string</p>
|
||||
* @param defaultValue <p>The default value to use if the string is null</p>
|
||||
* @return <p>The input string, or the default value</p>
|
||||
*/
|
||||
@NotNull
|
||||
public static String parseString(@Nullable String input, @NotNull String defaultValue) {
|
||||
return Objects.requireNonNullElse(input, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a boolean
|
||||
*
|
||||
* @param input <p>The input string</p>
|
||||
* @param defaultValue <p>The default value to use if the string is null</p>
|
||||
* @return <p>The parsed boolean, or the default value</p>
|
||||
*/
|
||||
public static boolean parseBoolean(@Nullable String input, boolean defaultValue) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Boolean.parseBoolean(input);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user