mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-13 04:06:31 +01:00
Merge pull request #16328 from Shadowghost/rating-fix
Fix Canadian rating and fallback to unrated if we have a CountryCode but no matching rating
This commit is contained in:
@@ -320,6 +320,14 @@ namespace Emby.Server.Implementations.Localization
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (ratingsDictionary is not null && rating.Length > countryCode.Length
|
||||
&& rating.StartsWith(countryCode, StringComparison.OrdinalIgnoreCase)
|
||||
&& (rating[countryCode.Length] == '-' || rating[countryCode.Length] == ':')
|
||||
&& ratingsDictionary.TryGetValue(rating[(countryCode.Length + 1)..].Trim(), out var normalizedValue))
|
||||
{
|
||||
return normalizedValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -345,35 +353,70 @@ namespace Emby.Server.Implementations.Localization
|
||||
}
|
||||
}
|
||||
|
||||
// Try splitting by : to handle "Germany: FSK-18"
|
||||
if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
|
||||
// Try splitting by country prefix separator to handle "US:PG-13", "Germany: FSK-18", "DE-FSK-18"
|
||||
if (TryGetRatingScoreBySeparator(rating, ':', out var result)
|
||||
|| TryGetRatingScoreBySeparator(rating, '-', out result))
|
||||
{
|
||||
var ratingLevelRightPart = rating.AsSpan().RightPart(':');
|
||||
if (ratingLevelRightPart.Length != 0)
|
||||
{
|
||||
return GetRatingScore(ratingLevelRightPart.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle prefix country code to handle "DE-18"
|
||||
if (rating.Contains('-', StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var ratingSpan = rating.AsSpan();
|
||||
|
||||
// Extract culture from country prefix
|
||||
var culture = FindLanguageInfo(ratingSpan.LeftPart('-').ToString());
|
||||
|
||||
var ratingLevelRightPart = ratingSpan.RightPart('-');
|
||||
if (ratingLevelRightPart.Length != 0)
|
||||
{
|
||||
// Check rating system of culture
|
||||
return GetRatingScore(ratingLevelRightPart.ToString(), culture?.TwoLetterISOLanguageName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetRatingScoreBySeparator(string rating, char separator, out ParentalRatingScore? result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (rating.IndexOf(separator, StringComparison.Ordinal) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ratingSpan = rating.AsSpan();
|
||||
var countryPart = ratingSpan.LeftPart(separator).Trim().ToString();
|
||||
var ratingPart = ratingSpan.RightPart(separator).Trim().ToString();
|
||||
if (ratingPart.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string? resolvedCountryCode = null;
|
||||
|
||||
if (_allParentalRatings.ContainsKey(countryPart))
|
||||
{
|
||||
resolvedCountryCode = countryPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
var culture = FindLanguageInfo(countryPart);
|
||||
if (culture is not null)
|
||||
{
|
||||
resolvedCountryCode = culture.TwoLetterISOLanguageName;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedCountryCode is not null
|
||||
&& _allParentalRatings.TryGetValue(resolvedCountryCode, out var countryRatings))
|
||||
{
|
||||
if (countryRatings.TryGetValue(ratingPart, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.LogWarning(
|
||||
"Rating '{Rating}' not found in the '{CountryCode}' rating system, treating as unrated",
|
||||
rating,
|
||||
resolvedCountryCode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Country not identified or no rating data available, try recursive lookup
|
||||
result = GetRatingScore(ratingPart, resolvedCountryCode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetLocalizedString(string phrase)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"supportsSubScores": true,
|
||||
"ratings": [
|
||||
{
|
||||
"ratingStrings": ["E", "G", "TV-Y", "TV-G"],
|
||||
"ratingStrings": ["C", "E", "G", "TV-Y", "TV-G"],
|
||||
"ratingScore": {
|
||||
"score": 0,
|
||||
"subScore": 0
|
||||
@@ -23,11 +23,18 @@
|
||||
"subScore": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ratingStrings": ["C8"],
|
||||
"ratingScore": {
|
||||
"score": 8,
|
||||
"subScore": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"ratingStrings": ["PG", "TV-PG"],
|
||||
"ratingScore": {
|
||||
"score": 9,
|
||||
"subScore": 0
|
||||
"score": 8,
|
||||
"subScore": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -38,7 +45,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ratingStrings": ["TV-14"],
|
||||
"ratingStrings": ["14+", "TV-14"],
|
||||
"ratingScore": {
|
||||
"score": 14,
|
||||
"subScore": 1
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Server.ServerSetupApp;
|
||||
@@ -12,7 +11,7 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// Migrate rating levels.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels))]
|
||||
[JellyfinMigration("2026-03-02T09:00:00", nameof(MigrateRatingLevels))]
|
||||
[JellyfinMigrationBackup(JellyfinDb = true)]
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
internal class MigrateRatingLevels : IDatabaseMigrationRoutine
|
||||
|
||||
@@ -241,6 +241,40 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
||||
Assert.Equal(expectedSubScore, score.SubScore);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("US:INVALID", "US")] // Colon separator, known country code, unknown rating
|
||||
[InlineData("us:INVALID", "US")] // Colon separator, lowercase country code
|
||||
[InlineData("DE-INVALID", "US")] // Hyphen separator, known language prefix, unknown rating
|
||||
[InlineData("ca:INVALID", "US")] // Colon separator, known country code (Canada)
|
||||
public async Task GetRatingScore_UnknownRatingWithKnownCountry_ReturnsNull(string rating, string countryCode)
|
||||
{
|
||||
var localizationManager = Setup(new ServerConfiguration
|
||||
{
|
||||
MetadataCountryCode = countryCode
|
||||
});
|
||||
await localizationManager.LoadAll();
|
||||
|
||||
Assert.Null(localizationManager.GetRatingScore(rating));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("us:R", "DE", 17, 0)] // Colon separator, explicit US country, valid US rating
|
||||
[InlineData("US:PG-13", "DE", 13, 0)] // Colon separator, explicit US country, valid US rating
|
||||
[InlineData("ca:R", "US", 18, 1)] // Colon separator, Canada country code, valid CA rating
|
||||
public async Task GetRatingScore_ValidRatingWithCountrySeparator_ReturnsScore(string rating, string countryCode, int expectedScore, int? expectedSubScore)
|
||||
{
|
||||
var localizationManager = Setup(new ServerConfiguration
|
||||
{
|
||||
MetadataCountryCode = countryCode
|
||||
});
|
||||
await localizationManager.LoadAll();
|
||||
|
||||
var score = localizationManager.GetRatingScore(rating);
|
||||
Assert.NotNull(score);
|
||||
Assert.Equal(expectedScore, score.Score);
|
||||
Assert.Equal(expectedSubScore, score.SubScore);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Default", "Default")]
|
||||
[InlineData("HeaderLiveTV", "Live TV")]
|
||||
|
||||
Reference in New Issue
Block a user