diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs index 263a7304..761d5e60 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs @@ -71,8 +71,9 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes ((FfmpegSubtitleStream)item.stream).Stream; - var streamArgs = item.stream.GetParameters(new FfmpegStream.GetParametersArgs() + var streamArgs = item.stream.GetParameters(new () { + Logger = args.Logger, OutputOverallIndex = overallIndex, OutputTypeIndex = actualIndex, SourceExtension = sourceExtension, diff --git a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegStream.cs b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegStream.cs index 359d590b..5e46b0cf 100644 --- a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegStream.cs +++ b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegStream.cs @@ -60,6 +60,11 @@ public abstract class FfmpegStream /// Gets or sets if the default flag should be set /// public bool UpdateDefaultFlag { get; set; } + + /// + /// Gets or sets the logger + /// + public ILogger Logger { get; set; } } } diff --git a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs index 0cc11d7e..76cecd2b 100644 --- a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs +++ b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs @@ -1,72 +1,67 @@ -namespace FileFlows.VideoNodes.FfmpegBuilderNodes.Models +using FileFlows.VideoNodes.Helpers; + +namespace FileFlows.VideoNodes.FfmpegBuilderNodes.Models; + +public class FfmpegSubtitleStream : FfmpegStream { - public class FfmpegSubtitleStream : FfmpegStream + /// + /// Gets or sets the source subtitle stream + /// + public SubtitleStream Stream { get; set; } + + /// + /// Gets or sets if this stream has changed + /// + public override bool HasChange => false; + + /// + /// Gets the parameters for this stream + /// + /// the arguments + /// the parameters to pass to FFmpeg for this stream + public override string[] GetParameters(GetParametersArgs args) { - public SubtitleStream Stream { get; set; } + if (Deleted) + return new string[] { }; - public override bool HasChange => false; - - public override string[] GetParameters(GetParametersArgs args) + bool containerSame = + string.Equals(args.SourceExtension, args.DestinationExtension, StringComparison.InvariantCultureIgnoreCase); + + string destCodec; + if(containerSame) + destCodec = "copy"; + else { - if (Deleted) + destCodec = SubtitleHelper.GetSubtitleCodec(args.DestinationExtension, Stream.Codec); + if (string.IsNullOrEmpty(destCodec)) + { + // this subtitle is not supported by the new container, remove it. + args.Logger?.WLog($"Subtitle stream is not supported in destination container, removing: {Stream.Codec} {Stream.Title ?? string.Empty}"); return new string[] { }; - - List results= new List { "-map", Stream.InputFileIndex + ":s:{sourceTypeIndex}", "-c:s:{index}" }; - - switch (args.DestinationExtension) - { - case "mkv": - { - if(Stream.Codec == "mov_text") - results.Add("srt"); - else - results.Add("copy"); - } - break; - case "mp4": - { - if (Helpers.SubtitleHelper.IsImageSubtitle(Stream.Codec)) - { - results.Add("copy"); - } - else - { - results.Add("mov_text"); - } - } - break; - default: - { - results.Add("copy"); - } - break; } - - - if (string.IsNullOrWhiteSpace(this.Title) == false) - { - // first s: means stream speicific, this is suppose to have :s:s - // https://stackoverflow.com/a/21059838 - results.Add($"-metadata:s:s:{args.OutputTypeIndex}"); - results.Add($"title={(this.Title == FfmpegStream.REMOVED ? "" : this.Title)}"); - } - if (string.IsNullOrWhiteSpace(this.Language) == false) - { - results.Add($"-metadata:s:s:{args.OutputTypeIndex}"); - results.Add($"language={(this.Language == FfmpegStream.REMOVED ? "" : this.Language)}"); - } - - if (Metadata.Any()) - { - results.AddRange(Metadata.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); - } - - if (args.UpdateDefaultFlag) - { - results.AddRange(new[] { "-disposition:a:" + args.OutputTypeIndex, this.IsDefault ? "default" : "0" }); - } - - return results.ToArray(); } + + List results= new List { "-map", Stream.InputFileIndex + ":s:{sourceTypeIndex}", "-c:s:{index}", destCodec }; + + if (string.IsNullOrWhiteSpace(this.Title) == false) + { + // first s: means stream specific, this is suppose to have :s:s + // https://stackoverflow.com/a/21059838 + results.Add($"-metadata:s:s:{args.OutputTypeIndex}"); + results.Add($"title={(this.Title == FfmpegStream.REMOVED ? "" : this.Title)}"); + } + if (string.IsNullOrWhiteSpace(this.Language) == false) + { + results.Add($"-metadata:s:s:{args.OutputTypeIndex}"); + results.Add($"language={(this.Language == FfmpegStream.REMOVED ? "" : this.Language)}"); + } + + if (Metadata.Any()) + results.AddRange(Metadata.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); + + if (args.UpdateDefaultFlag) + results.AddRange(new[] { "-disposition:a:" + args.OutputTypeIndex, this.IsDefault ? "default" : "0" }); + + return results.ToArray(); } -} +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderRemoveUnsupportedSubtitles.cs b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderRemoveUnsupportedSubtitles.cs new file mode 100644 index 00000000..b05e41d1 --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderRemoveUnsupportedSubtitles.cs @@ -0,0 +1,86 @@ +// namespace FileFlows.VideoNodes.FfmpegBuilderNodes; +// +// public class FfmpegBuilderRemoveUnsupportedSubtitles : FfmpegBuilderNode +// { +// public override string Icon => "fas fa-comment"; +// +// public override int Outputs => 2; +// +// public readonly string[] Mp4Subtitles = new[] +// { +// "ass", // Advanced SubStation Alpha +// "aqtitle", // AQTitle +// "cap", // Cheetah CAP +// "dcin", // D-Cinema subtitles +// "dvb_subtitle", // DVB Teletext +// "dvd_subtitle", // DVD subtitle +// "es", // Enhanced Subtitle +// "fabsubtitler", // FAB Subtitler +// "fcpxml", // Final Cut Pro X +// "jacosub", // JACOsub subtitle +// "microdvd", // MicroDVD subtitle +// "mpl2", // MPL2 subtitle +// "mpsub", // MPlayer subtitle +// "bin", // Opaque binary subtitle (internal) +// "pjs", // PJS (Phoenix Japanimation Society) subtitle +// "realtext", // RealText subtitle format +// "sami", // SAMI subtitle format +// "srt", // SubRip subtitle +// "ssa", // SubStation Alpha subtitle +// "subviewer", // SubViewer 1.0 subtitle +// "subviewer1", // SubViewer 2.0 subtitle +// "teletext", // Teletext subtitle +// "ttml", // Timed Text Markup Language +// "ttxt", // TurboTitler subtitle +// "webvtt", // WebVTT subtitle +// "zerog" // ZeroG subtitle +// }; +// +// public readonly string[] MkvSubtitles = new[] +// { +// "ass", // Advanced SubStation Alpha +// "ssa", // SubStation Alpha subtitle +// "srt", // SubRip subtitle +// "subrip", // SubRip subtitle (alternative name) +// "vtt", // WebVTT subtitle +// "webvtt", // WebVTT subtitle (alternative name) +// "smi", // SAMI subtitle format +// "sami", // SAMI subtitle format (alternative name) +// "rt", // RealText subtitle format +// "realtext", // RealText subtitle format (alternative name) +// "stl", // EBU STL (Subtitling Data Exchange Format) +// "ttml", // Timed Text Markup Language +// "ttml_legacy" // Timed Text Markup Language (legacy name) +// }; +// +// public readonly string[] WebMSubtitles = new[] +// { +// "ass", // Advanced SubStation Alpha +// "ssa", // SubStation Alpha subtitle +// "srt", // SubRip subtitle +// "subrip", // SubRip subtitle (alternative name) +// "vtt", // WebVTT subtitle +// "webvtt", // WebVTT subtitle (alternative name) +// "ttml", // Timed Text Markup Language +// "ttml_legacy" // Timed Text Markup Language (legacy name) +// }; +// +// +// +// +// public override int Execute(NodeParameters args) +// { +// this.Init(args); +// bool removing = false; +// string[] unsupported = new[] { "" }; +// foreach (var stream in Model.SubtitleStreams) +// { +// if (unsupported.Contains(stream.Stream.Codec?.ToLower())) +// { +// stream.Deleted = true; +// removing = true; +// } +// } +// return removing ? 1 : 2; +// } +// } diff --git a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderUnsupportedMP4Subtitles.cs b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderUnsupportedMP4Subtitles.cs deleted file mode 100644 index dbcf75b6..00000000 --- a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderUnsupportedMP4Subtitles.cs +++ /dev/null @@ -1,25 +0,0 @@ -//namespace FileFlows.VideoNodes.FfmpegBuilderNodes -//{ -// public class FfmpegBuilderUnsupportedMP4Subtitles : FfmpegBuilderNode -// { -// public override string Icon => "fas fa-comment"; - -// public override int Outputs => 2; - -// public override int Execute(NodeParameters args) -// { -// this.Init(args); -// bool removing = false; -// string[] unsupported = new[] { "" }; -// foreach (var stream in Model.SubtitleStreams) -// { -// if (unsupported.Contains(stream.Stream.Codec?.ToLower())) -// { -// stream.Deleted = true; -// removing = true; -// } -// } -// return removing ? 1 : 2; -// } -// } -//} diff --git a/VideoNodes/Helpers/SubtitleHelper.cs b/VideoNodes/Helpers/SubtitleHelper.cs index 32b219ce..c3f6699e 100644 --- a/VideoNodes/Helpers/SubtitleHelper.cs +++ b/VideoNodes/Helpers/SubtitleHelper.cs @@ -1,21 +1,83 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FileFlows.VideoNodes.Helpers; +namespace FileFlows.VideoNodes.Helpers; /// /// Helper for Subtitles /// internal class SubtitleHelper { + + public static readonly string[] MkvSubtitles = new[] + { + "ass", // Advanced SubStation Alpha + "ssa", // SubStation Alpha subtitle + "srt", // SubRip subtitle + "subrip", // SubRip subtitle (alternative name) + "vtt", // WebVTT subtitle + "webvtt", // WebVTT subtitle (alternative name) + "smi", // SAMI subtitle format + "sami", // SAMI subtitle format (alternative name) + "rt", // RealText subtitle format + "realtext", // RealText subtitle format (alternative name) + "stl", // EBU STL (Subtitling Data Exchange Format) + "ttml", // Timed Text Markup Language + "ttml_legacy" // Timed Text Markup Language (legacy name) + }; + /// /// Tests if a subtitle is an image based subtitle /// /// the subtitle codec /// true if the subtitle is an image based subtitle internal static bool IsImageSubtitle(string codec) - => Regex.IsMatch(codec.Replace("_", ""), "dvbsub|dvdsub|pgs|xsub", RegexOptions.IgnoreCase); + => Regex.IsMatch(codec.Replace("_", ""), "dvbsub|pgs|xsub|vobsub", RegexOptions.IgnoreCase); + + /// + /// Determines the appropriate subtitle codec for conversion based on the container type and current subtitle codec. + /// + /// The container type (mp4, mkv, webm). + /// The current subtitle codec. + /// The appropriate subtitle codec for conversion, or null container does not support this codec. + public static string? GetSubtitleCodec(string containerType, string currentCodec) + { + // Check if the current subtitle codec is image-based + bool isImageBased = IsImageSubtitle(currentCodec); + + // Determine the appropriate subtitle codec based on the container type and if the current codec is image-based or text-based + switch (containerType.ToLower()) + { + case "mp4": + if (isImageBased) + { + // MP4 container does not support image-based subtitles, so conversion is not possible + return null; + } + return "mov_text"; + case "mkv": + if (isImageBased) + return "hdmv_pgs_subtitle"; + if (IsSupportedSubtitleCodecMKV(currentCodec) == false) + return "srt"; // or "ssa" or any other supported codec + return currentCodec; + case "webm": + if (isImageBased) + { + // WebM container does not support image-based subtitles, so conversion is not possible + return null; + } + // WebM container supports text-based subtitles in the webvtt codec + return "webvtt"; + default: + // Invalid or unsupported container type + return null; + } + } + + /// + /// Checks if the subtitle codec is supported in MKV container. + /// + /// The subtitle codec to check. + /// True if the codec is supported in MKV, False otherwise. + private static bool IsSupportedSubtitleCodecMKV(string codec) + => Array.IndexOf(MkvSubtitles, codec.ToLower()) >= 0; + }