diff options
Diffstat (limited to 'MediaBrowser.Controller/Entities')
28 files changed, 358 insertions, 179 deletions
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 5e0d1bb45..a02802f41 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities return CreateResolveArgs(directoryService, true).FileSystemChildren; } - protected override List<BaseItem> LoadChildren() + protected override IReadOnlyList<BaseItem> LoadChildren() { lock (_childIdsLock) { diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index 1625c748a..b085398c5 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Entities.Audio IReadOnlyList<string> Artists { get; set; } } - public static class Extentions + public static class Extensions { public static IEnumerable<string> GetAllArtists<T>(this T item) where T : IHasArtist, IHasAlbumArtist diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index a0aae8769..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; @@ -21,6 +23,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class MusicAlbum. /// </summary> + [Common.RequiresSourceSerialisation] public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer { public MusicAlbum() diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 1ab6c9706..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; @@ -21,6 +23,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class MusicArtist. /// </summary> + [Common.RequiresSourceSerialisation] public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo<ArtistInfo> { [JsonIgnore] @@ -84,7 +87,7 @@ namespace MediaBrowser.Controller.Entities.Audio return !IsAccessedByName; } - public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query) { if (query.IncludeItemTypes.Length == 0) { @@ -110,15 +113,15 @@ namespace MediaBrowser.Controller.Entities.Audio return base.IsSaveLocalMetadataEnabled(); } - protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, bool allowRemoveRoot, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken) + protected override async Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, bool allowRemoveRoot, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken) { if (IsAccessedByName) { // Should never get in here anyway - return Task.CompletedTask; + return; } - return base.ValidateChildrenInternal(progress, recursive, refreshChildMetadata, false, refreshOptions, directoryService, cancellationToken); + await base.ValidateChildrenInternal(progress, recursive, refreshChildMetadata, false, refreshOptions, directoryService, cancellationToken).ConfigureAwait(false); } public override List<string> GetUserDataKeys() @@ -137,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/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 7448d02ea..65669e680 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class MusicGenre. /// </summary> + [Common.RequiresSourceSerialisation] public class MusicGenre : BaseItem, IItemByName { [JsonIgnore] @@ -64,7 +65,7 @@ namespace MediaBrowser.Controller.Entities.Audio return true; } - public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query) { query.GenreIds = new[] { Id }; query.IncludeItemTypes = new[] { BaseItemKind.MusicVideo, BaseItemKind.Audio, BaseItemKind.MusicAlbum, BaseItemKind.MusicArtist }; diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index 782481fbc..666bf2a75 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Providers; namespace MediaBrowser.Controller.Entities { + [Common.RequiresSourceSerialisation] public class AudioBook : Audio.Audio, IHasSeries, IHasLookupInfo<SongInfo> { [JsonIgnore] diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index eb605f6c8..c2efa4ad3 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using System.IO; using System.Linq; @@ -11,15 +12,20 @@ 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; +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; @@ -29,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 @@ -47,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) { @@ -443,7 +448,7 @@ namespace MediaBrowser.Controller.Entities return Array.Empty<string>(); } - return new[] { Path }; + return [Path]; } } @@ -479,6 +484,8 @@ namespace MediaBrowser.Controller.Entities public static IItemRepository ItemRepository { get; set; } + public static IChapterManager ChapterManager { get; set; } + public static IFileSystem FileSystem { get; set; } public static IUserDataManager UserDataManager { get; set; } @@ -574,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> @@ -915,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)); @@ -1041,7 +1051,7 @@ namespace MediaBrowser.Controller.Entities return PlayAccess.Full; } - public virtual List<MediaStream> GetMediaStreams() + public virtual IReadOnlyList<MediaStream> GetMediaStreams() { return MediaSourceManager.GetMediaStreams(new MediaStreamQuery { @@ -1054,7 +1064,7 @@ namespace MediaBrowser.Controller.Entities return false; } - public virtual List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution) + public virtual IReadOnlyList<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution) { if (SourceType == SourceType.Channel) { @@ -1088,7 +1098,7 @@ namespace MediaBrowser.Controller.Entities return 1; }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) .ThenByDescending(i => i, new MediaSourceWidthComparator()) - .ToList(); + .ToArray(); } protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() @@ -1256,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> @@ -1299,7 +1309,7 @@ namespace MediaBrowser.Controller.Entities return false; } - if (GetParents().Any(i => !i.IsVisible(user))) + if (GetParents().Any(i => !i.IsVisible(user, true))) { return false; } @@ -1353,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) @@ -1384,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; @@ -1398,6 +1423,22 @@ namespace MediaBrowser.Controller.Entities public virtual bool RequiresRefresh() { + if (string.IsNullOrEmpty(Path) || DateModified == default) + { + return false; + } + + var info = FileSystem.GetFileSystemInfo(Path); + if (info.Exists) + { + if (info.IsDirectory) + { + return info.LastWriteTimeUtc != DateModified; + } + + return info.LastWriteTimeUtc != DateModified; + } + return false; } @@ -1521,18 +1562,20 @@ namespace MediaBrowser.Controller.Entities /// Determines if a given user has access to this item. /// </summary> /// <param name="user">The user.</param> + /// <param name="skipAllowedTagsCheck">Don't check for allowed tags.</param> /// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns> /// <exception cref="ArgumentNullException">If user is null.</exception> - public bool IsParentalAllowed(User user) + public bool IsParentalAllowed(User user, bool skipAllowedTagsCheck) { ArgumentNullException.ThrowIfNull(user); - if (!IsVisibleViaTags(user)) + if (!IsVisibleViaTags(user, skipAllowedTagsCheck)) { return false; } - var maxAllowedRating = user.MaxParentalAgeRating; + var maxAllowedRating = user.MaxParentalRatingScore; + var maxAllowedSubRating = user.MaxParentalRatingSubScore; var rating = CustomRatingForComparison; if (string.IsNullOrEmpty(rating)) @@ -1546,10 +1589,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); @@ -1561,10 +1604,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; @@ -1578,7 +1626,7 @@ namespace MediaBrowser.Controller.Entities return null; } - return LocalizationManager.GetRatingLevel(rating); + return LocalizationManager.GetRatingScore(rating); } public List<string> GetInheritedTags() @@ -1599,7 +1647,7 @@ namespace MediaBrowser.Controller.Entities return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); } - private bool IsVisibleViaTags(User user) + private bool IsVisibleViaTags(User user, bool skipAllowedTagsCheck) { var allTags = GetInheritedTags(); if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase))) @@ -1614,7 +1662,7 @@ namespace MediaBrowser.Controller.Entities } var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags); - if (allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase))) + if (!skipAllowedTagsCheck && allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase))) { return false; } @@ -1654,13 +1702,14 @@ namespace MediaBrowser.Controller.Entities /// Default is just parental allowed. Can be overridden for more functionality. /// </summary> /// <param name="user">The user.</param> + /// <param name="skipAllowedTagsCheck">Don't check for allowed tags.</param> /// <returns><c>true</c> if the specified user is visible; otherwise, <c>false</c>.</returns> /// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception> - public virtual bool IsVisible(User user) + public virtual bool IsVisible(User user, bool skipAllowedTagsCheck = false) { ArgumentNullException.ThrowIfNull(user); - return IsParentalAllowed(user); + return IsParentalAllowed(user, skipAllowedTagsCheck); } public virtual bool IsVisibleStandalone(User user) @@ -1675,7 +1724,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"; } @@ -1769,7 +1818,6 @@ namespace MediaBrowser.Controller.Entities public void AddStudio(string name) { ArgumentException.ThrowIfNullOrEmpty(name); - var current = Studios; if (!current.Contains(name, StringComparison.OrdinalIgnoreCase)) @@ -1781,21 +1829,21 @@ namespace MediaBrowser.Controller.Entities } else { - Studios = [..current, name]; + Studios = [.. current, name]; } } } public void SetStudios(IEnumerable<string> names) { - Studios = names.Distinct().ToArray(); + Studios = names.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } /// <summary> /// Adds a genre to the item. /// </summary> /// <param name="name">The name.</param> - /// <exception cref="ArgumentNullException">Throwns if name is null.</exception> + /// <exception cref="ArgumentNullException">Throws if name is null.</exception> public void AddGenre(string name) { ArgumentException.ThrowIfNullOrEmpty(name); @@ -1803,7 +1851,7 @@ namespace MediaBrowser.Controller.Entities var genres = Genres; if (!genres.Contains(name, StringComparison.OrdinalIgnoreCase)) { - Genres = [..genres, name]; + Genres = [.. genres, name]; } } @@ -1821,7 +1869,10 @@ namespace MediaBrowser.Controller.Entities { ArgumentNullException.ThrowIfNull(user); - var data = UserDataManager.GetUserData(user, this); + var data = UserDataManager.GetUserData(user, this) ?? new UserItemData() + { + Key = GetUserDataKeys().First(), + }; if (datePlayed.HasValue) { @@ -1964,7 +2015,7 @@ namespace MediaBrowser.Controller.Entities public void RemoveImage(ItemImageInfo image) { - RemoveImages(new[] { image }); + RemoveImages([image]); } public void RemoveImages(IEnumerable<ItemImageInfo> deletedImages) @@ -1974,11 +2025,11 @@ namespace MediaBrowser.Controller.Entities public void AddImage(ItemImageInfo image) { - ImageInfos = [..ImageInfos, image]; + ImageInfos = [.. ImageInfos, image]; } - public virtual Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken) - => LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken); + public virtual async Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken) + => await LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken).ConfigureAwait(false); /// <summary> /// Validates that images within the item are still on the filesystem. @@ -1999,7 +2050,7 @@ namespace MediaBrowser.Controller.Entities continue; } - (deletedImages ??= new List<ItemImageInfo>()).Add(imageInfo); + (deletedImages ??= []).Add(imageInfo); } var anyImagesRemoved = deletedImages?.Count > 0; @@ -2031,7 +2082,7 @@ namespace MediaBrowser.Controller.Entities { if (imageType == ImageType.Chapter) { - var chapter = ItemRepository.GetChapter(this, imageIndex); + var chapter = ChapterManager.GetChapter(Id, imageIndex); if (chapter is null) { @@ -2081,7 +2132,7 @@ namespace MediaBrowser.Controller.Entities if (image.Type == ImageType.Chapter) { - var chapters = ItemRepository.GetChapters(this); + var chapters = ChapterManager.GetChapters(Id); for (var i = 0; i < chapters.Count; i++) { if (chapters[i].ImagePath == image.Path) @@ -2202,11 +2253,7 @@ namespace MediaBrowser.Controller.Entities { return new[] { - new FileSystemMetadata - { - FullName = Path, - IsDirectory = IsFolder - } + FileSystem.GetFileSystemInfo(Path) }.Concat(GetLocalMetadataFilesToDelete()); } @@ -2214,7 +2261,7 @@ namespace MediaBrowser.Controller.Entities { if (IsFolder || !IsInMixedFolder) { - return new List<FileSystemMetadata>(); + return []; } var filename = System.IO.Path.GetFileNameWithoutExtension(Path); @@ -2367,7 +2414,7 @@ namespace MediaBrowser.Controller.Entities } } - protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken) + protected async Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken) { var newOptions = new MetadataRefreshOptions(options) { @@ -2428,10 +2475,10 @@ namespace MediaBrowser.Controller.Entities } } - return ownedItem.RefreshMetadata(newOptions, cancellationToken); + await ownedItem.RefreshMetadata(newOptions, cancellationToken).ConfigureAwait(false); } - protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken) + protected async Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken) { var newOptions = new MetadataRefreshOptions(options) { @@ -2441,9 +2488,7 @@ namespace MediaBrowser.Controller.Entities var id = LibraryManager.GetNewItemId(path, typeof(Video)); // Try to retrieve it from the db. If we don't find it, use the resolved version - var video = LibraryManager.GetItemById(id) as Video; - - if (video is null) + if (LibraryManager.GetItemById(id) is not Video video) { video = LibraryManager.ResolvePath(FileSystem.GetFileSystemInfo(path)) as Video; @@ -2452,15 +2497,15 @@ namespace MediaBrowser.Controller.Entities if (video is null) { - return Task.FromResult(true); + return; } if (video.OwnerId.IsEmpty()) { - video.OwnerId = this.Id; + video.OwnerId = Id; } - return RefreshMetadataForOwnedItem(video, copyTitleMetadata, newOptions, cancellationToken); + await RefreshMetadataForOwnedItem(video, copyTitleMetadata, newOptions, cancellationToken).ConfigureAwait(false); } public string GetEtag(User user) @@ -2472,10 +2517,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() @@ -2495,7 +2540,7 @@ namespace MediaBrowser.Controller.Entities public virtual IEnumerable<Guid> GetIdsForAncestorQuery() { - return new[] { Id }; + return [Id]; } public virtual double? GetRefreshProgress() @@ -2509,11 +2554,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; @@ -2524,7 +2587,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="children">Media children.</param> /// <returns><c>true</c> if the rating was updated; otherwise <c>false</c>.</returns> - public bool UpdateRatingToItems(IList<BaseItem> children) + public bool UpdateRatingToItems(IReadOnlyList<BaseItem> children) { var currentOfficialRating = OfficialRating; @@ -2533,8 +2596,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/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 66dea1084..518766937 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Providers; namespace MediaBrowser.Controller.Entities { + [Common.RequiresSourceSerialisation] public class Book : BaseItem, IHasLookupInfo<BookInfo>, IHasSeries { public Book() diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4ead477f8..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; @@ -96,11 +96,11 @@ namespace MediaBrowser.Controller.Entities return GetLibraryOptions(Path); } - public override bool IsVisible(User user) + public override bool IsVisible(User user, bool skipAllowedTagsCheck = false) { if (GetLibraryOptions().Enabled) { - return base.IsVisible(user); + return base.IsVisible(user, skipAllowedTagsCheck); } return false; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 83c19a54e..e0c3b0a93 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Security; @@ -11,8 +12,11 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; -using Jellyfin.Data.Entities; +using J2N.Collections.Generic.Extensions; +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; @@ -217,7 +221,7 @@ namespace MediaBrowser.Controller.Entities LibraryManager.CreateItem(item, this); } - public override bool IsVisible(User user) + public override bool IsVisible(User user, bool skipAllowedTagsCheck = false) { if (this is ICollectionFolder && this is not BasePluginFolder) { @@ -239,7 +243,7 @@ namespace MediaBrowser.Controller.Entities } } - return base.IsVisible(user); + return base.IsVisible(user, skipAllowedTagsCheck); } /// <summary> @@ -247,7 +251,7 @@ namespace MediaBrowser.Controller.Entities /// We want this synchronous. /// </summary> /// <returns>Returns children.</returns> - protected virtual List<BaseItem> LoadChildren() + protected virtual IReadOnlyList<BaseItem> LoadChildren() { // logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path); // just load our children from the repo - the library will be validated and maintained in other processes @@ -528,13 +532,13 @@ namespace MediaBrowser.Controller.Entities } } - private Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) + private async Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - return RunTasks( + await RunTasks( (baseItem, innerProgress) => RefreshChildMetadata(baseItem, refreshOptions, recursive && baseItem.IsFolder, innerProgress, cancellationToken), children, progress, - cancellationToken); + cancellationToken).ConfigureAwait(false); } private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) @@ -575,13 +579,13 @@ namespace MediaBrowser.Controller.Entities /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) + private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) { - return RunTasks( + await RunTasks( (folder, innerProgress) => folder.ValidateChildrenInternal(innerProgress, true, false, false, null, directoryService, cancellationToken), children, progress, - cancellationToken); + cancellationToken).ConfigureAwait(false); } /// <summary> @@ -659,7 +663,7 @@ namespace MediaBrowser.Controller.Entities /// Get our children from the repo - stubbed for now. /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - protected List<BaseItem> GetCachedChildren() + protected IReadOnlyList<BaseItem> GetCachedChildren() { return ItemRepository.GetItemList(new InternalItemsQuery { @@ -1060,11 +1064,6 @@ namespace MediaBrowser.Controller.Entities return false; } - if (queryParent is Series) - { - return false; - } - if (queryParent is Season) { return false; @@ -1084,12 +1083,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; } @@ -1200,22 +1202,22 @@ namespace MediaBrowser.Controller.Entities return false; } - if (request.IsHD.HasValue) + if (request.Is4K.HasValue) { return false; } - if (request.IsLocked.HasValue) + if (request.IsHD.HasValue) { return false; } - if (request.IsPlaceHolder.HasValue) + if (request.IsLocked.HasValue) { return false; } - if (request.IsPlayed.HasValue) + if (request.IsPlaceHolder.HasValue) { return false; } @@ -1240,11 +1242,6 @@ namespace MediaBrowser.Controller.Entities return false; } - if (request.GenreIds.Count > 0) - { - return false; - } - if (request.VideoTypes.Length > 0) { return false; @@ -1265,17 +1262,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; } @@ -1283,14 +1278,14 @@ namespace MediaBrowser.Controller.Entities return true; } - public List<BaseItem> GetChildren(User user, bool includeLinkedChildren) + public IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren) { ArgumentNullException.ThrowIfNull(user); return GetChildren(user, includeLinkedChildren, new InternalItemsQuery(user)); } - public virtual List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public virtual IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { ArgumentNullException.ThrowIfNull(user); @@ -1304,7 +1299,7 @@ namespace MediaBrowser.Controller.Entities AddChildren(user, includeLinkedChildren, result, false, query); - return result.Values.ToList(); + return result.Values.ToArray(); } protected virtual IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) @@ -1369,7 +1364,7 @@ namespace MediaBrowser.Controller.Entities } } - public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) + public virtual IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) { ArgumentNullException.ThrowIfNull(user); @@ -1377,35 +1372,35 @@ namespace MediaBrowser.Controller.Entities AddChildren(user, true, result, true, query); - return result.Values; + return result.Values.ToArray(); } /// <summary> /// Gets the recursive children. /// </summary> /// <returns>IList{BaseItem}.</returns> - public IList<BaseItem> GetRecursiveChildren() + public IReadOnlyList<BaseItem> GetRecursiveChildren() { return GetRecursiveChildren(true); } - public IList<BaseItem> GetRecursiveChildren(bool includeLinkedChildren) + public IReadOnlyList<BaseItem> GetRecursiveChildren(bool includeLinkedChildren) { return GetRecursiveChildren(i => true, includeLinkedChildren); } - public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter) + public IReadOnlyList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter) { return GetRecursiveChildren(filter, true); } - public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter, bool includeLinkedChildren) + public IReadOnlyList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter, bool includeLinkedChildren) { var result = new Dictionary<Guid, BaseItem>(); AddChildrenToList(result, includeLinkedChildren, true, filter); - return result.Values.ToList(); + return result.Values.ToArray(); } /// <summary> @@ -1556,11 +1551,12 @@ namespace MediaBrowser.Controller.Entities /// Gets the linked children. /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - public IEnumerable<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos() + public IReadOnlyList<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos() { return LinkedChildren .Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i))) - .Where(i => i.Item2 is not null); + .Where(i => i.Item2 is not null) + .ToArray(); } protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index ddf62dd4c..6ec78a270 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Genre. /// </summary> + [Common.RequiresSourceSerialisation] public class Genre : BaseItem, IItemByName { /// <summary> @@ -61,7 +62,7 @@ namespace MediaBrowser.Controller.Entities return false; } - public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query) { query.GenreIds = new[] { Id }; query.ExcludeItemTypes = new[] diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 90d9bdd2d..ad35494c2 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -22,8 +22,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="enablePathSubstitution"><c>true</c> to enable path substitution, <c>false</c> to not.</param> /// <returns>A list of media sources.</returns> - List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); + IReadOnlyList<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); - List<MediaStream> GetMediaStreams(); + IReadOnlyList<MediaStream> GetMediaStreams(); } } diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index cac8aa61a..4928bda7a 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> public interface IItemByName { - IList<BaseItem> GetTaggedItems(InternalItemsQuery query); + IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query); } public interface IHasDualAccess : IItemByName diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 1461a3680..d50f3d075 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; 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 MediaBrowser.Controller.Dto; using MediaBrowser.Model.Entities; @@ -37,7 +39,6 @@ namespace MediaBrowser.Controller.Entities IncludeItemTypes = Array.Empty<BaseItemKind>(); ItemIds = Array.Empty<Guid>(); MediaTypes = Array.Empty<MediaType>(); - MinSimilarityScore = 20; OfficialRatings = Array.Empty<string>(); OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); PersonIds = Array.Empty<Guid>(); @@ -71,8 +72,6 @@ namespace MediaBrowser.Controller.Entities public User? User { get; set; } - public BaseItem? SimilarTo { get; set; } - public bool? IsFolder { get; set; } public bool? IsFavorite { get; set; } @@ -233,9 +232,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; } @@ -295,8 +294,6 @@ namespace MediaBrowser.Controller.Entities public DtoOptions DtoOptions { get; set; } - public int MinSimilarityScore { get; set; } - public string? HasNoAudioTrackWithLanguage { get; set; } public string? HasNoInternalSubtitleTrackWithLanguage { get; set; } @@ -309,6 +306,8 @@ namespace MediaBrowser.Controller.Entities public bool? IsDeadStudio { get; set; } + public bool? IsDeadGenre { get; set; } + public bool? IsDeadPerson { get; set; } /// <summary> @@ -363,16 +362,17 @@ 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); } + 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); IncludeInheritedTags = user.GetPreference(PreferenceKind.AllowedTags); 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 a07187d2f..dd5852823 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -4,10 +4,13 @@ using System; 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; @@ -91,7 +94,7 @@ namespace MediaBrowser.Controller.Entities.Movies return Enumerable.Empty<BaseItem>(); } - protected override List<BaseItem> LoadChildren() + protected override IReadOnlyList<BaseItem> LoadChildren() { if (IsLegacyBoxSet) { @@ -99,7 +102,7 @@ namespace MediaBrowser.Controller.Entities.Movies } // Save a trip to the database - return new List<BaseItem>(); + return []; } public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) @@ -127,16 +130,16 @@ namespace MediaBrowser.Controller.Entities.Movies return LibraryManager.Sort(items, user, new[] { sortBy }, SortOrder.Ascending); } - public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { var children = base.GetChildren(user, includeLinkedChildren, query); - return Sort(children, user).ToList(); + return Sort(children, user).ToArray(); } - public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) + public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) { var children = base.GetRecursiveChildren(user, query); - return Sort(children, user).ToList(); + return Sort(children, user).ToArray(); } public BoxSetInfo GetLookupInfo() @@ -144,14 +147,14 @@ namespace MediaBrowser.Controller.Entities.Movies return GetItemLookupInfo<BoxSetInfo>(); } - public override bool IsVisible(User user) + public override bool IsVisible(User user, bool skipAllowedTagsCheck = false) { if (IsLegacyBoxSet) { - return base.IsVisible(user); + return base.IsVisible(user, skipAllowedTagsCheck); } - if (base.IsVisible(user)) + if (base.IsVisible(user, skipAllowedTagsCheck)) { if (LinkedChildren.Length == 0) { @@ -194,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 5292bd772..24b1843ce 100644 --- a/MediaBrowser.Controller/Entities/PeopleHelper.cs +++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs @@ -10,11 +10,13 @@ namespace MediaBrowser.Controller.Entities { public static class PeopleHelper { - public static void AddPerson(List<PersonInfo> people, PersonInfo person) + public static void AddPerson(ICollection<PersonInfo> people, PersonInfo person) { 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/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 7f265084f..5cc4d322f 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// This is the full Person object that can be retrieved with all of it's data. /// </summary> + [Common.RequiresSourceSerialisation] public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo> { /// <summary> @@ -62,7 +63,7 @@ namespace MediaBrowser.Controller.Entities return value; } - public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query) { query.PersonIds = new[] { Id }; diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs index 3df0b0b78..0ed870bac 100644 --- a/MediaBrowser.Controller/Entities/PersonInfo.cs +++ b/MediaBrowser.Controller/Entities/PersonInfo.cs @@ -17,8 +17,14 @@ namespace MediaBrowser.Controller.Entities public PersonInfo() { ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + Id = Guid.NewGuid(); } + /// <summary> + /// Gets or Sets the PersonId. + /// </summary> + public Guid Id { get; set; } + public Guid ItemId { get; set; } /// <summary> diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs index a7ecb9061..5b31b4f11 100644 --- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs +++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; namespace MediaBrowser.Controller.Entities { + [Common.RequiresSourceSerialisation] public class PhotoAlbum : Folder { [JsonIgnore] diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index a3736a4bf..9103b09a9 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Studio. /// </summary> + [Common.RequiresSourceSerialisation] public class Studio : BaseItem, IItemByName { /// <summary> @@ -63,7 +64,7 @@ namespace MediaBrowser.Controller.Entities return true; } - public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query) { query.StudioIds = new[] { Id }; 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 181b9be2b..408161b03 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -7,9 +7,11 @@ 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; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Querying; @@ -19,6 +21,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Class Season. /// </summary> + [RequiresSourceSerialisation] public class Season : Folder, IHasSeries, IHasLookupInfo<SeasonInfo> { [JsonIgnore] @@ -132,7 +135,7 @@ namespace MediaBrowser.Controller.Entities.TV var series = Series; if (series is not null) { - return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000", CultureInfo.InvariantCulture); + return series.PresentationUniqueKey + "-" + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); } } @@ -150,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); @@ -255,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 3e0e7b1c5..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() { @@ -189,12 +190,12 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetSeasons(user, new DtoOptions(true)); } - public List<BaseItem> GetSeasons(User user, DtoOptions options) + public IReadOnlyList<BaseItem> GetSeasons(User user, DtoOptions options) { var query = new InternalItemsQuery(user) { @@ -225,6 +226,21 @@ 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); @@ -367,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 65d81b23e..bc7e22d9a 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; @@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Entities } } - protected override List<BaseItem> LoadChildren() + protected override IReadOnlyList<BaseItem> LoadChildren() { lock (_childIdsLock) { diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index e4fb340f7..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; @@ -134,7 +134,7 @@ namespace MediaBrowser.Controller.Entities } /// <inheritdoc /> - public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) + public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) { query.SetUser(user); query.Recursive = true; @@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities } /// <inheritdoc /> - protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) + protected override IReadOnlyList<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) { return GetChildren(user, false); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 420349f35..1eb3c8f50 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; @@ -236,7 +238,7 @@ namespace MediaBrowser.Controller.Entities return ConvertToResult(_libraryManager.GetItemList(query)); } - private QueryResult<BaseItem> ConvertToResult(List<BaseItem> items) + private QueryResult<BaseItem> ConvertToResult(IReadOnlyList<BaseItem> items) { return new QueryResult<BaseItem>(items); } @@ -922,6 +924,11 @@ namespace MediaBrowser.Controller.Entities } } + if (query.ExcludeItemIds.Contains(item.Id)) + { + return false; + } + return true; } diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index afdaf448b..37820296c 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Year. /// </summary> + [Common.RequiresSourceSerialisation] public class Year : BaseItem, IItemByName { [JsonIgnore] @@ -55,7 +56,7 @@ namespace MediaBrowser.Controller.Entities return true; } - public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) + public IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query) { if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { |
