< 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
0%
Covered lines: 0
Uncovered lines: 120
Coverable lines: 120
Total lines: 380
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 88
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 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: 380 1/23/2026 - 12:11:06 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: 380

Coverage delta

Coverage delta 1 -1

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]
 053        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]
 065        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            {
 079                var seriesId = SeriesId;
 080                if (seriesId.IsEmpty())
 81                {
 082                    seriesId = FindSeriesId();
 83                }
 84
 085                return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series);
 86            }
 87        }
 88
 89        [JsonIgnore]
 90        public Season Season
 91        {
 92            get
 93            {
 094                var seasonId = SeasonId;
 095                if (seasonId.IsEmpty())
 96                {
 097                    seasonId = FindSeasonId();
 98                }
 99
 0100                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        public override List<string> GetUserDataKeys()
 157        {
 0158            var list = base.GetUserDataKeys();
 159
 0160            var series = Series;
 0161            if (series is not null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
 162            {
 0163                var seriesUserDataKeys = series.GetUserDataKeys();
 0164                var take = seriesUserDataKeys.Count;
 0165                if (seriesUserDataKeys.Count > 1)
 166                {
 0167                    take--;
 168                }
 169
 0170                var newList = seriesUserDataKeys.GetRange(0, take);
 0171                var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.T
 0172                for (int i = 0; i < take; i++)
 173                {
 0174                    newList[i] = newList[i] + suffix;
 175                }
 176
 0177                newList.AddRange(list);
 0178                list = newList;
 179            }
 180
 0181            return list;
 182        }
 183
 184        public string FindSeriesPresentationUniqueKey()
 0185            => Series?.PresentationUniqueKey;
 186
 187        public string FindSeasonName()
 188        {
 0189            var season = Season;
 190
 0191            if (season is null)
 192            {
 0193                if (ParentIndexNumber.HasValue)
 194                {
 0195                    return "Season " + ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture);
 196                }
 197
 0198                return "Season Unknown";
 199            }
 200
 0201            return season.Name;
 202        }
 203
 204        public string FindSeriesName()
 205        {
 0206            var series = Series;
 0207            return series is null ? SeriesName : series.Name;
 208        }
 209
 210        public Guid FindSeasonId()
 211        {
 0212            var season = FindParent<Season>();
 213
 214            // Episodes directly in series folder
 0215            if (season is null)
 216            {
 0217                var series = Series;
 218
 0219                if (series is not null && ParentIndexNumber.HasValue)
 220                {
 0221                    var findNumber = ParentIndexNumber.Value;
 222
 0223                    season = series.Children
 0224                        .OfType<Season>()
 0225                        .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
 226                }
 227            }
 228
 0229            return season is null ? Guid.Empty : season.Id;
 230        }
 231
 232        /// <summary>
 233        /// Creates the name of the sort.
 234        /// </summary>
 235        /// <returns>System.String.</returns>
 236        protected override string CreateSortName()
 237        {
 0238            return (ParentIndexNumber is not null ? ParentIndexNumber.Value.ToString("000 - ", CultureInfo.InvariantCult
 0239                    + (IndexNumber is not null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : s
 240        }
 241
 242        /// <summary>
 243        /// Determines whether [contains episode number] [the specified number].
 244        /// </summary>
 245        /// <param name="number">The number.</param>
 246        /// <returns><c>true</c> if [contains episode number] [the specified number]; otherwise, <c>false</c>.</returns>
 247        public bool ContainsEpisodeNumber(int number)
 248        {
 0249            if (IndexNumber.HasValue)
 250            {
 0251                if (IndexNumberEnd.HasValue)
 252                {
 0253                    return number >= IndexNumber.Value && number <= IndexNumberEnd.Value;
 254                }
 255
 0256                return IndexNumber.Value == number;
 257            }
 258
 0259            return false;
 260        }
 261
 262        public Guid FindSeriesId()
 263        {
 0264            var series = FindParent<Series>();
 0265            return series is null ? Guid.Empty : series.Id;
 266        }
 267
 268        public override IEnumerable<Guid> GetAncestorIds()
 269        {
 0270            var list = base.GetAncestorIds().ToList();
 271
 0272            var seasonId = SeasonId;
 273
 0274            if (!seasonId.IsEmpty() && !list.Contains(seasonId))
 275            {
 0276                list.Add(seasonId);
 277            }
 278
 0279            return list;
 280        }
 281
 282        public override IEnumerable<FileSystemMetadata> GetDeletePaths()
 283        {
 0284            return new[]
 0285            {
 0286                new FileSystemMetadata
 0287                {
 0288                    FullName = Path,
 0289                    IsDirectory = IsFolder
 0290                }
 0291            }.Concat(GetLocalMetadataFilesToDelete());
 292        }
 293
 294        public override UnratedItem GetBlockUnratedType()
 295        {
 0296            return UnratedItem.Series;
 297        }
 298
 299        public EpisodeInfo GetLookupInfo()
 300        {
 0301            var id = GetItemLookupInfo<EpisodeInfo>();
 302
 0303            var series = Series;
 304
 0305            if (series is not null)
 306            {
 0307                id.SeriesProviderIds = series.ProviderIds;
 0308                id.SeriesDisplayOrder = series.DisplayOrder;
 309            }
 310
 0311            if (Season is not null)
 312            {
 0313                id.SeasonProviderIds = Season.ProviderIds;
 314            }
 315
 0316            id.IsMissingEpisode = IsMissingEpisode;
 0317            id.IndexNumberEnd = IndexNumberEnd;
 318
 0319            return id;
 320        }
 321
 322        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 323        {
 0324            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 325
 0326            if (!IsLocked)
 327            {
 0328                if (SourceType == SourceType.Library || SourceType == SourceType.LiveTV)
 329                {
 0330                    var libraryOptions = LibraryManager.GetLibraryOptions(this);
 0331                    if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(Container, "mp4", StringComparison.Or
 332                    {
 333                        try
 334                        {
 0335                            var mediaInfo = MediaEncoder.GetMediaInfo(
 0336                                new MediaInfoRequest
 0337                                {
 0338                                    MediaSource = GetMediaSources(false)[0],
 0339                                    MediaType = DlnaProfileType.Video
 0340                                },
 0341                                CancellationToken.None).GetAwaiter().GetResult();
 0342                            if (mediaInfo.ParentIndexNumber > 0)
 343                            {
 0344                                ParentIndexNumber = mediaInfo.ParentIndexNumber;
 345                            }
 346
 0347                            if (mediaInfo.IndexNumber > 0)
 348                            {
 0349                                IndexNumber = mediaInfo.IndexNumber;
 350                            }
 351
 0352                            if (!string.IsNullOrEmpty(mediaInfo.ShowName))
 353                            {
 0354                                SeriesName = mediaInfo.ShowName;
 355                            }
 0356                        }
 0357                        catch (Exception ex)
 358                        {
 0359                            Logger.LogError(ex, "Error reading the episode information with ffprobe. Episode: {EpisodeIn
 0360                        }
 361                    }
 362
 363                    try
 364                    {
 0365                        if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata))
 366                        {
 0367                            hasChanges = true;
 368                        }
 0369                    }
 0370                    catch (Exception ex)
 371                    {
 0372                        Logger.LogError(ex, "Error in FillMissingEpisodeNumbersFromPath. Episode: {Episode}", Path ?? Na
 0373                    }
 374                }
 375            }
 376
 0377            return hasChanges;
 378        }
 379    }
 380}