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
|
||||
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()) {
|
||||
throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" +
|
||||
" 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>
|
||||
*/
|
||||
private boolean isFullSubtitle() {
|
||||
String titleLowercase = getTitle().toLowerCase();
|
||||
String titleLowercase = getTitle().toLowerCase().trim();
|
||||
return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z]+songs?.*") &&
|
||||
!titleLowercase.matches(".*songs?[ &/a-z]+si(gn|ng)s?.*") &&
|
||||
!titleLowercase.matches(".*forced.*") &&
|
||||
|
@ -3,6 +3,7 @@ package net.knarcraft.ffmpegconverter.utility;
|
||||
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
|
||||
import net.knarcraft.ffmpegconverter.container.ProcessResult;
|
||||
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
|
||||
import net.knarcraft.ffmpegconverter.property.StreamType;
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.OtherStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
@ -73,13 +74,15 @@ public final class FFMpegHelper {
|
||||
* @param ffprobePath <p>The path/command to ffprobe</p>
|
||||
* @param file <p>The file to probe</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>
|
||||
* @throws IOException <p>If the process can't be readProcess</p>
|
||||
*/
|
||||
@NotNull
|
||||
public static StreamProbeResult probeFile(@NotNull String ffprobePath, @NotNull File file,
|
||||
@NotNull List<String> subtitleFormats) throws IOException {
|
||||
return parseStreams(ffprobePath, probeForStreams(ffprobePath, file), file, subtitleFormats);
|
||||
@NotNull List<String> 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 file <p>The file currently being converted.</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>
|
||||
*/
|
||||
@NotNull
|
||||
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<>();
|
||||
int relativeAudioIndex = 0;
|
||||
int relativeVideoIndex = 0;
|
||||
@ -297,30 +316,50 @@ public final class FFMpegHelper {
|
||||
for (String stream : streams) {
|
||||
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
||||
Map<StreamTag, String> streamInfo = getStreamInfo(streamParts);
|
||||
StreamType streamType = getStreamType(streamInfo);
|
||||
|
||||
String codecType = ValueParsingHelper.parseString(streamInfo.get(StreamTag.CODEC_TYPE), "");
|
||||
switch (codecType) {
|
||||
case "video":
|
||||
// Some attached covers are marked as video streams
|
||||
if (ValueParsingHelper.parseInt(streamInfo.get(StreamTag.DISPOSITION_ATTACHED_PIC), 0) != 1) {
|
||||
parsedStreams.add(new VideoStream(streamInfo, 0, relativeVideoIndex++));
|
||||
} else {
|
||||
parsedStreams.add(new OtherStream(streamInfo, 0));
|
||||
}
|
||||
switch (streamType) {
|
||||
case VIDEO:
|
||||
parsedStreams.add(new VideoStream(streamInfo, 0, relativeVideoIndex++));
|
||||
break;
|
||||
case "audio":
|
||||
case AUDIO:
|
||||
parsedStreams.add(new AudioStream(streamInfo, 0, relativeAudioIndex++));
|
||||
break;
|
||||
case "subtitle":
|
||||
case SUBTITLE:
|
||||
parsedStreams.add(new SubtitleStream(streamInfo, 0, relativeSubtitleIndex++));
|
||||
break;
|
||||
default:
|
||||
case OTHER:
|
||||
parsedStreams.add(new OtherStream(streamInfo, 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
StreamProbeResult probeResult = new StreamProbeResult(List.of(file), parsedStreams);
|
||||
getExternalSubtitles(probeResult, ffprobePath, file.getParentFile(), file.getName(), subtitleFormats);
|
||||
return probeResult;
|
||||
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), "");
|
||||
switch (codecType) {
|
||||
case "video":
|
||||
// Some attached covers are marked as video streams
|
||||
if (ValueParsingHelper.parseInt(streamInfo.get(StreamTag.DISPOSITION_ATTACHED_PIC), 0) != 1) {
|
||||
return StreamType.VIDEO;
|
||||
} else {
|
||||
return StreamType.OTHER;
|
||||
}
|
||||
case "audio":
|
||||
return StreamType.AUDIO;
|
||||
case "subtitle":
|
||||
return StreamType.SUBTITLE;
|
||||
default:
|
||||
return StreamType.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,52 +376,64 @@ public final class FFMpegHelper {
|
||||
continue;
|
||||
}
|
||||
String[] keyValue = part.split("=");
|
||||
String value = keyValue.length > 1 ? keyValue[1] : "";
|
||||
|
||||
StreamTag tag = StreamTag.getFromString(keyValue[0]);
|
||||
if (tag != null) {
|
||||
streamInfo.put(tag, keyValue[1]);
|
||||
streamInfo.put(tag, value);
|
||||
}
|
||||
}
|
||||
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 ffprobePath <p>The path/command to ffprobe</p>
|
||||
* @param directory <p>The directory containing the file</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,
|
||||
@NotNull String ffprobePath, @NotNull File directory,
|
||||
@NotNull String convertingFile, @NotNull List<String> subtitleFormats) throws IOException {
|
||||
private static void getExternalStreams(@NotNull StreamProbeResult streamProbeResult,
|
||||
@NotNull String ffprobePath, @NotNull File directory,
|
||||
@NotNull String convertingFile,
|
||||
@NotNull List<String> formats) throws IOException {
|
||||
//Find all files in the same directory with external subtitle formats
|
||||
File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
|
||||
// TODO: Generalize this for external audio tracks
|
||||
File[] files = FileUtil.listFilesRecursive(directory, formats, 1);
|
||||
|
||||
//Return early if no files were found
|
||||
if (subtitleFiles == null) {
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
subtitleFilesList = ListUtil.getMatching(subtitleFilesList,
|
||||
(subtitleFile) -> subtitleFile.getName().contains(fileTitle));
|
||||
filesList = ListUtil.getMatching(filesList, (file) -> file.getName().contains(fileTitle));
|
||||
|
||||
for (File subtitleFile : subtitleFilesList) {
|
||||
for (File file : filesList) {
|
||||
int inputIndex = streamProbeResult.parsedFiles().size();
|
||||
streamProbeResult.parsedFiles().add(subtitleFile);
|
||||
streamProbeResult.parsedFiles().add(file);
|
||||
//Probe the files and add them to the result list
|
||||
List<String> streams = probeForStreams(ffprobePath, subtitleFile);
|
||||
int relativeIndex = 0;
|
||||
List<String> streams = probeForStreams(ffprobePath, file);
|
||||
|
||||
int audioIndex = 0;
|
||||
int subtitleIndex = 0;
|
||||
int videoIndex = 0;
|
||||
for (String stream : streams) {
|
||||
String[] streamParts = stream.split(PROBE_SPLIT_CHARACTER);
|
||||
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