mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-10-26 16:45:09 +00:00
Backport pull request #17844 from jellyfin/release-12.z
Match parental ratings case-insensitively
Original-merge: 7d0a33b762
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
This commit is contained in:
1 parent
8150ee2484
commit
89af37462e
2 files changed
+65
-7
No files matched your search
@@ -139,7 +139,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
var ratingSystem = await JsonSerializer.DeserializeAsync<ParentalRatingSystem>(stream, _jsonOptions).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
|
||||
|
||||
var dict = new Dictionary<string, ParentalRatingScore?>();
|
||||
var dict = new Dictionary<string, ParentalRatingScore?>(StringComparer.OrdinalIgnoreCase);
|
||||
if (ratingSystem.Ratings is not null)
|
||||
{
|
||||
foreach (var ratingEntry in ratingSystem.Ratings)
|
||||
@@ -374,12 +374,25 @@ namespace Emby.Server.Implementations.Localization
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(rating);
|
||||
|
||||
// Handle unrated content. This has to happen before the split below,
|
||||
// because some of the unrated values contain a '/' themselves (e.g. "n/a").
|
||||
if (IsUnrated(rating))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Some providers may list multiple ratings separated by '/' (e.g. "SE:15 / SE:15+ / SE:Från 15 år").
|
||||
// Try each one in order and use the first that resolves.
|
||||
var ratingValues = rating.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
foreach (var ratingValue in ratingValues)
|
||||
{
|
||||
// A single entry of such a list may be unrated while a later one still resolves
|
||||
if (IsUnrated(ratingValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var score = GetSingleRatingScore(ratingValue, countryCode);
|
||||
if (score is not null)
|
||||
{
|
||||
@@ -390,17 +403,19 @@ namespace Emby.Server.Implementations.Localization
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a rating value marks the content as unrated.
|
||||
/// </summary>
|
||||
/// <param name="rating">Rating value to check.</param>
|
||||
/// <returns>Returns true if the value is an unrated marker.</returns>
|
||||
private static bool IsUnrated(ReadOnlySpan<char> rating)
|
||||
=> _unratedValues.Contains(rating.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a single rating value to a score.
|
||||
/// </summary>
|
||||
private ParentalRatingScore? GetSingleRatingScore(string rating, string? countryCode)
|
||||
{
|
||||
// Handle unrated content
|
||||
if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert ints directly
|
||||
// This may override some of the locale specific age ratings (but those always map to the same age)
|
||||
if (TryParseRatingAsScore(rating, out var ratingAge))
|
||||
|
||||
@@ -212,6 +212,30 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
||||
Assert.Equal(expectedSubScore, score.SubScore);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// Rating strings are stored mixed-case in the *.json rating systems and must match regardless of casing
|
||||
[InlineData("btl", "se", 0, null)] // Direct lookup, lowercase of "Btl"
|
||||
[InlineData("BARNTILLÅTEN", "se", 0, null)] // Direct lookup, uppercase incl. diacritics
|
||||
[InlineData("SE-BTL", "se", 0, null)] // Country prefix stripped against the configured country
|
||||
[InlineData("SE-BTL", "us", 0, null)] // Country prefix resolved via the separator fallback
|
||||
[InlineData("Från 7 År", "se", 7, null)] // Diacritic casing (json has "Från 7 år")
|
||||
[InlineData("SE-Från 7 År", "us", 7, null)] // Same, via the separator fallback
|
||||
[InlineData("fsk-16", "de", 16, null)] // Not Sweden specific: lowercase of "FSK-16"
|
||||
public async Task GetRatingScore_IsCaseInsensitive_Success(string value, string countryCode, int? expectedScore, int? expectedSubScore)
|
||||
{
|
||||
var localizationManager = Setup(new ServerConfiguration
|
||||
{
|
||||
MetadataCountryCode = countryCode
|
||||
});
|
||||
await localizationManager.LoadAll();
|
||||
|
||||
var score = localizationManager.GetRatingScore(value);
|
||||
|
||||
Assert.NotNull(score);
|
||||
Assert.Equal(expectedScore, score.Score);
|
||||
Assert.Equal(expectedSubScore, score.SubScore);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0", 0, null)]
|
||||
[InlineData("1", 1, null)]
|
||||
@@ -241,6 +265,25 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
||||
Assert.Null(localizationManager.GetRatingScore("unrated"));
|
||||
Assert.Null(localizationManager.GetRatingScore("Not Rated"));
|
||||
Assert.Null(localizationManager.GetRatingScore("n/a"));
|
||||
Assert.Null(localizationManager.GetRatingScore("N/A"));
|
||||
Assert.Null(localizationManager.GetRatingScore(" n/a "));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// "NR" and "UR" are rating strings of some systems, so they must stay unrated when listed alongside others
|
||||
[InlineData("NR / R", 17, 0)]
|
||||
[InlineData("unrated / R", 17, 0)]
|
||||
[InlineData("R / NR", 17, 0)]
|
||||
public async Task GetRatingLevel_SkipsUnratedListEntries_Success(string value, int? expectedScore, int? expectedSubScore)
|
||||
{
|
||||
var localizationManager = Setup(new ServerConfiguration { MetadataCountryCode = "us" });
|
||||
await localizationManager.LoadAll();
|
||||
|
||||
var score = localizationManager.GetRatingScore(value);
|
||||
|
||||
Assert.NotNull(score);
|
||||
Assert.Equal(expectedScore, score.Score);
|
||||
Assert.Equal(expectedSubScore, score.SubScore);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in new issue
Block a user