Adds a new converter for purely re-ordering and filtering streams
All checks were successful
KnarCraft/FFmpegConvert/pipeline/head This commit looks good

This commit is contained in:
Kristian Knarvik 2024-05-20 19:33:51 +02:00
parent 380a1b800a
commit c3c89fcb75
4 changed files with 168 additions and 19 deletions

View File

@ -8,6 +8,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.StreamOrderConverter;
import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed; import net.knarcraft.ffmpegconverter.converter.SubtitleEmbed;
import net.knarcraft.ffmpegconverter.converter.VideoConverter; import net.knarcraft.ffmpegconverter.converter.VideoConverter;
import net.knarcraft.ffmpegconverter.converter.WebAnimeConverter; import net.knarcraft.ffmpegconverter.converter.WebAnimeConverter;
@ -94,7 +95,8 @@ public class FFMpegConvert {
7. MKV to MP4 transcoder 7. MKV to MP4 transcoder
8. DownScaleConverter 8. DownScaleConverter
9. mp4 Subtitle Embed 9. mp4 Subtitle Embed
10. Anime to h265 all streams""", 1, 10); 10. Anime to h265 all streams
11. Stream reorder""", 1, 11);
return switch (choice) { return switch (choice) {
case 1 -> generateWebAnimeConverter(); case 1 -> generateWebAnimeConverter();
@ -107,6 +109,7 @@ public class FFMpegConvert {
case 8 -> generateDownScaleConverter(); case 8 -> generateDownScaleConverter();
case 9 -> new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH); case 9 -> new SubtitleEmbed(FFPROBE_PATH, FFMPEG_PATH);
case 10 -> generateAnimeConverter(); case 10 -> generateAnimeConverter();
case 11 -> generateStreamOrderConverter();
default -> null; default -> null;
}; };
} }
@ -194,6 +197,34 @@ public class FFMpegConvert {
} }
} }
/**
* Initializes and returns a stream reorder converter
*
* @return <p>The initialized stream order converter</p>
*/
@Nullable
private static Converter generateStreamOrderConverter() {
OutputUtil.println("Note that a * in the sort order matches any stream not yet matched, and 0 matches " +
"undefined language streams. The subtitle name filter will include any streams with titles " +
"containing the filter, but if it contains RegEx expressions, a RegEx match will be performed " +
"instead.\nYour input: ");
OutputUtil.println("<Audio sort order> <Subtitle sort order> [Subtitle name filter]");
List<String> input = readInput(3);
if (input.size() < 2) {
return null;
}
String audioLanguages = input.get(0);
String subtitleLanguages = input.get(1);
String subtitleNameFilter = "*";
if (input.size() > 2) {
subtitleNameFilter = input.get(2);
}
return new StreamOrderConverter(FFPROBE_PATH, FFMPEG_PATH, audioLanguages, subtitleLanguages, subtitleNameFilter);
}
/** /**
* Initializes and returns the anime converter * Initializes and returns the anime converter
* *

View File

@ -0,0 +1,129 @@
package net.knarcraft.ffmpegconverter.converter;
import net.knarcraft.ffmpegconverter.FFMpegConvert;
import net.knarcraft.ffmpegconverter.config.Configuration;
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
import net.knarcraft.ffmpegconverter.converter.module.ConverterModule;
import net.knarcraft.ffmpegconverter.converter.module.DebugModule;
import net.knarcraft.ffmpegconverter.converter.module.ModuleExecutor;
import net.knarcraft.ffmpegconverter.converter.module.mapping.MapAllModule;
import net.knarcraft.ffmpegconverter.converter.module.mapping.SetDefaultStreamModule;
import net.knarcraft.ffmpegconverter.converter.module.output.CopyAudioModule;
import net.knarcraft.ffmpegconverter.converter.module.output.CopySubtitlesModule;
import net.knarcraft.ffmpegconverter.converter.module.output.CopyVideoModule;
import net.knarcraft.ffmpegconverter.converter.module.output.FastStartModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetOutputFileModule;
import net.knarcraft.ffmpegconverter.converter.module.output.SetStreamLanguageModule;
import net.knarcraft.ffmpegconverter.converter.sorter.AudioLanguageSorter;
import net.knarcraft.ffmpegconverter.converter.sorter.MinimalSubtitleSorter;
import net.knarcraft.ffmpegconverter.converter.sorter.SpecialAudioSorter;
import net.knarcraft.ffmpegconverter.converter.sorter.StreamSorter;
import net.knarcraft.ffmpegconverter.converter.sorter.SubtitleLanguageSorter;
import net.knarcraft.ffmpegconverter.converter.sorter.SubtitleTitleSorter;
import net.knarcraft.ffmpegconverter.property.MinimalSubtitlePreference;
import net.knarcraft.ffmpegconverter.streams.AudioStream;
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
import net.knarcraft.ffmpegconverter.streams.VideoStream;
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A converter for converting anime, keeping all streams
*/
public class StreamOrderConverter extends AbstractConverter {
private final List<String> audioLanguages;
private final List<String> subtitleLanguages;
private final MinimalSubtitlePreference subtitlePreference;
private final String subtitleNameFilter;
/**
* Instantiates a new anime converter
*
* @param ffprobePath <p>Path/command to ffprobe.</p>
* @param ffmpegPath <p>Path/command to ffmpeg.</p>
* @param audioLanguages <p>The language priority for languages</p>
* @param subtitleLanguages <p>The language priority for subtitles</p>
*/
public StreamOrderConverter(@NotNull String ffprobePath, @NotNull String ffmpegPath,
@NotNull String audioLanguages, @NotNull String subtitleLanguages,
@NotNull String subtitleNameFilter) {
super("mkv");
Configuration configuration = FFMpegConvert.getConfiguration();
this.ffprobePath = ffprobePath;
this.ffmpegPath = ffmpegPath;
this.audioLanguages = List.of(audioLanguages.split(","));
this.subtitleLanguages = List.of(subtitleLanguages.split(","));
this.subtitlePreference = configuration.getMinimalSubtitlePreference();
this.subtitleNameFilter = subtitleNameFilter;
}
@Override
@Nullable
public FFMpegCommand generateConversionCommand(@NotNull String executable,
@NotNull StreamProbeResult probeResult,
@NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
List<ConverterModule> modules = new ArrayList<>();
if (this.debug) {
modules.add(new DebugModule(90, 120));
}
modules.add(new FastStartModule());
//Map all video streams
List<VideoStream> videoStreams = probeResult.getVideoStreams();
modules.add(new MapAllModule<>(videoStreams));
setOutputIndexes(videoStreams);
//Get the first audio stream in accordance with chosen languages
StreamSorter<AudioStream> audioSorter = new AudioLanguageSorter(this.audioLanguages)
.append(new SpecialAudioSorter(MinimalSubtitlePreference.REJECT));
List<AudioStream> sortedAudio = audioSorter.chainSort(probeResult.getAudioStreams());
setOutputIndexes(sortedAudio);
modules.add(new MapAllModule<>(sortedAudio));
modules.add(new SetDefaultStreamModule<>(sortedAudio, 0));
modules.add(new SetStreamLanguageModule<>(sortedAudio));
//Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
StreamSorter<SubtitleStream> subtitleSorter = new SubtitleTitleSorter(
List.of(this.subtitleNameFilter.split(",")))
.append(new SubtitleLanguageSorter(this.subtitleLanguages))
.append(new MinimalSubtitleSorter(this.subtitlePreference));
List<SubtitleStream> sortedSubtitles = subtitleSorter.chainSort(probeResult.getSubtitleStreams());
setOutputIndexes(sortedSubtitles);
modules.add(new MapAllModule<>(sortedSubtitles));
modules.add(new SetDefaultStreamModule<>(sortedSubtitles, 0));
modules.add(new SetStreamLanguageModule<>(sortedSubtitles));
OutputUtil.printDebug("Subtitle preference: " + this.subtitleLanguages + ", Subtitle name filter: " +
this.subtitleNameFilter + ", Minimal Subtitle Preference: " + this.subtitlePreference.name() +
", Sorted order: " + Arrays.toString(sortedSubtitles.toArray()));
for (AudioStream audioStream : sortedAudio) {
modules.add(new CopyAudioModule(audioStream));
}
modules.add(new CopySubtitlesModule());
modules.add(new CopyVideoModule());
// Map all attached streams, such as fonts and covers
modules.add(new MapAllModule<>(probeResult.getOtherStreams()));
modules.add(new SetOutputFileModule(outFile));
new ModuleExecutor(command, modules).execute();
return command;
}
@Override
@NotNull
public List<String> getValidFormats() {
return this.videoFormats;
}
}

View File

@ -1,7 +1,7 @@
package net.knarcraft.ffmpegconverter.converter.sorter; package net.knarcraft.ffmpegconverter.converter.sorter;
import net.knarcraft.ffmpegconverter.FFMpegConvert;
import net.knarcraft.ffmpegconverter.streams.SubtitleStream; import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@ -45,17 +45,15 @@ public class SubtitleTitleSorter extends AbstractSorter<SubtitleStream> {
boolean isRegEx = isValidRegularExpression(filter) && hasSpecialRegexCharacters(filter); boolean isRegEx = isValidRegularExpression(filter) && hasSpecialRegexCharacters(filter);
if (FFMpegConvert.getConfiguration().isDebugEnabled()) { OutputUtil.printDebug("Filtering subtitles by filter " + filter + ". RegEx is " + isRegEx);
System.out.println("Filtering subtitles by filter " + filter + ". RegEx is " + isRegEx);
}
for (SubtitleStream subtitleStream : input) { for (SubtitleStream subtitleStream : input) {
String title = subtitleStream.getTitle().trim().toLowerCase(); String title = subtitleStream.getTitle().trim().toLowerCase();
// Add the subtitle if the filter matches, and it hasn't been added already // Add the subtitle if the filter matches, and it hasn't been added already
boolean matches = filter.trim().equals("*") || (isRegEx && title.matches(filter)) || boolean matches = filter.trim().equals("*") || (isRegEx && title.matches(filter.toLowerCase())) ||
(!isRegEx && title.contains(filter)); (!isRegEx && title.contains(filter.toLowerCase()));
if (matches && !output.contains(subtitleStream)) { if (matches && !output.contains(subtitleStream)) {
System.out.println("Subtitle stream with title " + title + " matches"); OutputUtil.printDebug("Subtitle stream with title " + title + " matches");
output.add(subtitleStream); output.add(subtitleStream);
} }
} }

View File

@ -50,17 +50,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
* @return <p>True if the subtitle translates everything.</p> * @return <p>True if the subtitle translates everything.</p>
*/ */
private boolean checkIfIsFullSubtitle() { private boolean checkIfIsFullSubtitle() {
String titleLowercase = getTitle().toLowerCase().trim(); return !getTitle().toLowerCase().trim().matches(".*si(ng|gn)s?[ &/a-z+]+(songs?)?([$ @-]+.*)?|" +
return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z+]+songs?.*") && ".*songs?[ &/a-z]+(si(gn|ng)s?)?([$ @-]+.*)?|.*forced.*|.*s&s.*|songs($| |-|@)+|si(gn|ng)s($| |-|@)+");
!titleLowercase.matches(".*songs?[ &/a-z]+si(gn|ng)s?.*") &&
!titleLowercase.matches(".*forced.*") &&
!titleLowercase.matches(".*s&s.*") &&
!titleLowercase.matches("signs?") &&
!titleLowercase.matches("songs?") &&
!titleLowercase.matches(".*signs only.*") &&
!titleLowercase.matches(".* signs .*") &&
!titleLowercase.matches("signs@.*") &&
!titleLowercase.matches("signs -.*");
} }
@Override @Override