FF-1731: Track selector

This commit is contained in:
John Andrews
2024-08-17 17:26:02 +12:00
parent c465a2843e
commit 647f1375d8
7 changed files with 269 additions and 35 deletions
+3 -6
View File
@@ -52,7 +52,7 @@ public class Matches : Node
value = varValue;
else
value = args.ReplaceVariables(match.Key, stripMissing: true);
string strValue = value?.ToString() ?? string.Empty;
string strValue = value?.ToString()?.Trim() ?? string.Empty;
if (GeneralHelper.IsRegex(match.Value))
{
@@ -76,12 +76,9 @@ public class Matches : Node
return output;
}
if (match.Value == strValue)
{
args.Logger?.ILog($"Match found '{match.Value}' = {strValue}");
if (args.StringHelper.Matches(match.Value, match.Value))
return output;
}
if (args.MathHelper.IsMathOperation(match.Value))
{
if (args.MathHelper.IsTrue(match.Value, strValue))
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,4 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Globalization;
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
@@ -8,7 +6,7 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// FFmpeg Builder: Add Audio Track
/// </summary>
public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
public class FfmpegBuilderAudioAddTrack : TrackSelectorFlowElement
{
/// <summary>
/// Gets the icon for this flow element
@@ -18,10 +16,13 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
/// Gets the help URL for this flow element
/// </summary>
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/audio-add-track";
/// <summary>
/// Gets or sets the index to insert this track
/// </summary>
[NumberInt(1)]
[NumberInt(3)]
[Range(0, 100)]
[DefaultValue(1)]
public int Index { get; set; }
@@ -29,7 +30,7 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
/// Gets or sets the codec to to use
/// </summary>
[DefaultValue("aac")]
[Select(nameof(CodecOptions), 1)]
[Select(nameof(CodecOptions), 4)]
public string Codec { get; set; }
private static List<ListOption> _CodecOptions;
@@ -59,7 +60,7 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
/// Gets or sets the audio channels for the new track
/// </summary>
[DefaultValue(2f)]
[Select(nameof(ChannelsOptions), 2)]
[Select(nameof(ChannelsOptions), 5)]
public float Channels { get; set; }
private static List<ListOption> _ChannelsOptions;
@@ -87,13 +88,13 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
/// <summary>
/// Gets or sets the bitrate
/// </summary>
[Select(nameof(BitrateOptions), 3)]
[Select(nameof(BitrateOptions), 6)]
public int Bitrate { get; set; }
/// <summary>
/// Gets or sets if the bitrate specified should be per channel
/// </summary>
[Boolean(4)]
[Boolean(7)]
public bool BitratePerChannel { get; set; }
private static List<ListOption> _BitrateOptions;
@@ -125,7 +126,7 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
/// Gets or sets the sample rate
/// </summary>
[DefaultValue(0)]
[Select(nameof(SampleRateOptions), 5)]
[Select(nameof(SampleRateOptions), 8)]
public int SampleRate { get; set; }
private static List<ListOption> _SampleRateOptions;
@@ -158,17 +159,17 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
/// Gets or sets the language of the new track
/// </summary>
[DefaultValue("eng")]
[TextVariable(6)]
[TextVariable(9)]
public string Language { get; set; }
/// <summary>
/// Gets or sets if the title of the new track should be removed
/// </summary>
[Boolean(7)]
[Boolean(10)]
public bool RemoveTitle { get; set; }
/// <summary>
/// Gets or sets the title of the new track
/// </summary>
[TextVariable(8)]
[TextVariable(11)]
[ConditionEquals(nameof(RemoveTitle), false)]
public string NewTitle { get; set; }
@@ -186,21 +187,21 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
}
var audio = new FfmpegAudioStream();
var bestAudio = GetBestAudioTrack(args, Model.AudioStreams.Select(x => x.Stream));
if (bestAudio == null)
var sourceAudio = GetSourceTrack<AudioStream>() ?? GetBestAudioTrack(args, Model.AudioStreams.Select(x => x.Stream));
if (sourceAudio == null)
{
args.Logger.ELog("No source audio track found");
args.FailureReason = "No source audio track found";
return -1;
}
audio.Stream = bestAudio;
audio.Stream = sourceAudio;
audio.Channels = audio.Stream.Channels;
bool directCopy = false;
if(bestAudio.Codec.ToLower() == this.Codec.ToLower())
if(sourceAudio.Codec.ToLower() == this.Codec.ToLower())
{
if((Channels == 0 || Math.Abs(Channels - bestAudio.Channels) < 0.05f) && Bitrate <= 2)
if((Channels == 0 || Math.Abs(Channels - sourceAudio.Channels) < 0.05f) && Bitrate <= 2)
{
directCopy = true;
}
@@ -208,8 +209,8 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode
if (directCopy)
{
audio.Codec = bestAudio.Codec;
args.Logger?.ILog($"Source audio is already in appropriate format, just copying that track: {bestAudio.IndexString}, Channels: {bestAudio.Channels}, Codec: {bestAudio.Codec}");
audio.Codec = sourceAudio.Codec;
args.Logger?.ILog($"Source audio is already in appropriate format, just copying that track: {sourceAudio.IndexString}, Channels: {sourceAudio.Channels}, Codec: {sourceAudio.Codec}");
}
else
{
@@ -0,0 +1,214 @@
using FileFlows.VideoNodes.Helpers;
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// Flow element that has a track selector
/// </summary>
public abstract class TrackSelectorFlowElement : FfmpegBuilderNode
{
/// <summary>
/// Gets or sets replacements to replace
/// </summary>
[Select(nameof(CustomTrackSelectionOptions), 1)]
[Required]
public bool CustomTrackSelection { get; set; }
private static List<ListOption> _CustomTrackSelection;
/// <summary>
/// Gets the codec options
/// </summary>
public static List<ListOption> CustomTrackSelectionOptions
{
get
{
if (_CustomTrackSelection == null)
{
_CustomTrackSelection = new List<ListOption>
{
new () { Label = "Automatic", Value = false },
new () { Label = "Custom", Value = true},
};
}
return _CustomTrackSelection;
}
}
/// <summary>
/// Gets or sets the track selection options
/// </summary>
[KeyValue(2, optionsProperty: nameof(TrackSelectionOptionsOptions), allowDuplicates: true)]
[ConditionEquals(nameof(CustomTrackSelection), true)]
[Required]
public List<KeyValuePair<string, string>> TrackSelectionOptions { get; set; }
private static List<ListOption> _TrackSelectionOptionsOptions;
/// <summary>
/// Gets or sets the stream type options
/// </summary>
public static List<ListOption> TrackSelectionOptionsOptions
{
get
{
if (_TrackSelectionOptionsOptions == null)
{
_TrackSelectionOptionsOptions = new List<ListOption>
{
new() { Label = "Channels", Value = "Channels" },
new() { Label = "Codec", Value = "Codec" },
new() { Label = "Language", Value = "Language" },
new() { Label = "Title", Value = "Title" }
};
}
return _TrackSelectionOptionsOptions;
}
}
/// <summary>
/// Gets the source audio from the parameters
/// </summary>
/// <returns>the source audio</returns>
internal T? GetSourceTrack<T>() 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<VideoFileStream> possible = new();
List<VideoFileStream>? 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)
{
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)
{
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;
}
/// <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)
{
if (stream is AudioStream audio == false)
{
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);
}
if (double.TryParse(value, out var dblValue) == false)
{
Args?.Logger?.WLog($"Failed to parse '{value}' as a double");
return false;
}
return Math.Abs(audio.Channels - dblValue) < 0.05f;
}
/// <summary>
/// Checks if the language matches
/// </summary>
/// <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)
{
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;
}
}
@@ -8,17 +8,14 @@ using VideoNodes.Tests;
namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests;
[TestClass]
public class FfmpegBuilder_AddAudioTests
public class FfmpegBuilder_AddAudioTests : TestBase
{
VideoInfo vii;
NodeParameters args;
private void Prepare()
{
const string file = @"D:\videos\unprocessed\basic.mkv";
var logger = new TestLogger();
const string ffmpeg = @"C:\utils\ffmpeg\ffmpeg.exe";
var vi = new VideoInfoHelper(ffmpeg, logger);
vii = vi.Read(file);
var vi = new VideoInfoHelper(FfmpegPath, Logger);
vii = vi.Read(TestFile_BasicMkv);
vii.AudioStreams = new List<AudioStream>
{
new AudioStream
@@ -54,9 +51,9 @@ public class FfmpegBuilder_AddAudioTests
Channels = 5.1f
}
};
args = new NodeParameters(file, logger, false, string.Empty, null);
args.GetToolPathActual = (string tool) => ffmpeg;
args.TempPath = @"D:\videos\temp";
args = new NodeParameters(TestFile_BasicMkv, Logger, false, string.Empty, new LocalFileService());
args.GetToolPathActual = (string tool) => FfmpegPath;
args.TempPath = TempPath;
args.Parameters.Add("VideoInfo", vii);
@@ -65,6 +62,27 @@ public class FfmpegBuilder_AddAudioTests
Assert.AreEqual(1, ffStart.Execute(args));
}
[TestMethod]
public void FfmpegBuilder_AddAudio_GetSource_1()
{
Prepare();
FfmpegBuilderAudioAddTrack ffAddAudio = new();
ffAddAudio.CustomTrackSelection = true;
ffAddAudio.TrackSelectionOptions = new()
{
new("Codec", "AC*"),
new("Language", "English"),
new("Codec", "!aac"),
};
ffAddAudio.PreExecute(args);
var source = ffAddAudio.GetSourceTrack<AudioStream>();
Assert.IsNotNull(source);
Logger.ILog("Source Track: " + source);
Assert.AreEqual("en", source.Language);
Assert.AreEqual("AC3", source.Codec);
}
[TestMethod]
public void FfmpegBuilder_AddAudio_AacStereo()
{
+4
View File
@@ -161,6 +161,10 @@
},
"Description": "Adds a new audio track to FFMPEG Builder which will be added once the builder is executed.",
"Fields": {
"CustomTrackSelection": "Source Track Selection",
"TrackSelectionOptions": "Track Selection",
"TrackSelectionOptionsKey": "Property",
"TrackSelectionOptionsValue": "Value",
"Index": "Index",
"Index-Help": "The index where to insert the new audio track. 0 based, so to insert the new audio track as the first track set this to 0.",
"Channels": "Channels",