FF-1733: Added variable substitution to the Fail Flow element

This commit is contained in:
John Andrews
2024-08-18 11:06:59 +12:00
parent 647f1375d8
commit 05d6bc9ab0
8 changed files with 416 additions and 80 deletions

View File

@@ -31,7 +31,7 @@ public class FailFlow : Node
/// <summary>
/// Gets or sets the reason to fail the flow
/// </summary>
[Text(1)]
[TextVariable(1)]
public string Reason { get; set; }
/// <summary>
@@ -41,9 +41,10 @@ public class FailFlow : Node
/// <returns>-1 to indicate the flow should fail</returns>
public override int Execute(NodeParameters args)
{
if (string.IsNullOrWhiteSpace(Reason) == false)
string reason = args.ReplaceVariables(Reason ?? string.Empty, stripMissing: true);
if (string.IsNullOrWhiteSpace(reason) == false)
{
args.Logger.ILog("Failing flow: " + Reason);
args.Logger.ILog("Failing flow: " + reason);
args.FailureReason = Reason;
}
else

View File

@@ -6,7 +6,7 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// FFmpeg Builder: Add Audio Track
/// </summary>
public class FfmpegBuilderAudioAddTrack : TrackSelectorFlowElement
public class FfmpegBuilderAudioAddTrack : TrackSelectorFlowElement<FfmpegBuilderAudioAddTrack>
{
/// <summary>
/// Gets the icon for this flow element

View File

@@ -0,0 +1,272 @@
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
using FileFlows.VideoNodes.Helpers;
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// FFmpeg Builder flow element that converts audio
/// </summary>
public class FfmpegBuilderAudioConvert : TrackSelectorFlowElement<FfmpegBuilderAudioConvert>
{
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/audio-convert";
/// <inheritdoc />
public override string Icon => "fas fa-comments";
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
protected override string AutomaticLabel => "All Audio";
/// <summary>
/// Gets or sets the codec to use
/// </summary>
[DefaultValue("aac")]
[Select(nameof(CodecOptions), 11)]
public string Codec { get; set; }
/// <summary>
/// The available codec options
/// </summary>
private static List<ListOption> _CodecOptions;
/// <summary>
/// Gets the available codec options
/// </summary>
public static List<ListOption> CodecOptions
{
get
{
if (_CodecOptions == null)
{
_CodecOptions = new List<ListOption>
{
new () { Label = "AAC", Value = "aac"},
new () { Label = "AC3", Value = "ac3"},
new () { Label = "DTS", Value = "dts" },
new () { Label = "EAC3", Value = "eac3" },
new () { Label = "FLAC", Value ="flac" },
new () { Label = "MP3", Value = "mp3"},
new () { Label = "PCM", Value ="pcm" },
new () { Label = "OPUS", Value = "opus"},
new () { Label = "Vorbis", Value ="libvorbis" },
};
}
return _CodecOptions;
}
}
/// <summary>
/// Gets or sets the PCM format
/// </summary>
[DefaultValue("pcm_s16le")]
[Select(nameof(PcmFormats), 12)]
[ConditionEquals(nameof(Codec), "pcm")]
public string PcmFormat { get; set; }
/// <summary>
/// The PCM options
/// </summary>
private static List<ListOption> _PcmFormats;
/// <summary>
/// Gets the PCM options
/// </summary>
public static List<ListOption> PcmFormats
{
get
{
if (_PcmFormats == null)
{
_PcmFormats = new List<ListOption>
{
new () { Label = "Common", Value = "###GROUP###" },
new () { Label = "Signed 16-bit Little Endian", Value = "pcm_s16le" },
new () { Label = "Signed 24-bit Little Endian", Value = "pcm_s24le"},
new () { Label = "Signed", Value = "###GROUP###" },
new () { Label = "8-bit", Value = "pcm_s8"},
new () { Label = "16-bit Little Endian", Value = "pcm_s16le" },
new () { Label = "16-bit Big Endian", Value = "pcm_s16be"},
new () { Label = "24-bit Little Endian", Value = "pcm_s24le"},
new () { Label = "24-bit Big Endian", Value = "pcm_s24be"},
new () { Label = "32-bit Little Endian", Value = "pcm_s32le"},
new () { Label = "32-bit Big Endian", Value = "pcm_s32be"},
new () { Label = "64-bit Little Endian", Value = "pcm_s64le"},
new () { Label = "64-bit Big Endian", Value = "pcm_s64be"},
new () { Label = "Floating-point", Value = "###GROUP###" },
new () { Label = "32-bit Little Endian", Value = "pcm_f32le"},
new () { Label = "32-bit Big Endian", Value = "pcm_f32be"},
new () { Label = "16-bit Little Endian", Value = "pcm_f16le"},
new () { Label = "24-bit Little Endian", Value = "pcm_f24le"},
new () { Label = "64-bit Little Endian", Value = "pcm_f64le"},
new () { Label = "64-bit Big Endian", Value = "pcm_f64be"},
};
}
return _PcmFormats;
}
}
/// <summary>
/// Gets or sets the number of channels for the converted audio
/// </summary>
[DefaultValue(0)]
[Select(nameof(ChannelsOptions), 13)]
public float Channels { get; set; }
/// <summary>
/// The channel options
/// </summary>
private static List<ListOption> _ChannelsOptions;
/// <summary>
/// Gets the channel options
/// </summary>
public static List<ListOption> ChannelsOptions
{
get
{
if (_ChannelsOptions == null)
{
_ChannelsOptions = new List<ListOption>
{
new () { Label = "Automatic", Value = 1000f},
new () { Label = "Same as source", Value = 0},
new () { Label = "Mono", Value = 1f},
new () { Label = "Stereo", Value = 2f},
new () { Label = "5.1", Value = 6},
new () { Label = "7.1", Value = 8}
};
}
return _ChannelsOptions;
}
}
/// <summary>
/// Gets or sets the bitrate
/// </summary>
[Select(nameof(BitrateOptions), 14)]
public int Bitrate { get; set; }
/// <summary>
/// The bitrate options
/// </summary>
private static List<ListOption> _BitrateOptions;
/// <summary>
/// Gets the bitrate options
/// </summary>
public static List<ListOption> BitrateOptions
{
get
{
if (_BitrateOptions == null)
{
_BitrateOptions = new List<ListOption>
{
new () { Label = "Automatic", Value = 0},
new () { Label = "Same as source", Value = 1},
};
for (int i = 64; i <= 2048; i += 32)
{
_BitrateOptions.Add(new ListOption { Label = i + " Kbps", Value = i });
}
}
return _BitrateOptions;
}
}
/// <summary>
/// Gets or sets if the bitrate specified should be per channel
/// </summary>
[Boolean(15)]
public bool BitratePerChannel { get; set; }
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
bool converting = false;
Regex? regex = null;
foreach (var track in Model.AudioStreams)
{
if (track.Deleted)
{
args.Logger?.ILog($"Stream {track} is deleted, skipping");
continue;
}
if (CustomTrackSelection && StreamMatches(track) == false)
{
args.Logger?.ILog("Stream does not match conditions: " + track);
continue;
}
bool convertResult = ConvertTrack(args, track);
if (convertResult)
{
args.Logger?.ILog($"Stream {track} matches and will be converted");
converting = true;
}
else
{
args.Logger?.ILog($"Stream {track} matches but will not be converted");
}
}
return converting ? 1 : 2;
}
/// <summary>
/// Converts and audio track
/// </summary>
/// <param name="args">the node arguments</param>
/// <param name="stream">teh stream to convert</param>
/// <returns>if the stream had to be converted or not</returns>
private bool ConvertTrack(NodeParameters args, FfmpegAudioStream stream)
{
string codec = Codec?.ToLowerInvariant() ?? string.Empty;
if (codec == "pcm")
codec = PcmFormat;
bool codecSame = stream.Stream.Codec?.ToLowerInvariant() == codec;
int originalChannels = FfmpegBuilderAudioAddTrack.GetAudioBitrateChannels(args.Logger, stream.Channels, string.Empty);
var channels = this.Channels > 999f ? 0 : this.Channels == 0 ? originalChannels : this.Channels;
int totalChannels = FfmpegBuilderAudioAddTrack.GetAudioBitrateChannels(args.Logger, channels, codec);
bool channelsSame = originalChannels == totalChannels;
int bitrate = Bitrate;
if (BitratePerChannel && bitrate > 0)
{
args.Logger?.ILog("Total channels: " + totalChannels);
args.Logger?.ILog("Bitrate Per Channel: " + bitrate);
if (bitrate == 1)
{
// same as source
// so we need to get the bitrate of the original, and divide that by the number of channels
// then times it by the number of channels we are now using
bitrate = (int)(totalChannels * ((stream.Stream.Bitrate / 1000) / stream.Stream.Channels));
}
else
{
bitrate = totalChannels * bitrate;
}
args.Logger?.ILog("Total Bitrate: " + bitrate);
}
bool bitrateSame = Bitrate < 2 || stream.Stream.Bitrate == 0 ||
Math.Abs(stream.Stream.Bitrate - Bitrate) < 0.05f;
if (codecSame && channelsSame && bitrateSame)
{
args.Logger.ILog($"Stream {stream} matches codec, channels, and bitrate, skipping conversion");
return false;
}
stream.Codec = Codec.ToLowerInvariant();
stream.EncodingParameters.AddRange(FfmpegBuilderAudioAddTrack.GetNewAudioTrackParameters(args, stream, codec, channels, bitrate, 0));
return true;
}
}

View File

@@ -14,6 +14,10 @@ public class FfmpegBuilderAudioConverter : FfmpegBuilderNode
public override string Icon => "fas fa-comments";
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override bool Obsolete => true;
/// <inheritdoc />
public override string ObsoleteMessage => "Replaced with Audio FFmpeg Builder: Audio Convert";
/// <summary>
/// Gets or sets the codec to use

View File

@@ -1,6 +1,6 @@
namespace FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
public abstract class FfmpegStream
public abstract class FfmpegStream : IVideoStream
{
public const string REMOVED = "###REMOVED###";

View File

@@ -1,3 +1,4 @@
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
using FileFlows.VideoNodes.Helpers;
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
@@ -5,7 +6,7 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// Flow element that has a track selector
/// </summary>
public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
public abstract class TrackSelectorFlowElement<T> : FfmpegBuilderNode where T : TrackSelectorFlowElement<T>
{
/// <summary>
/// Gets or sets replacements to replace
@@ -24,16 +25,27 @@ public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
{
if (_CustomTrackSelection == null)
{
var instance = (T)Activator.CreateInstance(typeof(T))!;
_CustomTrackSelection = new List<ListOption>
{
new () { Label = "Automatic", Value = false },
new () { Label = "Custom", Value = true},
new () { Label = instance.AutomaticLabel, Value = false },
new () { Label = instance.CustomLabel, Value = true},
};
}
return _CustomTrackSelection;
}
}
/// <summary>
/// Gets the label for the automatic selection
/// </summary>
protected virtual string AutomaticLabel => "Automatic";
/// <summary>
/// Gets the label for the custom selection
/// </summary>
protected virtual string CustomLabel => "Custom";
/// <summary>
/// Gets or sets the track selection options
/// </summary>
@@ -99,56 +111,7 @@ public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
foreach (var stream in sourceStreams)
{
bool matches = true;
foreach (var kv in TrackSelectionOptions)
{
var key = kv.Key?.ToLowerInvariant() ?? string.Empty;
var kvValue = Args.ReplaceVariables(kv.Value?.Replace("{orig}", "{OriginalLanguage}") ?? string.Empty, stripMissing: true);
switch (key)
{
case "language":
if (LanguageMatches(stream, kv.Value))
Args?.Logger?.ILog($"Language Matches '{stream}' = {kv.Value}");
else
{
Args?.Logger?.ILog($"Language does not match '{stream}' = {kv.Value}");
matches = false;
}
break;
case "codec":
if(Args.StringHelper.Matches(kv.Value, stream.Codec))
Args?.Logger?.ILog($"Stream '{stream}' Codec '{stream.Codec}' matches '{kv.Value}'");
else
{
Args?.Logger?.ILog($"Stream '{stream}' Codec '{stream.Codec}' does not match {kv.Value}");
matches = false;
}
break;
case "title":
if(Args.StringHelper.Matches(kv.Value, stream.Title))
Args?.Logger?.ILog($"Stream '{stream}' Title '{stream.Codec}' does match '{kv.Value}'");
else
{
Args?.Logger?.ILog($"Stream '{stream}' Title '{stream.Codec}' does not match '{kv.Value}'");
matches = false;
}
break;
case "channels":
if (ChannelsMatches(stream, kv.Value))
Args?.Logger?.ILog($"Channels Matches '{stream}' = {kv.Value}");
else
{
Args?.Logger?.ILog($"Channels does not match '{stream}' = {kv.Value}");
matches = false;
}
break;
}
if (matches == false)
break;
}
if (matches == false)
if (StreamMatches(stream) == false)
{
Args?.Logger?.ILog($"Track '{stream}' not suitable");
continue;
@@ -167,23 +130,87 @@ public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
}
/// <summary>
/// Tests if a stream matches the specified conditions
/// </summary>
/// <param name="stream">the stream to check</param>
/// <returns>true if matches, otherwise false</returns>
protected bool StreamMatches(IVideoStream stream)
{
foreach (var kv in TrackSelectionOptions)
{
var key = kv.Key?.ToLowerInvariant() ?? string.Empty;
var kvValue = Args.ReplaceVariables(kv.Value?.Replace("{orig}", "{OriginalLanguage}") ?? string.Empty,
stripMissing: true);
switch (key)
{
case "language":
if (LanguageMatches(stream, kvValue))
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;
}
}
return true;
}
/// <summary>
/// Checks if the channels matches
/// </summary>
/// <param name="value">the value to check</param>
/// <returns>true if channels matches, otherwise false</returns>
private bool ChannelsMatches(VideoFileStream stream, string value)
private bool ChannelsMatches(IVideoStream stream, string value)
{
if (stream is AudioStream audio == false)
float? channels = null;
if (stream is AudioStream audio)
channels = audio.Channels;
else if(stream is FfmpegAudioStream ffAudio)
channels = ffAudio.Channels;
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 '{audio.Channels}'");
return Args.MathHelper.IsTrue(value, audio.Channels);
Args?.Logger?.DLog($"Is Math operation '{value}' comparing '{channels}'");
return Args.MathHelper.IsTrue(value, channels.Value);
}
if (double.TryParse(value, out var dblValue) == false)
@@ -192,7 +219,7 @@ public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
return false;
}
return Math.Abs(audio.Channels - dblValue) < 0.05f;
return Math.Abs(channels.Value - dblValue) < 0.05f;
}
/// <summary>
@@ -201,14 +228,10 @@ public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
/// <param name="stream">the stream to check</param>
/// <param name="value">the value to check</param>
/// <returns>true if language matches, otherwise false</returns>
private bool LanguageMatches(VideoFileStream stream, string value)
private bool LanguageMatches(IVideoStream stream, string value)
{
if (value.StartsWith('='))
value = value[1..];
if (stream is AudioStream audio)
return LanguageHelper.AreSame(audio.Language, value);
if (stream is SubtitleStream sub)
return LanguageHelper.AreSame(sub.Language, value);
return false;
return LanguageHelper.AreSame(stream.Language, value);
}
}

View File

@@ -46,10 +46,27 @@ public class VideoInfo
public List<AttachmentStream> Attachments { get; set; } = new();
}
public interface IVideoStream
{
/// <summary>
/// Gets or sets the stream title (name)
/// </summary>
string Title { get; set; }
/// <summary>
/// Gets or sets the codec of the stream
/// </summary>
string Codec { get; set; }
/// <summary>
/// Gets or sets the stream language
/// </summary>
string Language { get; set; }
}
/// <summary>
/// Metadata about a stream in a video file
/// </summary>
public class VideoFileStream
public class VideoFileStream : IVideoStream
{
/// <summary>
/// The original index of the stream in the overall video
@@ -63,6 +80,10 @@ public class VideoFileStream
/// The stream title (name)
/// </summary>
public string Title { get; set; } = "";
/// <summary>
/// The language of the stream
/// </summary>
public string Language { get; set; }
/// <summary>
/// The bitrate(BPS) of the video stream in bytes per second
@@ -167,10 +188,6 @@ public class VideoStream : VideoFileStream
/// </summary>
public class AudioStream : VideoFileStream
{
/// <summary>
/// The language of the stream
/// </summary>
public string Language { get; set; }
/// <summary>
/// The channels of the stream
@@ -213,11 +230,6 @@ public class AudioStream : VideoFileStream
/// </summary>
public class SubtitleStream : VideoFileStream
{
/// <summary>
/// The language of the stream
/// </summary>
public string Language { get; set; }
/// <summary>
/// If this is a forced subtitle
/// </summary>

View File

@@ -205,6 +205,30 @@
"UseLanguageCode-Help": "If the language code of the audio track should be used instead of the title"
}
},
"FfmpegBuilderAudioConvert": {
"Label": "FFMPEG Builder: Audio Convert",
"Outputs": {
"1": "Tracks to be converted",
"2": "No tracks to be converted"
},
"Description": "Allows you to convert all tracks matching the parameters to be converted.",
"Fields": {
"CustomTrackSelection": "Convert",
"TrackSelectionOptions": "Matching",
"TrackSelectionOptionsKey": "Property",
"TrackSelectionOptionsValue": "Value",
"Channels": "Channels",
"Channels-Help": "The number of channels this new audio track will be.\nIf you specify more channels than the source, FFMPEG will automatically upmix it.\nIf you specify fewer channels than the source, FFMPEG will automatically down mix it.",
"PcmFormat": "Format",
"PcmFormat-Help": "The PCM format to use for encoding PCM audio.",
"Bitrate": "Bitrate",
"Bitrate-Help": "Bitrate of the audio track",
"Codec": "Codec",
"Codec-Help": "The codec to use to encode the audio",
"BitratePerChannel": "Bitrate Per Channel",
"BitratePerChannel-Help": "If the bitrate specified should be per channel"
}
},
"FfmpegBuilderAudioConverter": {
"Label": "FFMPEG Builder: Audio Converter",
"Outputs": {