aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs272
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs7
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs80
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/0-prefer.csv11
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/au.csv20
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/be.csv17
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/br.csv14
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/ca.csv26
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/co.csv15
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/de.csv22
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/dk.csv11
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/es.csv30
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/fi.csv20
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/fr.csv17
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/gb.csv29
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/ie.csv15
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/jp.csv15
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/kz.csv13
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/mx.csv12
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/nl.csv14
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/no.csv15
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/nz.csv26
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/ro.csv7
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/ru.csv11
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/se.csv15
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/uk.csv29
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/us.csv73
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs49
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs159
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs3
-rw-r--r--Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs86
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs19
-rw-r--r--MediaBrowser.Model/Entities/ParentalRating.cs4
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs25
36 files changed, 793 insertions, 396 deletions
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 90f03995e..0aa943270 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -3202,7 +3202,8 @@ namespace Emby.Server.Implementations.Data
return IsAlphaNumeric(value);
}
- private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
+#nullable enable
+ private List<string> GetWhereClauses(InternalItemsQuery query, IStatement? statement)
{
if (query.IsResumable ?? false)
{
@@ -3677,7 +3678,6 @@ namespace Emby.Server.Implementations.Data
if (statement is not null)
{
nameContains = FixUnicodeChars(nameContains);
-
statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%");
}
}
@@ -3803,13 +3803,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.ArtistIds)
{
var paramName = "@ArtistIds" + index;
-
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
- if (statement is not null)
- {
- statement.TryBind(paramName, artistId);
- }
-
+ statement?.TryBind(paramName, artistId);
index++;
}
@@ -3824,13 +3819,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.AlbumArtistIds)
{
var paramName = "@ArtistIds" + index;
-
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
- if (statement is not null)
- {
- statement.TryBind(paramName, artistId);
- }
-
+ statement?.TryBind(paramName, artistId);
index++;
}
@@ -3845,13 +3835,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.ContributingArtistIds)
{
var paramName = "@ArtistIds" + index;
-
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
- if (statement is not null)
- {
- statement.TryBind(paramName, artistId);
- }
-
+ statement?.TryBind(paramName, artistId);
index++;
}
@@ -3866,13 +3851,8 @@ namespace Emby.Server.Implementations.Data
foreach (var albumId in query.AlbumIds)
{
var paramName = "@AlbumIds" + index;
-
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
- if (statement is not null)
- {
- statement.TryBind(paramName, albumId);
- }
-
+ statement?.TryBind(paramName, albumId);
index++;
}
@@ -3887,13 +3867,8 @@ namespace Emby.Server.Implementations.Data
foreach (var artistId in query.ExcludeArtistIds)
{
var paramName = "@ExcludeArtistId" + index;
-
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
- if (statement is not null)
- {
- statement.TryBind(paramName, artistId);
- }
-
+ statement?.TryBind(paramName, artistId);
index++;
}
@@ -3908,13 +3883,8 @@ namespace Emby.Server.Implementations.Data
foreach (var genreId in query.GenreIds)
{
var paramName = "@GenreId" + index;
-
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
- if (statement is not null)
- {
- statement.TryBind(paramName, genreId);
- }
-
+ statement?.TryBind(paramName, genreId);
index++;
}
@@ -3929,11 +3899,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in query.Genres)
{
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
- if (statement is not null)
- {
- statement.TryBind("@Genre" + index, GetCleanValue(item));
- }
-
+ statement?.TryBind("@Genre" + index, GetCleanValue(item));
index++;
}
@@ -3948,11 +3914,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in tags)
{
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
- if (statement is not null)
- {
- statement.TryBind("@Tag" + index, GetCleanValue(item));
- }
-
+ statement?.TryBind("@Tag" + index, GetCleanValue(item));
index++;
}
@@ -3967,11 +3929,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in excludeTags)
{
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
- if (statement is not null)
- {
- statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
- }
-
+ statement?.TryBind("@ExcludeTag" + index, GetCleanValue(item));
index++;
}
@@ -3986,14 +3944,8 @@ namespace Emby.Server.Implementations.Data
foreach (var studioId in query.StudioIds)
{
var paramName = "@StudioId" + index;
-
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
-
- if (statement is not null)
- {
- statement.TryBind(paramName, studioId);
- }
-
+ statement?.TryBind(paramName, studioId);
index++;
}
@@ -4008,11 +3960,7 @@ namespace Emby.Server.Implementations.Data
foreach (var item in query.OfficialRatings)
{
clauses.Add("OfficialRating=@OfficialRating" + index);
- if (statement is not null)
- {
- statement.TryBind("@OfficialRating" + index, item);
- }
-
+ statement?.TryBind("@OfficialRating" + index, item);
index++;
}
@@ -4020,34 +3968,96 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(clause);
}
- if (query.MinParentalRating.HasValue)
+ var ratingClauseBuilder = new StringBuilder("(");
+ if (query.HasParentalRating ?? false)
{
- whereClauses.Add("InheritedParentalRatingValue>=@MinParentalRating");
- if (statement is not null)
+ ratingClauseBuilder.Append("InheritedParentalRatingValue not null");
+ if (query.MinParentalRating.HasValue)
{
- statement.TryBind("@MinParentalRating", query.MinParentalRating.Value);
+ ratingClauseBuilder.Append(" AND InheritedParentalRatingValue >= @MinParentalRating");
+ statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
}
- }
- if (query.MaxParentalRating.HasValue)
+ if (query.MaxParentalRating.HasValue)
+ {
+ ratingClauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
+ statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
+ }
+ }
+ else if (query.BlockUnratedItems.Length > 0)
{
- whereClauses.Add("InheritedParentalRatingValue<=@MaxParentalRating");
+ var paramName = "@UnratedType";
+ var index = 0;
+ string blockedUnratedItems = string.Join(',', query.BlockUnratedItems.Select(_ => paramName + index++));
+ ratingClauseBuilder.Append("(InheritedParentalRatingValue is null AND UnratedType not in (" + blockedUnratedItems + "))");
+
if (statement is not null)
{
- statement.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
+ for (var ind = 0; ind < query.BlockUnratedItems.Length; ind++)
+ {
+ statement.TryBind(paramName + ind, query.BlockUnratedItems[ind].ToString());
+ }
}
- }
- if (query.HasParentalRating.HasValue)
- {
- if (query.HasParentalRating.Value)
+ if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
{
- whereClauses.Add("InheritedParentalRatingValue > 0");
+ ratingClauseBuilder.Append(" OR (");
}
- else
+
+ if (query.MinParentalRating.HasValue)
{
- whereClauses.Add("InheritedParentalRatingValue = 0");
+ ratingClauseBuilder.Append("InheritedParentalRatingValue >= @MinParentalRating");
+ statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
}
+
+ if (query.MaxParentalRating.HasValue)
+ {
+ if (query.MinParentalRating.HasValue)
+ {
+ ratingClauseBuilder.Append(" AND ");
+ }
+
+ ratingClauseBuilder.Append("InheritedParentalRatingValue <= @MaxParentalRating");
+ statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
+ }
+
+ if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
+ {
+ ratingClauseBuilder.Append(")");
+ }
+
+ if (!(query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue))
+ {
+ ratingClauseBuilder.Append(" OR InheritedParentalRatingValue not null");
+ }
+ }
+ else if (query.MinParentalRating.HasValue)
+ {
+ ratingClauseBuilder.Append("InheritedParentalRatingValue is null OR (InheritedParentalRatingValue >= @MinParentalRating");
+ statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
+
+ if (query.MaxParentalRating.HasValue)
+ {
+ ratingClauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
+ statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
+ }
+
+ ratingClauseBuilder.Append(")");
+ }
+ else if (query.MaxParentalRating.HasValue)
+ {
+ ratingClauseBuilder.Append("InheritedParentalRatingValue is null OR InheritedParentalRatingValue <= @MaxParentalRating");
+ statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
+ }
+ else if (!query.HasParentalRating ?? false)
+ {
+ ratingClauseBuilder.Append("InheritedParentalRatingValue is null");
+ }
+
+ var ratingClauseString = ratingClauseBuilder.ToString();
+ if (!string.Equals(ratingClauseString, "(", StringComparison.OrdinalIgnoreCase))
+ {
+ whereClauses.Add(ratingClauseString + ")");
}
if (query.HasOfficialRating.HasValue)
@@ -4089,37 +4099,25 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
- if (statement is not null)
- {
- statement.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
- }
+ statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
- if (statement is not null)
- {
- statement.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
- }
+ statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
- if (statement is not null)
- {
- statement.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
- }
+ statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
- if (statement is not null)
- {
- statement.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
- }
+ statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
}
if (query.HasSubtitles.HasValue)
@@ -4169,15 +4167,11 @@ namespace Emby.Server.Implementations.Data
if (query.Years.Length == 1)
{
whereClauses.Add("ProductionYear=@Years");
- if (statement is not null)
- {
- statement.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
- }
+ statement?.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
}
else if (query.Years.Length > 1)
{
var val = string.Join(',', query.Years);
-
whereClauses.Add("ProductionYear in (" + val + ")");
}
@@ -4185,10 +4179,7 @@ namespace Emby.Server.Implementations.Data
if (isVirtualItem.HasValue)
{
whereClauses.Add("IsVirtualItem=@IsVirtualItem");
- if (statement is not null)
- {
- statement.TryBind("@IsVirtualItem", isVirtualItem.Value);
- }
+ statement?.TryBind("@IsVirtualItem", isVirtualItem.Value);
}
if (query.IsSpecialSeason.HasValue)
@@ -4219,31 +4210,22 @@ namespace Emby.Server.Implementations.Data
if (queryMediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
- if (statement is not null)
- {
- statement.TryBind("@MediaTypes", queryMediaTypes[0]);
- }
+ statement?.TryBind("@MediaTypes", queryMediaTypes[0]);
}
else if (queryMediaTypes.Length > 1)
{
var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'"));
-
whereClauses.Add("MediaType in (" + val + ")");
}
if (query.ItemIds.Length > 0)
{
var includeIds = new List<string>();
-
var index = 0;
foreach (var id in query.ItemIds)
{
includeIds.Add("Guid = @IncludeId" + index);
- if (statement is not null)
- {
- statement.TryBind("@IncludeId" + index, id);
- }
-
+ statement?.TryBind("@IncludeId" + index, id);
index++;
}
@@ -4253,16 +4235,11 @@ namespace Emby.Server.Implementations.Data
if (query.ExcludeItemIds.Length > 0)
{
var excludeIds = new List<string>();
-
var index = 0;
foreach (var id in query.ExcludeItemIds)
{
excludeIds.Add("Guid <> @ExcludeId" + index);
- if (statement is not null)
- {
- statement.TryBind("@ExcludeId" + index, id);
- }
-
+ statement?.TryBind("@ExcludeId" + index, id);
index++;
}
@@ -4283,11 +4260,7 @@ namespace Emby.Server.Implementations.Data
var paramName = "@ExcludeProviderId" + index;
excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")");
- if (statement is not null)
- {
- statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
- }
-
+ statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
index++;
break;
@@ -4312,7 +4285,7 @@ namespace Emby.Server.Implementations.Data
}
// TODO this seems to be an idea for a better schema where ProviderIds are their own table
- // buut this is not implemented
+ // but this is not implemented
// hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
// TODO this is a really BAD way to do it since the pair:
@@ -4326,11 +4299,7 @@ namespace Emby.Server.Implementations.Data
hasProviderIds.Add("ProviderIds like " + paramName);
// this replaces the placeholder with a value, here: %key=val%
- if (statement is not null)
- {
- statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
- }
-
+ statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
index++;
break;
@@ -4407,11 +4376,7 @@ namespace Emby.Server.Implementations.Data
if (query.AncestorIds.Length == 1)
{
whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
-
- if (statement is not null)
- {
- statement.TryBind("@AncestorId", query.AncestorIds[0]);
- }
+ statement?.TryBind("@AncestorId", query.AncestorIds[0]);
}
if (query.AncestorIds.Length > 1)
@@ -4424,39 +4389,13 @@ namespace Emby.Server.Implementations.Data
{
var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
- if (statement is not null)
- {
- statement.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
- }
+ statement?.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
}
if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey))
{
whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey");
-
- if (statement is not null)
- {
- statement.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
- }
- }
-
- if (query.BlockUnratedItems.Length == 1)
- {
- whereClauses.Add("(InheritedParentalRatingValue > 0 or UnratedType <> @UnratedType)");
- if (statement is not null)
- {
- statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString());
- }
- }
-
- if (query.BlockUnratedItems.Length > 1)
- {
- var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
- whereClauses.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))",
- inClause));
+ statement?.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
}
if (query.ExcludeInheritedTags.Length > 0)
@@ -4605,6 +4544,7 @@ namespace Emby.Server.Implementations.Data
return whereClauses;
}
+#nullable disable
/// <summary>
/// Formats a where clause for the specified provider.
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 5103b1fbf..e928f1ff3 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -83,13 +83,14 @@ namespace Emby.Server.Implementations.Dto
/// <inheritdoc />
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
{
- var returnItems = new BaseItemDto[items.Count];
+ var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList();
+ var returnItems = new BaseItemDto[accessibleItems.Count];
var programTuples = new List<(BaseItem, BaseItemDto)>();
var channelTuples = new List<(BaseItemDto, LiveTvChannel)>();
- for (int index = 0; index < items.Count; index++)
+ for (int index = 0; index < accessibleItems.Count; index++)
{
- var item = items[index];
+ var item = accessibleItems[index];
var dto = GetBaseItemDtoInternal(item, options, user, owner);
if (item is LiveTvChannel tvChannel)
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index b418c7877..6e2a33fd5 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
@@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Localization
private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
- private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
+ private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated", "nr" };
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger<LocalizationManager> _logger;
@@ -86,12 +87,10 @@ namespace Emby.Server.Implementations.Localization
var name = parts[0];
dict.Add(name, new ParentalRating(name, value));
}
-#if DEBUG
else
{
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
}
-#endif
}
_allParentalRatings[countryCode] = dict;
@@ -184,7 +183,56 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc />
public IEnumerable<ParentalRating> GetParentalRatings()
- => GetParentalRatingsDictionary().Values;
+ {
+ var ratings = GetParentalRatingsDictionary().Values.ToList();
+
+ // Add common ratings to ensure them being available for selection.
+ // Based on the US rating system due to it being the main source of rating in the metadata providers
+ // Minimum rating possible
+ if (!ratings.Any(x => x.Value == 0))
+ {
+ ratings.Add(new ParentalRating("Approved", 0));
+ }
+
+ // Matches PG (this has different age restrictions depending on country)
+ if (!ratings.Any(x => x.Value == 10))
+ {
+ ratings.Add(new ParentalRating("10", 10));
+ }
+
+ // Matches PG-13
+ if (!ratings.Any(x => x.Value == 13))
+ {
+ ratings.Add(new ParentalRating("13", 13));
+ }
+
+ // Matches TV-14
+ if (!ratings.Any(x => x.Value == 14))
+ {
+ ratings.Add(new ParentalRating("14", 14));
+ }
+
+ // Catchall if max rating of country is less than 21
+ // Using 21 instead of 18 to be sure to allow access to all rated content except adult and banned
+ if (!ratings.Any(x => x.Value >= 21))
+ {
+ ratings.Add(new ParentalRating("21", 21));
+ }
+
+ // A lot of countries don't excplicitly have a seperate rating for adult content
+ if (!ratings.Any(x => x.Value == 1000))
+ {
+ ratings.Add(new ParentalRating("XXX", 1000));
+ }
+
+ // A lot of countries don't excplicitly have a seperate rating for banned content
+ if (!ratings.Any(x => x.Value == 1001))
+ {
+ ratings.Add(new ParentalRating("Banned", 1001));
+ }
+
+ return ratings.OrderBy(r => r.Value);
+ }
/// <summary>
/// Gets the parental ratings dictionary.
@@ -194,6 +242,7 @@ namespace Emby.Server.Implementations.Localization
{
var countryCode = _configurationManager.Configuration.MetadataCountryCode;
+ // Fall back to US ratings if no country code is specified or country code does not exist.
if (string.IsNullOrEmpty(countryCode))
{
countryCode = "us";
@@ -205,15 +254,15 @@ namespace Emby.Server.Implementations.Localization
}
/// <summary>
- /// Gets the ratings.
+ /// Gets the ratings for a country.
/// </summary>
/// <param name="countryCode">The country code.</param>
/// <returns>The ratings.</returns>
private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
{
- _allParentalRatings.TryGetValue(countryCode, out var value);
+ _allParentalRatings.TryGetValue(countryCode, out var countryValue);
- return value;
+ return countryValue;
}
/// <inheritdoc />
@@ -221,12 +270,14 @@ namespace Emby.Server.Implementations.Localization
{
ArgumentException.ThrowIfNullOrEmpty(rating);
+ // Handle unrated content
if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Fairly common for some users to have "Rated R" in their rating field
+ rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase);
rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
var ratingsDictionary = GetParentalRatingsDictionary();
@@ -246,18 +297,17 @@ namespace Emby.Server.Implementations.Localization
}
// Try splitting by : to handle "Germany: FSK 18"
- var index = rating.IndexOf(':', StringComparison.Ordinal);
- if (index != -1)
+ if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
{
- var trimmedRating = rating.AsSpan(index).TrimStart(':').Trim();
+ return GetRatingLevel(rating.AsSpan().RightPart(':').ToString());
+ }
- if (!trimmedRating.IsEmpty)
- {
- return GetRatingLevel(trimmedRating.ToString());
- }
+ // Remove prefix country code to handle "DE-18"
+ if (rating.Contains('-', StringComparison.OrdinalIgnoreCase))
+ {
+ return GetRatingLevel(rating.AsSpan().RightPart('-').ToString());
}
- // TODO: Further improve by normalizing out all spaces and dashes
return null;
}
diff --git a/Emby.Server.Implementations/Localization/Ratings/0-prefer.csv b/Emby.Server.Implementations/Localization/Ratings/0-prefer.csv
new file mode 100644
index 000000000..36886ba76
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/0-prefer.csv
@@ -0,0 +1,11 @@
+E,0
+EC,0
+T,7
+M,18
+AO,18
+UR,18
+RP,18
+X,1000
+XX,1000
+XXX,1000
+XXXX,1000
diff --git a/Emby.Server.Implementations/Localization/Ratings/au.csv b/Emby.Server.Implementations/Localization/Ratings/au.csv
index 11f4ed94c..4ab808ae9 100644
--- a/Emby.Server.Implementations/Localization/Ratings/au.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/au.csv
@@ -1,7 +1,13 @@
-AU-G,1
-AU-PG,5
-AU-M,6
-AU-MA15+,7
-AU-R18+,9
-AU-X18+,10
-AU-RC,11
+Exempt,0
+G,0
+7+,7
+M,15
+MA,15
+MA15+,15
+PG,16
+16+,16
+R,18
+R18+,18
+X18+,18
+18+,18
+X,1000
diff --git a/Emby.Server.Implementations/Localization/Ratings/be.csv b/Emby.Server.Implementations/Localization/Ratings/be.csv
index d3937caf7..d171a7132 100644
--- a/Emby.Server.Implementations/Localization/Ratings/be.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/be.csv
@@ -1,6 +1,11 @@
-BE-AL,1
-BE-MG6,2
-BE-6,3
-BE-9,5
-BE-12,6
-BE-16,8
+AL,0
+KT,0
+TOUS,0
+MG6,6
+6,6
+9,9
+KNT,12
+12,12
+14,14
+16,16
+18,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/br.csv b/Emby.Server.Implementations/Localization/Ratings/br.csv
index e5edaf62c..5ec1eb262 100644
--- a/Emby.Server.Implementations/Localization/Ratings/br.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/br.csv
@@ -1,6 +1,8 @@
-BR-L,1
-BR-10,5
-BR-12,7
-BR-14,8
-BR-16,8
-BR-18,9
+Livre,0
+L,0
+ER,9
+10,10
+12,12
+14,14
+16,16
+18,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/ca.csv b/Emby.Server.Implementations/Localization/Ratings/ca.csv
index 5aef0580f..336ee2806 100644
--- a/Emby.Server.Implementations/Localization/Ratings/ca.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/ca.csv
@@ -1,6 +1,20 @@
-CA-G,1
-CA-PG,5
-CA-14A,7
-CA-A,8
-CA-18A,9
-CA-R,10
+E,0
+G,0
+TV-Y,0
+TV-G,0
+TV-Y7,7
+TV-Y7-FV,7
+PG,9
+TV-PG,9
+PG-13,13
+13+,13
+TV-14,14
+14A,14
+16+,16
+NC-17,17
+R,18
+TV-MA,18
+18A,18
+18+,18
+A,1000
+Prohibited,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/co.csv b/Emby.Server.Implementations/Localization/Ratings/co.csv
index 9684fa052..e1e96c590 100644
--- a/Emby.Server.Implementations/Localization/Ratings/co.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/co.csv
@@ -1,8 +1,7 @@
-CO-T,1
-CO-7,5
-CO-12,7
-CO-15,8
-CO-18,10
-CO-X,100
-CO-BANNED,15
-CO-E,15
+T,0
+7,7
+12,12
+15,15
+18,18
+X,1000
+Prohibited,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/de.csv b/Emby.Server.Implementations/Localization/Ratings/de.csv
index f944a140d..d633a5dab 100644
--- a/Emby.Server.Implementations/Localization/Ratings/de.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/de.csv
@@ -1,10 +1,12 @@
-DE-0,1
-FSK-0,1
-DE-6,5
-FSK-6,5
-DE-12,7
-FSK-12,7
-DE-16,8
-FSK-16,8
-DE-18,9
-FSK-18,9
+Educational,0
+Infoprogramm,0
+FSK-0,0
+0,0
+FSK-6,6
+6,6
+FSK-12,12
+12,12
+FSK-16,16
+16,16
+FSK-18,18
+18,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/dk.csv b/Emby.Server.Implementations/Localization/Ratings/dk.csv
index 5364ae1f2..4ef63b2ea 100644
--- a/Emby.Server.Implementations/Localization/Ratings/dk.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/dk.csv
@@ -1,4 +1,7 @@
-DA-A,1
-DA-7,5
-DA-11,6
-DA-15,8
+F,0
+A,0
+7,7
+11,11
+12,12
+15,15
+16,16
diff --git a/Emby.Server.Implementations/Localization/Ratings/es.csv b/Emby.Server.Implementations/Localization/Ratings/es.csv
index 887d91ba6..0bc1d3f7d 100644
--- a/Emby.Server.Implementations/Localization/Ratings/es.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/es.csv
@@ -1,6 +1,24 @@
-ES-A,1
-ES-APTA,1
-ES-7,3
-ES-12,6
-ES-16,8
-ES-18,11
+A,0
+A/fig,0
+A/i,0
+A/fig/i,0
+APTA,0
+TP,0
+0+,0
+6+,6
+7/fig,7
+7/i,7
+7/i/fig,7
+7,7
+9+,9
+10,10
+12,12
+12/fig,12
+13,13
+14,14
+16,16
+16/fig,16
+18,18
+18/fig,18
+X,1000
+Banned,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/fi.csv b/Emby.Server.Implementations/Localization/Ratings/fi.csv
index 782785890..7ff92f259 100644
--- a/Emby.Server.Implementations/Localization/Ratings/fi.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/fi.csv
@@ -1,10 +1,10 @@
-FI-S,1
-FI-T,1
-FI-7,4
-FI-12,5
-FI-16,8
-FI-18,9
-FI-K7,4
-FI-K12,5
-FI-K16,8
-FI-K18,9
+S,0
+T,0
+K7,7
+7,7
+K12,12
+12,12
+K16,16
+16,16
+K18,18
+18,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/fr.csv b/Emby.Server.Implementations/Localization/Ratings/fr.csv
index f586a3fa9..774a70589 100644
--- a/Emby.Server.Implementations/Localization/Ratings/fr.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/fr.csv
@@ -1,5 +1,12 @@
-FR-U,1
-FR-10,5
-FR-12,7
-FR-16,9
-FR-18,10
+Public Averti,0
+Tous Publics,0
+U,0
+0+,0
+6+,6
+9+,9
+10,10
+12,12
+14+,14
+16,16
+18,18
+X,1000
diff --git a/Emby.Server.Implementations/Localization/Ratings/gb.csv b/Emby.Server.Implementations/Localization/Ratings/gb.csv
index c1f7d0452..75b1c2058 100644
--- a/Emby.Server.Implementations/Localization/Ratings/gb.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/gb.csv
@@ -1,7 +1,22 @@
-GB-U,1
-GB-PG,5
-GB-12,6
-GB-12A,7
-GB-15,8
-GB-18,9
-GB-R18,15
+All,0
+E,0
+G,0
+U,0
+0+,0
+6+,6
+7+,7
+PG,8
+9+,9
+12,12
+12+,12
+12A,12
+Teen,13
+13+,13
+14+,14
+15,15
+16,16
+Caution,18
+18,18
+Mature,1000
+Adult,1000
+R18,1000
diff --git a/Emby.Server.Implementations/Localization/Ratings/ie.csv b/Emby.Server.Implementations/Localization/Ratings/ie.csv
index e42be5cd4..6ef2e5012 100644
--- a/Emby.Server.Implementations/Localization/Ratings/ie.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/ie.csv
@@ -1,6 +1,9 @@
-IE-G,1
-IE-PG,5
-IE-12A,7
-IE-15A,8
-IE-16,9
-IE-18,10
+G,4
+PG,12
+12,12
+12A,12
+12PG,12
+15,15
+15A,15
+16,16
+18,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/jp.csv b/Emby.Server.Implementations/Localization/Ratings/jp.csv
index a8fc2d143..bfb5fdaae 100644
--- a/Emby.Server.Implementations/Localization/Ratings/jp.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/jp.csv
@@ -1,4 +1,11 @@
-JP-G,1
-JP-PG12,7
-JP-15+,8
-JP-18+,10
+A,0
+G,0
+B,12
+PG12,12
+C,15
+15+,15
+R15+,15
+16+,16
+D,17
+Z,18
+18+,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.csv b/Emby.Server.Implementations/Localization/Ratings/kz.csv
index d546bff53..e26b32b67 100644
--- a/Emby.Server.Implementations/Localization/Ratings/kz.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/kz.csv
@@ -1,7 +1,6 @@
-KZ-6-,0
-KZ-6+,6
-KZ-12+,12
-KZ-14+,14
-KZ-16+,16
-KZ-18+,18
-KZ-21+,21
+K,0
+БА,12
+Б14,14
+E16,16
+E18,18
+HA,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/mx.csv b/Emby.Server.Implementations/Localization/Ratings/mx.csv
index 785a8ba22..305912f23 100644
--- a/Emby.Server.Implementations/Localization/Ratings/mx.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/mx.csv
@@ -1,6 +1,6 @@
-MX-AA,1
-MX-A,5
-MX-B,7
-MX-B-15,8
-MX-C,9
-MX-D,10
+A,0
+AA,0
+B,12
+B-15,15
+C,18
+D,1000
diff --git a/Emby.Server.Implementations/Localization/Ratings/nl.csv b/Emby.Server.Implementations/Localization/Ratings/nl.csv
index 8c005092e..44f372b2d 100644
--- a/Emby.Server.Implementations/Localization/Ratings/nl.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/nl.csv
@@ -1,6 +1,8 @@
-NL-AL,1
-NL-MG6,2
-NL-6,3
-NL-9,5
-NL-12,6
-NL-16,8
+AL,0
+MG6,6
+6,6
+9,9
+12,12
+14,14
+16,16
+18,18
diff --git a/Emby.Server.Implementations/Localization/Ratings/no.csv b/Emby.Server.Implementations/Localization/Ratings/no.csv
index 127407be8..c8f8e93db 100644
--- a/Emby.Server.Implementations/Localization/Ratings/no.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/no.csv
@@ -1,6 +1,9 @@
-NO-A,1
-NO-6,3
-NO-9,4
-NO-12,5
-NO-15,8
-NO-18,9
+A,0
+6,6
+7,7
+9,9
+11,11
+12,12
+15,15
+18,18
+Not approved,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/nz.csv b/Emby.Server.Implementations/Localization/Ratings/nz.csv
index bba99b764..f617f0c39 100644
--- a/Emby.Server.Implementations/Localization/Ratings/nz.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/nz.csv
@@ -1,11 +1,15 @@
-NZ-G,1
-NZ-PG,5
-NZ-M,6
-NZ-R13,7
-NZ-RP13,7
-NZ-R15,8
-NZ-RP16,9
-NZ-R16,9
-NZ-R18,10
-NZ-R,10
-NZ-MA,10
+Exempt,0
+G,0
+GY,13
+PG,13
+R13,13
+RP13,13
+R15,15
+M,16
+R16,16
+RP16,16
+GA,18
+R18,18
+MA,1000
+R,1001
+Objectionable,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/ro.csv b/Emby.Server.Implementations/Localization/Ratings/ro.csv
index 4089b282f..44c23e248 100644
--- a/Emby.Server.Implementations/Localization/Ratings/ro.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/ro.csv
@@ -1 +1,6 @@
-RO-AG,1
+AG,0
+AP-12,12
+N-15,15
+IM-18,18
+IM-18-XXX,1000
+IC,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/ru.csv b/Emby.Server.Implementations/Localization/Ratings/ru.csv
index 1bc94affd..8b264070b 100644
--- a/Emby.Server.Implementations/Localization/Ratings/ru.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/ru.csv
@@ -1,5 +1,6 @@
-RU-0+,1
-RU-6+,3
-RU-12+,7
-RU-16+,9
-RU-18+,10
+0+,0
+6+,6
+12+,12
+16+,16
+18+,18
+Refused classification,1001
diff --git a/Emby.Server.Implementations/Localization/Ratings/se.csv b/Emby.Server.Implementations/Localization/Ratings/se.csv
index 1443c07df..e129c3561 100644
--- a/Emby.Server.Implementations/Localization/Ratings/se.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/se.csv
@@ -1,5 +1,10 @@
-SE-Btl,1
-SE-Barntillåten,1
-SE-7,3
-SE-11,5
-SE-15,8
+Alla,0
+Barntillåten,0
+Btl,0
+0+,0
+7,7
+9+,9
+10+,10
+11,11
+14,14
+15,15
diff --git a/Emby.Server.Implementations/Localization/Ratings/uk.csv b/Emby.Server.Implementations/Localization/Ratings/uk.csv
index 6c8005b3f..75b1c2058 100644
--- a/Emby.Server.Implementations/Localization/Ratings/uk.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/uk.csv
@@ -1,7 +1,22 @@
-UK-U,1
-UK-PG,5
-UK-12,7
-UK-12A,7
-UK-15,9
-UK-18,10
-UK-R18,15
+All,0
+E,0
+G,0
+U,0
+0+,0
+6+,6
+7+,7
+PG,8
+9+,9
+12,12
+12+,12
+12A,12
+Teen,13
+13+,13
+14+,14
+15,15
+16,16
+Caution,18
+18,18
+Mature,1000
+Adult,1000
+R18,1000
diff --git a/Emby.Server.Implementations/Localization/Ratings/us.csv b/Emby.Server.Implementations/Localization/Ratings/us.csv
index 34c897fe3..d103ddf42 100644
--- a/Emby.Server.Implementations/Localization/Ratings/us.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/us.csv
@@ -1,23 +1,50 @@
-TV-Y,1
-APPROVED,1
-G,1
-E,1
-EC,1
-TV-G,1
-TV-Y7,3
-TV-Y7-FV,4
-PG,5
-TV-PG,5
-PG-13,7
-T,7
-TV-14,8
-R,9
-M,9
-TV-MA,9
-NC-17,10
-AO,15
-RP,15
-UR,15
-NR,15
-X,15
-XXX,100
+Approved,0
+G,0
+TV-G,0
+TV-Y,0
+TV-Y7,7
+TV-Y7-FV,7
+PG,10
+PG-13,13
+TV-PG,13
+TV-PG-D,13
+TV-PG-L,13
+TV-PG-S,13
+TV-PG-V,13
+TV-PG-DL,13
+TV-PG-DS,13
+TV-PG-DV,13
+TV-PG-LS,13
+TV-PG-LV,13
+TV-PG-SV,13
+TV-PG-DLS,13
+TV-PG-DLV,13
+TV-PG-DSV,13
+TV-PG-LSV,13
+TV-PG-DLSV,13
+TV-14,14
+TV-14-D,14
+TV-14-L,14
+TV-14-S,14
+TV-14-V,14
+TV-14-DL,14
+TV-14-DS,14
+TV-14-DV,14
+TV-14-LS,14
+TV-14-LV,14
+TV-14-SV,14
+TV-14-DLS,14
+TV-14-DLV,14
+TV-14-DSV,14
+TV-14-LSV,14
+TV-14-DLSV,14
+NC-17,17
+R,17
+TV-MA,17
+TV-MA-L,17
+TV-MA-S,17
+TV-MA-V,17
+TV-MA-LS,17
+TV-MA-LV,17
+TV-MA-SV,17
+TV-MA-LSV,17
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index 230fbfb2c..9c7148241 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -98,7 +98,7 @@ public class ItemUpdateController : BaseJellyfinApiController
}).ToList());
}
- UpdateItem(request, item);
+ await UpdateItem(request, item).ConfigureAwait(false);
item.OnMetadataChanged();
@@ -147,7 +147,7 @@ public class ItemUpdateController : BaseJellyfinApiController
var info = new MetadataEditorInfo
{
- ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(),
+ ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
Countries = _localizationManager.GetCountries().ToArray(),
Cultures = _localizationManager.GetCultures().ToArray()
@@ -224,7 +224,7 @@ public class ItemUpdateController : BaseJellyfinApiController
return NoContent();
}
- private void UpdateItem(BaseItemDto request, BaseItem item)
+ private async Task UpdateItem(BaseItemDto request, BaseItem item)
{
item.Name = request.Name;
item.ForcedSortName = request.ForcedSortName;
@@ -266,9 +266,50 @@ public class ItemUpdateController : BaseJellyfinApiController
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null;
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null;
item.ProductionYear = request.ProductionYear;
- item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
+
+ request.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
+ item.OfficialRating = request.OfficialRating;
item.CustomRating = request.CustomRating;
+ if (item is Series rseries)
+ {
+ foreach (Season season in rseries.Children)
+ {
+ season.OfficialRating = request.OfficialRating;
+ season.CustomRating = request.CustomRating;
+ season.OnMetadataChanged();
+ await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+
+ foreach (Episode ep in season.Children)
+ {
+ ep.OfficialRating = request.OfficialRating;
+ ep.CustomRating = request.CustomRating;
+ ep.OnMetadataChanged();
+ await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+ }
+ else if (item is Season season)
+ {
+ foreach (Episode ep in season.Children)
+ {
+ ep.OfficialRating = request.OfficialRating;
+ ep.CustomRating = request.CustomRating;
+ ep.OnMetadataChanged();
+ await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+ else if (item is MusicAlbum album)
+ {
+ foreach (BaseItem track in album.Children)
+ {
+ track.OfficialRating = request.OfficialRating;
+ track.CustomRating = request.CustomRating;
+ track.OnMetadataChanged();
+ await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
if (request.ProductionLocations is not null)
{
item.ProductionLocations = request.ProductionLocations;
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 728e62810..377526729 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -411,6 +411,13 @@ public class ItemsController : BaseJellyfinApiController
query.SeriesStatuses = seriesStatus;
}
+ // Exclude Blocked Unrated Items
+ var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
+ if (blockedUnratedItems is not null)
+ {
+ query.BlockUnratedItems = blockedUnratedItems;
+ }
+
// ExcludeLocationTypes
if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
{
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index 1233995b4..2c4fe9186 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -72,7 +73,7 @@ public class UserLibraryController : BaseJellyfinApiController
/// <param name="userId">User id.</param>
/// <param name="itemId">Item id.</param>
/// <response code="200">Item returned.</response>
- /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
+ /// <returns>An <see cref="OkResult"/> containing the item.</returns>
[HttpGet("Users/{userId}/Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
@@ -86,11 +87,19 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+
if (item is null)
{
return NotFound();
}
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User);
@@ -139,11 +148,19 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+
if (item is null)
{
return NotFound();
}
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User);
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
@@ -162,7 +179,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
- return MarkFavorite(userId, itemId, true);
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
+ var item = itemId.Equals(default)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
+ return MarkFavorite(user, item, true);
}
/// <summary>
@@ -176,7 +215,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
- return MarkFavorite(userId, itemId, false);
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
+ var item = itemId.Equals(default)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
+ return MarkFavorite(user, item, false);
}
/// <summary>
@@ -190,7 +251,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
- return UpdateUserItemRatingInternal(userId, itemId, null);
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
+ var item = itemId.Equals(default)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
+ return UpdateUserItemRatingInternal(user, item, null);
}
/// <summary>
@@ -205,7 +288,29 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes)
{
- return UpdateUserItemRatingInternal(userId, itemId, likes);
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
+ var item = itemId.Equals(default)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
+ return UpdateUserItemRatingInternal(user, item, likes);
}
/// <summary>
@@ -228,13 +333,20 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+
if (item is null)
{
return NotFound();
}
- var dtoOptions = new DtoOptions().AddClientFields(User);
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+ var dtoOptions = new DtoOptions().AddClientFields(User);
if (item is IHasTrailers hasTrailers)
{
var trailers = hasTrailers.LocalTrailers;
@@ -266,11 +378,19 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+
if (item is null)
{
return NotFound();
}
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
var dtoOptions = new DtoOptions().AddClientFields(User);
return Ok(item
@@ -385,15 +505,11 @@ public class UserLibraryController : BaseJellyfinApiController
/// <summary>
/// Marks the favorite.
/// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="itemId">The item id.</param>
+ /// <param name="user">The user.</param>
+ /// <param name="item">The item.</param>
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
- private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite)
+ private UserItemDataDto MarkFavorite(User user, BaseItem item, bool isFavorite)
{
- var user = _userManager.GetUserById(userId);
-
- var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
-
// Get the user data for this item
var data = _userDataRepository.GetUserData(user, item);
@@ -408,15 +524,11 @@ public class UserLibraryController : BaseJellyfinApiController
/// <summary>
/// Updates the user item rating.
/// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="itemId">The item id.</param>
+ /// <param name="user">The user.</param>
+ /// <param name="item">The item.</param>
/// <param name="likes">if set to <c>true</c> [likes].</param>
- private UserItemDataDto UpdateUserItemRatingInternal(Guid userId, Guid itemId, bool? likes)
+ private UserItemDataDto UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes)
{
- var user = _userManager.GetUserById(userId);
-
- var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
-
// Get the user data for this item
var data = _userDataRepository.GetUserData(user, item);
@@ -455,6 +567,13 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound();
}
+ if (item is not UserRootFolder
+ // Check the item is visible for the user
+ && !item.IsVisible(user))
+ {
+ return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
+ }
+
var result = await _lyricManager.GetLyrics(item).ConfigureAwait(false);
if (result is not null)
{
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index 8a6ab7932..d4bf81f10 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -22,7 +22,8 @@ namespace Jellyfin.Server.Migrations
private static readonly Type[] _preStartupMigrationTypes =
{
typeof(PreStartupRoutines.CreateNetworkConfiguration),
- typeof(PreStartupRoutines.MigrateMusicBrainzTimeout)
+ typeof(PreStartupRoutines.MigrateMusicBrainzTimeout),
+ typeof(PreStartupRoutines.MigrateRatingLevels)
};
/// <summary>
diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs
new file mode 100644
index 000000000..465bbd7fe
--- /dev/null
+++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Globalization;
+using System.IO;
+
+using Emby.Server.Implementations;
+using MediaBrowser.Controller;
+using Microsoft.Extensions.Logging;
+using SQLitePCL.pretty;
+
+namespace Jellyfin.Server.Migrations.PreStartupRoutines
+{
+ /// <summary>
+ /// Migrate rating levels to new rating level system.
+ /// </summary>
+ internal class MigrateRatingLevels : IMigrationRoutine
+ {
+ private const string DbFilename = "library.db";
+ private readonly ILogger<MigrateRatingLevels> _logger;
+ private readonly IServerApplicationPaths _applicationPaths;
+
+ public MigrateRatingLevels(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory)
+ {
+ _applicationPaths = applicationPaths;
+ _logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}");
+
+ /// <inheritdoc/>
+ public string Name => "MigrateRatingLevels";
+
+ /// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ var dataPath = _applicationPaths.DataPath;
+ var dbPath = Path.Combine(dataPath, DbFilename);
+ using (var connection = SQLite3.Open(
+ dbPath,
+ ConnectionFlags.ReadWrite,
+ null))
+ {
+ // Back up the database before deleting any entries
+ for (int i = 1; ; i++)
+ {
+ var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
+ if (!File.Exists(bakPath))
+ {
+ try
+ {
+ File.Copy(dbPath, bakPath);
+ _logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
+ throw;
+ }
+ }
+ }
+
+ // Migrate parental rating levels to new schema
+ _logger.LogInformation("Migrating parental rating levels.");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating = 'NR'");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = ''");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = 0");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 100");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 15");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 10");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 9");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 16 WHERE InheritedParentalRatingValue = 8");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 7");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 6");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 5");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 7 WHERE InheritedParentalRatingValue = 4");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 3");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 2");
+ connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 0 WHERE InheritedParentalRatingValue = 1");
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 3d683052f..b8601cccd 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -554,7 +554,7 @@ namespace MediaBrowser.Controller.Entities
public string OfficialRating { get; set; }
[JsonIgnore]
- public int InheritedParentalRatingValue { get; set; }
+ public int? InheritedParentalRatingValue { get; set; }
/// <summary>
/// Gets or sets the critic rating.
@@ -1534,12 +1534,6 @@ namespace MediaBrowser.Controller.Entities
}
var maxAllowedRating = user.MaxParentalAgeRating;
-
- if (maxAllowedRating is null)
- {
- return true;
- }
-
var rating = CustomRatingForComparison;
if (string.IsNullOrEmpty(rating))
@@ -1549,12 +1543,13 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating))
{
+ Logger.LogDebug("{0} has no parental rating set.", Name);
return !GetBlockUnratedValue(user);
}
var value = LocalizationManager.GetRatingLevel(rating);
- // Could not determine the integer value
+ // Could not determine rating level
if (!value.HasValue)
{
var isAllowed = !GetBlockUnratedValue(user);
@@ -1567,7 +1562,7 @@ namespace MediaBrowser.Controller.Entities
return isAllowed;
}
- return value.Value <= maxAllowedRating.Value;
+ return !maxAllowedRating.HasValue || value.Value <= maxAllowedRating.Value;
}
public int? GetInheritedParentalRatingValue()
@@ -1627,10 +1622,10 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets the block unrated value.
+ /// Gets a bool indicating if access to the unrated item is blocked or not.
/// </summary>
/// <param name="user">The configuration.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ /// <returns><c>true</c> if blocked, <c>false</c> otherwise.</returns>
protected virtual bool GetBlockUnratedValue(User user)
{
// Don't block plain folders that are unrated. Let the media underneath get blocked
@@ -2517,7 +2512,7 @@ namespace MediaBrowser.Controller.Entities
var item = this;
- var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? 0;
+ var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? null;
if (inheritedParentalRatingValue != item.InheritedParentalRatingValue)
{
item.InheritedParentalRatingValue = inheritedParentalRatingValue;
diff --git a/MediaBrowser.Model/Entities/ParentalRating.cs b/MediaBrowser.Model/Entities/ParentalRating.cs
index 17b2868a3..c92640818 100644
--- a/MediaBrowser.Model/Entities/ParentalRating.cs
+++ b/MediaBrowser.Model/Entities/ParentalRating.cs
@@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Entities
{
}
- public ParentalRating(string name, int value)
+ public ParentalRating(string name, int? value)
{
Name = name;
Value = value;
@@ -28,6 +28,6 @@ namespace MediaBrowser.Model.Entities
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
- public int Value { get; set; }
+ public int? Value { get; set; }
}
}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index cc7d57eb0..80f5e2c37 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -46,6 +46,7 @@ namespace MediaBrowser.Model.Users
LoginAttemptsBeforeLockout = -1;
MaxActiveSessions = 0;
+ MaxParentalRating = null;
EnableAllChannels = true;
EnabledChannels = Array.Empty<Guid>();
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
index 16eb7a75c..ab3682ccf 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
@@ -83,11 +83,11 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
await localizationManager.LoadAll();
var ratings = localizationManager.GetParentalRatings().ToList();
- Assert.Equal(23, ratings.Count);
+ Assert.Equal(53, ratings.Count);
var tvma = ratings.FirstOrDefault(x => x.Name.Equals("TV-MA", StringComparison.Ordinal));
Assert.NotNull(tvma);
- Assert.Equal(9, tvma!.Value);
+ Assert.Equal(17, tvma!.Value);
}
[Fact]
@@ -100,21 +100,21 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
await localizationManager.LoadAll();
var ratings = localizationManager.GetParentalRatings().ToList();
- Assert.Equal(10, ratings.Count);
+ Assert.Equal(18, ratings.Count);
var fsk = ratings.FirstOrDefault(x => x.Name.Equals("FSK-12", StringComparison.Ordinal));
Assert.NotNull(fsk);
- Assert.Equal(7, fsk!.Value);
+ Assert.Equal(12, fsk!.Value);
}
[Theory]
- [InlineData("CA-R", "CA", 10)]
- [InlineData("FSK-16", "DE", 8)]
- [InlineData("FSK-18", "DE", 9)]
- [InlineData("FSK-18", "US", 9)]
- [InlineData("TV-MA", "US", 9)]
- [InlineData("XXX", "asdf", 100)]
- [InlineData("Germany: FSK-18", "DE", 9)]
+ [InlineData("CA-R", "CA", 18)]
+ [InlineData("FSK-16", "DE", 16)]
+ [InlineData("FSK-18", "DE", 18)]
+ [InlineData("FSK-18", "US", 18)]
+ [InlineData("TV-MA", "US", 17)]
+ [InlineData("XXX", "asdf", 1000)]
+ [InlineData("Germany: FSK-18", "DE", 18)]
public async Task GetRatingLevel_GivenValidString_Success(string value, string countryCode, int expectedLevel)
{
var localizationManager = Setup(new ServerConfiguration()
@@ -135,6 +135,9 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
UICulture = "de-DE"
});
await localizationManager.LoadAll();
+ Assert.Null(localizationManager.GetRatingLevel("NR"));
+ Assert.Null(localizationManager.GetRatingLevel("unrated"));
+ Assert.Null(localizationManager.GetRatingLevel("Not Rated"));
Assert.Null(localizationManager.GetRatingLevel("n/a"));
}