FF-1775: Updated track sorter to use String Operations

This commit is contained in:
John Andrews
2024-09-12 13:45:13 +12:00
parent 15095ad531
commit b06869f990
5 changed files with 96 additions and 242 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -213,7 +213,9 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode
sorterLength = 2;
}
var sortValue = Math.Round(SortValue(args, stream, Sorters[i], sorterLength)).ToString(CultureInfo.InvariantCulture);
var sortValue = Math.Round(SortValue(args, stream, Sorters[i], sorterLength))
.ToString(CultureInfo.InvariantCulture);
// Trim the sort value to sorter Length characters
string trimmedValue = sortValue[..Math.Min(sortValue.Length, sorterLength)];
@@ -226,30 +228,6 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode
return sortKey.TrimEnd('|');
}
/// <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.
@@ -284,17 +262,10 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode
if (property == nameof(stream.Language))
{
object? oOriginalLanguage = null;
args?.Variables?.TryGetValue("OriginalLanguage", out oOriginalLanguage);
var originalLanguage = LanguageHelper.GetIso2Code(oOriginalLanguage?.ToString() ?? string.Empty);
comparison = string.Join("|",
comparison.Split('|', StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
{
if (x?.ToLowerInvariant()?.StartsWith("orig") == true)
return originalLanguage;
return LanguageHelper.GetIso2Code(x);
}).Where(x => string.IsNullOrWhiteSpace(x) == false).ToArray());
bool matches = LanguageHelper.Matches(args, comparison, stream.Language);
if (invert)
return matches ? 1 : 0;
return matches ? 0 : 1;
}
var value = property switch
@@ -320,129 +291,16 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode
result = dblValue;
}
}
else if (IsMathOperation(comparison))
result = ApplyMathOperation(value.ToString(), comparison) ? 0 : 1;
else if (GeneralHelper.IsRegex(comparison))
result = Regex.IsMatch(value.ToString(), comparison, RegexOptions.IgnoreCase) ? 0 : 1;
else if (args.MathHelper.IsMathOperation(comparison))
result = args.MathHelper.IsTrue(comparison, value.ToString()) ? 0 : 1;
else if (value != null && double.TryParse(value.ToString(), out double dbl))
result = dbl;
else if (property == nameof(FfmpegStream.Title) && string.IsNullOrWhiteSpace(comparison) == false && string.IsNullOrWhiteSpace(value?.ToString()) == false)
result = value?.ToString()?.ToLowerInvariant()?.Contains(comparison.ToLowerInvariant()) == true ? 0 : 1;
else
result = string.Equals(value?.ToString() ?? string.Empty, comparison ?? string.Empty, StringComparison.OrdinalIgnoreCase) ? 0 : 1;
result = args.StringHelper.Matches(comparison, value) ? 0 : 1;
return invert ? InvertBits(result, sorterLength) : 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("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"))
{
return adjustedComparison[..^2] switch
{
{ } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000 )
.ToString(CultureInfo.InvariantCulture),
_ => comparisonValue
};
}
if (adjustedComparison.EndsWith("tb"))
{
return adjustedComparison[..^2] switch
{
{ } 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;
}
/// <summary>
/// Inverts the bits of a double value.
/// </summary>
@@ -474,49 +332,4 @@ public class FfmpegBuilderTrackSorter : FfmpegBuilderNode
}
}
/// <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>
/// 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..].Trim()));
case ">=":
return Convert.ToDouble(value) >= Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()));
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;
}
switch (operation.Substring(0, 1))
{
case "<":
return Convert.ToDouble(value) < Convert.ToDouble(AdjustComparisonValue(operation[1..].Trim()));
case ">":
return Convert.ToDouble(value) > Convert.ToDouble(AdjustComparisonValue(operation[1..].Trim()));
case "=":
return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[1..].Trim()))) < 0.05f;
}
return false;
}
}

View File

@@ -250,7 +250,7 @@ public class VideoHasStream : VideoNode
return false;
}
if (string.IsNullOrEmpty(lang) == false && LanguageMatches(args, lang, x.Language) == false)
if (string.IsNullOrEmpty(lang) == false && LanguageHelper.Matches(args, lang, x.Language) == false)
return false;
double xChannels = Math.Round(x.Channels, 1);
@@ -295,7 +295,7 @@ public class VideoHasStream : VideoNode
return false;
}
if (string.IsNullOrEmpty(lang) == false && LanguageMatches(args, lang, x.Language) == false)
if (string.IsNullOrEmpty(lang) == false && LanguageHelper.Matches(args, lang, x.Language) == false)
return false;
if (string.IsNullOrEmpty(Default) == false)
@@ -330,45 +330,6 @@ public class VideoHasStream : VideoNode
return found ? 1 : 2;
}
/// <summary>
/// Tests if a language matches
/// </summary>
/// <param name="args">the node parameters</param>
/// <param name="lang">the language string to test</param>
/// <param name="streamLanguage">the language of the stream</param>
/// <returns>the match result</returns>
private bool LanguageMatches(NodeParameters args, string lang, string streamLanguage)
{
lang = args.ReplaceVariables(lang.Replace("{orig}", "{OriginalLanguage}"), stripMissing: false);
if (args.Variables.TryGetValue("OriginalLanguage", out var oOrigLanguage) && oOrigLanguage is string origLanguage &&
string.IsNullOrWhiteSpace(origLanguage) == false)
{
lang = lang.Replace("OriginalLanguage", origLanguage, StringComparison.InvariantCultureIgnoreCase);
lang = lang.Replace("original", origLanguage);
lang = lang.Replace("orig", origLanguage);
}
string iso1 = LanguageHelper.GetIso1Code(streamLanguage);
string iso2 = LanguageHelper.GetIso2Code(streamLanguage);
string english = LanguageHelper.GetEnglishFor(streamLanguage);
var iso1Matches = ValueMatch(lang, iso1) == MatchResult.Matched;
var iso2Matches = ValueMatch(lang, iso2) == MatchResult.Matched;
var engMatches = ValueMatch(lang, english) == MatchResult.Matched;
bool anyMatches = iso1Matches || iso2Matches || engMatches;
if(anyMatches == false)
{
args.Logger.ILog("Language does not match: " + streamLanguage);
return false;
}
if(iso1Matches)
args.Logger?.ILog($"Language ISO-1 match found: '{iso1}' vs '{lang}'");
if(iso2Matches)
args.Logger?.ILog($"Language ISO-2 match found: '{iso2}' vs '{lang}'");
if(engMatches)
args.Logger?.ILog($"Language English match found: '{english}' vs '{lang}'");
return true;
}
/// <summary>
/// Tests if a value matches the pattern

View File

@@ -254,7 +254,7 @@ public class FfmpegBuilder_TrackSorterTests : VideoTestBase
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("Codec", "^ac3$"),
new ("Codec", "/^ac3$/"),
};
// Act
@@ -271,6 +271,38 @@ public class FfmpegBuilder_TrackSorterTests : VideoTestBase
Assert.AreEqual("2 / fr / eac3 / 5.1", streams[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnAc3()
{
// Arrange
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "en", Codec = "ac3" },
new() { Index = 2, Channels = 5.1f, Language = "fr", Codec = "eac3" },
new() { Index = 3, Channels = 7.1f, Language = "en", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new ("Codec", "ac3"),
};
// Act
var result = trackSorter.ProcessStreams(args, 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 / ac3 / 2.0", streams[0].ToString());
Assert.AreEqual("3 / en / ac3 / 7.1", streams[1].ToString());
Assert.AreEqual("2 / fr / eac3 / 5.1", streams[2].ToString());
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnMultipleSorters()
{
@@ -593,6 +625,54 @@ public class FfmpegBuilder_TrackSorterTests : VideoTestBase
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnLanguage_EndsWith()
{
// Arrange
var trackSorter = new FfmpegBuilderTrackSorter();
List<FfmpegAudioStream> streams = new List<FfmpegAudioStream>
{
new() { Index = 1, Channels = 2, Language = "eng", Codec = "aac" },
new() { Index = 2, Channels = 2, Language = "fr", Codec = "aac" },
new() { Index = 3, Channels = 5.1f, Language = "en", Codec = "eac3" },
new() { Index = 4, Channels = 5.1f, Language = "ger", Codec = "ac3" },
new() { Index = 5, Channels = 5.1f, Language = "German", Codec = "dts" },
new() { Index = 6, Channels = 5.1f, Language = "English", Codec = "aac" },
new() { Index = 7, Channels = 5.1f, Language = "eng", Codec = "ac3" },
};
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Language", "*lish")
};
// Act
var sorted = trackSorter.SortStreams(args, streams);
// Assert
Assert.AreEqual(1, sorted[0].Index);
Assert.AreEqual(3, sorted[1].Index);
Assert.AreEqual(6, sorted[2].Index);
Assert.AreEqual(7, sorted[3].Index);
// non english
Assert.AreEqual(2, sorted[4].Index);
Assert.AreEqual(4, sorted[5].Index);
Assert.AreEqual(5, sorted[6].Index);
// Additional assertions for logging
Assert.AreEqual("eng", sorted[0].Language);
Assert.AreEqual("en", sorted[1].Language);
Assert.AreEqual("English", sorted[2].Language);
Assert.AreEqual("eng", sorted[3].Language);
Assert.AreEqual("fr", sorted[4].Language);
Assert.AreEqual("ger", sorted[5].Language);
Assert.AreEqual("German", sorted[6].Language);
}
[TestMethod]
public void ProcessStreams_SortsStreamsBasedOnLanguageRegex()
@@ -613,7 +693,7 @@ public class FfmpegBuilder_TrackSorterTests : VideoTestBase
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Language", "en|deu")
new("Language", "/en|deu/")
};
// Act
@@ -663,7 +743,7 @@ public class FfmpegBuilder_TrackSorterTests : VideoTestBase
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("Language", "orig|deu")
new("Language", "/orig|deu/")
};
// Act
@@ -795,7 +875,7 @@ public class FfmpegBuilder_TrackSorterTests : VideoTestBase
// Mock sorters by different properties
trackSorter.Sorters = new List<KeyValuePair<string, string>>
{
new("TitleDesc", "Commentary"),
new("TitleDesc", "*Commentary*"),
new("Language", "English")
};
trackSorter.StreamType = "Audio";