< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.Video
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/Video.cs
Line coverage
13%
Covered lines: 20
Uncovered lines: 133
Coverable lines: 153
Total lines: 566
Line coverage: 13%
Branch coverage
14%
Covered branches: 11
Total branches: 78
Branch coverage: 14.1%
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/Video.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.Enums;
 13using Jellyfin.Extensions;
 14using MediaBrowser.Controller.Library;
 15using MediaBrowser.Controller.LiveTv;
 16using MediaBrowser.Controller.Persistence;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Model.Dto;
 19using MediaBrowser.Model.Entities;
 20using MediaBrowser.Model.IO;
 21using MediaBrowser.Model.MediaInfo;
 22
 23namespace MediaBrowser.Controller.Entities
 24{
 25    /// <summary>
 26    /// Class Video.
 27    /// </summary>
 28    public class Video : BaseItem,
 29        IHasAspectRatio,
 30        ISupportsPlaceHolders,
 31        IHasMediaSources
 32    {
 42633        public Video()
 34        {
 42635            AdditionalParts = Array.Empty<string>();
 42636            LocalAlternateVersions = Array.Empty<string>();
 42637            SubtitleFiles = Array.Empty<string>();
 42638            AudioFiles = Array.Empty<string>();
 42639            LinkedAlternateVersions = Array.Empty<LinkedChild>();
 42640        }
 41
 42        [JsonIgnore]
 43        public string PrimaryVersionId { get; set; }
 44
 45        public string[] AdditionalParts { get; set; }
 46
 47        public string[] LocalAlternateVersions { get; set; }
 48
 49        public LinkedChild[] LinkedAlternateVersions { get; set; }
 50
 51        [JsonIgnore]
 052        public override bool SupportsPlayedStatus => true;
 53
 54        [JsonIgnore]
 055        public override bool SupportsPeople => true;
 56
 57        [JsonIgnore]
 058        public override bool SupportsInheritedParentImages => true;
 59
 60        [JsonIgnore]
 61        public override bool SupportsPositionTicksResume
 62        {
 63            get
 64            {
 065                var extraType = ExtraType;
 066                if (extraType.HasValue)
 67                {
 068                    if (extraType.Value == Model.Entities.ExtraType.Sample)
 69                    {
 070                        return false;
 71                    }
 72
 073                    if (extraType.Value == Model.Entities.ExtraType.ThemeVideo)
 74                    {
 075                        return false;
 76                    }
 77
 078                    if (extraType.Value == Model.Entities.ExtraType.Trailer)
 79                    {
 080                        return false;
 81                    }
 82                }
 83
 084                return true;
 85            }
 86        }
 87
 88        [JsonIgnore]
 089        public override bool SupportsThemeMedia => true;
 90
 91        /// <summary>
 92        /// Gets or sets the timestamp.
 93        /// </summary>
 94        /// <value>The timestamp.</value>
 95        public TransportStreamTimestamp? Timestamp { get; set; }
 96
 97        /// <summary>
 98        /// Gets or sets the subtitle paths.
 99        /// </summary>
 100        /// <value>The subtitle paths.</value>
 101        public string[] SubtitleFiles { get; set; }
 102
 103        /// <summary>
 104        /// Gets or sets the audio paths.
 105        /// </summary>
 106        /// <value>The audio paths.</value>
 107        public string[] AudioFiles { get; set; }
 108
 109        /// <summary>
 110        /// Gets or sets a value indicating whether this instance has subtitles.
 111        /// </summary>
 112        /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
 113        public bool HasSubtitles { get; set; }
 114
 115        public bool IsPlaceHolder { get; set; }
 116
 117        /// <summary>
 118        /// Gets or sets the default index of the video stream.
 119        /// </summary>
 120        /// <value>The default index of the video stream.</value>
 121        public int? DefaultVideoStreamIndex { get; set; }
 122
 123        /// <summary>
 124        /// Gets or sets the type of the video.
 125        /// </summary>
 126        /// <value>The type of the video.</value>
 127        public VideoType VideoType { get; set; }
 128
 129        /// <summary>
 130        /// Gets or sets the type of the iso.
 131        /// </summary>
 132        /// <value>The type of the iso.</value>
 133        public IsoType? IsoType { get; set; }
 134
 135        /// <summary>
 136        /// Gets or sets the video3 D format.
 137        /// </summary>
 138        /// <value>The video3 D format.</value>
 139        public Video3DFormat? Video3DFormat { get; set; }
 140
 141        /// <summary>
 142        /// Gets or sets the aspect ratio.
 143        /// </summary>
 144        /// <value>The aspect ratio.</value>
 145        public string AspectRatio { get; set; }
 146
 147        [JsonIgnore]
 0148        public override bool SupportsAddingToPlaylist => true;
 149
 150        [JsonIgnore]
 151        public int MediaSourceCount
 152        {
 153            get
 154            {
 0155                return GetMediaSourceCount();
 156            }
 157        }
 158
 159        [JsonIgnore]
 46160        public bool IsStacked => AdditionalParts.Length > 0;
 161
 162        [JsonIgnore]
 4163        public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
 164
 165        public static IRecordingsManager RecordingsManager { get; set; }
 166
 167        [JsonIgnore]
 168        public override SourceType SourceType
 169        {
 170            get
 171            {
 28172                if (IsActiveRecording())
 173                {
 0174                    return SourceType.LiveTV;
 175                }
 176
 28177                return base.SourceType;
 178            }
 179        }
 180
 181        [JsonIgnore]
 182        public bool IsCompleteMedia
 183        {
 184            get
 185            {
 0186                if (SourceType == SourceType.Channel)
 187                {
 0188                    return !Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase);
 189                }
 190
 0191                return !IsActiveRecording();
 192            }
 193        }
 194
 195        [JsonIgnore]
 0196        protected virtual bool EnableDefaultVideoUserDataKeys => true;
 197
 198        [JsonIgnore]
 199        public override string ContainingFolderPath
 200        {
 201            get
 202            {
 46203                if (IsStacked)
 204                {
 0205                    return System.IO.Path.GetDirectoryName(Path);
 206                }
 207
 46208                if (!IsPlaceHolder)
 209                {
 46210                    if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
 211                    {
 1212                        return Path;
 213                    }
 214                }
 215
 45216                return base.ContainingFolderPath;
 217            }
 218        }
 219
 220        [JsonIgnore]
 221        public override string FileNameWithoutExtension
 222        {
 223            get
 224            {
 31225                if (IsFileProtocol)
 226                {
 31227                    if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
 228                    {
 0229                        return System.IO.Path.GetFileName(Path);
 230                    }
 231
 31232                    return System.IO.Path.GetFileNameWithoutExtension(Path);
 233                }
 234
 0235                return null;
 236            }
 237        }
 238
 239        /// <summary>
 240        /// Gets a value indicating whether [is3 D].
 241        /// </summary>
 242        /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
 243        [JsonIgnore]
 0244        public bool Is3D => Video3DFormat.HasValue;
 245
 246        /// <summary>
 247        /// Gets the type of the media.
 248        /// </summary>
 249        /// <value>The type of the media.</value>
 250        [JsonIgnore]
 0251        public override MediaType MediaType => MediaType.Video;
 252
 253        private int GetMediaSourceCount(HashSet<Guid> callstack = null)
 254        {
 0255            callstack ??= new();
 0256            if (!string.IsNullOrEmpty(PrimaryVersionId))
 257            {
 0258                var item = LibraryManager.GetItemById(PrimaryVersionId);
 0259                if (item is Video video)
 260                {
 0261                    if (callstack.Contains(video.Id))
 262                    {
 0263                        return video.LinkedAlternateVersions.Length + video.LocalAlternateVersions.Length + 1;
 264                    }
 265
 0266                    callstack.Add(video.Id);
 0267                    return video.GetMediaSourceCount(callstack);
 268                }
 269            }
 270
 0271            return LinkedAlternateVersions.Length + LocalAlternateVersions.Length + 1;
 272        }
 273
 274        public override List<string> GetUserDataKeys()
 275        {
 0276            var list = base.GetUserDataKeys();
 277
 0278            if (EnableDefaultVideoUserDataKeys)
 279            {
 0280                if (ExtraType.HasValue)
 281                {
 0282                    var key = this.GetProviderId(MetadataProvider.Tmdb);
 0283                    if (!string.IsNullOrEmpty(key))
 284                    {
 0285                        list.Insert(0, GetUserDataKey(key));
 286                    }
 287
 0288                    key = this.GetProviderId(MetadataProvider.Imdb);
 0289                    if (!string.IsNullOrEmpty(key))
 290                    {
 0291                        list.Insert(0, GetUserDataKey(key));
 292                    }
 293                }
 294                else
 295                {
 0296                    var key = this.GetProviderId(MetadataProvider.Imdb);
 0297                    if (!string.IsNullOrEmpty(key))
 298                    {
 0299                        list.Insert(0, key);
 300                    }
 301
 0302                    key = this.GetProviderId(MetadataProvider.Tmdb);
 0303                    if (!string.IsNullOrEmpty(key))
 304                    {
 0305                        list.Insert(0, key);
 306                    }
 307                }
 308            }
 309
 0310            return list;
 311        }
 312
 313        public void SetPrimaryVersionId(string id)
 314        {
 0315            if (string.IsNullOrEmpty(id))
 316            {
 0317                PrimaryVersionId = null;
 318            }
 319            else
 320            {
 0321                PrimaryVersionId = id;
 322            }
 323
 0324            PresentationUniqueKey = CreatePresentationUniqueKey();
 0325        }
 326
 327        public override string CreatePresentationUniqueKey()
 328        {
 0329            if (!string.IsNullOrEmpty(PrimaryVersionId))
 330            {
 0331                return PrimaryVersionId;
 332            }
 333
 0334            return base.CreatePresentationUniqueKey();
 335        }
 336
 337        public override bool CanDownload()
 338        {
 0339            if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
 340            {
 0341                return false;
 342            }
 343
 0344            return IsFileProtocol;
 345        }
 346
 347        protected override bool IsActiveRecording()
 348        {
 28349            return RecordingsManager.GetActiveRecordingInfo(Path) is not null;
 350        }
 351
 352        public override bool CanDelete()
 353        {
 0354            if (IsActiveRecording())
 355            {
 0356                return false;
 357            }
 358
 0359            return base.CanDelete();
 360        }
 361
 362        public IEnumerable<Guid> GetAdditionalPartIds()
 363        {
 0364            return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
 365        }
 366
 367        public IEnumerable<Guid> GetLocalAlternateVersionIds()
 368        {
 0369            return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
 370        }
 371
 372        private string GetUserDataKey(string providerId)
 373        {
 0374            var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
 375
 376            // Make sure different trailers have their own data.
 0377            if (RunTimeTicks.HasValue)
 378            {
 0379                key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
 380            }
 381
 0382            return key;
 383        }
 384
 385        public IEnumerable<Video> GetLinkedAlternateVersions()
 386        {
 0387            return LinkedAlternateVersions
 0388                .Select(GetLinkedChild)
 0389                .Where(i => i is not null)
 0390                .OfType<Video>()
 0391                .OrderBy(i => i.SortName);
 392        }
 393
 394        /// <summary>
 395        /// Gets the additional parts.
 396        /// </summary>
 397        /// <returns>IEnumerable{Video}.</returns>
 398        public IOrderedEnumerable<Video> GetAdditionalParts()
 399        {
 0400            return GetAdditionalPartIds()
 0401                .Select(i => LibraryManager.GetItemById(i))
 0402                .Where(i => i is not null)
 0403                .OfType<Video>()
 0404                .OrderBy(i => i.SortName);
 405        }
 406
 407        internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
 408        {
 0409            var updateType = base.UpdateFromResolvedItem(newItem);
 410
 0411            if (newItem is Video newVideo)
 412            {
 0413                if (!AdditionalParts.SequenceEqual(newVideo.AdditionalParts, StringComparer.Ordinal))
 414                {
 0415                    AdditionalParts = newVideo.AdditionalParts;
 0416                    updateType |= ItemUpdateType.MetadataImport;
 417                }
 418
 0419                if (!LocalAlternateVersions.SequenceEqual(newVideo.LocalAlternateVersions, StringComparer.Ordinal))
 420                {
 0421                    LocalAlternateVersions = newVideo.LocalAlternateVersions;
 0422                    updateType |= ItemUpdateType.MetadataImport;
 423                }
 424
 0425                if (VideoType != newVideo.VideoType)
 426                {
 0427                    VideoType = newVideo.VideoType;
 0428                    updateType |= ItemUpdateType.MetadataImport;
 429                }
 430            }
 431
 0432            return updateType;
 433        }
 434
 435        protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystem
 436        {
 437            var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwa
 438
 439            if (IsStacked)
 440            {
 441                var tasks = AdditionalParts
 442                    .Select(i => RefreshMetadataForOwnedVideo(options, true, i, cancellationToken));
 443
 444                await Task.WhenAll(tasks).ConfigureAwait(false);
 445            }
 446
 447            // Must have a parent to have additional parts or alternate versions
 448            // In other words, it must be part of the Parent/Child tree
 449            // The additional parts won't have additional parts themselves
 450            if (IsFileProtocol && SupportsOwnedItems)
 451            {
 452                if (!IsStacked)
 453                {
 454                    RefreshLinkedAlternateVersions();
 455
 456                    var tasks = LocalAlternateVersions
 457                        .Select(i => RefreshMetadataForOwnedVideo(options, false, i, cancellationToken));
 458
 459                    await Task.WhenAll(tasks).ConfigureAwait(false);
 460                }
 461            }
 462
 463            return hasChanges;
 464        }
 465
 466        private void RefreshLinkedAlternateVersions()
 467        {
 0468            foreach (var child in LinkedAlternateVersions)
 469            {
 470                // Reset the cached value
 0471                if (child.ItemId.IsNullOrEmpty())
 472                {
 0473                    child.ItemId = null;
 474                }
 475            }
 0476        }
 477
 478        /// <inheritdoc />
 479        public override async Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationTo
 480        {
 481            await base.UpdateToRepositoryAsync(updateReason, cancellationToken).ConfigureAwait(false);
 482
 483            var localAlternates = GetLocalAlternateVersionIds()
 484                .Select(i => LibraryManager.GetItemById(i))
 485                .Where(i => i is not null);
 486
 487            foreach (var item in localAlternates)
 488            {
 489                item.ImageInfos = ImageInfos;
 490                item.Overview = Overview;
 491                item.ProductionYear = ProductionYear;
 492                item.PremiereDate = PremiereDate;
 493                item.CommunityRating = CommunityRating;
 494                item.OfficialRating = OfficialRating;
 495                item.Genres = Genres;
 496                item.ProviderIds = ProviderIds;
 497
 498                await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataDownload, cancellationToken).ConfigureAwait(fa
 499            }
 500        }
 501
 502        public override IEnumerable<FileSystemMetadata> GetDeletePaths()
 503        {
 0504            if (!IsInMixedFolder)
 505            {
 0506                return new[]
 0507                {
 0508                    new FileSystemMetadata
 0509                    {
 0510                        FullName = ContainingFolderPath,
 0511                        IsDirectory = true
 0512                    }
 0513                };
 514            }
 515
 0516            return base.GetDeletePaths();
 517        }
 518
 519        public virtual MediaStream GetDefaultVideoStream()
 520        {
 0521            if (!DefaultVideoStreamIndex.HasValue)
 522            {
 0523                return null;
 524            }
 525
 0526            return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
 0527            {
 0528                ItemId = Id,
 0529                Index = DefaultVideoStreamIndex.Value
 0530            }).FirstOrDefault();
 531        }
 532
 533        protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
 534        {
 0535            var list = new List<(BaseItem, MediaSourceType)>
 0536            {
 0537                (this, MediaSourceType.Default)
 0538            };
 539
 0540            list.AddRange(GetLinkedAlternateVersions().Select(i => ((BaseItem)i, MediaSourceType.Grouping)));
 541
 0542            if (!string.IsNullOrEmpty(PrimaryVersionId))
 543            {
 0544                if (LibraryManager.GetItemById(PrimaryVersionId) is Video primary)
 545                {
 0546                    var existingIds = list.Select(i => i.Item1.Id).ToList();
 0547                    list.Add((primary, MediaSourceType.Grouping));
 0548                    list.AddRange(primary.GetLinkedAlternateVersions().Where(i => !existingIds.Contains(i.Id)).Select(i 
 549                }
 550            }
 551
 0552            var localAlternates = list
 0553                .SelectMany(i =>
 0554                {
 0555                    return i.Item1 is Video video ? video.GetLocalAlternateVersionIds() : Enumerable.Empty<Guid>();
 0556                })
 0557                .Select(LibraryManager.GetItemById)
 0558                .Where(i => i is not null)
 0559                .ToList();
 560
 0561            list.AddRange(localAlternates.Select(i => (i, MediaSourceType.Default)));
 562
 0563            return list;
 564        }
 565    }
 566}