FF-371 - FFmpeg builder now automatically converting subtitles if container changes

This commit is contained in:
John Andrews
2023-06-04 16:17:29 +12:00
parent d65860094b
commit 95436948d5
6 changed files with 222 additions and 98 deletions

View File

@@ -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,

View File

@@ -60,6 +60,11 @@ public abstract class FfmpegStream
/// Gets or sets if the default flag should be set
/// </summary>
public bool UpdateDefaultFlag { get; set; }
/// <summary>
/// Gets or sets the logger
/// </summary>
public ILogger Logger { get; set; }
}
}

View File

@@ -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
/// <summary>
/// Gets or sets the source subtitle stream
/// </summary>
public SubtitleStream Stream { get; set; }
/// <summary>
/// Gets or sets if this stream has changed
/// </summary>
public override bool HasChange => false;
/// <summary>
/// Gets the parameters for this stream
/// </summary>
/// <param name="args">the arguments</param>
/// <returns>the parameters to pass to FFmpeg for this stream</returns>
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<string> results= new List<string> { "-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<string> results= new List<string> { "-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();
}
}
}

View File

@@ -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;
// }
// }

View File

@@ -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;
// }
// }
//}

View File

@@ -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;
/// <summary>
/// Helper for Subtitles
/// </summary>
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)
};
/// <summary>
/// Tests if a subtitle is an image based subtitle
/// </summary>
/// <param name="codec">the subtitle codec</param>
/// <returns>true if the subtitle is an image based subtitle</returns>
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);
/// <summary>
/// Determines the appropriate subtitle codec for conversion based on the container type and current subtitle codec.
/// </summary>
/// <param name="containerType">The container type (mp4, mkv, webm).</param>
/// <param name="currentCodec">The current subtitle codec.</param>
/// <returns>The appropriate subtitle codec for conversion, or null container does not support this codec.</returns>
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;
}
}
/// <summary>
/// Checks if the subtitle codec is supported in MKV container.
/// </summary>
/// <param name="codec">The subtitle codec to check.</param>
/// <returns>True if the codec is supported in MKV, False otherwise.</returns>
private static bool IsSupportedSubtitleCodecMKV(string codec)
=> Array.IndexOf(MkvSubtitles, codec.ToLower()) >= 0;
}