using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities { /// /// Class Video /// public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio { public bool IsMultiPart { get; set; } public List AdditionalPartIds { get; set; } public Video() { PlayableStreamFileNames = new List(); AdditionalPartIds = new List(); } /// /// Gets or sets a value indicating whether this instance has subtitles. /// /// true if this instance has subtitles; otherwise, false. public bool HasSubtitles { get; set; } /// /// Gets or sets the video bit rate. /// /// The video bit rate. public int? VideoBitRate { get; set; } /// /// Gets or sets the default index of the video stream. /// /// The default index of the video stream. public int? DefaultVideoStreamIndex { get; set; } /// /// Gets or sets the type of the video. /// /// The type of the video. public VideoType VideoType { get; set; } /// /// Gets or sets the type of the iso. /// /// The type of the iso. public IsoType? IsoType { get; set; } /// /// Gets or sets the video3 D format. /// /// The video3 D format. public Video3DFormat? Video3DFormat { get; set; } /// /// If the video is a folder-rip, this will hold the file list for the largest playlist /// public List PlayableStreamFileNames { get; set; } /// /// Gets the playable stream files. /// /// List{System.String}. public List GetPlayableStreamFiles() { return GetPlayableStreamFiles(Path); } /// /// Gets or sets the aspect ratio. /// /// The aspect ratio. public string AspectRatio { get; set; } /// /// Should be overridden to return the proper folder where metadata lives /// /// The meta location. [IgnoreDataMember] public override string MetaLocation { get { return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path; } } /// /// Needed because the resolver stops at the movie folder and we find the video inside. /// /// true if [use parent path to create resolve args]; otherwise, false. protected override bool UseParentPathToCreateResolveArgs { get { if (IsInMixedFolder) { return false; } return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart; } } public string MainFeaturePlaylistName { get; set; } /// /// Gets the playable stream files. /// /// The root path. /// List{System.String}. public List GetPlayableStreamFiles(string rootPath) { if (PlayableStreamFileNames == null) { return null; } var allFiles = Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories).ToList(); return PlayableStreamFileNames.Select(name => allFiles.FirstOrDefault(f => string.Equals(System.IO.Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase))) .Where(f => !string.IsNullOrEmpty(f)) .ToList(); } /// /// Gets a value indicating whether [is3 D]. /// /// true if [is3 D]; otherwise, false. [IgnoreDataMember] public bool Is3D { get { return Video3DFormat.HasValue; } } public bool IsHD { get; set; } /// /// Gets the type of the media. /// /// The type of the media. public override string MediaType { get { return Model.Entities.MediaType.Video; } } /// /// Overrides the base implementation to refresh metadata for local trailers /// /// The cancellation token. /// if set to true [is new item]. /// if set to true [force]. /// if set to true [allow slow providers]. /// The reset resolve args. /// true if a provider reports we changed public override async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) { // Kick off a task to refresh the main item var result = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs).ConfigureAwait(false); var additionalPartsChanged = false; // Must have a parent to have additional parts // In other words, it must be part of the Parent/Child tree // The additional parts won't have additional parts themselves if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null) { try { additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); } catch (IOException ex) { Logger.ErrorException("Error loading additional parts for {0}.", ex, Name); } } return additionalPartsChanged || result; } /// /// Refreshes the additional parts. /// /// The cancellation token. /// if set to true [force save]. /// if set to true [force refresh]. /// if set to true [allow slow providers]. /// Task{System.Boolean}. private async Task RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) { var newItems = LoadAdditionalParts().ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds); var tasks = newItems.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); AdditionalPartIds = newItemIds; return itemsChanged || results.Contains(true); } /// /// Loads the additional parts. /// /// IEnumerable{Video}. private IEnumerable