Adds a converter to embed subtitles
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				KnarCraft/FFmpegConvert/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	KnarCraft/FFmpegConvert/pipeline/head This commit looks good
				
			This commit is contained in:
		
							
								
								
									
										14
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								pom.xml
									
									
									
									
									
								
							@@ -71,6 +71,20 @@
 | 
				
			|||||||
                    <target>${java.version}</target>
 | 
					                    <target>${java.version}</target>
 | 
				
			||||||
                </configuration>
 | 
					                </configuration>
 | 
				
			||||||
            </plugin>
 | 
					            </plugin>
 | 
				
			||||||
 | 
					            <plugin>
 | 
				
			||||||
 | 
					                <groupId>org.apache.maven.plugins</groupId>
 | 
				
			||||||
 | 
					                <artifactId>maven-jar-plugin</artifactId>
 | 
				
			||||||
 | 
					                <version>3.1.0</version>
 | 
				
			||||||
 | 
					                <configuration>
 | 
				
			||||||
 | 
					                    <archive>
 | 
				
			||||||
 | 
					                        <manifest>
 | 
				
			||||||
 | 
					                            <addClasspath>true</addClasspath>
 | 
				
			||||||
 | 
					                            <classpathPrefix>lib/</classpathPrefix>
 | 
				
			||||||
 | 
					                            <mainClass>net.knarcraft.ffmpegconverter.Main</mainClass>
 | 
				
			||||||
 | 
					                        </manifest>
 | 
				
			||||||
 | 
					                    </archive>
 | 
				
			||||||
 | 
					                </configuration>
 | 
				
			||||||
 | 
					            </plugin>
 | 
				
			||||||
        </plugins>
 | 
					        </plugins>
 | 
				
			||||||
    </build>
 | 
					    </build>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import net.knarcraft.ffmpegconverter.converter.DownScaleConverter;
 | 
				
			|||||||
import net.knarcraft.ffmpegconverter.converter.MKVToMP4Transcoder;
 | 
					import net.knarcraft.ffmpegconverter.converter.MKVToMP4Transcoder;
 | 
				
			||||||
import net.knarcraft.ffmpegconverter.converter.MkvH264Converter;
 | 
					import net.knarcraft.ffmpegconverter.converter.MkvH264Converter;
 | 
				
			||||||
import net.knarcraft.ffmpegconverter.converter.MkvH265ReducedConverter;
 | 
					import net.knarcraft.ffmpegconverter.converter.MkvH265ReducedConverter;
 | 
				
			||||||
 | 
					import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed;
 | 
				
			||||||
import net.knarcraft.ffmpegconverter.converter.VideoConverter;
 | 
					import net.knarcraft.ffmpegconverter.converter.VideoConverter;
 | 
				
			||||||
import net.knarcraft.ffmpegconverter.converter.WebVideoConverter;
 | 
					import net.knarcraft.ffmpegconverter.converter.WebVideoConverter;
 | 
				
			||||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
 | 
					import net.knarcraft.ffmpegconverter.utility.FileUtil;
 | 
				
			||||||
@@ -66,7 +67,7 @@ class Main {
 | 
				
			|||||||
    private static Converter loadConverter() throws IOException {
 | 
					    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" +
 | 
					        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 " +
 | 
					                "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", 1, 8);
 | 
					                "converter\n7. MKV to MP4 transcoder\n8. DownScaleConverter\n9. mp4 Subtitle Embed", 1, 9);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        switch (choice) {
 | 
					        switch (choice) {
 | 
				
			||||||
            case 1:
 | 
					            case 1:
 | 
				
			||||||
@@ -85,6 +86,8 @@ class Main {
 | 
				
			|||||||
                return generateMKVToMP4Transcoder();
 | 
					                return generateMKVToMP4Transcoder();
 | 
				
			||||||
            case 8:
 | 
					            case 8:
 | 
				
			||||||
                return generateDownScaleConverter();
 | 
					                return generateDownScaleConverter();
 | 
				
			||||||
 | 
					            case 9:
 | 
				
			||||||
 | 
					                return new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					package net.knarcraft.ffmpegconverter.converter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.knarcraft.ffmpegconverter.streams.StreamObject;
 | 
				
			||||||
 | 
					import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
 | 
				
			||||||
 | 
					import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A converter to embed adjacent subtitles into mp4 files
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class SubtitleEmbed extends AbstractConverter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Initializes variables used by the abstract converter
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param ffprobePath <p>Path/command to ffprobe.</p>
 | 
				
			||||||
 | 
					     * @param ffmpegPath  <p>Path/command to ffmpeg.</p>
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public SubtitleEmbed(String ffprobePath, String ffmpegPath) {
 | 
				
			||||||
 | 
					        super(null);
 | 
				
			||||||
 | 
					        this.ffprobePath = ffprobePath;
 | 
				
			||||||
 | 
					        this.ffmpegPath = ffmpegPath;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String[] getValidFormats() {
 | 
				
			||||||
 | 
					        return new String[]{"mp4"};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String[] generateConversionCommand(String executable, File file, List<StreamObject> streams, String outFile) {
 | 
				
			||||||
 | 
					        List<String> command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, file.getName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<SubtitleStream> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
 | 
				
			||||||
 | 
					        List<SubtitleStream> externalSubtitles = new ArrayList<>();
 | 
				
			||||||
 | 
					        for (SubtitleStream subtitleStream : subtitleStreams) {
 | 
				
			||||||
 | 
					            if (!subtitleStream.isInternalSubtitle()) {
 | 
				
			||||||
 | 
					                externalSubtitles.add(subtitleStream);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (externalSubtitles.isEmpty()) {
 | 
				
			||||||
 | 
					            System.err.println("No external subtitles found for " + file.getName());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (SubtitleStream subtitleStream : externalSubtitles) {
 | 
				
			||||||
 | 
					            command.add("-i");
 | 
				
			||||||
 | 
					            command.add(subtitleStream.getFile());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.add("-c");
 | 
				
			||||||
 | 
					        command.add("copy");
 | 
				
			||||||
 | 
					        command.add("-c:s");
 | 
				
			||||||
 | 
					        command.add("mov_text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int i = 0;
 | 
				
			||||||
 | 
					        for (SubtitleStream subtitleStream : subtitleStreams) {
 | 
				
			||||||
 | 
					            if (subtitleStream.getLanguage() != null) {
 | 
				
			||||||
 | 
					                command.add("-metadata:s:s:" + i);
 | 
				
			||||||
 | 
					                command.add("language=" + subtitleStream.getLanguage());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            i++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.debug) {
 | 
				
			||||||
 | 
					            FFMpegHelper.addDebugArguments(command, 50, 120);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.add(outFile);
 | 
				
			||||||
 | 
					        return command.toArray(new String[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,9 @@
 | 
				
			|||||||
package net.knarcraft.ffmpegconverter.streams;
 | 
					package net.knarcraft.ffmpegconverter.streams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.knarcraft.ffmpegconverter.utility.FileUtil;
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.NotNull;
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.Nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * An object representation of a subtitle stream in a media file
 | 
					 * An object representation of a subtitle stream in a media file
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -22,8 +26,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
 | 
				
			|||||||
     * @param file             <p>The file containing the subtitle.</p>
 | 
					     * @param file             <p>The file containing the subtitle.</p>
 | 
				
			||||||
     * @param isInternalStream <p>Whether this subtitle stream is in the video file itself</p>
 | 
					     * @param isInternalStream <p>Whether this subtitle stream is in the video file itself</p>
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public SubtitleStream(String codecName, int absoluteIndex, int relativeIndex, String language, String title,
 | 
					    public SubtitleStream(@Nullable String codecName, int absoluteIndex, int relativeIndex, @Nullable String language,
 | 
				
			||||||
                          String file, boolean isInternalStream) {
 | 
					                          @Nullable String title, @NotNull String file, boolean isInternalStream) {
 | 
				
			||||||
        this.codecName = codecName;
 | 
					        this.codecName = codecName;
 | 
				
			||||||
        this.absoluteIndex = absoluteIndex;
 | 
					        this.absoluteIndex = absoluteIndex;
 | 
				
			||||||
        this.language = language;
 | 
					        this.language = language;
 | 
				
			||||||
@@ -33,6 +37,13 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
 | 
				
			|||||||
        this.isImageSubtitle = isImageSubtitle();
 | 
					        this.isImageSubtitle = isImageSubtitle();
 | 
				
			||||||
        this.isInternalStream = isInternalStream;
 | 
					        this.isInternalStream = isInternalStream;
 | 
				
			||||||
        this.file = file;
 | 
					        this.file = file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.language == null || this.language.isEmpty()) {
 | 
				
			||||||
 | 
					            String possibleLanguage = FileUtil.getLanguage(file);
 | 
				
			||||||
 | 
					            if (possibleLanguage != null) {
 | 
				
			||||||
 | 
					                this.language = possibleLanguage;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ import java.util.List;
 | 
				
			|||||||
public final class FFMpegHelper {
 | 
					public final class FFMpegHelper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå";
 | 
					    private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå";
 | 
				
			||||||
 | 
					    private static String[] subtitleFormats = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private FFMpegHelper() {
 | 
					    private FFMpegHelper() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -305,7 +306,9 @@ public final class FFMpegHelper {
 | 
				
			|||||||
            throws IOException {
 | 
					            throws IOException {
 | 
				
			||||||
        List<StreamObject> parsedStreams = new ArrayList<>();
 | 
					        List<StreamObject> parsedStreams = new ArrayList<>();
 | 
				
			||||||
        //Find all files in the same directory with external subtitle formats
 | 
					        //Find all files in the same directory with external subtitle formats
 | 
				
			||||||
        String[] subtitleFormats = FileUtil.readFileLines("subtitle_formats.txt");
 | 
					        if (subtitleFormats == null) {
 | 
				
			||||||
 | 
					            subtitleFormats = FileUtil.readFileLines("subtitle_formats.txt");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
 | 
					        File[] subtitleFiles = FileUtil.listFilesRecursive(directory, subtitleFormats, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //Return early if no files were found
 | 
					        //Return early if no files were found
 | 
				
			||||||
@@ -315,9 +318,11 @@ public final class FFMpegHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        String fileTitle = FileUtil.stripExtension(convertingFile);
 | 
					        String fileTitle = FileUtil.stripExtension(convertingFile);
 | 
				
			||||||
        List<File> subtitleFilesList = new ArrayList<>(Arrays.asList(subtitleFiles));
 | 
					        List<File> subtitleFilesList = new ArrayList<>(Arrays.asList(subtitleFiles));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //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,
 | 
					        subtitleFilesList = ListUtil.getMatching(subtitleFilesList,
 | 
				
			||||||
                (subtitleFile) -> subtitleFile.getName().contains(fileTitle));
 | 
					                (subtitleFile) -> subtitleFile.getName().contains(fileTitle));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (File subtitleFile : subtitleFilesList) {
 | 
					        for (File subtitleFile : subtitleFilesList) {
 | 
				
			||||||
            //Probe the files and add them to the result list
 | 
					            //Probe the files and add them to the result list
 | 
				
			||||||
            String[] streams = probeForStreams(ffprobePath, subtitleFile);
 | 
					            String[] streams = probeForStreams(ffprobePath, subtitleFile);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
package net.knarcraft.ffmpegconverter.utility;
 | 
					package net.knarcraft.ffmpegconverter.utility;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.NotNull;
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.Nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.BufferedReader;
 | 
					import java.io.BufferedReader;
 | 
				
			||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
@@ -139,4 +142,43 @@ public final class FileUtil {
 | 
				
			|||||||
        return file.substring(0, file.lastIndexOf('.'));
 | 
					        return file.substring(0, file.lastIndexOf('.'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the locale specifying the language of the given file name
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param fileName <p>The file name to check</p>
 | 
				
			||||||
 | 
					     * @return <p>The locale, or null if no locale could be parsed</p>
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static @Nullable String getLanguage(@NotNull String fileName) {
 | 
				
			||||||
 | 
					        fileName = stripExtension(fileName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String possibleLanguage = getExtension(fileName);
 | 
				
			||||||
 | 
					        // NRK Nett-TV has a tendency to use nb-ttv for Norwegian for some reason 
 | 
				
			||||||
 | 
					        possibleLanguage = possibleLanguage.replace("nb-ttv", "nb-nor");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Some languages are specified by using "-en" or "-English" or ".en" or ".English" at the end of file names
 | 
				
			||||||
 | 
					        if (possibleLanguage.length() <= 1 || (possibleLanguage.length() >= 4 &&
 | 
				
			||||||
 | 
					                (!possibleLanguage.contains("-") || possibleLanguage.length() >= 8))) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Hope the text is an actual valid language
 | 
				
			||||||
 | 
					        if (!possibleLanguage.contains("-")) {
 | 
				
			||||||
 | 
					            return possibleLanguage;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make sure the "-" has at least two characters on each side
 | 
				
			||||||
 | 
					        String[] parts = possibleLanguage.split("-");
 | 
				
			||||||
 | 
					        if (parts[0].length() < 2 || parts[1].length() < 2) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (parts[1].length() == 3) {
 | 
				
			||||||
 | 
					            // Return three-letter country code
 | 
				
			||||||
 | 
					            return parts[1].toLowerCase();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Return en-US country code
 | 
				
			||||||
 | 
					            return parts[0].substring(0, 2).toLowerCase() + "-" + parts[1].substring(0, 2).toUpperCase();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
4
 | 
					5
 | 
				
			||||||
idx
 | 
					idx
 | 
				
			||||||
sub
 | 
					sub
 | 
				
			||||||
srt
 | 
					srt
 | 
				
			||||||
ass
 | 
					ass
 | 
				
			||||||
 | 
					vtt
 | 
				
			||||||
		Reference in New Issue
	
	Block a user