< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.TV.Episode
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/TV/Episode.cs
Line coverage
6%
Covered lines: 8
Uncovered lines: 113
Coverable lines: 121
Total lines: 386
Line coverage: 6.6%
Branch coverage
4%
Covered branches: 4
Total branches: 92
Branch coverage: 4.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/5/2026 - 12:13:57 AM Line coverage: 0% (0/122) Branch coverage: 0% (0/88) Total lines: 3825/4/2026 - 12:15:16 AM Line coverage: 0% (0/120) Branch coverage: 0% (0/88) Total lines: 3805/7/2026 - 12:15:44 AM Line coverage: 5% (6/120) Branch coverage: 4.5% (4/88) Total lines: 3805/27/2026 - 12:15:38 AM Line coverage: 4.9% (6/121) Branch coverage: 4.3% (4/92) Total lines: 3866/2/2026 - 12:15:49 AM Line coverage: 6.6% (8/121) Branch coverage: 4.3% (4/92) Total lines: 386 3/5/2026 - 12:13:57 AM Line coverage: 0% (0/122) Branch coverage: 0% (0/88) Total lines: 3825/4/2026 - 12:15:16 AM Line coverage: 0% (0/120) Branch coverage: 0% (0/88) Total lines: 3805/7/2026 - 12:15:44 AM Line coverage: 5% (6/120) Branch coverage: 4.5% (4/88) Total lines: 3805/27/2026 - 12:15:38 AM Line coverage: 4.9% (6/121) Branch coverage: 4.3% (4/92) Total lines: 3866/2/2026 - 12:15:49 AM Line coverage: 6.6% (8/121) Branch coverage: 4.3% (4/92) Total lines: 386

Coverage delta

Coverage delta 5 -5

Metrics

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/Entities/TV/Episode.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 Jellyfin.Data.Enums;
 12using Jellyfin.Extensions;
 13using MediaBrowser.Controller.MediaEncoding;
 14using MediaBrowser.Controller.Providers;
 15using MediaBrowser.Model.Dlna;
 16using MediaBrowser.Model.Entities;
 17using MediaBrowser.Model.IO;
 18using Microsoft.Extensions.Logging;
 19
 20namespace MediaBrowser.Controller.Entities.TV
 21{
 22    /// <summary>
 23    /// Class Episode.
 24    /// </summary>
 25    public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
 26    {
 27        public static IMediaEncoder MediaEncoder { get; set; }
 28
 29        /// <inheritdoc />
 30        [JsonIgnore]
 031        public IReadOnlyList<BaseItem> LocalTrailers => GetExtras([Model.Entities.ExtraType.Trailer]).ToArray();
 32
 33        /// <summary>
 34        /// Gets or sets the season in which it aired.
 35        /// </summary>
 36        /// <value>The aired season.</value>
 37        public int? AirsBeforeSeasonNumber { get; set; }
 38
 39        public int? AirsAfterSeasonNumber { get; set; }
 40
 41        public int? AirsBeforeEpisodeNumber { get; set; }
 42
 43        /// <summary>
 44        /// Gets or sets the ending episode number for double episodes.
 45        /// </summary>
 46        /// <value>The index number.</value>
 47        public int? IndexNumberEnd { get; set; }
 48
 49        [JsonIgnore]
 050        protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
 51
 52        [JsonIgnore]
 353        public override bool SupportsInheritedParentImages => true;
 54
 55        [JsonIgnore]
 056        public override bool SupportsPeople => true;
 57
 58        [JsonIgnore]
 059        public int? AiredSeasonNumber => AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber;
 60
 61        [JsonIgnore]
 062        public override Folder LatestItemsIndexContainer => Series;
 63
 64        [JsonIgnore]
 365        public override Guid DisplayParentId => SeasonId;
 66
 67        [JsonIgnore]
 068        protected override bool EnableDefaultVideoUserDataKeys => false;
 69
 70        /// <summary>
 71        /// Gets the Episode's Series Instance.
 72        /// </summary>
 73        /// <value>The series.</value>
 74        [JsonIgnore]
 75        public Series Series
 76        {
 77            get
 78            {
 679                var seriesId = SeriesId;
 680                if (seriesId.IsEmpty())
 81                {
 082                    seriesId = FindSeriesId();
 83                }
 84
 685                return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series);
 86            }
 87        }
 88
 89        [JsonIgnore]
 90        public Season Season
 91        {
 92            get
 93            {
 394                var seasonId = SeasonId;
 395                if (seasonId.IsEmpty())
 96                {
 097                    seasonId = FindSeasonId();
 98                }
 99
 3100                return seasonId.IsEmpty() ? null : (LibraryManager.GetItemById(seasonId) as Season);
 101            }
 102        }
 103
 104        [JsonIgnore]
 0105        public bool IsInSeasonFolder => FindParent<Season>() is not null;
 106
 107        [JsonIgnore]
 108        public string SeriesPresentationUniqueKey { get; set; }
 109
 110        [JsonIgnore]
 111        public string SeriesName { get; set; }
 112
 113        [JsonIgnore]
 114        public string SeasonName { get; set; }
 115
 116        [JsonIgnore]
 117        public override bool SupportsRemoteImageDownloading
 118        {
 119            get
 120            {
 0121                if (IsMissingEpisode)
 122                {
 0123                    return false;
 124                }
 125
 0126                return true;
 127            }
 128        }
 129
 130        [JsonIgnore]
 0131        public bool IsMissingEpisode => LocationType == LocationType.Virtual;
 132
 133        [JsonIgnore]
 134        public Guid SeasonId { get; set; }
 135
 136        [JsonIgnore]
 137        public Guid SeriesId { get; set; }
 138
 139        public string FindSeriesSortName()
 140        {
 0141            var series = Series;
 0142            return series is null ? SeriesName : series.SortName;
 143        }
 144
 145        public override double GetDefaultPrimaryImageAspectRatio()
 146        {
 147            // hack for tv plugins
 0148            if (SourceType == SourceType.Channel)
 149            {
 0150                return 0;
 151            }
 152
 0153            return 16.0 / 9;
 154        }
 155
 156        /// <inheritdoc />
 157        public override string GetInheritedOriginalLanguage()
 158        {
 0159            return OriginalLanguage ?? Series?.GetInheritedOriginalLanguage();
 160        }
 161
 162        public override List<string> GetUserDataKeys()
 163        {
 0164            var list = base.GetUserDataKeys();
 165
 0166            var series = Series;
 0167            if (series is not null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
 168            {
 0169                var seriesUserDataKeys = series.GetUserDataKeys();
 0170                var take = seriesUserDataKeys.Count;
 0171                if (seriesUserDataKeys.Count > 1)
 172                {
 0173                    take--;
 174                }
 175
 0176                var newList = seriesUserDataKeys.GetRange(0, take);
 0177                var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.T
 0178                for (int i = 0; i < take; i++)
 179                {
 0180                    newList[i] = newList[i] + suffix;
 181                }
 182
 0183                newList.AddRange(list);
 0184                list = newList;
 185            }
 186
 0187            return list;
 188        }
 189
 190        public string FindSeriesPresentationUniqueKey()
 0191            => Series?.PresentationUniqueKey;
 192
 193        public string FindSeasonName()
 194        {
 0195            var season = Season;
 196
 0197            if (season is null)
 198            {
 0199                if (ParentIndexNumber.HasValue)
 200                {
 0201                    return "Season " + ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture);
 202                }
 203
 0204                return "Season Unknown";
 205            }
 206
 0207            return season.Name;
 208        }
 209
 210        public string FindSeriesName()
 211        {
 0212            var series = Series;
 0213            return series is null ? SeriesName : series.Name;
 214        }
 215
 216        public Guid FindSeasonId()
 217        {
 0218            var season = FindParent<Season>();
 219
 220            // Episodes directly in series folder
 0221            if (season is null)
 222            {
 0223                var series = Series;
 224
 0225                if (series is not null && ParentIndexNumber.HasValue)
 226                {
 0227                    var findNumber = ParentIndexNumber.Value;
 228
 0229                    season = series.Children
 0230                        .OfType<Season>()
 0231                        .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
 232                }
 233            }
 234
 0235            return season is null ? Guid.Empty : season.Id;
 236        }
 237
 238        /// <summary>
 239        /// Creates the name of the sort.
 240        /// </summary>
 241        /// <returns>System.String.</returns>
 242        protected override string CreateSortName()
 243        {
 0244            return (ParentIndexNumber is not null ? ParentIndexNumber.Value.ToString("000 - ", CultureInfo.InvariantCult
 0245                    + (IndexNumber is not null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : s
 246        }
 247
 248        /// <summary>
 249        /// Determines whether [contains episode number] [the specified number].
 250        /// </summary>
 251        /// <param name="number">The number.</param>
 252        /// <returns><c>true</c> if [contains episode number] [the specified number]; otherwise, <c>false</c>.</returns>
 253        public bool ContainsEpisodeNumber(int number)
 254        {
 0255            if (IndexNumber.HasValue)
 256            {
 0257                if (IndexNumberEnd.HasValue)
 258                {
 0259                    return number >= IndexNumber.Value && number <= IndexNumberEnd.Value;
 260                }
 261
 0262                return IndexNumber.Value == number;
 263            }
 264
 0265            return false;
 266        }
 267
 268        public Guid FindSeriesId()
 269        {
 0270            var series = FindParent<Series>();
 0271            return series is null ? Guid.Empty : series.Id;
 272        }
 273
 274        public override IEnumerable<Guid> GetAncestorIds()
 275        {
 0276            var list = base.GetAncestorIds().ToList();
 277
 0278            var seasonId = SeasonId;
 279
 0280            if (!seasonId.IsEmpty() && !list.Contains(seasonId))
 281            {
 0282                list.Add(seasonId);
 283            }
 284
 0285            return list;
 286        }
 287
 288        public override IEnumerable<FileSystemMetadata> GetDeletePaths()
 289        {
 0290            return new[]
 0291            {
 0292                new FileSystemMetadata
 0293                {
 0294                    FullName = Path,
 0295                    IsDirectory = IsFolder
 0296                }
 0297            }.Concat(GetLocalMetadataFilesToDelete());
 298        }
 299
 300        public override UnratedItem GetBlockUnratedType()
 301        {
 0302            return UnratedItem.Series;
 303        }
 304
 305        public EpisodeInfo GetLookupInfo()
 306        {
 0307            var id = GetItemLookupInfo<EpisodeInfo>();
 308
 0309            var series = Series;
 310
 0311            if (series is not null)
 312            {
 0313                id.SeriesProviderIds = series.ProviderIds;
 0314                id.SeriesDisplayOrder = series.DisplayOrder;
 315            }
 316
 0317            if (Season is not null)
 318            {
 0319                id.SeasonProviderIds = Season.ProviderIds;
 320            }
 321
 0322            id.IsMissingEpisode = IsMissingEpisode;
 0323            id.IndexNumberEnd = IndexNumberEnd;
 324
 0325            return id;
 326        }
 327
 328        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 329        {
 0330            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 331
 0332            if (!IsLocked)
 333            {
 0334                if (SourceType == SourceType.Library || SourceType == SourceType.LiveTV)
 335                {
 0336                    var libraryOptions = LibraryManager.GetLibraryOptions(this);
 0337                    if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(Container, "mp4", StringComparison.Or
 338                    {
 339                        try
 340                        {
 0341                            var mediaInfo = MediaEncoder.GetMediaInfo(
 0342                                new MediaInfoRequest
 0343                                {
 0344                                    MediaSource = GetMediaSources(false)[0],
 0345                                    MediaType = DlnaProfileType.Video
 0346                                },
 0347                                CancellationToken.None).GetAwaiter().GetResult();
 0348                            if (mediaInfo.ParentIndexNumber > 0)
 349                            {
 0350                                ParentIndexNumber = mediaInfo.ParentIndexNumber;
 351                            }
 352
 0353                            if (mediaInfo.IndexNumber > 0)
 354                            {
 0355                                IndexNumber = mediaInfo.IndexNumber;
 356                            }
 357
 0358                            if (!string.IsNullOrEmpty(mediaInfo.ShowName))
 359                            {
 0360                                SeriesName = mediaInfo.ShowName;
 361                            }
 0362                        }
 0363                        catch (Exception ex)
 364                        {
 0365                            Logger.LogError(ex, "Error reading the episode information with ffprobe. Episode: {EpisodeIn
 0366                        }
 367                    }
 368
 369                    try
 370                    {
 0371                        if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata))
 372                        {
 0373                            hasChanges = true;
 374                        }
 0375                    }
 0376                    catch (Exception ex)
 377                    {
 0378                        Logger.LogError(ex, "Error in FillMissingEpisodeNumbersFromPath. Episode: {Episode}", Path ?? Na
 0379                    }
 380                }
 381            }
 382
 0383            return hasChanges;
 384        }
 385    }
 386}