using FileFlows.VideoNodes.FfmpegBuilderNodes.Models; using FileFlows.VideoNodes.Helpers; namespace FileFlows.VideoNodes.FfmpegBuilderNodes; /// /// Flow element that has a track selector /// public abstract class TrackSelectorFlowElement : FfmpegBuilderNode where T : TrackSelectorFlowElement { /// /// Gets or sets replacements to replace /// [Select(nameof(CustomTrackSelectionOptions), 1)] [Required] public bool CustomTrackSelection { get; set; } private static List _CustomTrackSelection; /// /// Gets the codec options /// public static List CustomTrackSelectionOptions { get { if (_CustomTrackSelection == null) { var instance = (T)Activator.CreateInstance(typeof(T))!; _CustomTrackSelection = new List { new () { Label = instance.AutomaticLabel, Value = false }, new () { Label = instance.CustomLabel, Value = true}, }; } return _CustomTrackSelection; } } /// /// Gets the label for the automatic selection /// protected virtual string AutomaticLabel => "Automatic"; /// /// Gets the label for the custom selection /// protected virtual string CustomLabel => "Custom"; /// /// Gets if this allows filtering by the index /// protected virtual bool AllowIndex => false; /// /// Gets or sets the track selection options /// [KeyValue(2, optionsProperty: nameof(TrackSelectionOptionsOptions), allowDuplicates: true)] [ConditionEquals(nameof(CustomTrackSelection), true)] [Required] public List> TrackSelectionOptions { get; set; } private static List _TrackSelectionOptionsOptions; /// /// Gets or sets the stream type options /// public static List TrackSelectionOptionsOptions { get { if (_TrackSelectionOptionsOptions == null) { var instance = (T)Activator.CreateInstance(typeof(T))!; _TrackSelectionOptionsOptions = new List { new() { Label = "Channels", Value = "Channels" }, new() { Label = "Codec", Value = "Codec" }, new() { Label = "Language", Value = "Language" }, new() { Label = "Title", Value = "Title" } }; if(instance.AllowIndex) _TrackSelectionOptionsOptions.Add(new () { Label = "Index", Value = "Index"}); } return _TrackSelectionOptionsOptions; } } /// /// Gets the source audio from the parameters /// /// the source audio internal T? GetSourceTrack() where T : VideoFileStream { if (CustomTrackSelection == false || TrackSelectionOptions?.Any() != true) { Args?.Logger?.ILog("Not using custom track selection"); return null; } if (Model?.AudioStreams?.Any() != true) return null; Args?.Logger?.ILog("Using custom track selection"); List possible = new(); List? sourceStreams = typeof(T).Name switch { nameof(VideoStream) => Model.VideoStreams.Select(x => (VideoFileStream)x.Stream).Distinct().ToList(), nameof(AudioStream) => Model.AudioStreams.Select(x => (VideoFileStream)x.Stream).Distinct().ToList(), nameof(SubtitleStream) => Model.SubtitleStreams.Select(x => (VideoFileStream)x.Stream).Distinct().ToList(), _ => null }; if (sourceStreams?.Any() != true) { Args?.Logger?.ILog("No source streams found"); return null; } foreach (var stream in sourceStreams) { if (StreamMatches(stream) == false) { Args?.Logger?.ILog($"Track '{stream}' not suitable"); continue; } Args?.Logger?.ILog($"Track '{stream}' suitable"); possible.Add(stream); } Args?.Logger?.ILog("Possible streams found: " + possible.Count); foreach (var stream in possible) Args?.Logger.ILog(" - " + stream); return possible.FirstOrDefault() as T; } /// /// Tests if a stream matches the specified conditions /// /// the stream to check /// the index of the stream in the model /// true if matches, otherwise false protected bool StreamMatches(IVideoStream stream, int? index = null) { foreach (var kv in TrackSelectionOptions) { var key = kv.Key?.ToLowerInvariant() ?? string.Empty; string kvValue = kv.Value?.Replace("{orig}", "{OriginalLanguage}") ?? string.Empty; kvValue = Args.ReplaceVariables(kvValue, stripMissing: true); switch (key) { case "language": if (LanguageHelper.Matches(Args, kvValue, stream.Language)) Args?.Logger?.ILog($"Language Matches '{stream}' = {kvValue}"); else { Args?.Logger?.ILog($"Language does not match '{stream}' = {kvValue}"); return false; } break; case "codec": if (Args.StringHelper.Matches(kvValue, stream.Codec)) Args?.Logger?.ILog($"Stream '{stream}' Codec '{stream.Codec}' matches '{kvValue}'"); else { Args?.Logger?.ILog($"Stream '{stream}' Codec '{stream.Codec}' does not match {kvValue}"); return false; } break; case "title": if (Args.StringHelper.Matches(kvValue, stream.Title)) Args?.Logger?.ILog($"Stream '{stream}' Title '{stream.Codec}' does match '{kvValue}'"); else { Args?.Logger?.ILog($"Stream '{stream}' Title '{stream.Codec}' does not match '{kvValue}'"); return false; } break; case "channels": if (ChannelsMatches(stream, kvValue)) Args?.Logger?.ILog($"Channels Matches '{stream}' = {kvValue}"); else { Args?.Logger?.ILog($"Channels does not match '{stream}' = {kvValue}"); return false; } break; case "index": { if (index == null) { Args?.Logger?.ILog($"No index given for stream '{stream}'"); return false; } if (Args.MathHelper.IsTrue(kvValue, index.Value)) Args?.Logger?.ILog($"Index Matches '{stream}[{index.Value}]' = {kvValue}"); else { Args?.Logger?.ILog($"Index does not match '{stream}[{index.Value}]' = {kvValue}"); return false; } break; } } } return true; } /// /// Checks if the channels matches /// /// the value to check /// true if channels matches, otherwise false private bool ChannelsMatches(IVideoStream stream, string value) { double? channels = null; if (stream is AudioStream audio) channels = Math.Round(audio.Channels, 1); else if(stream is FfmpegAudioStream ffAudio) channels = Math.Round(ffAudio.Channels, 1); else { Args?.Logger?.WLog("Not an audio stream, cannot test Channels"); return false; } if (Args?.MathHelper.IsMathOperation(value) == true) { Args?.Logger?.DLog($"Is Math operation '{value}' comparing '{channels}'"); return Args.MathHelper.IsTrue(value, channels.Value); } if (double.TryParse(value, out var dblValue) == false) { Args?.Logger?.WLog($"Failed to parse '{value}' as a double"); return false; } return Math.Abs(channels.Value - dblValue) < 0.05f; } }