From 2c75d91ccefb701201a056974232851fab74c4a2 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Wed, 27 Mar 2024 12:36:11 +0100 Subject: [PATCH] Adds a converter to embed subtitles --- pom.xml | 14 ++++ .../net/knarcraft/ffmpegconverter/Main.java | 5 +- .../converter/SubtitleEmbed.java | 76 +++++++++++++++++++ .../streams/SubtitleStream.java | 15 +++- .../ffmpegconverter/utility/FFMpegHelper.java | 7 +- .../ffmpegconverter/utility/FileUtil.java | 42 ++++++++++ src/main/resources/subtitle_formats.txt | 5 +- 7 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java diff --git a/pom.xml b/pom.xml index b93eb2e..1909335 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,20 @@ ${java.version} + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + true + lib/ + net.knarcraft.ffmpegconverter.Main + + + + diff --git a/src/main/java/net/knarcraft/ffmpegconverter/Main.java b/src/main/java/net/knarcraft/ffmpegconverter/Main.java index c4a58d8..25f114b 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/Main.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/Main.java @@ -7,6 +7,7 @@ import net.knarcraft.ffmpegconverter.converter.DownScaleConverter; import net.knarcraft.ffmpegconverter.converter.MKVToMP4Transcoder; import net.knarcraft.ffmpegconverter.converter.MkvH264Converter; import net.knarcraft.ffmpegconverter.converter.MkvH265ReducedConverter; +import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed; import net.knarcraft.ffmpegconverter.converter.VideoConverter; import net.knarcraft.ffmpegconverter.converter.WebVideoConverter; import net.knarcraft.ffmpegconverter.utility.FileUtil; @@ -66,7 +67,7 @@ class Main { 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" + "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) { case 1: @@ -85,6 +86,8 @@ class Main { return generateMKVToMP4Transcoder(); case 8: return generateDownScaleConverter(); + case 9: + return new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH); } return null; } diff --git a/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java b/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java new file mode 100644 index 0000000..209f724 --- /dev/null +++ b/src/main/java/net/knarcraft/ffmpegconverter/converter/SubtitleEmbed.java @@ -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

Path/command to ffprobe.

+ * @param ffmpegPath

Path/command to ffmpeg.

+ */ + 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 streams, String outFile) { + List command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, file.getName()); + + List subtitleStreams = filterStreamsByType(streams, SubtitleStream.class); + List 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]); + } + +} diff --git a/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java b/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java index 12b0073..4fe24e7 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/streams/SubtitleStream.java @@ -1,5 +1,9 @@ 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 */ @@ -22,8 +26,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject { * @param file

The file containing the subtitle.

* @param isInternalStream

Whether this subtitle stream is in the video file itself

*/ - public SubtitleStream(String codecName, int absoluteIndex, int relativeIndex, String language, String title, - String file, boolean isInternalStream) { + public SubtitleStream(@Nullable String codecName, int absoluteIndex, int relativeIndex, @Nullable String language, + @Nullable String title, @NotNull String file, boolean isInternalStream) { this.codecName = codecName; this.absoluteIndex = absoluteIndex; this.language = language; @@ -33,6 +37,13 @@ public class SubtitleStream extends AbstractStream implements StreamObject { this.isImageSubtitle = isImageSubtitle(); this.isInternalStream = isInternalStream; this.file = file; + + if (this.language == null || this.language.isEmpty()) { + String possibleLanguage = FileUtil.getLanguage(file); + if (possibleLanguage != null) { + this.language = possibleLanguage; + } + } } /** diff --git a/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java b/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java index 1a8f863..5a47779 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/utility/FFMpegHelper.java @@ -19,6 +19,7 @@ import java.util.List; public final class FFMpegHelper { private static final String PROBE_SPLIT_CHARACTER = "øæåÆØå"; + private static String[] subtitleFormats = null; private FFMpegHelper() { @@ -305,7 +306,9 @@ public final class FFMpegHelper { throws IOException { List parsedStreams = new ArrayList<>(); //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); //Return early if no files were found @@ -315,9 +318,11 @@ public final class FFMpegHelper { String fileTitle = FileUtil.stripExtension(convertingFile); List subtitleFilesList = new ArrayList<>(Arrays.asList(subtitleFiles)); + //Finds the files which are subtitles probably belonging to the file subtitleFilesList = ListUtil.getMatching(subtitleFilesList, (subtitleFile) -> subtitleFile.getName().contains(fileTitle)); + for (File subtitleFile : subtitleFilesList) { //Probe the files and add them to the result list String[] streams = probeForStreams(ffprobePath, subtitleFile); diff --git a/src/main/java/net/knarcraft/ffmpegconverter/utility/FileUtil.java b/src/main/java/net/knarcraft/ffmpegconverter/utility/FileUtil.java index f8d1c13..94386ba 100644 --- a/src/main/java/net/knarcraft/ffmpegconverter/utility/FileUtil.java +++ b/src/main/java/net/knarcraft/ffmpegconverter/utility/FileUtil.java @@ -1,5 +1,8 @@ package net.knarcraft.ffmpegconverter.utility; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -139,4 +142,43 @@ public final class FileUtil { return file.substring(0, file.lastIndexOf('.')); } + /** + * Gets the locale specifying the language of the given file name + * + * @param fileName

The file name to check

+ * @return

The locale, or null if no locale could be parsed

+ */ + 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(); + } + } + } diff --git a/src/main/resources/subtitle_formats.txt b/src/main/resources/subtitle_formats.txt index bbd093e..4dfd314 100644 --- a/src/main/resources/subtitle_formats.txt +++ b/src/main/resources/subtitle_formats.txt @@ -1,5 +1,6 @@ -4 +5 idx sub srt -ass \ No newline at end of file +ass +vtt \ No newline at end of file