From 13863ec9e2ce8a39ad2f16504a74280768c22ca2 Mon Sep 17 00:00:00 2001 From: John Andrews Date: Fri, 2 Feb 2024 11:31:28 +1300 Subject: [PATCH] FF-1255 - added ffmpeg model logging --- .../FfmpegBuilderNodes/FfmpegBuilderNode.cs | 26 ++- .../FfmpegBuilderTrackSorter.cs | 84 +++++++- .../Models/FfmpegAudioStream.cs | 5 +- .../Models/FfmpegSubtitleStream.cs | 6 +- .../Models/FfmpegVideoStream.cs | 198 ++++++++++-------- 5 files changed, 213 insertions(+), 106 deletions(-) diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs index 7c1248f5..9fad61fa 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs @@ -44,8 +44,30 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes if(this is FfmpegBuilderStart == false && Model == null) throw new Exception("FFMPEG Builder Model not set, you must add and use the \"FFMPEG Builder Start\" node first"); - if (this is FfmpegBuilderStart == false && Model.VideoInfo == null) - throw new Exception("FFMPEG Builder VideoInfo is null"); + if (this is FfmpegBuilderStart == false) + { + if (Model.VideoInfo == null) + throw new Exception("FFMPEG Builder VideoInfo is null"); + string header = "------------------------ Starting FFmpeg Builder Model ------------------------"; + args.Logger?.ILog(header); + foreach (var stream in Model.VideoStreams ?? new List()) + { + string line = "| Video Stream: " + stream; + args.Logger?.ILog(line + new string(' ', Math.Max(1, header.Length - line.Length - 1)) + '|'); + } + foreach (var stream in Model.AudioStreams ?? new List()) + { + string line = "| Audio Stream: " + stream; + args.Logger?.ILog(line + new string(' ', Math.Max(1, header.Length - line.Length - 1)) + '|'); + } + foreach (var stream in Model.SubtitleStreams ?? new List()) + { + string line = "| Subtitle Stream: " + stream; + args.Logger?.ILog(line + new string(' ', Math.Max(1, header.Length - line.Length - 1)) + '|'); + } + + args.Logger?.ILog(new string('-', header.Length)); + } return true; } diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderTrackSorter.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderTrackSorter.cs index b6df5632..6db11106 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderTrackSorter.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderTrackSorter.cs @@ -136,7 +136,15 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode // Replace the unsorted items with the sorted ones for (int i = 0; i < streams.Count; i++) { - changes |= streams[i] != orderedStreams[i]; + bool newDefault = i == 0; + bool changed = streams[i] != orderedStreams[i] || orderedStreams[i].IsDefault != newDefault; + streams[i].IsDefault = newDefault; + if (changed) + { + streams[i].ForcedChange = true; + orderedStreams[i].ForcedChange = true; + } + changes |= changed; streams[i] = orderedStreams[i]; } @@ -145,12 +153,13 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode internal List SortStreams(NodeParameters args, List streams) where T : FfmpegStream { - if (streams?.Any() != true || Sorters?.Any() != true) + if (streams?.Any() != true || Sorters?.Any() != true || streams.Count == 1) return streams; return streams.OrderBy(stream => { - var sortKey = GetSortKey(args, stream); + // we add a 1 to deleted ones first, so they are always sorted after non-deleted ones + var sortKey = (stream.Deleted ? "1|" : "0|") + GetSortKey(args, stream); args.Logger?.ILog(stream.ToString() + " -> sort key = " + sortKey); return sortKey; }) @@ -311,8 +320,8 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode // Make an educated guess for Mbps to kbps conversion return adjustedComparison[..^4] switch { - { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000).ToString( - CultureInfo.InvariantCulture), + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000) + .ToString(CultureInfo.InvariantCulture), _ => comparisonValue }; } @@ -321,32 +330,84 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode // Make an educated guess for kbps to bps conversion return adjustedComparison[..^4] switch { - { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000).ToString( - CultureInfo.InvariantCulture), + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000) + .ToString(CultureInfo.InvariantCulture), + _ => comparisonValue + }; + } + if (adjustedComparison.EndsWith("kb")) + { + return adjustedComparison[..^2] switch + { + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000 ) + .ToString(CultureInfo.InvariantCulture), + _ => comparisonValue + }; + } + if (adjustedComparison.EndsWith("mb")) + { + return adjustedComparison[..^2] switch + { + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000 ) + .ToString(CultureInfo.InvariantCulture), _ => comparisonValue }; } if (adjustedComparison.EndsWith("gb")) { - // Make an educated guess for GB to bytes conversion return adjustedComparison[..^2] switch { - { } value when double.TryParse(value, out var numericValue) => (numericValue * Math.Pow(1024, 3)) + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000 ) .ToString(CultureInfo.InvariantCulture), _ => comparisonValue }; } if (adjustedComparison.EndsWith("tb")) { - // Make an educated guess for TB to bytes conversion return adjustedComparison[..^2] switch { - { } value when double.TryParse(value, out var numericValue) => (numericValue * Math.Pow(1024, 4)) + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000_000) .ToString(CultureInfo.InvariantCulture), _ => comparisonValue }; } + if (adjustedComparison.EndsWith("kib")) + { + return adjustedComparison[..^3] switch + { + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_024 ) + .ToString(CultureInfo.InvariantCulture), + _ => comparisonValue + }; + } + if (adjustedComparison.EndsWith("mib")) + { + return adjustedComparison[..^3] switch + { + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_048_576 ) + .ToString(CultureInfo.InvariantCulture), + _ => comparisonValue + }; + } + if (adjustedComparison.EndsWith("gib")) + { + return adjustedComparison[..^3] switch + { + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_099_511_627_776 ) + .ToString(CultureInfo.InvariantCulture), + _ => comparisonValue + }; + } + if (adjustedComparison.EndsWith("tib")) + { + return adjustedComparison[..^3] switch + { + { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000_000) + .ToString(CultureInfo.InvariantCulture), + _ => comparisonValue + }; + } return comparisonValue; } @@ -420,6 +481,7 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode case "==": return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()))) < 0.05f; case "!=": + case "<>": return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()))) > 0.05f; } diff --git a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegAudioStream.cs b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegAudioStream.cs index 12dbdb83..611a9bb4 100644 --- a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegAudioStream.cs +++ b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegAudioStream.cs @@ -99,7 +99,7 @@ public override string ToString() { if (Stream != null) - return Stream.ToString(); + return Stream.ToString() + (Deleted ? " / Deleted" : ""); // can be null in unit tests return string.Join(" / ", new string[] { @@ -107,7 +107,8 @@ Language, Codec, Title, - Channels > 0 ? Channels.ToString("0.0") : null + Channels > 0 ? Channels.ToString("0.0") : null, + Deleted ? "Deleted" : null }.Where(x => string.IsNullOrWhiteSpace(x) == false)); } } diff --git a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs index 8e4b7fab..814b3aa2 100644 --- a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs +++ b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegSubtitleStream.cs @@ -92,14 +92,16 @@ public class FfmpegSubtitleStream : FfmpegStream public override string ToString() { if (Stream != null) - return Stream.ToString(); + return Stream.ToString() + (Deleted ? " / Deleted" : ""); + // can be null in unit tests return string.Join(" / ", new string[] { Index.ToString(), Language, Codec, - Title + Title, + Deleted ? "Deleted" : null }.Where(x => string.IsNullOrWhiteSpace(x) == false)); } } \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegVideoStream.cs b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegVideoStream.cs index 9de5d312..c18d895c 100644 --- a/VideoNodes/FfmpegBuilderNodes/Models/FfmpegVideoStream.cs +++ b/VideoNodes/FfmpegBuilderNodes/Models/FfmpegVideoStream.cs @@ -1,115 +1,135 @@ -namespace FileFlows.VideoNodes.FfmpegBuilderNodes.Models +namespace FileFlows.VideoNodes.FfmpegBuilderNodes.Models; + +public class FfmpegVideoStream : FfmpegStream { - public class FfmpegVideoStream : FfmpegStream + public VideoStream Stream { get; set; } + + private List _Filter = new List(); + public List Filter { - public VideoStream Stream { get; set; } - - private List _Filter = new List(); - public List Filter + get => _Filter; + set { - get => _Filter; - set - { - _Filter = value ?? new List(); - } + _Filter = value ?? new List(); } - private List _OptionalFilter = new List(); + } + private List _OptionalFilter = new List(); - /// - /// Gets or sets filters that will process but only if processing is needed, these won't trigger a has changed - /// value of the video file by themselves - /// - public List OptionalFilter + /// + /// Gets or sets filters that will process but only if processing is needed, these won't trigger a has changed + /// value of the video file by themselves + /// + public List OptionalFilter + { + get => _OptionalFilter; + set { - get => _OptionalFilter; - set - { - _OptionalFilter = value ?? new List(); - } + _OptionalFilter = value ?? new List(); } + } - private List _EncodingParameters = new List(); - public List EncodingParameters + private List _EncodingParameters = new List(); + public List EncodingParameters + { + get => _EncodingParameters; + set { - get => _EncodingParameters; - set - { - _EncodingParameters = value ?? new List(); - } + _EncodingParameters = value ?? new List(); } - // private List _OptionalEncodingParameters = new List(); - // /// - // /// Gets or sets encoding paramaters that will process but only if processing is needed, these won't trigger a has changed - // /// value of the video file by themselves - // /// - // public List OptionalEncodingParameters - // { - // get => _OptionalEncodingParameters; - // set - // { - // _OptionalEncodingParameters = value ?? new List(); - // } - // } - private List _AdditionalParameters = new List(); - public List AdditionalParameters + } + // private List _OptionalEncodingParameters = new List(); + // /// + // /// Gets or sets encoding paramaters that will process but only if processing is needed, these won't trigger a has changed + // /// value of the video file by themselves + // /// + // public List OptionalEncodingParameters + // { + // get => _OptionalEncodingParameters; + // set + // { + // _OptionalEncodingParameters = value ?? new List(); + // } + // } + private List _AdditionalParameters = new List(); + public List AdditionalParameters + { + get => _AdditionalParameters; + set { - get => _AdditionalParameters; - set - { - _AdditionalParameters = value ?? new List(); - } + _AdditionalParameters = value ?? new List(); } - public override bool HasChange => EncodingParameters.Any() || Filter.Any() || AdditionalParameters.Any(); + } + public override bool HasChange => EncodingParameters.Any() || Filter.Any() || AdditionalParameters.Any(); - public override string[] GetParameters(GetParametersArgs args) + public override string[] GetParameters(GetParametersArgs args) + { + if (Deleted) + return new string[] { }; + + var results = new List { "-map", "0:v:{sourceTypeIndex}" }; + if (Filter.Any() == false && EncodingParameters.Any() == false && AdditionalParameters.Any() == false) { - if (Deleted) - return new string[] { }; - - var results = new List { "-map", "0:v:{sourceTypeIndex}" }; - if (Filter.Any() == false && EncodingParameters.Any() == false && AdditionalParameters.Any() == false) + results.Add("-c:v:{index}"); + results.Add("copy"); + return results.ToArray(); + } + else + { + if (EncodingParameters.Any()) { - results.Add("-c:v:{index}"); - results.Add("copy"); - return results.ToArray(); + results.Add("-c:v:" + Stream.TypeIndex); + results.AddRange(EncodingParameters.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); + // if(OptionalEncodingParameters.Any()) + // results.AddRange(OptionalEncodingParameters.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); } else { - if (EncodingParameters.Any()) - { - results.Add("-c:v:" + Stream.TypeIndex); - results.AddRange(EncodingParameters.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); - // if(OptionalEncodingParameters.Any()) - // results.AddRange(OptionalEncodingParameters.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); - } - else - { - // we need to set this codec since a filter will be applied, so we cant copy it. - //results.Add("copy"); - } - if (AdditionalParameters.Any()) - results.AddRange(AdditionalParameters.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); - - if (Filter.Any() || OptionalFilter.Any()) - { - results.Add("-filter:v:" + args.OutputTypeIndex); - results.Add(string.Join(", ", Filter.Concat(OptionalFilter)).Replace("{index}", args.OutputTypeIndex.ToString())); - } + // we need to set this codec since a filter will be applied, so we cant copy it. + //results.Add("copy"); } + if (AdditionalParameters.Any()) + results.AddRange(AdditionalParameters.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); - if (string.IsNullOrWhiteSpace(this.Title) == false) + if (Filter.Any() || OptionalFilter.Any()) { - results.Add($"-metadata:s:v:{args.OutputTypeIndex}"); - results.Add($"title={(this.Title == FfmpegStream.REMOVED ? "" : this.Title)}"); + results.Add("-filter:v:" + args.OutputTypeIndex); + results.Add(string.Join(", ", Filter.Concat(OptionalFilter)).Replace("{index}", args.OutputTypeIndex.ToString())); } - - if (Metadata.Any()) - { - results.AddRange(Metadata.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); - } - - return results.ToArray(); } + + if (string.IsNullOrWhiteSpace(this.Title) == false) + { + results.Add($"-metadata:s:v:{args.OutputTypeIndex}"); + results.Add($"title={(this.Title == FfmpegStream.REMOVED ? "" : this.Title)}"); + } + + if (Metadata.Any()) + { + results.AddRange(Metadata.Select(x => x.Replace("{index}", args.OutputTypeIndex.ToString()))); + } + + return results.ToArray(); + } + + + + /// + /// Converts the object to a string + /// + /// the string representation of stream + public override string ToString() + { + if (Stream != null) + return Stream.ToString() + (Deleted ? " / Deleted" : ""); + + // can be null in unit tests + return string.Join(" / ", new string[] + { + Index.ToString(), + Codec, + Title, + Deleted ? "Deleted" : null + }.Where(x => string.IsNullOrWhiteSpace(x) == false)); } }