< 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: 524
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            Children = null; // invalidate cached children.
 301            // Refresh bottom up, seasons and episodes first, then the series
 302            var items = GetRecursiveChildren();
 303
 304            var totalItems = items.Count;
 305            var numComplete = 0;
 306
 307            // Refresh seasons
 308            foreach (var item in items)
 309            {
 310                if (item is not Season)
 311                {
 312                    continue;
 313                }
 314
 315                cancellationToken.ThrowIfCancellationRequested();
 316
 317                if (refreshOptions.RefreshItem(item))
 318                {
 319                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 320                }
 321
 322                numComplete++;
 323                double percent = numComplete;
 324                percent /= totalItems;
 325                progress.Report(percent * 100);
 326            }
 327
 328            // Refresh episodes and other children
 329            foreach (var item in items)
 330            {
 331                if (item is Season)
 332                {
 333                    continue;
 334                }
 335
 336                cancellationToken.ThrowIfCancellationRequested();
 337
 338                bool skipItem = item is Episode episode
 339                    && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
 340                    && !refreshOptions.ReplaceAllMetadata
 341                    && episode.IsMissingEpisode
 342                    && episode.LocationType == LocationType.Virtual
 343                    && episode.PremiereDate.HasValue
 344                    && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30;
 345
 346                if (!skipItem)
 347                {
 348                    if (refreshOptions.RefreshItem(item))
 349                    {
 350                        await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 351                    }
 352                }
 353
 354                numComplete++;
 355                double percent = numComplete;
 356                percent /= totalItems;
 357                progress.Report(percent * 100);
 358            }
 359
 360            refreshOptions = new MetadataRefreshOptions(refreshOptions);
 361            await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
 362        }
 363
 364        public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options, bool shouldIncludeMi
 365        {
 0366            var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons;
 367
 368            // add optimization when this setting is not enabled
 0369            var seriesKey = queryFromSeries ?
 0370                GetUniqueSeriesKey(this) :
 0371                GetUniqueSeriesKey(parentSeason);
 372
 0373            var query = new InternalItemsQuery(user)
 0374            {
 0375                AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
 0376                SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
 0377                IncludeItemTypes = new[] { BaseItemKind.Episode },
 0378                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
 0379                DtoOptions = options
 0380            };
 381
 0382            if (!shouldIncludeMissingEpisodes)
 383            {
 0384                query.IsMissing = false;
 385            }
 386
 387            IReadOnlyList<BaseItem> allItems;
 0388            if (SourceType == SourceType.Channel)
 389            {
 390                try
 391                {
 0392                    query.Parent = parentSeason;
 0393                    query.ChannelIds = [ChannelId];
 0394                    allItems = [.. ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationTok
 0395                }
 0396                catch
 397                {
 398                    // Already logged at lower levels
 0399                    return [];
 400                }
 401            }
 402            else
 403            {
 0404                allItems = LibraryManager.GetItemList(query);
 405            }
 406
 0407            return GetSeasonEpisodes(parentSeason, user, allItems, options, shouldIncludeMissingEpisodes);
 0408        }
 409
 410        public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, IEnumerable<BaseItem> allSeriesEpisodes,
 411        {
 0412            if (allSeriesEpisodes is null)
 413            {
 0414                return GetSeasonEpisodes(parentSeason, user, options, shouldIncludeMissingEpisodes);
 415            }
 416
 0417            var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.Di
 418
 0419            var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
 420
 0421            return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending).ToList();
 422        }
 423
 424        /// <summary>
 425        /// Filters the episodes by season.
 426        /// </summary>
 427        /// <param name="episodes">The episodes.</param>
 428        /// <param name="parentSeason">The season.</param>
 429        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
 430        /// <returns>The set of episodes.</returns>
 431        public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, 
 432        {
 0433            var seasonNumber = parentSeason.IndexNumber;
 0434            var seasonPresentationKey = GetUniqueSeriesKey(parentSeason);
 435
 0436            var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
 437
 0438            return episodes.Where(episode =>
 0439            {
 0440                var episodeItem = (Episode)episode;
 0441
 0442                var currentSeasonNumber = supportSpecialsInSeason ? episodeItem.AiredSeasonNumber : episode.ParentIndexN
 0443                if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.V
 0444                {
 0445                    return true;
 0446                }
 0447
 0448                if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType
 0449                {
 0450                    return true;
 0451                }
 0452
 0453                var season = episodeItem.Season;
 0454                return season is not null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComp
 0455            });
 456        }
 457
 458        /// <summary>
 459        /// Filters the episodes by season.
 460        /// </summary>
 461        /// <param name="episodes">The episodes.</param>
 462        /// <param name="seasonNumber">The season.</param>
 463        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
 464        /// <returns>The set of episodes.</returns>
 465        public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool 
 466        {
 0467            if (!includeSpecials || seasonNumber < 1)
 468            {
 0469                return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
 470            }
 471
 0472            return episodes.Where(i =>
 0473            {
 0474                var episode = i;
 0475
 0476                if (episode is not null)
 0477                {
 0478                    var currentSeasonNumber = episode.AiredSeasonNumber;
 0479
 0480                    return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
 0481                }
 0482
 0483                return false;
 0484            });
 485        }
 486
 487        protected override bool GetBlockUnratedValue(User user)
 488        {
 0489            return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series);
 490        }
 491
 492        public override UnratedItem GetBlockUnratedType()
 493        {
 0494            return UnratedItem.Series;
 495        }
 496
 497        public SeriesInfo GetLookupInfo()
 498        {
 0499            var info = GetItemLookupInfo<SeriesInfo>();
 500
 0501            return info;
 502        }
 503
 504        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 505        {
 0506            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 507
 0508            if (!ProductionYear.HasValue)
 509            {
 0510                var info = LibraryManager.ParseName(Name);
 511
 0512                var yearInName = info.Year;
 513
 0514                if (yearInName.HasValue)
 515                {
 0516                    ProductionYear = yearInName;
 0517                    hasChanges = true;
 518                }
 519            }
 520
 0521            return hasChanges;
 522        }
 523    }
 524}

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)