FF-1253 - track sorter

This commit is contained in:
John Andrews
2024-02-01 21:34:54 +13:00
parent c19bfa2aab
commit 72aefd13a4
13 changed files with 862 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ public class PatternReplacer : Node
internal bool UnitTest = false;
[KeyValue(1)]
[KeyValue(1, null)]
[Required]
public List<KeyValuePair<string, string>> Replacements{ get; set; }

View File

@@ -61,7 +61,7 @@ public class WebRequest : Node
}
[KeyValue(4)]
[KeyValue(4, null)]
public List<KeyValuePair<string, string>> Headers { get; set; }
[TextArea(5)]

View File

@@ -18,7 +18,7 @@ public class EmbyUpdater: Node
[Text(2)]
public string AccessToken { get; set; }
[KeyValue(3)]
[KeyValue(3, null)]
public List<KeyValuePair<string, string>> Mapping { get; set; }
public override int Execute(NodeParameters args)

View File

@@ -14,7 +14,7 @@
[Required]
public string AccessToken { get; set; }
[KeyValue(3)]
[KeyValue(3, null)]
public List<KeyValuePair<string, string>> Mapping { get; set; }
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -20,7 +20,7 @@ public abstract class PlexNode:Node
[Text(2)]
public string AccessToken { get; set; }
[KeyValue(3)]
[KeyValue(3, null)]
public List<KeyValuePair<string, string>> Mapping { get; set; }
public override int Execute(NodeParameters args)

View File

@@ -15,7 +15,7 @@
[Required]
public string AccessToken { get; set; }
[KeyValue(3)]
[KeyValue(3, null)]
public List<KeyValuePair<string, string>> Mapping { get; set; }
}
}

View File

@@ -102,6 +102,14 @@ public class FfmpegBuilderKeepOriginalLanguage: FfmpegBuilderNode
return changes > 0 ? 1 : 2;
}
/// <summary>
/// Processes the streams
/// </summary>
/// <param name="args">the node parameters</param>
/// <param name="streams">the streams to process for deletion</param>
/// <param name="originalLanguage">the original language of the source material</param>
/// <typeparam name="T">the stream type</typeparam>
/// <returns>the number of streams changed</returns>
private int ProcessStreams<T>(NodeParameters args, List<T> streams, string originalLanguage) where T : FfmpegStream
{
if (streams?.Any() != true)

View File

@@ -0,0 +1,357 @@
using System.Globalization;
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
using FileFlows.VideoNodes.Helpers;
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// FFmpeg Builder: Track Sorter
/// </summary>
public class FfmpegBuilderTrackSorter : FfmpegBuilderNode
{
/// <summary>
/// Gets the number of output nodes
/// </summary>
public override int Outputs => 2;
/// <summary>
/// Gets the icon
/// </summary>
public override string Icon => "fas fa-sort-alpha-down";
/// <summary>
/// Gets the help URL
/// </summary>
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/track-sorter";
/// <summary>
/// Gets or sets the stream type
/// </summary>
[Select(nameof(StreamTypeOptions), 1)]
public string StreamType { get; set; }
[KeyValue(1, nameof(SorterOptions))]
[Required]
public List<KeyValuePair<string, string>> Sorters { get; set; }
private static List<ListOption> _StreamTypeOptions;
/// <summary>
/// Gets or sets the stream type options
/// </summary>
public static List<ListOption> StreamTypeOptions
{
get
{
if (_StreamTypeOptions == null)
{
_StreamTypeOptions = new List<ListOption>
{
new() { Label = "Audio", Value = "Audio" },
new() { Label = "Subtitle", Value = "Subtitle" }
};
}
return _StreamTypeOptions;
}
}
private static List<ListOption> _SorterOptions;
/// <summary>
/// Gets or sets the sorter options
/// </summary>
public static List<ListOption> SorterOptions
{
get
{
if (_SorterOptions == null)
{
_SorterOptions = new List<ListOption>
{
new() { Label = "Bitrate", Value = "Bitrate" },
new() { Label = "Bitrate Reversed", Value = "BitrateDesc" },
new() { Label = "Channels", Value = "Channels" },
new() { Label = "Channels Reversed", Value = "ChannelsDesc" },
new() { Label = "Codec", Value = "Codec" },
new() { Label = "Codec Reversed", Value = "CodecDesc" },
new() { Label = "Language", Value = "Language" },
new() { Label = "Language Reversed", Value = "LanguageDesc" },
};
}
return _SorterOptions;
}
}
/// <summary>
/// Executes the flow element
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the next output node</returns>
public override int Execute(NodeParameters args)
{
return 1;
}
/// <summary>
/// Processes the streams
/// </summary>
/// <param name="logger">the logger parameters</param>
/// <param name="streams">the streams to process for deletion</param>
/// <typeparam name="T">the stream type</typeparam>
/// <returns>if any changes were made</returns>
internal bool ProcessStreams<T>(ILogger logger, List<T> streams, int sortIndex = 0) where T : FfmpegStream
{
if (streams?.Any() != true || Sorters?.Any() != true || sortIndex >= Sorters.Count)
return false;
var orderedStreams = SortStreams(streams);
// Replace the unsorted items with the sorted ones
for (int i = 0; i < streams.Count; i++)
{
streams[i] = orderedStreams[i];
}
return true;
}
internal List<T> SortStreams<T>(List<T> streams) where T : FfmpegStream
{
if (streams?.Any() != true || Sorters?.Any() != true)
return streams;
return streams.OrderBy(stream => GetSortKey(stream))
.ToList();
}
private string GetSortKey<T>(T stream) where T : FfmpegStream
{
string sortKey = "";
for (int i = 0; i < Sorters.Count; i++)
{
var sortValue = Math.Round(SortValue<T>(stream, Sorters[i])).ToString();
// Trim the sort value to 15 characters
string trimmedValue = sortValue[..Math.Min(sortValue.Length, 15)];
// Pad the trimmed value with left zeros if needed
string paddedValue = trimmedValue.PadLeft(15, '0');
// Concatenate the padded value to the sort key
sortKey += paddedValue;
}
return sortKey;
}
/// <summary>
/// Tests if two lists are the same
/// </summary>
/// <param name="original">the original list</param>
/// <param name="reordered">the reordered list</param>
/// <typeparam name="T">the type of items</typeparam>
/// <returns>true if the lists are the same, otherwise false</returns>
public bool AreSame<T>(List<T> original, List<T> reordered) where T : FfmpegStream
{
for (int i = 0; i < reordered.Count; i++)
{
if (reordered[i] != original[i])
{
return false;
}
}
return true;
}
/// <summary>
/// Calculates the sort value for a stream property based on the specified sorter.
/// </summary>
/// <typeparam name="T">Type of the stream.</typeparam>
/// <param name="stream">The stream instance.</param>
/// <param name="sorter">The key-value pair representing the sorter.</param>
/// <returns>The calculated sort value for the specified property and sorter.</returns>
public static double SortValue<T>(T stream, KeyValuePair<string, string> sorter) where T : FfmpegStream
{
string property = sorter.Key;
bool invert = property.EndsWith("Desc");
if (invert)
property = property[..^4]; // remove "Desc"
string comparison = sorter.Value;
var value = property switch
{
nameof(FfmpegStream.Codec) => stream.Codec,
nameof(AudioStream.Bitrate) => (stream is FfmpegAudioStream audioStream) ? audioStream?.Stream?.Bitrate : null,
_ => stream.GetType().GetProperty(property)?.GetValue(stream, null)
};
double result;
if (value != null && value is string == false && string.IsNullOrWhiteSpace(comparison) &&
double.TryParse(value.ToString(), out double dblValue))
{
// invert the bits of dbl value and return that
result = dblValue;
}
else if (IsMathOperation(comparison))
result = ApplyMathOperation(value.ToString(), comparison) ? 0 : 1;
else if (IsRegex(comparison))
result = Regex.IsMatch(value.ToString(), comparison, RegexOptions.IgnoreCase) ? 0 : 1;
else if (value != null && double.TryParse(value.ToString(), out double dbl))
result = dbl;
else
result = string.Equals(value?.ToString() ?? string.Empty, comparison ?? string.Empty, StringComparison.OrdinalIgnoreCase) ? 0 : 1;
return invert ? InvertBits(result) : result;
}
/// <summary>
/// Adjusts the comparison string by handling common mistakes in units and converting them into full numbers.
/// </summary>
/// <param name="comparisonValue">The original comparison string to be adjusted.</param>
/// <returns>The adjusted comparison string with corrected units or the original comparison if no adjustments are made.</returns>
private static string AdjustComparisonValue(string comparisonValue)
{
if (string.IsNullOrWhiteSpace(comparisonValue))
return string.Empty;
string adjustedComparison = comparisonValue.ToLower().Trim();
// Handle common mistakes in units
if (adjustedComparison.EndsWith("mbps"))
{
// 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),
_ => comparisonValue
};
}
if (adjustedComparison.EndsWith("kbps"))
{
// 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),
_ => 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))
.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))
.ToString(CultureInfo.InvariantCulture),
_ => comparisonValue
};
}
return comparisonValue;
}
/// <summary>
/// Inverts the bits of a double value.
/// </summary>
/// <param name="value">The double value to invert.</param>
/// <returns>The inverted double value.</returns>
private static double InvertBits(double value)
{
// Convert the double to a string with 15 characters above the decimal point
string stringValue = Math.Round(value, 0).ToString("F0");
// Invert the digits and pad left with zeros
char[] charArray = stringValue.PadLeft(15, '0').ToCharArray();
for (int i = 0; i < charArray.Length; i++)
{
charArray[i] = (char)('9' - (charArray[i] - '0'));
}
// Parse the inverted string back to a double
double invertedDouble;
if (double.TryParse(new string(charArray), out invertedDouble))
{
return invertedDouble;
}
else
{
// Handle parsing error
throw new InvalidOperationException("Failed to parse inverted double string.");
}
}
/// <summary>
/// Checks if the comparison string represents a mathematical operation.
/// </summary>
/// <param name="comparison">The comparison string to check.</param>
/// <returns>True if the comparison is a mathematical operation, otherwise false.</returns>
private static bool IsMathOperation(string comparison)
{
// Check if the comparison string starts with <=, <, >, >=, ==, or =
return new[] { "<=", "<", ">", ">=", "==", "=" }.Any(comparison.StartsWith);
}
/// <summary>
/// Checks if the comparison string represents a regular expression.
/// </summary>
/// <param name="comparison">The comparison string to check.</param>
/// <returns>True if the comparison is a regular expression, otherwise false.</returns>
private static bool IsRegex(string comparison)
{
return new[] { "?", "|", "^", "$" }.Any(ch => comparison.Contains(ch));
}
/// <summary>
/// Applies a mathematical operation to the value based on the specified operation string.
/// </summary>
/// <param name="value">The value to apply the operation to.</param>
/// <param name="operation">The operation string representing the mathematical operation.</param>
/// <returns>True if the mathematical operation is successful, otherwise false.</returns>
private static bool ApplyMathOperation(string value, string operation)
{
// This is a basic example; you may need to handle different operators
switch (operation.Substring(0, 2))
{
case "<=":
return Convert.ToDouble(value) <= Convert.ToDouble(AdjustComparisonValue(operation[2..]));
case ">=":
return Convert.ToDouble(value) >= Convert.ToDouble(AdjustComparisonValue(operation[2..]));
case "==":
return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[2..]))) < 0.05f;
case "!=":
return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[2..]))) > 0.05f;
}
switch (operation.Substring(0, 1))
{
case "<":
return Convert.ToDouble(value) < Convert.ToDouble(AdjustComparisonValue(operation[1..]));
case ">":
return Convert.ToDouble(value) > Convert.ToDouble(AdjustComparisonValue(operation[1..]));
case "=":
return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[1..]))) < 0.05f;
}
return false;
}
}

View File

@@ -93,6 +93,18 @@
}
public override string ToString()
=> Stream.ToString();
{
if (Stream != null)
return Stream.ToString();
// can be null in unit tests
return string.Join(" / ", new string[]
{
Index.ToString(),
Language,
Codec,
Title,
Channels > 0 ? Channels.ToString("0.0") : null
}.Where(x => string.IsNullOrWhiteSpace(x) == false));
}
}
}

View File

@@ -0,0 +1,462 @@
#if(DEBUG)
using FileFlows.VideoNodes.FfmpegBuilderNodes;
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VideoNodes.Tests;
namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests;
[TestClass]
public class FfmpegBuilder_TrackSorterTests
{
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnSorters()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac" },
new() { Index = 2, Channels = 2, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 5.1f, Language = "en", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Language", "en"),
new("Channels", ">=5.1"),
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(3, sorted[0].Index);
Assert.AreEqual(1, sorted[1].Index);
Assert.AreEqual(2, sorted[2].Index);
// Additional assertions for logging
Assert.AreEqual("3 / en / ac3 / 5.1", sorted[0].ToString());
Assert.AreEqual("1 / en / aac / 2.0", sorted[1].ToString());
Assert.AreEqual("2 / fr / aac / 2.0", sorted[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnChannels()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac" },
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Channels", ">=5.1"),
};
// Act
var result = trackSorter.ProcessStreams(logger, streams);
// Assert
Assert.AreEqual(2, streams[0].Index);
Assert.AreEqual(3, streams[1].Index);
Assert.AreEqual(1, streams[2].Index);
// Additional assertions for logging
Assert.AreEqual("2 / fr / aac / 5.1", streams[0].ToString());
Assert.AreEqual("3 / en / ac3 / 7.1", streams[1].ToString());
Assert.AreEqual("1 / en / aac / 2.0", streams[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnLanguageThenCodec()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac" },
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Language", "en"),
new("Codec", "ac3"),
};
// Act
var result = trackSorter.ProcessStreams(logger, streams);
// Assert
Assert.AreEqual(3, streams[0].Index);
Assert.AreEqual(1, streams[1].Index);
Assert.AreEqual(2, streams[2].Index);
// Additional assertions for logging
Assert.AreEqual("3 / en / ac3 / 7.1", streams[0].ToString());
Assert.AreEqual("1 / en / aac / 2.0", streams[1].ToString());
Assert.AreEqual("2 / fr / aac / 5.1", streams[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnCustomMathOperation()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac" },
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Channels", ">4.0"),
};
// Act
var result = trackSorter.ProcessStreams(logger, streams);
// Assert
Assert.AreEqual(2, streams[0].Index);
Assert.AreEqual(3, streams[1].Index);
Assert.AreEqual(1, streams[2].Index);
// Additional assertions for logging
Assert.AreEqual("2 / fr / aac / 5.1", streams[0].ToString());
Assert.AreEqual("3 / en / ac3 / 7.1", streams[1].ToString());
Assert.AreEqual("1 / en / aac / 2.0", streams[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnRegex()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac" },
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("Language", "^en$"),
};
// Act
var result = trackSorter.ProcessStreams(logger, streams);
// Assert
Assert.AreEqual(1, streams[0].Index);
Assert.AreEqual(3, streams[1].Index);
Assert.AreEqual(2, streams[2].Index);
// Additional assertions for logging
Assert.AreEqual("1 / en / aac / 2.0", streams[0].ToString());
Assert.AreEqual("3 / en / ac3 / 7.1", streams[1].ToString());
Assert.AreEqual("2 / fr / aac / 5.1", streams[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnMultipleSorters()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac" },
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" },
new() { Index = 4, Channels = 2, Language = "fr", Codec = "ac3" },
new() { Index = 5, Channels = 5.1f, Language = "en", Codec = "ac3" },
new() { Index = 6, Channels = 7.1f, Language = "fr", Codec = "aac" },
new() { Index = 7, Channels = 2, Language = "en", Codec = "ac3" },
new() { Index = 8, Channels = 5.1f, Language = "fr", Codec = "ac3" },
new() { Index = 9, Channels = 7.1f, Language = "en", Codec = "aac" },
new() { Index = 10, Channels = 2, Language = "fr", Codec = "aac" },
};
// Mock sorters by different properties and custom math operations
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Language", "en"),
new("Channels", ">4.0"),
new("Codec", "ac3")
};
// Act
var result = trackSorter.ProcessStreams(logger, streams);
// Assert
// en
Assert.AreEqual(3, streams[0].Index);
Assert.AreEqual(5, streams[1].Index);
Assert.AreEqual(9, streams[2].Index);
Assert.AreEqual(7, streams[3].Index);
Assert.AreEqual(1, streams[4].Index);
// >5 non en, 2, 6, 8 , 8 first cause ac3
Assert.AreEqual(8, streams[5].Index);
Assert.AreEqual(2, streams[6].Index);
Assert.AreEqual(6, streams[7].Index);
Assert.AreEqual(4, streams[8].Index);
Assert.AreEqual(10, streams[9].Index);
// Additional assertions for logging
// Add assertions for the log messages if needed
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnBitrate()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac", Stream = new AudioStream() { Bitrate = 140_000_000 }},
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "dts" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 20_000_000 }},
new() { Index = 4, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("Bitrate", "")
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(3, sorted[0].Index);
Assert.AreEqual(1, sorted[1].Index);
Assert.AreEqual(2, sorted[2].Index);
Assert.AreEqual(4, sorted[3].Index);
// Additional assertions for logging
Assert.AreEqual(20_000_000, sorted[0].Stream.Bitrate);
Assert.AreEqual(140_000_000, sorted[1].Stream.Bitrate);
Assert.AreEqual(200_000_000, sorted[2].Stream.Bitrate);
Assert.AreEqual(200_000_000, sorted[3].Stream.Bitrate);
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnChannelsAsc()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac", Stream = new AudioStream() { Bitrate = 140_000_000 }},
new() { Index = 2, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 20_000_000 }},
new() { Index = 3, Channels = 5.1f, Language = "fr", Codec = "dts" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
new() { Index = 4, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("Channels", "")
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(1, sorted[0].Index);
Assert.AreEqual(3, sorted[1].Index);
Assert.AreEqual(2, sorted[2].Index);
Assert.AreEqual(4, sorted[3].Index);
// Additional assertions for logging
Assert.AreEqual(2.0f, sorted[0].Channels);
Assert.AreEqual(5.1f, sorted[1].Channels);
Assert.AreEqual(7.1f, sorted[2].Channels);
Assert.AreEqual(7.1f, sorted[3].Channels);
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnChannelsDesc()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac", Stream = new AudioStream() { Bitrate = 140_000_000 }},
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "dts" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 20_000_000 }},
new() { Index = 4, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("ChannelsDesc", "")
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(3, sorted[0].Index);
Assert.AreEqual(4, sorted[1].Index);
Assert.AreEqual(2, sorted[2].Index);
Assert.AreEqual(1, sorted[3].Index);
// Additional assertions for logging
Assert.AreEqual(7.1f, sorted[0].Channels);
Assert.AreEqual(7.1f, sorted[1].Channels);
Assert.AreEqual(5.1f, sorted[2].Channels);
Assert.AreEqual(2.0f, sorted[3].Channels);
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnBitrateInvert()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac", Stream = new AudioStream() { Bitrate = 140_000_000 }},
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "dts" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 20_000_000 }},
new() { Index = 4, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("BitrateDesc", "")
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(2, sorted[0].Index);
Assert.AreEqual(4, sorted[1].Index);
Assert.AreEqual(1, sorted[2].Index);
Assert.AreEqual(3, sorted[3].Index);
// Additional assertions for logging
Assert.AreEqual(200_000_000, sorted[0].Stream.Bitrate);
Assert.AreEqual(200_000_000, sorted[1].Stream.Bitrate);
Assert.AreEqual(140_000_000, sorted[2].Stream.Bitrate);
Assert.AreEqual(20_000_000, sorted[3].Stream.Bitrate);
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnBitrateAndCodec()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac", Stream = new AudioStream() { Bitrate = 140_000_000 }},
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "dts" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 20_000_000 }},
new() { Index = 4, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("BitrateDesc", ""),
new ("Codec", "ac3"),
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(4, sorted[0].Index);
Assert.AreEqual(2, sorted[1].Index);
Assert.AreEqual(1, sorted[2].Index);
Assert.AreEqual(3, sorted[3].Index);
// Additional assertions for logging
Assert.AreEqual(200_000_000, sorted[0].Stream.Bitrate);
Assert.AreEqual(200_000_000, sorted[1].Stream.Bitrate);
Assert.AreEqual(140_000_000, sorted[2].Stream.Bitrate);
Assert.AreEqual(20_000_000, sorted[3].Stream.Bitrate);
Assert.AreEqual("ac3", sorted[0].Codec);
Assert.AreEqual("dts", sorted[1].Codec);
Assert.AreEqual("aac", sorted[2].Codec);
Assert.AreEqual("ac3", sorted[3].Codec);
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnBitrateUnit()
{
// Arrange
var logger = new TestLogger();
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "aac", Stream = new AudioStream() { Bitrate = 140_000_000 }},
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "dts" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 20_000_000 }},
new() { Index = 4, Channels = 7.1f, Language = "en", Codec = "ac3" , Stream = new AudioStream() { Bitrate = 200_000_000 }},
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("Bitrate", ">=150Mbps")
};
// Act
var sorted = trackSorter.SortStreams(streams);
// Assert
Assert.AreEqual(2, sorted[0].Index);
Assert.AreEqual(4, sorted[1].Index);
Assert.AreEqual(1, sorted[2].Index);
Assert.AreEqual(3, sorted[3].Index);
// Additional assertions for logging
Assert.AreEqual(200_000_000, sorted[0].Stream.Bitrate);
Assert.AreEqual(200_000_000, sorted[1].Stream.Bitrate);
Assert.AreEqual(140_000_000, sorted[2].Stream.Bitrate);
Assert.AreEqual(20_000_000, sorted[3].Stream.Bitrate);
}
}
#endif

View File

@@ -365,6 +365,22 @@
"MakeFirst-Help": "If the original language track should also become the first track."
}
},
"FfmpegBuilderTrackSorter": {
"Label": "FFMPEG Builder: Track Sorter",
"Outputs": {
"1": "Tracks have been modified",
"2": "No tracks have been changed"
},
"Description": "This flow element sorts tracks based on sorting options set by the user.",
"Fields": {
"StreamType": "Type",
"StreamType-Help": "The type of tracks that should be updated",
"Sorters": "Sorters",
"Sorters-Help": "Add one or more sorting options to sort the tracks by.",
"SortersKey": "Field",
"SortersValue": "Value"
}
},
"FfmpegBuilderSubtitleClearDefault": {
"Label": "FFMPEG Builder: Subtitle Clear Default",
"Description": "This node will clear the default flag from subtitles.",