< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.TV.Series
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/TV/Series.cs
Line coverage
1%
Covered lines: 3
Uncovered lines: 191
Coverable lines: 194
Total lines: 523
Line coverage: 1.5%
Branch coverage
0%
Covered branches: 0
Total branches: 54
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/Entities/TV/Series.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Globalization;
 8using System.Linq;
 9using System.Text.Json.Serialization;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using Jellyfin.Data;
 13using Jellyfin.Data.Enums;
 14using Jellyfin.Database.Implementations.Entities;
 15using Jellyfin.Database.Implementations.Enums;
 16using MediaBrowser.Controller.Dto;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.Querying;
 20using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
 21
 22namespace MediaBrowser.Controller.Entities.TV
 23{
 24    /// <summary>
 25    /// Class Series.
 26    /// </summary>
 27    public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer, ISuppo
 28    {
 10529        public Series()
 30        {
 10531            AirDays = Array.Empty<DayOfWeek>();
 10532        }
 33
 34        public DayOfWeek[] AirDays { get; set; }
 35
 36        public string AirTime { get; set; }
 37
 38        [JsonIgnore]
 039        public override bool SupportsAddingToPlaylist => true;
 40
 41        [JsonIgnore]
 042        public override bool IsPreSorted => true;
 43
 44        [JsonIgnore]
 045        public override bool SupportsDateLastMediaAdded => true;
 46
 47        [JsonIgnore]
 048        public override bool SupportsInheritedParentImages => false;
 49
 50        [JsonIgnore]
 051        public override bool SupportsPeople => true;
 52
 53        /// <inheritdoc />
 54        [JsonIgnore]
 055        public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
 056            .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
 057            .ToArray();
 58
 59        /// <summary>
 60        /// Gets or sets the display order.
 61        /// </summary>
 62        /// <remarks>
 63        /// Valid options are airdate, dvd or absolute.
 64        /// </remarks>
 65        public string DisplayOrder { get; set; }
 66
 67        /// <summary>
 68        /// Gets or sets the status.
 69        /// </summary>
 70        /// <value>The status.</value>
 71        public SeriesStatus? Status { get; set; }
 72
 73        public override double GetDefaultPrimaryImageAspectRatio()
 74        {
 075            double value = 2;
 076            value /= 3;
 77
 078            return value;
 79        }
 80
 81        public override string CreatePresentationUniqueKey()
 82        {
 083            if (LibraryManager.GetLibraryOptions(this).EnableAutomaticSeriesGrouping)
 84            {
 085                var userdatakeys = GetUserDataKeys();
 86
 087                if (userdatakeys.Count > 1)
 88                {
 089                    return AddLibrariesToPresentationUniqueKey(userdatakeys[0]);
 90                }
 91            }
 92
 093            return base.CreatePresentationUniqueKey();
 94        }
 95
 96        private string AddLibrariesToPresentationUniqueKey(string key)
 97        {
 098            var lang = GetPreferredMetadataLanguage();
 099            if (!string.IsNullOrEmpty(lang))
 100            {
 0101                key += "-" + lang;
 102            }
 103
 0104            var folders = LibraryManager.GetCollectionFolders(this)
 0105                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
 0106                .ToArray();
 107
 0108            if (folders.Length == 0)
 109            {
 0110                return key;
 111            }
 112
 0113            return key + "-" + string.Join('-', folders);
 114        }
 115
 116        private static string GetUniqueSeriesKey(BaseItem series)
 117        {
 0118            return series.GetPresentationUniqueKey();
 119        }
 120
 121        public override int GetChildCount(User user)
 122        {
 0123            var seriesKey = GetUniqueSeriesKey(this);
 124
 0125            var result = LibraryManager.GetCount(new InternalItemsQuery(user)
 0126            {
 0127                AncestorWithPresentationUniqueKey = null,
 0128                SeriesPresentationUniqueKey = seriesKey,
 0129                IncludeItemTypes = new[] { BaseItemKind.Season },
 0130                IsVirtualItem = false,
 0131                Limit = 0,
 0132                DtoOptions = new DtoOptions(false)
 0133                {
 0134                    EnableImages = false
 0135                }
 0136            });
 137
 0138            return result;
 139        }
 140
 141        public override int GetRecursiveChildCount(User user)
 142        {
 0143            var seriesKey = GetUniqueSeriesKey(this);
 144
 0145            var query = new InternalItemsQuery(user)
 0146            {
 0147                AncestorWithPresentationUniqueKey = null,
 0148                SeriesPresentationUniqueKey = seriesKey,
 0149                DtoOptions = new DtoOptions(false)
 0150                {
 0151                    EnableImages = false
 0152                }
 0153            };
 154
 0155            if (query.IncludeItemTypes.Length == 0)
 156            {
 0157                query.IncludeItemTypes = new[] { BaseItemKind.Episode };
 158            }
 159
 0160            query.IsVirtualItem = false;
 0161            query.Limit = 0;
 0162            var totalRecordCount = LibraryManager.GetCount(query);
 163
 0164            return totalRecordCount;
 165        }
 166
 167        /// <summary>
 168        /// Gets the user data key.
 169        /// </summary>
 170        /// <returns>System.String.</returns>
 171        public override List<string> GetUserDataKeys()
 172        {
 0173            var list = base.GetUserDataKeys();
 174
 0175            if (this.TryGetProviderId(MetadataProvider.Imdb, out var key))
 176            {
 0177                list.Insert(0, key);
 178            }
 179
 0180            if (this.TryGetProviderId(MetadataProvider.Tvdb, out key))
 181            {
 0182                list.Insert(0, key);
 183            }
 184
 0185            if (this.TryGetProviderId(MetadataProvider.Custom, out key))
 186            {
 0187                list.Insert(0, key);
 188            }
 189
 0190            return list;
 191        }
 192
 193        public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery qu
 194        {
 0195            return GetSeasons(user, new DtoOptions(true));
 196        }
 197
 198        public IReadOnlyList<BaseItem> GetSeasons(User user, DtoOptions options)
 199        {
 0200            var query = new InternalItemsQuery(user)
 0201            {
 0202                DtoOptions = options
 0203            };
 204
 0205            SetSeasonQueryOptions(query, user);
 206
 0207            return LibraryManager.GetItemList(query);
 208        }
 209
 210        private void SetSeasonQueryOptions(InternalItemsQuery query, User user)
 211        {
 0212            var seriesKey = GetUniqueSeriesKey(this);
 213
 0214            query.AncestorWithPresentationUniqueKey = null;
 0215            query.SeriesPresentationUniqueKey = seriesKey;
 0216            query.IncludeItemTypes = new[] { BaseItemKind.Season };
 0217            query.OrderBy = new[] { (ItemSortBy.IndexNumber, SortOrder.Ascending) };
 218
 0219            if (user is not null && !user.DisplayMissingEpisodes)
 220            {
 0221                query.IsMissing = false;
 222            }
 0223        }
 224
 225        protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
 226        {
 0227            var user = query.User;
 228
 0229            if (SourceType == SourceType.Channel)
 230            {
 231                try
 232                {
 0233                    query.Parent = this;
 0234                    query.ChannelIds = [ChannelId];
 0235                    return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None)
 236                }
 0237                catch
 238                {
 239                    // Already logged at lower levels
 0240                    return new QueryResult<BaseItem>();
 241                }
 242            }
 243
 0244            if (query.Recursive)
 245            {
 0246                var seriesKey = GetUniqueSeriesKey(this);
 247
 0248                query.AncestorWithPresentationUniqueKey = null;
 0249                query.SeriesPresentationUniqueKey = seriesKey;
 250
 0251                if (query.IncludeItemTypes.Length == 0)
 252                {
 0253                    query.IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season };
 254                }
 255
 0256                query.IsVirtualItem = false;
 0257                return LibraryManager.GetItemsResult(query);
 258            }
 259
 0260            SetSeasonQueryOptions(query, user);
 261
 0262            return LibraryManager.GetItemsResult(query);
 0263        }
 264
 265        public IEnumerable<BaseItem> GetEpisodes(User user, DtoOptions options, bool shouldIncludeMissingEpisodes)
 266        {
 0267            var seriesKey = GetUniqueSeriesKey(this);
 268
 0269            var query = new InternalItemsQuery(user)
 0270            {
 0271                AncestorWithPresentationUniqueKey = null,
 0272                SeriesPresentationUniqueKey = seriesKey,
 0273                IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season },
 0274                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
 0275                DtoOptions = options,
 0276            };
 277
 0278            if (!shouldIncludeMissingEpisodes)
 279            {
 0280                query.IsMissing = false;
 281            }
 282
 0283            var allItems = LibraryManager.GetItemList(query);
 284
 0285            var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
 286
 0287            var allEpisodes = allItems.OfType<Season>()
 0288                .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes, options, shouldIncludeMissingEpisodes))
 0289                .Reverse();
 290
 291            // Specials could appear twice based on above - once in season 0, once in the aired season
 292            // This depends on settings for that series
 293            // When this happens, remove the duplicate from season 0
 294
 0295            return allEpisodes.DistinctBy(i => i.Id).Reverse();
 296        }
 297
 298        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, Cancella
 299        {
 300            // Refresh bottom up, seasons and episodes first, then the series
 301            var items = GetRecursiveChildren();
 302
 303            var totalItems = items.Count;
 304            var numComplete = 0;
 305
 306            // Refresh seasons
 307            foreach (var item in items)
 308            {
 309                if (item is not Season)
 310                {
 311                    continue;
 312                }
 313
 314                cancellationToken.ThrowIfCancellationRequested();
 315
 316                if (refreshOptions.RefreshItem(item))
 317                {
 318                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 319                }
 320
 321                numComplete++;
 322                double percent = numComplete;
 323                percent /= totalItems;
 324                progress.Report(percent * 100);
 325            }
 326
 327            // Refresh episodes and other children
 328            foreach (var item in items)
 329            {
 330                if (item is Season)
 331                {
 332                    continue;
 333                }
 334
 335                cancellationToken.ThrowIfCancellationRequested();
 336
 337                bool skipItem = item is Episode episode
 338                    && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
 339                    && !refreshOptions.ReplaceAllMetadata
 340                    && episode.IsMissingEpisode
 341                    && episode.LocationType == LocationType.Virtual
 342                    && episode.PremiereDate.HasValue
 343                    && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30;
 344
 345                if (!skipItem)
 346                {
 347                    if (refreshOptions.RefreshItem(item))
 348                    {
 349                        await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 350                    }
 351                }
 352
 353                numComplete++;
 354                double percent = numComplete;
 355                percent /= totalItems;
 356                progress.Report(percent * 100);
 357            }
 358
 359            refreshOptions = new MetadataRefreshOptions(refreshOptions);
 360            await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
 361        }
 362
 363        public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options, bool shouldIncludeMi
 364        {
 0365            var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons;
 366
 367            // add optimization when this setting is not enabled
 0368            var seriesKey = queryFromSeries ?
 0369                GetUniqueSeriesKey(this) :
 0370                GetUniqueSeriesKey(parentSeason);
 371
 0372            var query = new InternalItemsQuery(user)
 0373            {
 0374                AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
 0375                SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
 0376                IncludeItemTypes = new[] { BaseItemKind.Episode },
 0377                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
 0378                DtoOptions = options
 0379            };
 380
 0381            if (!shouldIncludeMissingEpisodes)
 382            {
 0383                query.IsMissing = false;
 384            }
 385
 386            IReadOnlyList<BaseItem> allItems;
 0387            if (SourceType == SourceType.Channel)
 388            {
 389                try
 390                {
 0391                    query.Parent = parentSeason;
 0392                    query.ChannelIds = [ChannelId];
 0393                    allItems = [.. ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationTok
 0394                }
 0395                catch
 396                {
 397                    // Already logged at lower levels
 0398                    return [];
 399                }
 400            }
 401            else
 402            {
 0403                allItems = LibraryManager.GetItemList(query);
 404            }
 405
 0406            return GetSeasonEpisodes(parentSeason, user, allItems, options, shouldIncludeMissingEpisodes);
 0407        }
 408
 409        public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, IEnumerable<BaseItem> allSeriesEpisodes,
 410        {
 0411            if (allSeriesEpisodes is null)
 412            {
 0413                return GetSeasonEpisodes(parentSeason, user, options, shouldIncludeMissingEpisodes);
 414            }
 415
 0416            var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.Di
 417
 0418            var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
 419
 0420            return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending).ToList();
 421        }
 422
 423        /// <summary>
 424        /// Filters the episodes by season.
 425        /// </summary>
 426        /// <param name="episodes">The episodes.</param>
 427        /// <param name="parentSeason">The season.</param>
 428        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
 429        /// <returns>The set of episodes.</returns>
 430        public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, 
 431        {
 0432            var seasonNumber = parentSeason.IndexNumber;
 0433            var seasonPresentationKey = GetUniqueSeriesKey(parentSeason);
 434
 0435            var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
 436
 0437            return episodes.Where(episode =>
 0438            {
 0439                var episodeItem = (Episode)episode;
 0440
 0441                var currentSeasonNumber = supportSpecialsInSeason ? episodeItem.AiredSeasonNumber : episode.ParentIndexN
 0442                if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.V
 0443                {
 0444                    return true;
 0445                }
 0446
 0447                if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType
 0448                {
 0449                    return true;
 0450                }
 0451
 0452                var season = episodeItem.Season;
 0453                return season is not null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComp
 0454            });
 455        }
 456
 457        /// <summary>
 458        /// Filters the episodes by season.
 459        /// </summary>
 460        /// <param name="episodes">The episodes.</param>
 461        /// <param name="seasonNumber">The season.</param>
 462        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
 463        /// <returns>The set of episodes.</returns>
 464        public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool 
 465        {
 0466            if (!includeSpecials || seasonNumber < 1)
 467            {
 0468                return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
 469            }
 470
 0471            return episodes.Where(i =>
 0472            {
 0473                var episode = i;
 0474
 0475                if (episode is not null)
 0476                {
 0477                    var currentSeasonNumber = episode.AiredSeasonNumber;
 0478
 0479                    return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
 0480                }
 0481
 0482                return false;
 0483            });
 484        }
 485
 486        protected override bool GetBlockUnratedValue(User user)
 487        {
 0488            return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series);
 489        }
 490
 491        public override UnratedItem GetBlockUnratedType()
 492        {
 0493            return UnratedItem.Series;
 494        }
 495
 496        public SeriesInfo GetLookupInfo()
 497        {
 0498            var info = GetItemLookupInfo<SeriesInfo>();
 499
 0500            return info;
 501        }
 502
 503        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 504        {
 0505            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 506
 0507            if (!ProductionYear.HasValue)
 508            {
 0509                var info = LibraryManager.ParseName(Name);
 510
 0511                var yearInName = info.Year;
 512
 0513                if (yearInName.HasValue)
 514                {
 0515                    ProductionYear = yearInName;
 0516                    hasChanges = true;
 517                }
 518            }
 519
 0520            return hasChanges;
 521        }
 522    }
 523}

Methods/Properties

.ctor()
get_SupportsAddingToPlaylist()
get_IsPreSorted()
get_SupportsDateLastMediaAdded()
get_SupportsInheritedParentImages()
get_SupportsPeople()
get_LocalTrailers()
GetDefaultPrimaryImageAspectRatio()
CreatePresentationUniqueKey()
AddLibrariesToPresentationUniqueKey(System.String)
GetUniqueSeriesKey(MediaBrowser.Controller.Entities.BaseItem)
GetChildCount(Jellyfin.Database.Implementations.Entities.User)
GetRecursiveChildCount(Jellyfin.Database.Implementations.Entities.User)
GetUserDataKeys()
GetChildren(Jellyfin.Database.Implementations.Entities.User,System.Boolean,MediaBrowser.Controller.Entities.InternalItemsQuery)
GetSeasons(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
SetSeasonQueryOptions(MediaBrowser.Controller.Entities.InternalItemsQuery,Jellyfin.Database.Implementations.Entities.User)
GetItemsInternal(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetEpisodes(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Dto.DtoOptions,System.Boolean)
GetSeasonEpisodes(MediaBrowser.Controller.Entities.TV.Season,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Dto.DtoOptions,System.Boolean)
GetSeasonEpisodes(MediaBrowser.Controller.Entities.TV.Season,Jellyfin.Database.Implementations.Entities.User,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Dto.DtoOptions,System.Boolean)
FilterEpisodesBySeason(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Entities.TV.Season,System.Boolean)
FilterEpisodesBySeason(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.TV.Episode>,System.Int32,System.Boolean)
GetBlockUnratedValue(Jellyfin.Database.Implementations.Entities.User)
GetBlockUnratedType()
GetLookupInfo()
BeforeMetadataRefresh(System.Boolean)