aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Entities
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2025-08-03 17:27:17 -0400
committerGitHub <noreply@github.com>2025-08-03 17:27:17 -0400
commit4b6fb6c4bb2478badad068ce18aabe0c2955db48 (patch)
tree15f986ee62327cceb8f5c8f009bcf08d10cfaa66 /MediaBrowser.Controller/Entities
parente7bc86ebb8496615e0b3f73eb4f13ab4c0913dc8 (diff)
parentdb7465e83d9cc07134a0bffad7ed17b1c7b873da (diff)
Merge branch 'master' into master
Diffstat (limited to 'MediaBrowser.Controller/Entities')
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs10
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs133
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs14
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs101
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs37
-rw-r--r--MediaBrowser.Controller/Entities/InternalPeopleQuery.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs6
-rw-r--r--MediaBrowser.Controller/Entities/PeopleHelper.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs39
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs22
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs48
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs4
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs2
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs21
16 files changed, 286 insertions, 161 deletions
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index f3873775b..d016d8f62 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -8,8 +8,10 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index ecb3ac3a6..58841e5b7 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -8,8 +8,10 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -138,11 +140,9 @@ namespace MediaBrowser.Controller.Entities.Audio
private static List<string> GetUserDataKeys(MusicArtist item)
{
var list = new List<string>();
- var id = item.GetProviderId(MetadataProvider.MusicBrainzArtist);
-
- if (!string.IsNullOrEmpty(id))
+ if (item.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out var externalId))
{
- list.Add("Artist-Musicbrainz-" + id);
+ list.Add("Artist-Musicbrainz-" + externalId);
}
list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics());
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 55553da49..bb0b26b8e 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -12,8 +12,10 @@ using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
@@ -21,7 +23,9 @@ using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaSegments;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
@@ -31,7 +35,6 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
@@ -49,7 +52,7 @@ namespace MediaBrowser.Controller.Entities
/// The supported image extensions.
/// </summary>
public static readonly string[] SupportedImageExtensions
- = new[] { ".png", ".jpg", ".jpeg", ".webp", ".tbn", ".gif", ".svg" };
+ = [".png", ".jpg", ".jpeg", ".webp", ".tbn", ".gif", ".svg"];
private static readonly List<string> _supportedExtensions = new List<string>(SupportedImageExtensions)
{
@@ -445,7 +448,7 @@ namespace MediaBrowser.Controller.Entities
return Array.Empty<string>();
}
- return new[] { Path };
+ return [Path];
}
}
@@ -481,7 +484,7 @@ namespace MediaBrowser.Controller.Entities
public static IItemRepository ItemRepository { get; set; }
- public static IChapterRepository ChapterRepository { get; set; }
+ public static IChapterManager ChapterManager { get; set; }
public static IFileSystem FileSystem { get; set; }
@@ -578,6 +581,9 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public int? InheritedParentalRatingValue { get; set; }
+ [JsonIgnore]
+ public int? InheritedParentalRatingSubValue { get; set; }
+
/// <summary>
/// Gets or sets the critic rating.
/// </summary>
@@ -919,7 +925,7 @@ namespace MediaBrowser.Controller.Entities
// Remove from middle if surrounded by spaces
sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
- // Remove from end if followed by a space
+ // Remove from end if preceeded by a space
if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
{
sortable = sortable.Remove(sortable.Length - (search.Length + 1));
@@ -1260,7 +1266,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Overrides the base implementation to refresh metadata for local trailers.
+ /// The base implementation to refresh metadata.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -1357,9 +1363,7 @@ namespace MediaBrowser.Controller.Entities
protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
{
- var path = ContainingFolderPath;
-
- return directoryService.GetFileSystemEntries(path);
+ return directoryService.GetFileSystemEntries(ContainingFolderPath);
}
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
@@ -1388,6 +1392,23 @@ namespace MediaBrowser.Controller.Entities
return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
});
+ // Cleanup removed extras
+ var removedExtraIds = item.ExtraIds.Where(e => !newExtraIds.Contains(e)).ToArray();
+ if (removedExtraIds.Length > 0)
+ {
+ var removedExtras = LibraryManager.GetItemList(new InternalItemsQuery()
+ {
+ ItemIds = removedExtraIds
+ });
+ foreach (var removedExtra in removedExtras)
+ {
+ LibraryManager.DeleteItem(removedExtra, new DeleteOptions()
+ {
+ DeleteFileLocation = false
+ });
+ }
+ }
+
await Task.WhenAll(tasks).ConfigureAwait(false);
item.ExtraIds = newExtraIds;
@@ -1402,7 +1423,14 @@ namespace MediaBrowser.Controller.Entities
public virtual bool RequiresRefresh()
{
- return false;
+ if (string.IsNullOrEmpty(Path) || DateModified == DateTime.MinValue)
+ {
+ return false;
+ }
+
+ var info = FileSystem.GetFileSystemInfo(Path);
+
+ return info.Exists && this.HasChanged(info.LastWriteTimeUtc);
}
public virtual List<string> GetUserDataKeys()
@@ -1537,7 +1565,8 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- var maxAllowedRating = user.MaxParentalAgeRating;
+ var maxAllowedRating = user.MaxParentalRatingScore;
+ var maxAllowedSubRating = user.MaxParentalRatingSubScore;
var rating = CustomRatingForComparison;
if (string.IsNullOrEmpty(rating))
@@ -1551,10 +1580,10 @@ namespace MediaBrowser.Controller.Entities
return !GetBlockUnratedValue(user);
}
- var value = LocalizationManager.GetRatingLevel(rating);
+ var ratingScore = LocalizationManager.GetRatingScore(rating);
// Could not determine rating level
- if (!value.HasValue)
+ if (ratingScore is null)
{
var isAllowed = !GetBlockUnratedValue(user);
@@ -1566,10 +1595,15 @@ namespace MediaBrowser.Controller.Entities
return isAllowed;
}
- return !maxAllowedRating.HasValue || value.Value <= maxAllowedRating.Value;
+ if (maxAllowedSubRating is not null)
+ {
+ return (ratingScore.SubScore ?? 0) <= maxAllowedSubRating && ratingScore.Score <= maxAllowedRating.Value;
+ }
+
+ return !maxAllowedRating.HasValue || ratingScore.Score <= maxAllowedRating.Value;
}
- public int? GetInheritedParentalRatingValue()
+ public ParentalRatingScore GetParentalRatingScore()
{
var rating = CustomRatingForComparison;
@@ -1583,7 +1617,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- return LocalizationManager.GetRatingLevel(rating);
+ return LocalizationManager.GetRatingScore(rating);
}
public List<string> GetInheritedTags()
@@ -1681,7 +1715,7 @@ namespace MediaBrowser.Controller.Entities
public virtual string GetClientTypeName()
{
- if (IsFolder && SourceType == SourceType.Channel && this is not Channel)
+ if (IsFolder && SourceType == SourceType.Channel && this is not Channel && this is not Season && this is not Series)
{
return "ChannelFolderItem";
}
@@ -1775,7 +1809,6 @@ namespace MediaBrowser.Controller.Entities
public void AddStudio(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);
-
var current = Studios;
if (!current.Contains(name, StringComparison.OrdinalIgnoreCase))
@@ -1794,7 +1827,7 @@ namespace MediaBrowser.Controller.Entities
public void SetStudios(IEnumerable<string> names)
{
- Studios = names.Distinct().ToArray();
+ Studios = names.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
/// <summary>
@@ -1960,9 +1993,10 @@ namespace MediaBrowser.Controller.Entities
}
// Remove from file system
- if (info.IsLocalFile)
+ var path = info.Path;
+ if (info.IsLocalFile && !string.IsNullOrWhiteSpace(path))
{
- FileSystem.DeleteFile(info.Path);
+ FileSystem.DeleteFile(path);
}
// Remove from item
@@ -1973,7 +2007,7 @@ namespace MediaBrowser.Controller.Entities
public void RemoveImage(ItemImageInfo image)
{
- RemoveImages(new[] { image });
+ RemoveImages([image]);
}
public void RemoveImages(IEnumerable<ItemImageInfo> deletedImages)
@@ -2008,7 +2042,7 @@ namespace MediaBrowser.Controller.Entities
continue;
}
- (deletedImages ??= new List<ItemImageInfo>()).Add(imageInfo);
+ (deletedImages ??= []).Add(imageInfo);
}
var anyImagesRemoved = deletedImages?.Count > 0;
@@ -2040,7 +2074,7 @@ namespace MediaBrowser.Controller.Entities
{
if (imageType == ImageType.Chapter)
{
- var chapter = ChapterRepository.GetChapter(this.Id, imageIndex);
+ var chapter = ChapterManager.GetChapter(Id, imageIndex);
if (chapter is null)
{
@@ -2090,7 +2124,7 @@ namespace MediaBrowser.Controller.Entities
if (image.Type == ImageType.Chapter)
{
- var chapters = ChapterRepository.GetChapters(this.Id);
+ var chapters = ChapterManager.GetChapters(Id);
for (var i = 0; i < chapters.Count; i++)
{
if (chapters[i].ImagePath == image.Path)
@@ -2211,11 +2245,7 @@ namespace MediaBrowser.Controller.Entities
{
return new[]
{
- new FileSystemMetadata
- {
- FullName = Path,
- IsDirectory = IsFolder
- }
+ FileSystem.GetFileSystemInfo(Path)
}.Concat(GetLocalMetadataFilesToDelete());
}
@@ -2223,7 +2253,7 @@ namespace MediaBrowser.Controller.Entities
{
if (IsFolder || !IsInMixedFolder)
{
- return new List<FileSystemMetadata>();
+ return [];
}
var filename = System.IO.Path.GetFileNameWithoutExtension(Path);
@@ -2479,10 +2509,10 @@ namespace MediaBrowser.Controller.Entities
protected virtual List<string> GetEtagValues(User user)
{
- return new List<string>
- {
+ return
+ [
DateLastSaved.Ticks.ToString(CultureInfo.InvariantCulture)
- };
+ ];
}
public virtual IEnumerable<Guid> GetAncestorIds()
@@ -2502,7 +2532,7 @@ namespace MediaBrowser.Controller.Entities
public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
{
- return new[] { Id };
+ return [Id];
}
public virtual double? GetRefreshProgress()
@@ -2516,11 +2546,29 @@ namespace MediaBrowser.Controller.Entities
var item = this;
- var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? null;
- if (inheritedParentalRatingValue != item.InheritedParentalRatingValue)
+ var rating = item.GetParentalRatingScore();
+ if (rating is not null)
{
- item.InheritedParentalRatingValue = inheritedParentalRatingValue;
- updateType |= ItemUpdateType.MetadataImport;
+ if (rating.Score != item.InheritedParentalRatingValue)
+ {
+ item.InheritedParentalRatingValue = rating.Score;
+ updateType |= ItemUpdateType.MetadataImport;
+ }
+
+ if (rating.SubScore != item.InheritedParentalRatingSubValue)
+ {
+ item.InheritedParentalRatingSubValue = rating.SubScore;
+ updateType |= ItemUpdateType.MetadataImport;
+ }
+ }
+ else
+ {
+ if (item.InheritedParentalRatingValue is not null)
+ {
+ item.InheritedParentalRatingValue = null;
+ item.InheritedParentalRatingSubValue = null;
+ updateType |= ItemUpdateType.MetadataImport;
+ }
}
return updateType;
@@ -2540,8 +2588,9 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(rating => (rating, LocalizationManager.GetRatingLevel(rating)))
- .OrderBy(i => i.Item2 ?? 1000)
+ .Select(rating => (rating, LocalizationManager.GetRatingScore(rating)))
+ .OrderBy(i => i.Item2 is null ? 1001 : i.Item2.Score)
+ .ThenBy(i => i.Item2 is null ? 1001 : i.Item2.SubScore)
.Select(i => i.rating);
OfficialRating = ratings.FirstOrDefault() ?? currentOfficialRating;
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index dcd22a3b4..668e2c1e2 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -114,5 +114,19 @@ namespace MediaBrowser.Controller.Entities
source.DeepCopy(dest);
return dest;
}
+
+ /// <summary>
+ /// Determines if the item has changed.
+ /// </summary>
+ /// <param name="source">The source object.</param>
+ /// <param name="asOf">The timestamp to detect changes as of.</param>
+ /// <typeparam name="T">Source type.</typeparam>
+ /// <returns>Whether the item has changed.</returns>
+ public static bool HasChanged<T>(this T source, DateTime asOf)
+ where T : BaseItem
+ {
+ ArgumentNullException.ThrowIfNull(source);
+ return source.DateModified.Subtract(asOf).Duration().TotalSeconds > 1;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index b7b5dac03..ca79e6245 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -11,8 +11,8 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index af6348e46..06cbcc2e1 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -11,10 +11,11 @@ using System.Security;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using System.Threading.Tasks.Dataflow;
using J2N.Collections.Generic.Extensions;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Collections;
@@ -23,6 +24,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LibraryTaskScheduler;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
@@ -47,6 +49,8 @@ namespace MediaBrowser.Controller.Entities
public static IUserViewManager UserViewManager { get; set; }
+ public static ILimitedConcurrencyLibraryScheduler LimitedConcurrencyLibraryScheduler { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is root.
/// </summary>
@@ -596,51 +600,13 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
private async Task RunTasks<T>(Func<T, IProgress<double>, Task> task, IList<T> children, IProgress<double> progress, CancellationToken cancellationToken)
{
- var childrenCount = children.Count;
- var childrenProgress = new double[childrenCount];
-
- void UpdateProgress()
- {
- progress.Report(childrenProgress.Average());
- }
-
- var fanoutConcurrency = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
- var parallelism = fanoutConcurrency > 0 ? fanoutConcurrency : Environment.ProcessorCount;
-
- var actionBlock = new ActionBlock<int>(
- async i =>
- {
- var innerProgress = new Progress<double>(innerPercent =>
- {
- // round the percent and only update progress if it changed to prevent excessive UpdateProgress calls
- var innerPercentRounded = Math.Round(innerPercent);
- if (childrenProgress[i] != innerPercentRounded)
- {
- childrenProgress[i] = innerPercentRounded;
- UpdateProgress();
- }
- });
-
- await task(children[i], innerProgress).ConfigureAwait(false);
-
- childrenProgress[i] = 100;
-
- UpdateProgress();
- },
- new ExecutionDataflowBlockOptions
- {
- MaxDegreeOfParallelism = parallelism,
- CancellationToken = cancellationToken,
- });
-
- for (var i = 0; i < childrenCount; i++)
- {
- await actionBlock.SendAsync(i, cancellationToken).ConfigureAwait(false);
- }
-
- actionBlock.Complete();
-
- await actionBlock.Completion.ConfigureAwait(false);
+ await LimitedConcurrencyLibraryScheduler
+ .Enqueue(
+ children.ToArray(),
+ task,
+ progress,
+ cancellationToken)
+ .ConfigureAwait(false);
}
/// <summary>
@@ -729,7 +695,7 @@ namespace MediaBrowser.Controller.Entities
items = GetRecursiveChildren(user, query);
}
- return PostFilterAndSort(items, query, true);
+ return PostFilterAndSort(items, query);
}
if (this is not UserRootFolder
@@ -993,10 +959,10 @@ namespace MediaBrowser.Controller.Entities
items = GetChildren(user, true, childQuery).Where(filter);
}
- return PostFilterAndSort(items, query, true);
+ return PostFilterAndSort(items, query);
}
- protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool enableSorting)
+ protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
{
var user = query.User;
@@ -1006,7 +972,7 @@ namespace MediaBrowser.Controller.Entities
items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
}
- #pragma warning disable CA1309
+#pragma warning disable CA1309
if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
{
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.InvariantCultureIgnoreCase) < 1);
@@ -1021,7 +987,7 @@ namespace MediaBrowser.Controller.Entities
{
items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultureIgnoreCase) == 1);
}
- #pragma warning restore CA1309
+#pragma warning restore CA1309
// This must be the last filter
if (!query.AdjacentTo.IsNullOrEmpty())
@@ -1029,7 +995,7 @@ namespace MediaBrowser.Controller.Entities
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
- return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);
+ return UserViewBuilder.SortAndPage(items, null, query, LibraryManager);
}
private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
@@ -1062,11 +1028,6 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (queryParent is Series)
- {
- return false;
- }
-
if (queryParent is Season)
{
return false;
@@ -1086,12 +1047,15 @@ namespace MediaBrowser.Controller.Entities
if (!param.HasValue)
{
- if (user is not null && !configurationManager.Configuration.EnableGroupingIntoCollections)
+ if (user is not null && query.IncludeItemTypes.Any(type =>
+ (type == BaseItemKind.Movie && !configurationManager.Configuration.EnableGroupingMoviesIntoCollections) ||
+ (type == BaseItemKind.Series && !configurationManager.Configuration.EnableGroupingShowsIntoCollections)))
{
return false;
}
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Movie))
+ if (query.IncludeItemTypes.Length == 0
+ || query.IncludeItemTypes.Any(type => type == BaseItemKind.Movie || type == BaseItemKind.Series))
{
param = true;
}
@@ -1222,11 +1186,6 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (request.IsPlayed.HasValue)
- {
- return false;
- }
-
if (!string.IsNullOrWhiteSpace(request.Person))
{
return false;
@@ -1267,17 +1226,15 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (request.MinCommunityRating.HasValue)
- {
- return false;
- }
-
- if (request.MinCriticRating.HasValue)
+ if (request.MinIndexNumber.HasValue)
{
return false;
}
- if (request.MinIndexNumber.HasValue)
+ if (request.OrderBy.Any(o =>
+ o.OrderBy == ItemSortBy.CommunityRating ||
+ o.OrderBy == ItemSortBy.CriticRating ||
+ o.OrderBy == ItemSortBy.Runtime))
{
return false;
}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 43f02fb72..b32b64f5d 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -3,8 +3,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Jellyfin.Data.Entities;
+using Diacritics.Extensions;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
@@ -230,9 +233,9 @@ namespace MediaBrowser.Controller.Entities
public int? IndexNumber { get; set; }
- public int? MinParentalRating { get; set; }
+ public ParentalRatingScore? MinParentalRating { get; set; }
- public int? MaxParentalRating { get; set; }
+ public ParentalRatingScore? MaxParentalRating { get; set; }
public bool? HasDeadParentId { get; set; }
@@ -304,6 +307,8 @@ namespace MediaBrowser.Controller.Entities
public bool? IsDeadStudio { get; set; }
+ public bool? IsDeadGenre { get; set; }
+
public bool? IsDeadPerson { get; set; }
/// <summary>
@@ -358,18 +363,26 @@ namespace MediaBrowser.Controller.Entities
public void SetUser(User user)
{
- MaxParentalRating = user.MaxParentalAgeRating;
-
- if (MaxParentalRating.HasValue)
+ var maxRating = user.MaxParentalRatingScore;
+ if (maxRating.HasValue)
{
- string other = UnratedItem.Other.ToString();
- BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
- .Where(i => i != other)
- .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
+ MaxParentalRating = new(maxRating.Value, user.MaxParentalRatingSubScore);
}
- ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
- IncludeInheritedTags = user.GetPreference(PreferenceKind.AllowedTags);
+ var other = UnratedItem.Other.ToString();
+ BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
+ .Where(i => i != other)
+ .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
+
+ ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags)
+ .Where(tag => !string.IsNullOrWhiteSpace(tag))
+ .Select(tag => tag.RemoveDiacritics().ToLowerInvariant())
+ .ToArray();
+
+ IncludeInheritedTags = user.GetPreference(PreferenceKind.AllowedTags)
+ .Where(tag => !string.IsNullOrWhiteSpace(tag))
+ .Select(tag => tag.RemoveDiacritics().ToLowerInvariant())
+ .ToArray();
User = user;
}
diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
index 3e1d89274..203a16a66 100644
--- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-using Jellyfin.Data.Entities;
+using Jellyfin.Database.Implementations.Entities;
namespace MediaBrowser.Controller.Entities
{
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index c9a93d0f5..dd5852823 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -7,8 +7,10 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text.Json.Serialization;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Querying;
@@ -195,7 +197,7 @@ namespace MediaBrowser.Controller.Entities.Movies
var expandedFolders = new List<Guid>();
return FlattenItems(this, expandedFolders)
- .SelectMany(i => LibraryManager.GetCollectionFolders(i))
+ .SelectMany(LibraryManager.GetCollectionFolders)
.Select(i => i.Id)
.Distinct()
.ToArray();
diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs
index 4141b1712..24b1843ce 100644
--- a/MediaBrowser.Controller/Entities/PeopleHelper.cs
+++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs
@@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
ArgumentNullException.ThrowIfNull(person);
ArgumentException.ThrowIfNullOrEmpty(person.Name);
+ person.Name = person.Name.Trim();
+
// Normalize
if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 46bad3f3b..6bdba36f9 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -7,12 +7,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
+using System.Threading;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.TV
@@ -22,6 +24,8 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
+ public static IMediaEncoder MediaEncoder { get; set; }
+
/// <inheritdoc />
[JsonIgnore]
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
@@ -325,6 +329,39 @@ namespace MediaBrowser.Controller.Entities.TV
{
if (SourceType == SourceType.Library || SourceType == SourceType.LiveTV)
{
+ var libraryOptions = LibraryManager.GetLibraryOptions(this);
+ if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(Container, "mp4", StringComparison.OrdinalIgnoreCase))
+ {
+ try
+ {
+ var mediaInfo = MediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaSource = GetMediaSources(false)[0],
+ MediaType = DlnaProfileType.Video
+ },
+ CancellationToken.None).GetAwaiter().GetResult();
+ if (mediaInfo.ParentIndexNumber > 0)
+ {
+ ParentIndexNumber = mediaInfo.ParentIndexNumber;
+ }
+
+ if (mediaInfo.IndexNumber > 0)
+ {
+ IndexNumber = mediaInfo.IndexNumber;
+ }
+
+ if (!string.IsNullOrEmpty(mediaInfo.ShowName))
+ {
+ SeriesName = mediaInfo.ShowName;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error reading the episode information with ffprobe. Episode: {EpisodeInfo}", Path);
+ }
+ }
+
try
{
if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata))
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index e3fbe8e4d..48211d99f 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -7,8 +7,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
-using Jellyfin.Data.Entities;
+using System.Threading;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Common;
using MediaBrowser.Controller.Dto;
@@ -152,6 +153,21 @@ namespace MediaBrowser.Controller.Entities.TV
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
{
+ if (SourceType == SourceType.Channel)
+ {
+ try
+ {
+ query.Parent = this;
+ query.ChannelIds = new[] { ChannelId };
+ return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None).GetAwaiter().GetResult();
+ }
+ catch
+ {
+ // Already logged at lower levels
+ return new QueryResult<BaseItem>();
+ }
+ }
+
if (query.User is null)
{
return base.GetItemsInternal(query);
@@ -163,7 +179,7 @@ namespace MediaBrowser.Controller.Entities.TV
var items = GetEpisodes(user, query.DtoOptions, true).Where(filter);
- return PostFilterAndSort(items, query, false);
+ return PostFilterAndSort(items, query);
}
/// <summary>
@@ -257,7 +273,7 @@ namespace MediaBrowser.Controller.Entities.TV
if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
{
- IndexNumber ??= LibraryManager.GetSeasonNumberFromPath(Path);
+ IndexNumber ??= LibraryManager.GetSeasonNumberFromPath(Path, ParentId);
// If a change was made record it
if (IndexNumber.HasValue)
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 137d91f1c..62c73d56f 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -9,12 +9,13 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Querying;
using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Series.
/// </summary>
- public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer
+ public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer, ISupportsBoxSetGrouping
{
public Series()
{
@@ -213,7 +214,7 @@ namespace MediaBrowser.Controller.Entities.TV
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
query.IncludeItemTypes = new[] { BaseItemKind.Season };
- query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
+ query.OrderBy = new[] { (ItemSortBy.IndexNumber, SortOrder.Ascending) };
if (user is not null && !user.DisplayMissingEpisodes)
{
@@ -225,16 +226,27 @@ namespace MediaBrowser.Controller.Entities.TV
{
var user = query.User;
+ if (SourceType == SourceType.Channel)
+ {
+ try
+ {
+ query.Parent = this;
+ query.ChannelIds = [ChannelId];
+ return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None).GetAwaiter().GetResult();
+ }
+ catch
+ {
+ // Already logged at lower levels
+ return new QueryResult<BaseItem>();
+ }
+ }
+
if (query.Recursive)
{
var seriesKey = GetUniqueSeriesKey(this);
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
- if (query.OrderBy.Count == 0)
- {
- query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
- }
if (query.IncludeItemTypes.Length == 0)
{
@@ -371,7 +383,25 @@ namespace MediaBrowser.Controller.Entities.TV
query.IsMissing = false;
}
- var allItems = LibraryManager.GetItemList(query);
+ IReadOnlyList<BaseItem> allItems;
+ if (SourceType == SourceType.Channel)
+ {
+ try
+ {
+ query.Parent = parentSeason;
+ query.ChannelIds = [ChannelId];
+ allItems = [.. ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None).GetAwaiter().GetResult().Items];
+ }
+ catch
+ {
+ // Already logged at lower levels
+ return [];
+ }
+ }
+ else
+ {
+ allItems = LibraryManager.GetItemList(query);
+ }
return GetSeasonEpisodes(parentSeason, user, allItems, options, shouldIncludeMissingEpisodes);
}
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 7ae4a4a2c..deed3631b 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -8,7 +8,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
+using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
@@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Entities
PresetViews = query.PresetViews
});
- return UserViewBuilder.SortAndPage(result, null, query, LibraryManager, true);
+ return UserViewBuilder.SortAndPage(result, null, query, LibraryManager);
}
public override int GetChildCount(User user)
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index f5ca3737c..dfa31315c 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -8,8 +8,8 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.TV;
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 4ec2e4c0a..7679d383f 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -6,8 +6,10 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Jellyfin.Data.Entities;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.TV;
@@ -436,22 +438,18 @@ namespace MediaBrowser.Controller.Entities
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
- return SortAndPage(items, totalRecordLimit, query, libraryManager, true);
+ return SortAndPage(items, totalRecordLimit, query, libraryManager);
}
public static QueryResult<BaseItem> SortAndPage(
IEnumerable<BaseItem> items,
int? totalRecordLimit,
InternalItemsQuery query,
- ILibraryManager libraryManager,
- bool enableSorting)
+ ILibraryManager libraryManager)
{
- if (enableSorting)
+ if (query.OrderBy.Count > 0)
{
- if (query.OrderBy.Count > 0)
- {
- items = libraryManager.Sort(items, query.User, query.OrderBy);
- }
+ items = libraryManager.Sort(items, query.User, query.OrderBy);
}
var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
@@ -922,6 +920,11 @@ namespace MediaBrowser.Controller.Entities
}
}
+ if (query.ExcludeItemIds.Contains(item.Id))
+ {
+ return false;
+ }
+
return true;
}