Adds external audio parsing and other fixes
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good
Trims subtitle tiles before checking if it's full Generalizes some stream parsing Fixes an exception when a stream tag is set, but has no value Looks for both subtitle and audio streams adjacent to the main file
This commit is contained in:
parent
ded88eb5b5
commit
92b46bdc9e
@ -48,7 +48,8 @@ public abstract class AbstractConverter implements Converter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void convert(@NotNull File file) throws IOException {
|
public void convert(@NotNull File file) throws IOException {
|
||||||
StreamProbeResult probeResult = FFMpegHelper.probeFile(this.ffprobePath, file, this.subtitleFormats);
|
StreamProbeResult probeResult = FFMpegHelper.probeFile(this.ffprobePath, file, this.subtitleFormats,
|
||||||
|
this.audioFormats);
|
||||||
if (probeResult.parsedStreams().isEmpty()) {
|
if (probeResult.parsedStreams().isEmpty()) {
|
||||||
throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" +
|
throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" +
|
||||||
" is not corrupt.");
|
" is not corrupt.");
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.knarcraft.ffmpegconverter.property;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of different stream types
|
||||||
|
*/
|
||||||
|
public enum StreamType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A video stream
|
||||||
|
*/
|
||||||
|
VIDEO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An audio stream
|
||||||
|
*/
|
||||||
|
AUDIO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A subtitle stream
|
||||||
|
*/
|
||||||
|
SUBTITLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* None of the above
|
||||||
|
*/
|
||||||
|
OTHER
|
||||||
|
|
||||||
|
}
|
@ -50,7 +50,7 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
|
|||||||
* @return <p>True if the subtitle translates everything.</p>
|
* @return <p>True if the subtitle translates everything.</p>
|
||||||
*/
|
*/
|
||||||
private boolean isFullSubtitle() {
|
private boolean isFullSubtitle() {
|
||||||
String titleLowercase = getTitle().toLowerCase();
|
String titleLowercase = getTitle().toLowerCase().trim();
|
||||||
return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z]+songs?.*") &&
|
return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z]+songs?.*") &&
|
||||||
!titleLowercase.matches(".*songs?[ &/a-z]+si(gn|ng)s?.*") &&
|
!titleLowercase.matches(".*songs?[ &/a-z]+si(gn|ng)s?.*") &&
|
||||||
!titleLowercase.matches(".*forced.*") &&
|
!titleLowercase.matches(".*forced.*") &&
|
||||||
|
@ -3,6 +3,7 @@ package net.knarcraft.ffmpegconverter.utility;
|
|||||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||||
import net.knarcraft.ffmpegconverter.container.ProcessResult;
|
import net.knarcraft.ffmpegconverter.container.ProcessResult;
|
||||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||||
|
import net.knarcraft.ffmpegconverter.property.StreamType;
|
||||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||||
import net.knarcraft.ffmpegconverter.streams.OtherStream;
|
import net.knarcraft.ffmpegconverter.streams.OtherStream;
|
||||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||||
@ -73,13 +74,15 @@ public final class FFMpegHelper {
|
|||||||
* @param ffprobePath <p>The path/command to ffprobe</p>
|
* @param ffprobePath <p>The path/command to ffprobe</p>
|
||||||
* @param file <p>The file to probe</p>
|
* @param file <p>The file to probe</p>
|
||||||
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
|
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
|
||||||
|
* @param audioFormats <p>The extensions to accept for external audio files</p>
|
||||||
* @return <p>A list of StreamObjects</p>
|
* @return <p>A list of StreamObjects</p>
|
||||||
* @throws IOException <p>If the process can't be readProcess</p>
|
* @throws IOException <p>If the process can't be readProcess</p>
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file,
|
public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file,
|
||||||
@NotNull List<String> subtitleFormats) throws IOException {
|
@NotNull List<String> subtitleFormats,
|
||||||
return parseStreams(ffprobePath, probeForStreams(ffprobePath, file), file, subtitleFormats);
|
@NotNull List<String> audioFormats) throws IOException {
|
||||||
|
return parseStreams(ffprobePath, probeForStreams(ffprobePath, file), file, subtitleFormats, audioFormats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,11 +287,27 @@ public final class FFMpegHelper {
|
|||||||
* @param streams <p>A list of all streams for the current file.</p>
|
* @param streams <p>A list of all streams for the current file.</p>
|
||||||
* @param file <p>The file currently being converted.</p>
|
* @param file <p>The file currently being converted.</p>
|
||||||
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
|
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
|
||||||
|
* @param audioFormats <p>The extensions to accept for external audio tracks</p>
|
||||||
* @return <p>A list of StreamObjects.</p>
|
* @return <p>A list of StreamObjects.</p>
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
private static StreamProbeResult parseStreams(@NotNull String ffprobePath, @NotNull List<String> streams,
|
private static StreamProbeResult parseStreams(@NotNull String ffprobePath, @NotNull List<String> streams,
|
||||||
@NotNull File file, @NotNull List<String> subtitleFormats) throws IOException {
|
@NotNull File file, @NotNull List<String> subtitleFormats,
|
||||||
|
@NotNull List<String> audioFormats) throws IOException {
|
||||||
|
StreamProbeResult probeResult = new StreamProbeResult(new ArrayList<>(List.of(file)), parseStreamObjects(streams));
|
||||||
|
getExternalStreams(probeResult, ffprobePath, file.getParentFile(), file.getName(), subtitleFormats);
|
||||||
|
getExternalStreams(probeResult, ffprobePath, file.getParentFile(), file.getName(), audioFormats);
|
||||||
|
return probeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the stream objects found in the given streams
|
||||||
|
*
|
||||||
|
* @param streams <p>The stream data to parse</p>
|
||||||
|
* @return <p>The parsed stream objects</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static List<StreamObject> parseStreamObjects(@NotNull List<String> streams) {
|
||||||
List<StreamObject> parsedStreams = new ArrayList<>();
|
List<StreamObject> parsedStreams = new ArrayList<>();
|
||||||
int relativeAudioIndex = 0;
|
int relativeAudioIndex = 0;
|
||||||
int relativeVideoIndex = 0;
|
int relativeVideoIndex = 0;
|
||||||
@ -297,31 +316,51 @@ public final class FFMpegHelper {
|
|||||||
for (String stream : streams) {
|
for (String stream : streams) {
|
||||||
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
||||||
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
||||||
|
StreamType streamType = getStreamType(streamInfo);
|
||||||
|
|
||||||
|
switch (streamType) {
|
||||||
|
case VIDEO:
|
||||||
|
parsedStreams.add(new VideoStream(streamInfo, 0, relativeVideoIndex++));
|
||||||
|
break;
|
||||||
|
case AUDIO:
|
||||||
|
parsedStreams.add(new AudioStream(streamInfo, 0, relativeAudioIndex++));
|
||||||
|
break;
|
||||||
|
case SUBTITLE:
|
||||||
|
parsedStreams.add(new SubtitleStream(streamInfo, 0, relativeSubtitleIndex++));
|
||||||
|
break;
|
||||||
|
case OTHER:
|
||||||
|
parsedStreams.add(new OtherStream(streamInfo, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsedStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the type of a stream from its stream info
|
||||||
|
*
|
||||||
|
* @param streamInfo <p>The information describing the stream</p>
|
||||||
|
* @return <p>The type of the stream</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static StreamType getStreamType(@NotNull Map<StreamTag, String> streamInfo) {
|
||||||
String codecType = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_TYPE), "");
|
String codecType = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_TYPE), "");
|
||||||
switch (codecType) {
|
switch (codecType) {
|
||||||
case "video":
|
case "video":
|
||||||
// Some attached covers are marked as video streams
|
// Some attached covers are marked as video streams
|
||||||
if (ValueParsingHelper.parseInt(streamInfo.get(StreamTag.DISPOSITION_ATTACHED_PIC), 0) != 1) {
|
if (ValueParsingHelper.parseInt(streamInfo.get(StreamTag.DISPOSITION_ATTACHED_PIC), 0) != 1) {
|
||||||
parsedStreams.add(new VideoStream(streamInfo, 0, relativeVideoIndex++));
|
return StreamType.VIDEO;
|
||||||
} else {
|
} else {
|
||||||
parsedStreams.add(new OtherStream(streamInfo, 0));
|
return StreamType.OTHER;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "audio":
|
case "audio":
|
||||||
parsedStreams.add(new AudioStream(streamInfo, 0, relativeAudioIndex++));
|
return StreamType.AUDIO;
|
||||||
break;
|
|
||||||
case "subtitle":
|
case "subtitle":
|
||||||
parsedStreams.add(new SubtitleStream(streamInfo, 0, relativeSubtitleIndex++));
|
return StreamType.SUBTITLE;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
parsedStreams.add(new OtherStream(streamInfo, 0));
|
return StreamType.OTHER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StreamProbeResult probeResult = new StreamProbeResult(List.of(file), parsedStreams);
|
|
||||||
getExternalSubtitles(probeResult, ffprobePath, file.getParentFile(), file.getName(), subtitleFormats);
|
|
||||||
return probeResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets stream info from the given raw stream info lines
|
* Gets stream info from the given raw stream info lines
|
||||||
@ -337,52 +376,64 @@ public final class FFMpegHelper {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String[] keyValue = part.split("=");
|
String[] keyValue = part.split("=");
|
||||||
|
String value = keyValue.length > 1 ? keyValue[1] : "";
|
||||||
|
|
||||||
StreamTag tag = StreamTag.getFromString(keyValue[0]);
|
StreamTag tag = StreamTag.getFromString(keyValue[0]);
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
streamInfo.put(tag, keyValue[1]);
|
streamInfo.put(tag, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return streamInfo;
|
return streamInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to find any external subtitles adjacent to the first input file, and appends it to the given probe result
|
* Tries to find any external files adjacent to the first input file, and appends it to the given probe result
|
||||||
*
|
*
|
||||||
* @param streamProbeResult <p>The stream probe result to append to</p>
|
* @param streamProbeResult <p>The stream probe result to append to</p>
|
||||||
* @param ffprobePath <p>The path/command to ffprobe</p>
|
* @param ffprobePath <p>The path/command to ffprobe</p>
|
||||||
* @param directory <p>The directory containing the file</p>
|
* @param directory <p>The directory containing the file</p>
|
||||||
* @param convertingFile <p>The first/main file to be converted</p>
|
* @param convertingFile <p>The first/main file to be converted</p>
|
||||||
* @param subtitleFormats <p>The extensions to accept for external subtitles</p>
|
* @param formats <p>The extensions to accept for external tracks</p>
|
||||||
*/
|
*/
|
||||||
private static void getExternalSubtitles(@NotNull StreamProbeResult streamProbeResult,
|
private static void getExternalStreams(@NotNull StreamProbeResult streamProbeResult,
|
||||||
@NotNull String ffprobePath, @NotNull File directory,
|
@NotNull String ffprobePath, @NotNull File directory,
|
||||||
@NotNull String convertingFile, @NotNull List<String> subtitleFormats) throws IOException {
|
@NotNull String convertingFile,
|
||||||
|
@NotNull List<String> formats) throws IOException {
|
||||||
//Find all files in the same directory with external subtitle formats
|
//Find all files in the same directory with external subtitle formats
|
||||||
File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
|
File[] files = FileUtil.listFilesRecursive(directory, formats, 1);
|
||||||
// TODO: Generalize this for external audio tracks
|
|
||||||
|
|
||||||
//Return early if no files were found
|
//Return early if no files were found
|
||||||
if (subtitleFiles == null) {
|
if (files == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String fileTitle = FileUtil.stripExtension(convertingFile);
|
String fileTitle = FileUtil.stripExtension(convertingFile);
|
||||||
List<File> subtitleFilesList = new ArrayList<>(Arrays.asList(subtitleFiles));
|
List<File> filesList = new ArrayList<>(Arrays.asList(files));
|
||||||
|
|
||||||
//Finds the files which are subtitles probably belonging to the file
|
//Finds the files which are subtitles probably belonging to the file
|
||||||
subtitleFilesList = ListUtil.getMatching(subtitleFilesList,
|
filesList = ListUtil.getMatching(filesList, (file) -> file.getName().contains(fileTitle));
|
||||||
(subtitleFile) -> subtitleFile.getName().contains(fileTitle));
|
|
||||||
|
|
||||||
for (File subtitleFile : subtitleFilesList) {
|
for (File file : filesList) {
|
||||||
int inputIndex = streamProbeResult.parsedFiles().size();
|
int inputIndex = streamProbeResult.parsedFiles().size();
|
||||||
streamProbeResult.parsedFiles().add(subtitleFile);
|
streamProbeResult.parsedFiles().add(file);
|
||||||
//Probe the files and add them to the result list
|
//Probe the files and add them to the result list
|
||||||
List<String> streams = probeForStreams(ffprobePath, subtitleFile);
|
List<String> streams = probeForStreams(ffprobePath, file);
|
||||||
int relativeIndex = 0;
|
|
||||||
|
int audioIndex = 0;
|
||||||
|
int subtitleIndex = 0;
|
||||||
|
int videoIndex = 0;
|
||||||
for (String stream : streams) {
|
for (String stream : streams) {
|
||||||
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
||||||
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
||||||
streamProbeResult.parsedStreams().add(new SubtitleStream(streamInfo, inputIndex, relativeIndex++));
|
StreamObject streamObject = null;
|
||||||
|
switch (getStreamType(streamInfo)) {
|
||||||
|
case SUBTITLE -> streamObject = new SubtitleStream(streamInfo, inputIndex, subtitleIndex++);
|
||||||
|
case AUDIO -> streamObject = new AudioStream(streamInfo, inputIndex, audioIndex++);
|
||||||
|
case VIDEO -> streamObject = new VideoStream(streamInfo, inputIndex, videoIndex++);
|
||||||
|
}
|
||||||
|
if (streamObject != null) {
|
||||||
|
streamProbeResult.parsedStreams().add(streamObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user