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:
		@@ -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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user