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,31 +316,51 @@ 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); | ||||
|  | ||||
|             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), ""); | ||||
|         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++)); | ||||
|                     return StreamType.VIDEO; | ||||
|                 } else { | ||||
|                         parsedStreams.add(new OtherStream(streamInfo, 0)); | ||||
|                     return StreamType.OTHER; | ||||
|                 } | ||||
|                     break; | ||||
|             case "audio": | ||||
|                     parsedStreams.add(new AudioStream(streamInfo, 0, relativeAudioIndex++)); | ||||
|                     break; | ||||
|                 return StreamType.AUDIO; | ||||
|             case "subtitle": | ||||
|                     parsedStreams.add(new SubtitleStream(streamInfo, 0, relativeSubtitleIndex++)); | ||||
|                     break; | ||||
|                 return StreamType.SUBTITLE; | ||||
|             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 | ||||
| @@ -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, | ||||
|     private static void getExternalStreams(@NotNull StreamProbeResult streamProbeResult, | ||||
|                                            @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 | ||||
|         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