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:
parent
2c75d91cce
commit
be88845731
2
pom.xml
2
pom.xml
@ -33,7 +33,7 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<java.version>16</java.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
|
@ -16,6 +16,7 @@ import net.knarcraft.ffmpegconverter.utility.OutputUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
@ -29,7 +30,7 @@ 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 Scanner READER = new Scanner(System.in, StandardCharsets.UTF_8);
|
||||
private static Converter converter = null;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
@ -65,31 +66,30 @@ class Main {
|
||||
* @throws IOException <p>If there's a problem getting user input.</p>
|
||||
*/
|
||||
private static Converter loadConverter() throws IOException {
|
||||
int choice = getChoice("Which converter do you want do use?\n1. Anime to web mp4\n2. Audio converter\n" +
|
||||
"3. Video converter\n4. Web video converter\n5. MKV to h264 converter\n6. MKV to h265 reduced " +
|
||||
"converter\n7. MKV to MP4 transcoder\n8. DownScaleConverter\n9. mp4 Subtitle Embed", 1, 9);
|
||||
int choice = getChoice("""
|
||||
Which converter do you want do use?
|
||||
1. Anime to web mp4
|
||||
2. Audio converter
|
||||
3. Video converter
|
||||
4. Web video converter
|
||||
5. MKV to h264 converter
|
||||
6. MKV to h265 reduced converter
|
||||
7. MKV to MP4 transcoder
|
||||
8. DownScaleConverter
|
||||
9. mp4 Subtitle Embed""", 1, 9);
|
||||
|
||||
switch (choice) {
|
||||
case 1:
|
||||
return generateAnimeConverter();
|
||||
case 2:
|
||||
return new AudioConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 3:
|
||||
return new VideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 4:
|
||||
return new WebVideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 5:
|
||||
return new MkvH264Converter(FFPROBE_PATH, FFMPEG_PATH);
|
||||
case 6:
|
||||
return new MkvH265ReducedConverter(FFPROBE_PATH, FFMPEG_PATH);
|
||||
case 7:
|
||||
return generateMKVToMP4Transcoder();
|
||||
case 8:
|
||||
return generateDownScaleConverter();
|
||||
case 9:
|
||||
return new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH);
|
||||
}
|
||||
return null;
|
||||
return switch (choice) {
|
||||
case 1 -> generateAnimeConverter();
|
||||
case 2 -> new AudioConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 3 -> new VideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 4 -> new WebVideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 5 -> new MkvH264Converter(FFPROBE_PATH, FFMPEG_PATH);
|
||||
case 6 -> new MkvH265ReducedConverter(FFPROBE_PATH, FFMPEG_PATH);
|
||||
case 7 -> generateMKVToMP4Transcoder();
|
||||
case 8 -> generateDownScaleConverter();
|
||||
case 9 -> new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,8 +105,8 @@ public abstract class AbstractConverter implements Converter {
|
||||
for (String language : languages) {
|
||||
for (G stream : streams) {
|
||||
String streamLanguage = stream.getLanguage();
|
||||
if (language.equals("*") || ((streamLanguage == null || streamLanguage.equals("und")) &&
|
||||
language.equals("0")) || (streamLanguage != null && streamLanguage.equals(language))) {
|
||||
if (language.equals("*") || (streamLanguage.equals("und") && language.equals("0")) ||
|
||||
streamLanguage.equals(language)) {
|
||||
sorted.add(stream);
|
||||
}
|
||||
}
|
||||
|
@ -58,10 +58,8 @@ public class SubtitleEmbed extends AbstractConverter {
|
||||
|
||||
int i = 0;
|
||||
for (SubtitleStream subtitleStream : subtitleStreams) {
|
||||
if (subtitleStream.getLanguage() != null) {
|
||||
command.add("-metadata:s:s:" + i);
|
||||
command.add("language=" + subtitleStream.getLanguage());
|
||||
}
|
||||
command.add("-metadata:s:s:" + i);
|
||||
command.add("language=" + subtitleStream.getLanguage());
|
||||
i++;
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,39 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.utility.ValueParsingHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An abstract implementation of a stream object implementing common methods
|
||||
*/
|
||||
public abstract class AbstractStream implements StreamObject {
|
||||
|
||||
int absoluteIndex;
|
||||
int relativeIndex;
|
||||
String codecName;
|
||||
String language;
|
||||
protected final int absoluteIndex;
|
||||
protected final int relativeIndex;
|
||||
protected final String codecName;
|
||||
protected String language;
|
||||
protected final boolean isDefault;
|
||||
protected final String title;
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract stream
|
||||
*
|
||||
* @param streamInfo <p>All info about the stream</p>
|
||||
* @param relativeIndex <p>The relative index of this stream, only considering streams of the same type</p>
|
||||
*/
|
||||
protected AbstractStream(@NotNull Map<StreamTag, String> streamInfo, int relativeIndex) {
|
||||
this.codecName = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_NAME), "");
|
||||
this.absoluteIndex = ValueParsingHelper.parseInt(streamInfo.get(StreamTag.INDEX), -1);
|
||||
this.language = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_LANGUAGE), "und");
|
||||
this.isDefault = ValueParsingHelper.parseBoolean(streamInfo.get(StreamTag.DISPOSITION_DEFAULT), false);
|
||||
this.title = ValueParsingHelper.parseString(streamInfo.get(StreamTag.TAG_TITLE), "");
|
||||
this.relativeIndex = relativeIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodecName() {
|
||||
public @NotNull String getCodecName() {
|
||||
return this.codecName;
|
||||
}
|
||||
|
||||
@ -26,8 +48,18 @@ public abstract class AbstractStream implements StreamObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguage() {
|
||||
public @NotNull String getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefault() {
|
||||
return this.isDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +1,26 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.utility.ValueParsingHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class represents a ffmpeg audio stream
|
||||
*/
|
||||
public class AudioStream extends AbstractStream implements StreamObject {
|
||||
|
||||
private final int channels;
|
||||
private final String title;
|
||||
|
||||
/**
|
||||
* Instantiates a new audio stream
|
||||
*
|
||||
* @param codecName <p>The codec of the audio stream.</p>
|
||||
* @param absoluteIndex <p>The index of the audio stream.</p>
|
||||
* @param streamInfo <p>All info about the stream</p>
|
||||
* @param relativeIndex <p>The index of the audio stream relative to other audio streams.</p>
|
||||
* @param language <p>The language of the audio stream.</p>
|
||||
* @param title <p>The title of the audio stream.</p>
|
||||
* @param channels <p>The number of channels for the audio stream.</p>
|
||||
*/
|
||||
public AudioStream(String codecName, int absoluteIndex, int relativeIndex, String language, String title,
|
||||
int channels) {
|
||||
this.codecName = codecName;
|
||||
this.absoluteIndex = absoluteIndex;
|
||||
this.language = language;
|
||||
this.title = title;
|
||||
this.relativeIndex = relativeIndex;
|
||||
this.channels = channels;
|
||||
public AudioStream(@NotNull Map<StreamTag, String> streamInfo, int relativeIndex) {
|
||||
super(streamInfo, relativeIndex);
|
||||
this.channels = ValueParsingHelper.parseInt(streamInfo.get(StreamTag.CHANNELS), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,16 +32,9 @@ public class AudioStream extends AbstractStream implements StreamObject {
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the audio stream
|
||||
* <p>
|
||||
* TODO: While this isn't useful right now, it would be useful if the software allowed first looking at available
|
||||
* steams, and then choose the correct stream based on the name
|
||||
*
|
||||
* @return <p>The title of the audio stream.</p>
|
||||
*/
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
@Override
|
||||
public @NotNull StreamType getStreamType() {
|
||||
return StreamType.AUDIO;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An object describing a generic video file stream
|
||||
*/
|
||||
@ -10,6 +12,7 @@ public interface StreamObject {
|
||||
*
|
||||
* @return <p>Codec name.</p>
|
||||
*/
|
||||
@NotNull
|
||||
String getCodecName();
|
||||
|
||||
/**
|
||||
@ -31,6 +34,29 @@ public interface StreamObject {
|
||||
*
|
||||
* @return <p>The language of the audio or subtitle stream.</p>
|
||||
*/
|
||||
@NotNull
|
||||
String getLanguage();
|
||||
|
||||
/**
|
||||
* Gets whether this stream is marked as the default stream of its type
|
||||
*
|
||||
* @return <p>True if this stream is marked as default</p>
|
||||
*/
|
||||
boolean isDefault();
|
||||
|
||||
/**
|
||||
* Gets the stream type of this stream object
|
||||
*
|
||||
* @return <p>The stream type for this stream</p>
|
||||
*/
|
||||
@NotNull
|
||||
StreamType getStreamType();
|
||||
|
||||
/**
|
||||
* Gets the title of the subtitle stream
|
||||
*
|
||||
* @return <p>The title of the subtitle stream.</p>
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,216 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A class for representing stream tags that might be found for streams in video files
|
||||
*/
|
||||
public enum StreamTag {
|
||||
|
||||
/**
|
||||
* The absolute index of this stream in the file
|
||||
*/
|
||||
INDEX("index", StreamType.ALL),
|
||||
/**
|
||||
* The name of the codec, useful for identification
|
||||
*/
|
||||
CODEC_NAME("codec_name=", StreamType.ALL),
|
||||
|
||||
/**
|
||||
* The long name of the codec, useful for displaying information
|
||||
*/
|
||||
CODEC_LONG_NAME("codec_long_name", StreamType.ALL),
|
||||
|
||||
/**
|
||||
* The profile the encoder is set to
|
||||
*/
|
||||
PROFILE("profile", StreamType.ALL),
|
||||
|
||||
/**
|
||||
* Whether the type of codec for the stream is audio, video or subtitle
|
||||
*/
|
||||
CODEC_TYPE("codec_type", StreamType.ALL),
|
||||
CODEC_TAG_STRING("codec_tag_string", StreamType.ALL),
|
||||
CODEC_TAG("codec_tag", StreamType.ALL),
|
||||
SAMPLE_FMT("sample_fmt", Set.of(StreamType.AUDIO)),
|
||||
SAMPLE_RATE("sample_rate", Set.of(StreamType.AUDIO)),
|
||||
|
||||
/**
|
||||
* The number of channels in an audio stream
|
||||
*/
|
||||
CHANNELS("channels", Set.of(StreamType.AUDIO)),
|
||||
|
||||
/**
|
||||
* Human-recognizable term for the number of audio channels, such as stereo, mono or surround
|
||||
*/
|
||||
CHANNEL_LAYOUT("channel_layout", Set.of(StreamType.AUDIO)),
|
||||
BITS_PER_SAMPLE("bits_per_sample", Set.of(StreamType.AUDIO)),
|
||||
INITIAL_PADDING("initial_padding", Set.of(StreamType.AUDIO)),
|
||||
|
||||
/**
|
||||
* The viewable video width
|
||||
*/
|
||||
WIDTH("width", StreamType.VIDEO_SUBTITLE),
|
||||
|
||||
/**
|
||||
* The viewable video height
|
||||
*/
|
||||
HEIGHT("height", StreamType.VIDEO_SUBTITLE),
|
||||
|
||||
/**
|
||||
* The original video width, before any padding was applied to account for resolution multiples
|
||||
*/
|
||||
CODED_WIDTH("coded_width", Set.of(StreamType.VIDEO)),
|
||||
|
||||
/**
|
||||
* The original video height, before any padding was applied to account for resolution multiples
|
||||
*/
|
||||
CODED_HEIGHT("coded_height", Set.of(StreamType.VIDEO)),
|
||||
CLOSED_CAPTIONS("closed_captions", Set.of(StreamType.VIDEO)),
|
||||
FILM_GRAIN("film_grain", Set.of(StreamType.VIDEO)),
|
||||
HAS_B_FRAMES("has_b_frames", Set.of(StreamType.VIDEO)),
|
||||
|
||||
/**
|
||||
* The aspect ratio used to stretch the video for playback
|
||||
*/
|
||||
SAMPLE_ASPECT_RATIO("sample_aspect_ratio", Set.of(StreamType.VIDEO)),
|
||||
|
||||
/**
|
||||
* The aspect ratio of the video stream
|
||||
*/
|
||||
DISPLAY_ASPECT_RATIO("display_aspect_ratio", Set.of(StreamType.VIDEO)),
|
||||
|
||||
/**
|
||||
* The pixel format used for the video stream
|
||||
*/
|
||||
PIX_FMT("pix_fmt", Set.of(StreamType.VIDEO)),
|
||||
|
||||
/**
|
||||
* The quality level of the video stream
|
||||
*/
|
||||
LEVEL("level", Set.of(StreamType.VIDEO)),
|
||||
COLOR_RANGE("color_range", Set.of(StreamType.VIDEO)),
|
||||
|
||||
/**
|
||||
* How colors are stored in the video stream's file
|
||||
*/
|
||||
COLOR_SPACE("color_space", Set.of(StreamType.VIDEO)),
|
||||
COLOR_TRANSFER("color_transfer", Set.of(StreamType.VIDEO)),
|
||||
COLOR_PRIMARIES("color_primaries", Set.of(StreamType.VIDEO)),
|
||||
CHROMA_LOCATION("chroma_location", Set.of(StreamType.VIDEO)),
|
||||
FIELD_ORDER("field_order", Set.of(StreamType.VIDEO)),
|
||||
REFS("refs", Set.of(StreamType.VIDEO)),
|
||||
IS_AVC("is_avc", Set.of(StreamType.VIDEO)),
|
||||
NAL_LENGTH_SIZE("nal_length_size", Set.of(StreamType.VIDEO)),
|
||||
ID("id", StreamType.ALL),
|
||||
R_FRAME_RATE("r_frame_rate", StreamType.ALL),
|
||||
AVERAGE_FRAME_RATE("avg_frame_rate", StreamType.ALL),
|
||||
TIME_BASE("time_base", StreamType.ALL),
|
||||
START_PTS("start_pts", StreamType.ALL),
|
||||
START_TIME("start_time", StreamType.ALL),
|
||||
DURATION_TS("duration_ts", StreamType.ALL),
|
||||
DURATION("duration", StreamType.ALL),
|
||||
BIT_RATE("bit_rate", StreamType.ALL),
|
||||
MAX_BIT_RATE("max_bit_rate", StreamType.ALL),
|
||||
BITS_PER_RAW_SAMPLE("bits_per_raw_sample", StreamType.ALL),
|
||||
NB_FRAMES("nb_frames", StreamType.ALL),
|
||||
NB_READ_FRAMES("nb_read_frames", StreamType.ALL),
|
||||
NB_READ_PACKETS("nb_read_packets", StreamType.ALL),
|
||||
EXTRA_DATA_SIZE("extradata_size", StreamType.VIDEO_SUBTITLE),
|
||||
DISPOSITION_DEFAULT("DISPOSITION:default", StreamType.ALL),
|
||||
DISPOSITION_DUB("DISPOSITION:dub", StreamType.ALL),
|
||||
DISPOSITION_ORIGINAL("DISPOSITION:original", StreamType.ALL),
|
||||
DISPOSITION_COMMENT("DISPOSITION:comment", StreamType.ALL),
|
||||
DISPOSITION_LYRICS("DISPOSITION:lyrics", StreamType.ALL),
|
||||
DISPOSITION_KARAOKE("DISPOSITION:karaoke", StreamType.ALL),
|
||||
DISPOSITION_FORCED("DISPOSITION:forced", StreamType.ALL),
|
||||
DISPOSITION_HEARING_IMPAIRED("DISPOSITION:hearing_impaired", StreamType.ALL),
|
||||
DISPOSITION_VISUAL_IMPAIRED("DISPOSITION:visual_impaired", StreamType.ALL),
|
||||
DISPOSITION_CLEAN_EFFECTS("DISPOSITION:clean_effects", StreamType.ALL),
|
||||
DISPOSITION_ATTACHED_PIC("DISPOSITION:attached_pic", StreamType.ALL),
|
||||
DISPOSITION_TIMED_THUMBNAILS("DISPOSITION:timed_thumbnails", StreamType.ALL),
|
||||
DISPOSITION_NON_DIEGETIC("DISPOSITION:non_diegetic", StreamType.ALL),
|
||||
DISPOSITION_CAPTIONS("DISPOSITION:captions", StreamType.ALL),
|
||||
DISPOSITION_DESCRIPTIONS("DISPOSITION:descriptions", StreamType.ALL),
|
||||
DISPOSITION_METADATA("DISPOSITION:metadata", StreamType.ALL),
|
||||
DISPOSITION_DEPENDENT("DISPOSITION:dependent", StreamType.ALL),
|
||||
DISPOSITION_STILL_IMAGE("DISPOSITION:still_image", StreamType.ALL),
|
||||
|
||||
/**
|
||||
* The language of the stream
|
||||
*/
|
||||
TAG_LANGUAGE("TAG:language", StreamType.ALL),
|
||||
|
||||
/**
|
||||
* The title of an audio stream
|
||||
*/
|
||||
TAG_TITLE("TAG:title", StreamType.ALL),
|
||||
TAG_BPS_ENG("TAG:BPS-eng", StreamType.ALL),
|
||||
TAG_DURATION_ENG("TAG:DURATION-eng", StreamType.ALL),
|
||||
TAG_NUMBER_OF_FRAMES_ENG("TAG:NUMBER_OF_FRAMES-eng", StreamType.ALL),
|
||||
TAG_NUMBER_OF_BYTES_ENG("TAG:NUMBER_OF_BYTES-eng", StreamType.ALL),
|
||||
TAG_SOURCE_ID_ENG("TAG:SOURCE_ID-eng", StreamType.ALL),
|
||||
TAG_SOURCE_ID("TAG:SOURCE_ID", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_APP_ENG("TAG:_STATISTICS_WRITING_APP-eng", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_APP("TAG:_STATISTICS_WRITING_APP", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_DATE_UTC_ENG("TAG:_STATISTICS_WRITING_DATE_UTC-eng", StreamType.ALL),
|
||||
TAG_STATISTICS_WRITING_DATE_UTC("TAG:_STATISTICS_WRITING_DATE_UTC", StreamType.ALL),
|
||||
TAG_STATISTICS_TAGS_ENG("TAG:_STATISTICS_TAGS-eng", StreamType.ALL),
|
||||
TAG_STATISTICS_TAGS("TAG:_STATISTICS_TAGS", StreamType.ALL),
|
||||
TAG_ENCODER("TAG:ENCODER", Set.of(StreamType.VIDEO)),
|
||||
TAG_DURATION("TAG:DURATION", StreamType.ALL),
|
||||
;
|
||||
|
||||
private static final Map<String, StreamTag> tagLookup = new HashMap<>();
|
||||
|
||||
private final @NotNull String tagString;
|
||||
private final Set<StreamType> applicableFor;
|
||||
|
||||
/**
|
||||
* Instantiates a new stream tag
|
||||
*
|
||||
* @param tagString <p>The tag string ffmpeg prints to specify this tag</p>
|
||||
*/
|
||||
StreamTag(@NotNull String tagString, @NotNull Set<StreamType> applicableFor) {
|
||||
this.tagString = tagString;
|
||||
this.applicableFor = applicableFor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the types of streams this tag is applicable for
|
||||
*
|
||||
* @return <p>The types of streams this tag is applicable for</p>
|
||||
*/
|
||||
@NotNull
|
||||
public Set<StreamType> getApplicableFor() {
|
||||
return new HashSet<>(this.applicableFor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stream tag defined by the given string
|
||||
*
|
||||
* @param input <p>The input string to parse</p>
|
||||
* @return <p>The corresponding stream tab, or null if not found</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static StreamTag getFromString(@NotNull String input) {
|
||||
if (tagLookup.isEmpty()) {
|
||||
for (StreamTag tag : StreamTag.values()) {
|
||||
tagLookup.put(tag.tagString, tag);
|
||||
}
|
||||
}
|
||||
return tagLookup.get(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.tagString;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A specifier for a type of stream
|
||||
*/
|
||||
public enum StreamType {
|
||||
|
||||
/**
|
||||
* A video stream
|
||||
*/
|
||||
VIDEO,
|
||||
|
||||
/**
|
||||
* An audio stream
|
||||
*/
|
||||
AUDIO,
|
||||
|
||||
/**
|
||||
* A subtitle stream
|
||||
*/
|
||||
SUBTITLE,
|
||||
;
|
||||
|
||||
/**
|
||||
* A set of all stream types
|
||||
*/
|
||||
public static final Set<StreamType> ALL = Set.of(VIDEO, AUDIO, SUBTITLE);
|
||||
|
||||
/**
|
||||
* A set of the video and subtitle stream types
|
||||
*/
|
||||
public static final Set<StreamType> VIDEO_SUBTITLE = Set.of(VIDEO, SUBTITLE);
|
||||
|
||||
}
|
@ -2,14 +2,14 @@ package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An object representation of a subtitle stream in a media file
|
||||
*/
|
||||
public class SubtitleStream extends AbstractStream implements StreamObject {
|
||||
|
||||
final private String title;
|
||||
final private String file;
|
||||
final private boolean isFullSubtitle;
|
||||
final private boolean isImageSubtitle;
|
||||
@ -18,22 +18,15 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
|
||||
/**
|
||||
* Instantiates a new subtitle stream
|
||||
*
|
||||
* @param codecName <p>The name of the codec for the subtitle stream.</p>
|
||||
* @param absoluteIndex <p>The index of the subtitle stream.</p>
|
||||
* @param streamInfo <p>All info about the stream</p>
|
||||
* @param relativeIndex <p>The index of the subtitle stream relative to other subtitle streams.</p>
|
||||
* @param language <p>The language of the subtitle stream.</p>
|
||||
* @param title <p>The title of the subtitle stream.</p>
|
||||
* @param file <p>The file containing the subtitle.</p>
|
||||
* @param isInternalStream <p>Whether this subtitle stream is in the video file itself</p>
|
||||
*/
|
||||
public SubtitleStream(@Nullable String codecName, int absoluteIndex, int relativeIndex, @Nullable String language,
|
||||
@Nullable String title, @NotNull String file, boolean isInternalStream) {
|
||||
this.codecName = codecName;
|
||||
this.absoluteIndex = absoluteIndex;
|
||||
this.language = language;
|
||||
this.title = title;
|
||||
public SubtitleStream(@NotNull Map<StreamTag, String> streamInfo, int relativeIndex,
|
||||
@NotNull String file, boolean isInternalStream) {
|
||||
super(streamInfo, relativeIndex);
|
||||
this.isFullSubtitle = isFullSubtitle();
|
||||
this.relativeIndex = relativeIndex;
|
||||
this.isImageSubtitle = isImageSubtitle();
|
||||
this.isInternalStream = isInternalStream;
|
||||
this.file = file;
|
||||
@ -46,15 +39,6 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the subtitle stream
|
||||
*
|
||||
* @return <p>The title of the subtitle stream.</p>
|
||||
*/
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file name of the file containing this subtitle
|
||||
*
|
||||
@ -120,4 +104,9 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
|
||||
!titleLowercase.contains("signs only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StreamType getStreamType() {
|
||||
return StreamType.SUBTITLE;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package net.knarcraft.ffmpegconverter.streams;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.utility.ValueParsingHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An object representation of a video stream in a media file
|
||||
*/
|
||||
@ -11,18 +16,13 @@ public class VideoStream extends AbstractStream implements StreamObject {
|
||||
/**
|
||||
* Instantiates a new video stream
|
||||
*
|
||||
* @param codec <p>The name of the codec for the video stream.</p>
|
||||
* @param absoluteIndex <p>The index of the video stream.</p>
|
||||
* @param streamInfo <p>All info about the stream</p>
|
||||
* @param relativeIndex <p>The index of the video stream relative to other video streams.</p>
|
||||
* @param width <p>The width of the video stream.</p>
|
||||
* @param height <p>The height of the video stream.</p>
|
||||
*/
|
||||
public VideoStream(String codec, int absoluteIndex, int relativeIndex, int width, int height) {
|
||||
this.codecName = codec;
|
||||
this.absoluteIndex = absoluteIndex;
|
||||
this.relativeIndex = relativeIndex;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
public VideoStream(@NotNull Map<StreamTag, String> streamInfo, int relativeIndex) {
|
||||
super(streamInfo, relativeIndex);
|
||||
this.width = ValueParsingHelper.parseInt(streamInfo.get(StreamTag.WIDTH), -1);
|
||||
this.height = ValueParsingHelper.parseInt(streamInfo.get(StreamTag.HEIGHT), -1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,4 +43,9 @@ public class VideoStream extends AbstractStream implements StreamObject {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StreamType getStreamType() {
|
||||
return StreamType.VIDEO;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
@ -25,6 +30,26 @@ public final class 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user