diff options
Diffstat (limited to 'MediaBrowser.Controller')
35 files changed, 1263 insertions, 994 deletions
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 97f40b537..abfdb41d8 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,6 +1,5 @@ -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Jellyfin.Extensions; @@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager { private readonly IServerConfigurationManager _serverConfigurationManager; - private int _metadataRefreshConcurrency = 0; + private int _metadataRefreshConcurrency; /// <summary> /// Initializes a new instance of the <see cref="BaseItemManager"/> class. @@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager /// Called when the configuration is updated. /// It will refresh the metadata throttler if the relevant config changed. /// </summary> - private void OnConfigurationUpdated(object sender, EventArgs e) + private void OnConfigurationUpdated(object? sender, EventArgs e) { int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency(); if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency) @@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager /// <summary> /// Creates the metadata refresh throttler. /// </summary> + [MemberNotNull(nameof(MetadataRefreshThrottler))] private void SetupMetadataThrottler() { MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency); diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index b2b36c040..e18994214 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; @@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager /// <returns><c>true</c> if image fetcher is enabled, else false.</returns> bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); } -}
\ No newline at end of file +} diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs index 7a0addd9f..ca7721991 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs @@ -1,7 +1,6 @@ -#nullable disable - -#pragma warning disable CA1002, CA2227, CS1591 +#pragma warning disable CS1591 +using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Channels @@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels { public ChannelItemResult() { - Items = new List<ChannelItemInfo>(); + Items = Array.Empty<ChannelItemInfo>(); } - public List<ChannelItemInfo> Items { get; set; } + public IReadOnlyList<ChannelItemInfo> Items { get; set; } public int? TotalRecordCount { get; set; } } diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index 76ad335c5..30f5f4efa 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CA2227, CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs index 8155cf3db..e538fa4b3 100644 --- a/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs +++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index 49cc39f04..b8c33ee5a 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections /// <summary> /// Occurs when [collection created]. /// </summary> - event EventHandler<CollectionCreatedEventArgs> CollectionCreated; + event EventHandler<CollectionCreatedEventArgs>? CollectionCreated; /// <summary> /// Occurs when [items added to collection]. /// </summary> - event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection; + event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection; /// <summary> /// Occurs when [items removed from collection]. /// </summary> - event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection; + event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection; /// <summary> /// Creates the collection. diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 44e2c45dd..43ad04dba 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index b51dc255c..a64919700 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System.Collections.Generic; @@ -22,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna /// </summary> /// <param name="headers">The headers.</param> /// <returns>DeviceProfile.</returns> - DeviceProfile GetProfile(IHeaderDictionary headers); + DeviceProfile? GetProfile(IHeaderDictionary headers); /// <summary> /// Gets the default profile. @@ -53,14 +51,14 @@ namespace MediaBrowser.Controller.Dlna /// </summary> /// <param name="id">The identifier.</param> /// <returns>DeviceProfile.</returns> - DeviceProfile GetProfile(string id); + DeviceProfile? GetProfile(string id); /// <summary> /// Gets the profile. /// </summary> /// <param name="deviceInfo">The device information.</param> /// <returns>DeviceProfile.</returns> - DeviceProfile GetProfile(DeviceIdentification deviceInfo); + DeviceProfile? GetProfile(DeviceIdentification deviceInfo); /// <summary> /// Gets the server description XML. diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 7bf1219ec..536668e50 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Controller.Entities.Audio public override bool SupportsPlayedStatus => true; [JsonIgnore] - public override bool SupportsPeople => false; + public override bool SupportsPeople => true; [JsonIgnore] public override bool SupportsAddingToPlaylist => true; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 23b97f70c..3b182f7c9 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1401 using System; using System.Collections.Generic; @@ -41,6 +41,22 @@ namespace MediaBrowser.Controller.Entities public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem> { /// <summary> + /// The trailer folder name. + /// </summary> + public const string TrailerFolderName = "trailers"; + public const string ThemeSongsFolderName = "theme-music"; + public const string ThemeSongFilename = "theme"; + public const string ThemeVideosFolderName = "backdrops"; + public const string ExtrasFolderName = "extras"; + public const string BehindTheScenesFolderName = "behind the scenes"; + public const string DeletedScenesFolderName = "deleted scenes"; + public const string InterviewFolderName = "interviews"; + public const string SceneFolderName = "scenes"; + public const string SampleFolderName = "samples"; + public const string ShortsFolderName = "shorts"; + public const string FeaturettesFolderName = "featurettes"; + + /// <summary> /// The supported image extensions. /// </summary> public static readonly string[] SupportedImageExtensions @@ -61,38 +77,21 @@ namespace MediaBrowser.Controller.Entities ".ttml" }; - protected BaseItem() - { - Tags = Array.Empty<string>(); - Genres = Array.Empty<string>(); - Studios = Array.Empty<string>(); - ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - LockedFields = Array.Empty<MetadataField>(); - ImageInfos = Array.Empty<ItemImageInfo>(); - ProductionLocations = Array.Empty<string>(); - RemoteTrailers = Array.Empty<MediaUrl>(); - ExtraIds = Array.Empty<Guid>(); - } - - public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; - public static char SlugChar = '-'; - /// <summary> - /// The trailer folder name. + /// Extra types that should be counted and displayed as "Special Features" in the UI. /// </summary> - public const string TrailerFolderName = "trailers"; - public const string ThemeSongsFolderName = "theme-music"; - public const string ThemeSongFilename = "theme"; - public const string ThemeVideosFolderName = "backdrops"; - public const string ExtrasFolderName = "extras"; - public const string BehindTheScenesFolderName = "behind the scenes"; - public const string DeletedScenesFolderName = "deleted scenes"; - public const string InterviewFolderName = "interviews"; - public const string SceneFolderName = "scenes"; - public const string SampleFolderName = "samples"; - public const string ShortsFolderName = "shorts"; - public const string FeaturettesFolderName = "featurettes"; + public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType> + { + Model.Entities.ExtraType.Unknown, + Model.Entities.ExtraType.BehindTheScenes, + Model.Entities.ExtraType.Clip, + Model.Entities.ExtraType.DeletedScene, + Model.Entities.ExtraType.Interview, + Model.Entities.ExtraType.Sample, + Model.Entities.ExtraType.Scene + }; + public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; public static readonly string[] AllExtrasTypesFolderNames = { ExtrasFolderName, @@ -105,6 +104,29 @@ namespace MediaBrowser.Controller.Entities FeaturettesFolderName }; + private string _sortName; + private Guid[] _themeSongIds; + private Guid[] _themeVideoIds; + + private string _forcedSortName; + + private string _name; + + public const char SlugChar = '-'; + + protected BaseItem() + { + Tags = Array.Empty<string>(); + Genres = Array.Empty<string>(); + Studios = Array.Empty<string>(); + ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + LockedFields = Array.Empty<MetadataField>(); + ImageInfos = Array.Empty<ItemImageInfo>(); + ProductionLocations = Array.Empty<string>(); + RemoteTrailers = Array.Empty<MediaUrl>(); + ExtraIds = Array.Empty<Guid>(); + } + [JsonIgnore] public Guid[] ThemeSongIds { @@ -194,8 +216,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsRemoteImageDownloading => true; - private string _name; - /// <summary> /// Gets or sets the name. /// </summary> @@ -328,12 +348,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool IsHidden => false; - public BaseItem GetOwner() - { - var ownerId = OwnerId; - return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId); - } - /// <summary> /// Gets the type of the location. /// </summary> @@ -379,13 +393,6 @@ namespace MediaBrowser.Controller.Entities } } - public bool IsPathProtocol(MediaProtocol protocol) - { - var current = PathProtocol; - - return current.HasValue && current.Value == protocol; - } - [JsonIgnore] public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File); @@ -423,35 +430,17 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool EnableAlphaNumericSorting => true; - private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1) - { - var list = new List<Tuple<StringBuilder, bool>>(); - - int thisMarker = 0; - - while (thisMarker < s1.Length) - { - char thisCh = s1[thisMarker]; + public virtual bool IsHD => Height >= 720; - var thisChunk = new StringBuilder(); - bool isNumeric = char.IsDigit(thisCh); + public bool IsShortcut { get; set; } - while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) - { - thisChunk.Append(thisCh); - thisMarker++; + public string ShortcutPath { get; set; } - if (thisMarker < s1.Length) - { - thisCh = s1[thisMarker]; - } - } + public int Width { get; set; } - list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric)); - } + public int Height { get; set; } - return list; - } + public Guid[] ExtraIds { get; set; } /// <summary> /// Gets the primary image path. @@ -463,72 +452,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); - public virtual bool CanDelete() - { - if (SourceType == SourceType.Channel) - { - return ChannelManager.CanDelete(this); - } - - return IsFileProtocol; - } - - public virtual bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) - { - if (user.HasPermission(PermissionKind.EnableContentDeletion)) - { - return true; - } - - var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders); - - if (SourceType == SourceType.Channel) - { - return allowed.Contains(ChannelId); - } - else - { - var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders); - - foreach (var folder in collectionFolders) - { - if (allowed.Contains(folder.Id)) - { - return true; - } - } - } - - return false; - } - - public bool CanDelete(User user, List<Folder> allCollectionFolders) - { - return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders); - } - - public bool CanDelete(User user) - { - var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); - - return CanDelete(user, allCollectionFolders); - } - - public virtual bool CanDownload() - { - return false; - } - - public virtual bool IsAuthorizedToDownload(User user) - { - return user.HasPermission(PermissionKind.EnableContentDownloading); - } - - public bool CanDownload(User user) - { - return CanDownload() && IsAuthorizedToDownload(user); - } - /// <summary> /// Gets or sets the date created. /// </summary> @@ -548,38 +471,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public DateTime DateLastRefreshed { get; set; } - /// <summary> - /// Gets or sets the logger. - /// </summary> - public static ILogger<BaseItem> Logger { get; set; } - - public static ILibraryManager LibraryManager { get; set; } - - public static IServerConfigurationManager ConfigurationManager { get; set; } - - public static IProviderManager ProviderManager { get; set; } - - public static ILocalizationManager LocalizationManager { get; set; } - - public static IItemRepository ItemRepository { get; set; } - - public static IFileSystem FileSystem { get; set; } - - public static IUserDataManager UserDataManager { get; set; } - - public static IChannelManager ChannelManager { get; set; } - - public static IMediaSourceManager MediaSourceManager { get; set; } - - /// <summary> - /// Returns a <see cref="string" /> that represents this instance. - /// </summary> - /// <returns>A <see cref="string" /> that represents this instance.</returns> - public override string ToString() - { - return Name; - } - [JsonIgnore] public bool IsLocked { get; set; } @@ -611,7 +502,45 @@ namespace MediaBrowser.Controller.Entities } } - private string _forcedSortName; + [JsonIgnore] + public bool EnableMediaSourceDisplay + { + get + { + if (SourceType == SourceType.Channel) + { + return ChannelManager.EnableMediaSourceDisplay(this); + } + + return true; + } + } + + [JsonIgnore] + public Guid ParentId { get; set; } + + /// <summary> + /// Gets or sets the logger. + /// </summary> + public static ILogger<BaseItem> Logger { get; set; } + + public static ILibraryManager LibraryManager { get; set; } + + public static IServerConfigurationManager ConfigurationManager { get; set; } + + public static IProviderManager ProviderManager { get; set; } + + public static ILocalizationManager LocalizationManager { get; set; } + + public static IItemRepository ItemRepository { get; set; } + + public static IFileSystem FileSystem { get; set; } + + public static IUserDataManager UserDataManager { get; set; } + + public static IChannelManager ChannelManager { get; set; } + + public static IMediaSourceManager MediaSourceManager { get; set; } /// <summary> /// Gets or sets the name of the forced sort. @@ -628,10 +557,6 @@ namespace MediaBrowser.Controller.Entities } } - private string _sortName; - private Guid[] _themeSongIds; - private Guid[] _themeVideoIds; - /// <summary> /// Gets or sets the name of the sort. /// </summary> @@ -660,164 +585,6 @@ namespace MediaBrowser.Controller.Entities set => _sortName = value; } - public string GetInternalMetadataPath() - { - var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; - - return GetInternalMetadataPath(basePath); - } - - protected virtual string GetInternalMetadataPath(string basePath) - { - if (SourceType == SourceType.Channel) - { - return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); - } - - ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture); - - return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); - } - - /// <summary> - /// Creates the name of the sort. - /// </summary> - /// <returns>System.String.</returns> - protected virtual string CreateSortName() - { - if (Name == null) - { - return null; // some items may not have name filled in properly - } - - if (!EnableAlphaNumericSorting) - { - return Name.TrimStart(); - } - - var sortable = Name.Trim().ToLowerInvariant(); - - foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) - { - sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal); - } - - foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) - { - sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal); - } - - foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) - { - // Remove from beginning if a space follows - if (sortable.StartsWith(search + " ", StringComparison.Ordinal)) - { - sortable = sortable.Remove(0, search.Length + 1); - } - - // Remove from middle if surrounded by spaces - sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal); - - // Remove from end if followed by a space - if (sortable.EndsWith(" " + search, StringComparison.Ordinal)) - { - sortable = sortable.Remove(sortable.Length - (search.Length + 1)); - } - } - - return ModifySortChunks(sortable); - } - - private string ModifySortChunks(string name) - { - var chunks = GetSortChunks(name); - - var builder = new StringBuilder(); - - foreach (var chunk in chunks) - { - var chunkBuilder = chunk.Item1; - - // This chunk is numeric - if (chunk.Item2) - { - while (chunkBuilder.Length < 10) - { - chunkBuilder.Insert(0, '0'); - } - } - - builder.Append(chunkBuilder); - } - - // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); - return builder.ToString().RemoveDiacritics(); - } - - [JsonIgnore] - public bool EnableMediaSourceDisplay - { - get - { - if (SourceType == SourceType.Channel) - { - return ChannelManager.EnableMediaSourceDisplay(this); - } - - return true; - } - } - - [JsonIgnore] - public Guid ParentId { get; set; } - - public void SetParent(Folder parent) - { - ParentId = parent == null ? Guid.Empty : parent.Id; - } - - public BaseItem GetParent() - { - var parentId = ParentId; - if (!parentId.Equals(Guid.Empty)) - { - return LibraryManager.GetItemById(parentId); - } - - return null; - } - - public IEnumerable<BaseItem> GetParents() - { - var parent = GetParent(); - - while (parent != null) - { - yield return parent; - - parent = parent.GetParent(); - } - } - - /// <summary> - /// Finds a parent of a given type. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <returns>``0.</returns> - public T FindParent<T>() - where T : Folder - { - foreach (var parent in GetParents()) - { - if (parent is T item) - { - return item; - } - } - - return null; - } - [JsonIgnore] public virtual Guid DisplayParentId { @@ -1001,6 +768,349 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Gets or sets the provider ids. + /// </summary> + /// <value>The provider ids.</value> + [JsonIgnore] + public Dictionary<string, string> ProviderIds { get; set; } + + [JsonIgnore] + public virtual Folder LatestItemsIndexContainer => null; + + [JsonIgnore] + public string PresentationUniqueKey { get; set; } + + [JsonIgnore] + public virtual bool EnableRememberingTrackSelections => true; + + [JsonIgnore] + public virtual bool IsTopParent + { + get + { + if (this is BasePluginFolder || this is Channel) + { + return true; + } + + if (this is IHasCollectionType view) + { + if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + if (GetParent() is AggregateFolder) + { + return true; + } + + return false; + } + } + + [JsonIgnore] + public virtual bool SupportsAncestors => true; + + [JsonIgnore] + public virtual bool StopRefreshIfLocalMetadataFound => true; + + [JsonIgnore] + protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; + + [JsonIgnore] + public virtual bool SupportsPeople => false; + + [JsonIgnore] + public virtual bool SupportsThemeMedia => false; + + [JsonIgnore] + public virtual bool SupportsInheritedParentImages => false; + + /// <summary> + /// Gets a value indicating whether this instance is folder. + /// </summary> + /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> + [JsonIgnore] + public virtual bool IsFolder => false; + + [JsonIgnore] + public virtual bool IsDisplayedAsFolder => false; + + /// <summary> + /// Gets or sets the remote trailers. + /// </summary> + /// <value>The remote trailers.</value> + public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } + + public virtual bool SupportsExternalTransfer => false; + + public virtual double GetDefaultPrimaryImageAspectRatio() + { + return 0; + } + + public virtual string CreatePresentationUniqueKey() + { + return Id.ToString("N", CultureInfo.InvariantCulture); + } + + public bool IsPathProtocol(MediaProtocol protocol) + { + var current = PathProtocol; + + return current.HasValue && current.Value == protocol; + } + + private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1) + { + var list = new List<Tuple<StringBuilder, bool>>(); + + int thisMarker = 0; + + while (thisMarker < s1.Length) + { + char thisCh = s1[thisMarker]; + + var thisChunk = new StringBuilder(); + bool isNumeric = char.IsDigit(thisCh); + + while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) + { + thisChunk.Append(thisCh); + thisMarker++; + + if (thisMarker < s1.Length) + { + thisCh = s1[thisMarker]; + } + } + + list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric)); + } + + return list; + } + + public virtual bool CanDelete() + { + if (SourceType == SourceType.Channel) + { + return ChannelManager.CanDelete(this); + } + + return IsFileProtocol; + } + + public virtual bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) + { + if (user.HasPermission(PermissionKind.EnableContentDeletion)) + { + return true; + } + + var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders); + + if (SourceType == SourceType.Channel) + { + return allowed.Contains(ChannelId); + } + else + { + var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders); + + foreach (var folder in collectionFolders) + { + if (allowed.Contains(folder.Id)) + { + return true; + } + } + } + + return false; + } + + public BaseItem GetOwner() + { + var ownerId = OwnerId; + return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId); + } + + public bool CanDelete(User user, List<Folder> allCollectionFolders) + { + return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders); + } + + public bool CanDelete(User user) + { + var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); + + return CanDelete(user, allCollectionFolders); + } + + public virtual bool CanDownload() + { + return false; + } + + public virtual bool IsAuthorizedToDownload(User user) + { + return user.HasPermission(PermissionKind.EnableContentDownloading); + } + + public bool CanDownload(User user) + { + return CanDownload() && IsAuthorizedToDownload(user); + } + + /// <summary> + /// Returns a <see cref="string" /> that represents this instance. + /// </summary> + /// <returns>A <see cref="string" /> that represents this instance.</returns> + public override string ToString() + { + return Name; + } + + public string GetInternalMetadataPath() + { + var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; + + return GetInternalMetadataPath(basePath); + } + + protected virtual string GetInternalMetadataPath(string basePath) + { + if (SourceType == SourceType.Channel) + { + return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); + } + + ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture); + + return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); + } + + /// <summary> + /// Creates the name of the sort. + /// </summary> + /// <returns>System.String.</returns> + protected virtual string CreateSortName() + { + if (Name == null) + { + return null; // some items may not have name filled in properly + } + + if (!EnableAlphaNumericSorting) + { + return Name.TrimStart(); + } + + var sortable = Name.Trim().ToLowerInvariant(); + + foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) + { + sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal); + } + + foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) + { + sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal); + } + + foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) + { + // Remove from beginning if a space follows + if (sortable.StartsWith(search + " ", StringComparison.Ordinal)) + { + sortable = sortable.Remove(0, search.Length + 1); + } + + // Remove from middle if surrounded by spaces + sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal); + + // Remove from end if followed by a space + if (sortable.EndsWith(" " + search, StringComparison.Ordinal)) + { + sortable = sortable.Remove(sortable.Length - (search.Length + 1)); + } + } + + return ModifySortChunks(sortable); + } + + private string ModifySortChunks(string name) + { + var chunks = GetSortChunks(name); + + var builder = new StringBuilder(); + + foreach (var chunk in chunks) + { + var chunkBuilder = chunk.Item1; + + // This chunk is numeric + if (chunk.Item2) + { + while (chunkBuilder.Length < 10) + { + chunkBuilder.Insert(0, '0'); + } + } + + builder.Append(chunkBuilder); + } + + // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); + return builder.ToString().RemoveDiacritics(); + } + + public BaseItem GetParent() + { + var parentId = ParentId; + if (!parentId.Equals(Guid.Empty)) + { + return LibraryManager.GetItemById(parentId); + } + + return null; + } + + public IEnumerable<BaseItem> GetParents() + { + var parent = GetParent(); + + while (parent != null) + { + yield return parent; + + parent = parent.GetParent(); + } + } + + /// <summary> + /// Finds a parent of a given type. + /// </summary> + /// <typeparam name="T">Type of parent.</typeparam> + /// <returns>``0.</returns> + public T FindParent<T>() + where T : Folder + { + foreach (var parent in GetParents()) + { + if (parent is T item) + { + return item; + } + } + + return null; + } + + /// <summary> /// Gets the play access. /// </summary> /// <param name="user">The user.</param> @@ -1405,14 +1515,46 @@ namespace MediaBrowser.Controller.Entities } } - [JsonIgnore] - protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; + protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) + { + if (!IsVisible(user)) + { + return false; + } - [JsonIgnore] - public virtual bool SupportsPeople => false; + if (GetParents().Any(i => !i.IsVisible(user))) + { + return false; + } - [JsonIgnore] - public virtual bool SupportsThemeMedia => false; + if (checkFolders) + { + var topParent = GetParents().LastOrDefault() ?? this; + + if (string.IsNullOrEmpty(topParent.Path)) + { + return true; + } + + var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList(); + + if (itemCollectionFolders.Count > 0) + { + var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList(); + if (!itemCollectionFolders.Any(userCollectionFolders.Contains)) + { + return false; + } + } + } + + return true; + } + + public void SetParent(Folder parent) + { + ParentId = parent == null ? Guid.Empty : parent.Id; + } /// <summary> /// Refreshes owned items such as trailers, theme videos, special features, etc. @@ -1609,29 +1751,6 @@ namespace MediaBrowser.Controller.Entities return themeSongsChanged; } - /// <summary> - /// Gets or sets the provider ids. - /// </summary> - /// <value>The provider ids.</value> - [JsonIgnore] - public Dictionary<string, string> ProviderIds { get; set; } - - [JsonIgnore] - public virtual Folder LatestItemsIndexContainer => null; - - public virtual double GetDefaultPrimaryImageAspectRatio() - { - return 0; - } - - public virtual string CreatePresentationUniqueKey() - { - return Id.ToString("N", CultureInfo.InvariantCulture); - } - - [JsonIgnore] - public string PresentationUniqueKey { get; set; } - public string GetPresentationUniqueKey() { return PresentationUniqueKey ?? CreatePresentationUniqueKey(); @@ -1929,58 +2048,9 @@ namespace MediaBrowser.Controller.Entities return IsVisibleStandaloneInternal(user, true); } - [JsonIgnore] - public virtual bool SupportsInheritedParentImages => false; - - protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) - { - if (!IsVisible(user)) - { - return false; - } - - if (GetParents().Any(i => !i.IsVisible(user))) - { - return false; - } - - if (checkFolders) - { - var topParent = GetParents().LastOrDefault() ?? this; - - if (string.IsNullOrEmpty(topParent.Path)) - { - return true; - } - - var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList(); - - if (itemCollectionFolders.Count > 0) - { - var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList(); - if (!itemCollectionFolders.Any(userCollectionFolders.Contains)) - { - return false; - } - } - } - - return true; - } - - /// <summary> - /// Gets a value indicating whether this instance is folder. - /// </summary> - /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> - [JsonIgnore] - public virtual bool IsFolder => false; - - [JsonIgnore] - public virtual bool IsDisplayedAsFolder => false; - public virtual string GetClientTypeName() { - if (IsFolder && SourceType == SourceType.Channel && !(this is Channel)) + if (IsFolder && SourceType == SourceType.Channel && this is not Channel) { return "ChannelFolderItem"; } @@ -2066,14 +2136,11 @@ namespace MediaBrowser.Controller.Entities return null; } - [JsonIgnore] - public virtual bool EnableRememberingTrackSelections => true; - /// <summary> /// Adds a studio to the item. /// </summary> /// <param name="name">The name.</param> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throws if name is null.</exception> public void AddStudio(string name) { if (string.IsNullOrEmpty(name)) @@ -2109,7 +2176,7 @@ namespace MediaBrowser.Controller.Entities /// Adds a genre to the item. /// </summary> /// <param name="name">The name.</param> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throwns if name is null.</exception> public void AddGenre(string name) { if (string.IsNullOrEmpty(name)) @@ -2132,8 +2199,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="user">The user.</param> /// <param name="datePlayed">The date played.</param> /// <param name="resetPosition">if set to <c>true</c> [reset position].</param> - /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throws if user is null.</exception> public virtual void MarkPlayed( User user, DateTime? datePlayed, @@ -2170,8 +2236,7 @@ namespace MediaBrowser.Controller.Entities /// Marks the unplayed. /// </summary> /// <param name="user">The user.</param> - /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throws if user is null.</exception> public virtual void MarkUnplayed(User user) { if (user == null) @@ -2271,6 +2336,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="type">The type.</param> /// <param name="index">The index.</param> + /// <returns>A task.</returns> public async Task DeleteImageAsync(ImageType type, int index) { var info = GetImageInfo(type, index); @@ -2308,6 +2374,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Validates that images within the item are still on the filesystem. /// </summary> + /// <param name="directoryService">The directory service to use.</param> + /// <returns><c>true</c> if the images validate, <c>false</c> if not.</returns> public bool ValidateImages(IDirectoryService directoryService) { var allFiles = ImageInfos @@ -2335,7 +2403,6 @@ namespace MediaBrowser.Controller.Entities /// <param name="imageType">Type of the image.</param> /// <param name="imageIndex">Index of the image.</param> /// <returns>System.String.</returns> - /// <exception cref="InvalidOperationException"> </exception> /// <exception cref="ArgumentNullException">Item is null.</exception> public string GetImagePath(ImageType imageType, int imageIndex) => GetImageInfo(imageType, imageIndex)?.Path; @@ -2821,39 +2888,6 @@ namespace MediaBrowser.Controller.Entities return GetParents().FirstOrDefault(parent => parent.IsTopParent); } - [JsonIgnore] - public virtual bool IsTopParent - { - get - { - if (this is BasePluginFolder || this is Channel) - { - return true; - } - - if (this is IHasCollectionType view) - { - if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - if (GetParent() is AggregateFolder) - { - return true; - } - - return false; - } - } - - [JsonIgnore] - public virtual bool SupportsAncestors => true; - - [JsonIgnore] - public virtual bool StopRefreshIfLocalMetadataFound => true; - public virtual IEnumerable<Guid> GetIdsForAncestorQuery() { return new[] { Id }; @@ -2888,6 +2922,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Updates the official rating based on content and returns true or false indicating if it changed. /// </summary> + /// <param name="children">Media children.</param> /// <returns><c>true</c> if the rating was updated; otherwise <c>false</c>.</returns> public bool UpdateRatingToItems(IList<BaseItem> children) { @@ -2921,12 +2956,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Gets or sets the remote trailers. - /// </summary> - /// <value>The remote trailers.</value> - public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } - - /// <summary> /// Get all extras associated with this item, sorted by <see cref="SortName"/>. /// </summary> /// <returns>An enumerable containing the items.</returns> @@ -2963,39 +2992,11 @@ namespace MediaBrowser.Controller.Entities } } - public virtual bool IsHD => Height >= 720; - - public bool IsShortcut { get; set; } - - public string ShortcutPath { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public Guid[] ExtraIds { get; set; } - public virtual long GetRunTimeTicksForPlayState() { return RunTimeTicks ?? 0; } - /// <summary> - /// Extra types that should be counted and displayed as "Special Features" in the UI. - /// </summary> - public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType> - { - Model.Entities.ExtraType.Unknown, - Model.Entities.ExtraType.BehindTheScenes, - Model.Entities.ExtraType.Clip, - Model.Entities.ExtraType.DeletedScene, - Model.Entities.ExtraType.Interview, - Model.Entities.ExtraType.Sample, - Model.Entities.ExtraType.Scene - }; - - public virtual bool SupportsExternalTransfer => false; - /// <inheritdoc /> public override bool Equals(object obj) { diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 89ad392a4..e88121212 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="source">The source object.</param> /// <param name="dest">The destination object.</param> + /// <typeparam name="T">Source type.</typeparam> + /// <typeparam name="TU">Destination type.</typeparam> public static void DeepCopy<T, TU>(this T source, TU dest) where T : BaseItem where TU : BaseItem @@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities /// Copies all properties on newly created object. Skips properties that do not exist. /// </summary> /// <param name="source">The source object.</param> + /// <typeparam name="T">Source type.</typeparam> + /// <typeparam name="TU">Destination type.</typeparam> + /// <returns>Destination object.</returns> public static TU DeepCopy<T, TU>(this T source) where T : BaseItem where TU : BaseItem, new() diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index 1bd25042f..272a37df1 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual string CollectionType => null; + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override bool SupportsPeople => false; + public override bool CanDelete() { return false; @@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities { return true; } - - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4f367fe2b..0fb4771dd 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities PhysicalFolderIds = Array.Empty<Guid>(); } + /// <summary> + /// Gets the display preferences id. + /// </summary> + /// <remarks> + /// Allow different display preferences for each collection folder. + /// </remarks> + /// <value>The display prefs id.</value> + [JsonIgnore] + public override Guid DisplayPreferencesId => Id; + + [JsonIgnore] + public override string[] PhysicalLocations => PhysicalLocationsList; + + public string[] PhysicalLocationsList { get; set; } + + public Guid[] PhysicalFolderIds { get; set; } + public static IXmlSerializer XmlSerializer { get; set; } public static IServerApplicationHost ApplicationHost { get; set; } @@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override IEnumerable<BaseItem> Children => GetActualChildren(); + [JsonIgnore] + public override bool SupportsPeople => false; + public override bool CanDelete() { return false; @@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities } } - /// <summary> - /// Gets the display preferences id. - /// </summary> - /// <remarks> - /// Allow different display preferences for each collection folder. - /// </remarks> - /// <value>The display prefs id.</value> - [JsonIgnore] - public override Guid DisplayPreferencesId => Id; - - [JsonIgnore] - public override string[] PhysicalLocations => PhysicalLocationsList; - - public string[] PhysicalLocationsList { get; set; } - - public Guid[] PhysicalFolderIds { get; set; } - public override bool IsSaveLocalMetadataEnabled() { return true; @@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities return result; } - - [JsonIgnore] - public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index d8bc0069c..9ce8eebe3 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Adds the trailer URL. /// </summary> + /// <param name="item">Media item.</param> + /// <param name="url">Trailer URL.</param> public static void AddTrailerUrl(this BaseItem item, string url) { if (string.IsNullOrEmpty(url)) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 34ad5bbbb..dd08c31ed 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities public override bool IsVisible(User user) { - if (this is ICollectionFolder && !(this is BasePluginFolder)) + if (this is ICollectionFolder && this is not BasePluginFolder) { var blockedMediaFolders = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedMediaFolders); if (blockedMediaFolders.Length > 0) @@ -673,7 +673,7 @@ namespace MediaBrowser.Controller.Entities { if (LinkedChildren.Length > 0) { - if (!(this is ICollectionFolder)) + if (this is not ICollectionFolder) { return GetChildren(user, true).Count; } @@ -730,7 +730,7 @@ namespace MediaBrowser.Controller.Entities return PostFilterAndSort(items, query, true); } - if (!(this is UserRootFolder) && !(this is AggregateFolder) && query.ParentId == Guid.Empty) + if (this is not UserRootFolder && this is not AggregateFolder && query.ParentId == Guid.Empty) { query.Parent = this; } @@ -805,7 +805,7 @@ namespace MediaBrowser.Controller.Entities { if (LinkedChildren.Length > 0) { - if (!(this is ICollectionFolder)) + if (this is not ICollectionFolder) { Logger.LogDebug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name); return true; @@ -1545,7 +1545,7 @@ namespace MediaBrowser.Controller.Entities var childOwner = child.GetOwner() ?? child; - if (childOwner != null && !(child is IItemByName)) + if (child is not IItemByName) { var childProtocol = childOwner.PathProtocol; if (!childProtocol.HasValue || childProtocol.Value != Model.MediaInfo.MediaProtocol.File) @@ -1669,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities /// <param name="user">The user.</param> /// <param name="datePlayed">The date played.</param> /// <param name="resetPosition">if set to <c>true</c> [reset position].</param> - /// <returns>Task.</returns> public override void MarkPlayed( User user, DateTime? datePlayed, @@ -1711,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities /// Marks the unplayed. /// </summary> /// <param name="user">The user.</param> - /// <returns>Task.</returns> public override void MarkUnplayed(User user) { var itemsResult = GetItemList(new InternalItemsQuery diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 98c3b3edf..90d9bdd2d 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the media sources. /// </summary> + /// <param name="enablePathSubstitution"><c>true</c> to enable path substitution, <c>false</c> to not.</param> + /// <returns>A list of media sources.</returns> List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); List<MediaStream> GetMediaStreams(); diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index 2bd9ded33..f4271678d 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the trailer count. /// </summary> + /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> public static int GetTrailerCount(this IHasTrailers item) => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count; @@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the trailer ids. /// </summary> + /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item) { @@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the trailers. /// </summary> + /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns> public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item) { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index b9e37269e..045c1b89f 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> + /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param> + /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 556624e14..c8feb1c94 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> + /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param> + /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 31c179bca..27c3ff81b 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV /// <value>The index number.</value> public int? IndexNumberEnd { get; set; } - public string FindSeriesSortName() - { - var series = Series; - return series == null ? SeriesName : series.SortName; - } - [JsonIgnore] protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1; @@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] protected override bool EnableDefaultVideoUserDataKeys => false; - public override double GetDefaultPrimaryImageAspectRatio() - { - // hack for tv plugins - if (SourceType == SourceType.Channel) - { - return 0; - } - - return 16.0 / 9; - } - - public override List<string> GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - var series = Series; - if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) - { - var seriesUserDataKeys = series.GetUserDataKeys(); - var take = seriesUserDataKeys.Count; - if (seriesUserDataKeys.Count > 1) - { - take--; - } - - var newList = seriesUserDataKeys.GetRange(0, take); - var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); - for (int i = 0; i < take; i++) - { - newList[i] = newList[i] + suffix; - } - - newList.AddRange(list); - list = newList; - } - - return list; - } - /// <summary> /// Gets the Episode's Series Instance. /// </summary> @@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public string SeasonName { get; set; } + [JsonIgnore] + public override bool SupportsRemoteImageDownloading + { + get + { + if (IsMissingEpisode) + { + return false; + } + + return true; + } + } + + [JsonIgnore] + public bool IsMissingEpisode => LocationType == LocationType.Virtual; + + [JsonIgnore] + public Guid SeasonId { get; set; } + + [JsonIgnore] + public Guid SeriesId { get; set; } + + public string FindSeriesSortName() + { + var series = Series; + return series == null ? SeriesName : series.SortName; + } + + public override double GetDefaultPrimaryImageAspectRatio() + { + // hack for tv plugins + if (SourceType == SourceType.Channel) + { + return 0; + } + + return 16.0 / 9; + } + + public override List<string> GetUserDataKeys() + { + var list = base.GetUserDataKeys(); + + var series = Series; + if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) + { + var seriesUserDataKeys = series.GetUserDataKeys(); + var take = seriesUserDataKeys.Count; + if (seriesUserDataKeys.Count > 1) + { + take--; + } + + var newList = seriesUserDataKeys.GetRange(0, take); + var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); + for (int i = 0; i < take; i++) + { + newList[i] = newList[i] + suffix; + } + + newList.AddRange(list); + list = newList; + } + + return list; + } + public string FindSeriesPresentationUniqueKey() { var series = Series; @@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV return false; } - [JsonIgnore] - public override bool SupportsRemoteImageDownloading - { - get - { - if (IsMissingEpisode) - { - return false; - } - - return true; - } - } - - [JsonIgnore] - public bool IsMissingEpisode => LocationType == LocationType.Virtual; - - [JsonIgnore] - public Guid SeasonId { get; set; } - - [JsonIgnore] - public Guid SeriesId { get; set; } - public Guid FindSeriesId() { var series = FindParent<Series>(); diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index aa62bb35b..926c7b045 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public override Guid DisplayParentId => SeriesId; + /// <summary> + /// Gets this Episode's Series Instance. + /// </summary> + /// <value>The series.</value> + [JsonIgnore] + public Series Series + { + get + { + var seriesId = SeriesId; + if (seriesId == Guid.Empty) + { + seriesId = FindSeriesId(); + } + + return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); + } + } + + [JsonIgnore] + public string SeriesPath + { + get + { + var series = Series; + + if (series != null) + { + return series.Path; + } + + return System.IO.Path.GetDirectoryName(Path); + } + } + + [JsonIgnore] + public string SeriesPresentationUniqueKey { get; set; } + + [JsonIgnore] + public string SeriesName { get; set; } + + [JsonIgnore] + public Guid SeriesId { get; set; } + public override double GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV return result; } - /// <summary> - /// Gets this Episode's Series Instance. - /// </summary> - /// <value>The series.</value> - [JsonIgnore] - public Series Series - { - get - { - var seriesId = SeriesId; - if (seriesId == Guid.Empty) - { - seriesId = FindSeriesId(); - } - - return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); - } - } - - [JsonIgnore] - public string SeriesPath - { - get - { - var series = Series; - - if (series != null) - { - return series.Path; - } - - return System.IO.Path.GetDirectoryName(Path); - } - } - public override string CreatePresentationUniqueKey() { if (IndexNumber.HasValue) @@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Gets the episodes. /// </summary> + /// <param name="user">The user.</param> + /// <param name="options">The options to use.</param> + /// <returns>Set of episodes.</returns> public List<BaseItem> GetEpisodes(User user, DtoOptions options) { return GetEpisodes(Series, user, options); @@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV return UnratedItem.Series; } - [JsonIgnore] - public string SeriesPresentationUniqueKey { get; set; } - - [JsonIgnore] - public string SeriesName { get; set; } - - [JsonIgnore] - public Guid SeriesId { get; set; } - public string FindSeriesPresentationUniqueKey() { var series = Series; @@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> + /// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 44d07b4a4..e4933e968 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV /// <value>The status.</value> public SeriesStatus? Status { get; set; } + [JsonIgnore] + public override bool StopRefreshIfLocalMetadataFound => false; + public override double GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -293,7 +296,7 @@ namespace MediaBrowser.Controller.Entities.TV // Refresh seasons foreach (var item in items) { - if (!(item is Season)) + if (item is not Season) { continue; } @@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Filters the episodes by season. /// </summary> + /// <param name="episodes">The episodes.</param> + /// <param name="parentSeason">The season.</param> + /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param> + /// <returns>The set of episodes.</returns> public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials) { var seasonNumber = parentSeason.IndexNumber; @@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Filters the episodes by season. /// </summary> + /// <param name="episodes">The episodes.</param> + /// <param name="seasonNumber">The season.</param> + /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param> + /// <returns>The set of episodes.</returns> public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) { if (!includeSpecials || seasonNumber < 1) @@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - - [JsonIgnore] - public override bool StopRefreshIfLocalMetadataFound => false; } } diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index 6ab2116d7..50ba9ef30 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -12,6 +12,13 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class UserItemData { + public const double MinLikeValue = 6.5; + + /// <summary> + /// The _rating. + /// </summary> + private double? _rating; + /// <summary> /// Gets or sets the user id. /// </summary> @@ -25,11 +32,6 @@ namespace MediaBrowser.Controller.Entities public string Key { get; set; } /// <summary> - /// The _rating. - /// </summary> - private double? _rating; - - /// <summary> /// Gets or sets the users 0-10 rating. /// </summary> /// <value>The rating.</value> @@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The index of the subtitle stream.</value> public int? SubtitleStreamIndex { get; set; } - public const double MinLikeValue = 6.5; - /// <summary> /// Gets or sets a value indicating whether the item is liked or not. /// This should never be serialized. diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 2b15a52f0..f3bf4749d 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class UserRootFolder : Folder { - private List<Guid> _childrenIds = null; private readonly object _childIdsLock = new object(); + private List<Guid> _childrenIds = null; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override bool SupportsPlayedStatus => false; + + [JsonIgnore] + protected override bool SupportsShortcutChildren => true; + + [JsonIgnore] + public override bool IsPreSorted => true; + + private void ClearCache() + { + lock (_childIdsLock) + { + _childrenIds = null; + } + } protected override List<BaseItem> LoadChildren() { @@ -39,20 +59,6 @@ namespace MediaBrowser.Controller.Entities } } - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override bool SupportsPlayedStatus => false; - - private void ClearCache() - { - lock (_childIdsLock) - { - _childrenIds = null; - } - } - protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query) { if (query.Recursive) @@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, true).Count; } - [JsonIgnore] - protected override bool SupportsShortcutChildren => true; - - [JsonIgnore] - public override bool IsPreSorted => true; - protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) { var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList(); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index d05b5df2f..7dd95b85c 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities ISupportsPlaceHolders, IHasMediaSources { + public Video() + { + AdditionalParts = Array.Empty<string>(); + LocalAlternateVersions = Array.Empty<string>(); + SubtitleFiles = Array.Empty<string>(); + LinkedAlternateVersions = Array.Empty<LinkedChild>(); + } + [JsonIgnore] public string PrimaryVersionId { get; set; } @@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities } } - public void SetPrimaryVersionId(string id) - { - if (string.IsNullOrEmpty(id)) - { - PrimaryVersionId = null; - } - else - { - PrimaryVersionId = id; - } - - PresentationUniqueKey = CreatePresentationUniqueKey(); - } - - public override string CreatePresentationUniqueKey() - { - if (!string.IsNullOrEmpty(PrimaryVersionId)) - { - return PrimaryVersionId; - } - - return base.CreatePresentationUniqueKey(); - } - [JsonIgnore] public override bool SupportsThemeMedia => true; @@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The aspect ratio.</value> public string AspectRatio { get; set; } - public Video() - { - AdditionalParts = Array.Empty<string>(); - LocalAlternateVersions = Array.Empty<string>(); - SubtitleFiles = Array.Empty<string>(); - LinkedAlternateVersions = Array.Empty<LinkedChild>(); - } - - public override bool CanDownload() - { - if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay) - { - return false; - } - - return IsFileProtocol; - } - [JsonIgnore] public override bool SupportsAddingToPlaylist => true; @@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0; - public IEnumerable<Guid> GetAdditionalPartIds() - { - return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - - public IEnumerable<Guid> GetLocalAlternateVersionIds() - { - return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - public static ILiveTvManager LiveTvManager { get; set; } [JsonIgnore] @@ -222,37 +178,77 @@ namespace MediaBrowser.Controller.Entities } } - protected override bool IsActiveRecording() + [JsonIgnore] + public bool IsCompleteMedia { - return LiveTvManager.GetActiveRecordingInfo(Path) != null; + get + { + if (SourceType == SourceType.Channel) + { + return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + } + + return !IsActiveRecording(); + } } - public override bool CanDelete() + [JsonIgnore] + protected virtual bool EnableDefaultVideoUserDataKeys => true; + + [JsonIgnore] + public override string ContainingFolderPath { - if (IsActiveRecording()) + get { - return false; - } + if (IsStacked) + { + return System.IO.Path.GetDirectoryName(Path); + } - return base.CanDelete(); + if (!IsPlaceHolder) + { + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) + { + return Path; + } + } + + return base.ContainingFolderPath; + } } [JsonIgnore] - public bool IsCompleteMedia + public override string FileNameWithoutExtension { get { - if (SourceType == SourceType.Channel) + if (IsFileProtocol) { - return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) + { + return System.IO.Path.GetFileName(Path); + } + + return System.IO.Path.GetFileNameWithoutExtension(Path); } - return !IsActiveRecording(); + return null; } } + /// <summary> + /// Gets a value indicating whether [is3 D]. + /// </summary> + /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value> [JsonIgnore] - protected virtual bool EnableDefaultVideoUserDataKeys => true; + public bool Is3D => Video3DFormat.HasValue; + + /// <summary> + /// Gets the type of the media. + /// </summary> + /// <value>The type of the media.</value> + [JsonIgnore] + public override string MediaType => Model.Entities.MediaType.Video; public override List<string> GetUserDataKeys() { @@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities return list; } + public void SetPrimaryVersionId(string id) + { + if (string.IsNullOrEmpty(id)) + { + PrimaryVersionId = null; + } + else + { + PrimaryVersionId = id; + } + + PresentationUniqueKey = CreatePresentationUniqueKey(); + } + + public override string CreatePresentationUniqueKey() + { + if (!string.IsNullOrEmpty(PrimaryVersionId)) + { + return PrimaryVersionId; + } + + return base.CreatePresentationUniqueKey(); + } + + public override bool CanDownload() + { + if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay) + { + return false; + } + + return IsFileProtocol; + } + + protected override bool IsActiveRecording() + { + return LiveTvManager.GetActiveRecordingInfo(Path) != null; + } + + public override bool CanDelete() + { + if (IsActiveRecording()) + { + return false; + } + + return base.CanDelete(); + } + + public IEnumerable<Guid> GetAdditionalPartIds() + { + return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); + } + + public IEnumerable<Guid> GetLocalAlternateVersionIds() + { + return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); + } + private string GetUserDataKey(string providerId) { var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant(); @@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities .OrderBy(i => i.SortName); } - [JsonIgnore] - public override string ContainingFolderPath - { - get - { - if (IsStacked) - { - return System.IO.Path.GetDirectoryName(Path); - } - - if (!IsPlaceHolder) - { - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) - { - return Path; - } - } - - return base.ContainingFolderPath; - } - } - - [JsonIgnore] - public override string FileNameWithoutExtension - { - get - { - if (IsFileProtocol) - { - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) - { - return System.IO.Path.GetFileName(Path); - } - - return System.IO.Path.GetFileNameWithoutExtension(Path); - } - - return null; - } - } - internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem) { var updateType = base.UpdateFromResolvedItem(newItem); @@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities return updateType; } - /// <summary> - /// Gets a value indicating whether [is3 D]. - /// </summary> - /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value> - [JsonIgnore] - public bool Is3D => Video3DFormat.HasValue; - - /// <summary> - /// Gets the type of the media. - /// </summary> - /// <value>The type of the media.</value> - [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Video; - protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9bae95a27..1be3a1ca4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.MediaEncoding { @@ -40,6 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding "ConstrainedHigh" }; + private static readonly Version minVersionForCudaOverlay = new Version(4, 4); + public EncodingHelper( IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder) @@ -109,17 +108,41 @@ namespace MediaBrowser.Controller.MediaEncoding private bool IsCudaSupported() { return _mediaEncoder.SupportsHwaccel("cuda") - && _mediaEncoder.SupportsFilter("scale_cuda", null) - && _mediaEncoder.SupportsFilter("yadif_cuda", null); + && _mediaEncoder.SupportsFilter("scale_cuda") + && _mediaEncoder.SupportsFilter("yadif_cuda") + && _mediaEncoder.SupportsFilter("hwupload_cuda"); } - private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options) { var videoStream = state.VideoStream; - return IsColorDepth10(state) + if (videoStream == null) + { + return false; + } + + return options.EnableTonemapping + && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && IsColorDepth10(state) && _mediaEncoder.SupportsHwaccel("opencl") - && options.EnableTonemapping - && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_opencl"); + } + + private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + { + var videoStream = state.VideoStream; + if (videoStream == null) + { + return false; + } + + return options.EnableTonemapping + && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && IsColorDepth10(state) + && _mediaEncoder.SupportsHwaccel("cuda") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName); } private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -135,23 +158,25 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return IsColorDepth10(state) + return options.EnableVppTonemapping + && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") - && options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_vaapi"); } // Hybrid VPP tonemapping for QSV with VAAPI if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return IsColorDepth10(state) + return options.EnableVppTonemapping + && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") - && _mediaEncoder.SupportsHwaccel("qsv") - && options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_vaapi") + && _mediaEncoder.SupportsHwaccel("qsv"); } // Native VPP tonemapping may come to QSV in the future. @@ -500,14 +525,19 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets the input argument. /// </summary> /// <param name="state">Encoding state.</param> - /// <param name="encodingOptions">Encoding options.</param> + /// <param name="options">Encoding options.</param> /// <returns>Input arguments.</returns> - public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) + public string GetInputArgument(EncodingJobInfo state, EncodingOptions options) { var arg = new StringBuilder(); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; - var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; + var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty; + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + var isMacOS = OperatingSystem.IsMacOS(); +#pragma warning disable CA1508 // Defaults to string.Empty var isSwDecoder = string.IsNullOrEmpty(videoDecoder); +#pragma warning restore CA1508 var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; @@ -515,26 +545,24 @@ namespace MediaBrowser.Controller.MediaEncoding var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); - var isWindows = OperatingSystem.IsWindows(); - var isLinux = OperatingSystem.IsLinux(); - var isMacOS = OperatingSystem.IsMacOS(); - var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions); + var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); if (!IsCopyCodec(outputVideoCodec)) { if (state.IsVideoRequest && _mediaEncoder.SupportsHwaccel("vaapi") - && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { if (isVaapiDecoder) { - if (isTonemappingSupported && !isVppTonemappingSupported) + if (isOpenclTonemappingSupported && !isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") - .Append(encodingOptions.VaapiDevice) - .Append(' ') - .Append("-init_hw_device opencl=ocl@va ") + .Append(options.VaapiDevice) + .Append(" -init_hw_device opencl=ocl@va ") .Append("-hwaccel_device va ") .Append("-hwaccel_output_format vaapi ") .Append("-filter_hw_device ocl "); @@ -543,14 +571,14 @@ namespace MediaBrowser.Controller.MediaEncoding { arg.Append("-hwaccel_output_format vaapi ") .Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) + .Append(options.VaapiDevice) .Append(' '); } } else if (!isVaapiDecoder && isVaapiEncoder) { arg.Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) + .Append(options.VaapiDevice) .Append(' '); } @@ -558,7 +586,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -594,9 +622,8 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder && isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") - .Append(encodingOptions.VaapiDevice) - .Append(' ') - .Append("-init_hw_device qsv@va ") + .Append(options.VaapiDevice) + .Append(" -init_hw_device qsv@va ") .Append("-hwaccel_output_format vaapi "); } @@ -605,7 +632,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562 @@ -613,22 +640,31 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) - && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) - || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) - && (isD3d11vaDecoder || isSwDecoder)))) + && ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder)))) { - if (isTonemappingSupported) + if (!isCudaTonemappingSupported && isOpenclTonemappingSupported) { arg.Append("-init_hw_device opencl=ocl:") - .Append(encodingOptions.OpenclDevice) - .Append(' ') - .Append("-filter_hw_device ocl "); + .Append(options.OpenclDevice) + .Append(" -filter_hw_device ocl "); } } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) + && (isD3d11vaDecoder || isSwDecoder)) + { + if (isOpenclTonemappingSupported) + { + arg.Append("-init_hw_device opencl=ocl:") + .Append(options.OpenclDevice) + .Append(" -filter_hw_device ocl "); + } + } + + if (state.IsVideoRequest + && string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { arg.Append("-hwaccel videotoolbox "); } @@ -1762,7 +1798,7 @@ namespace MediaBrowser.Controller.MediaEncoding var request = state.BaseRequest; - var inputChannels = audioStream?.Channels; + var inputChannels = audioStream.Channels; if (inputChannels <= 0) { @@ -2013,14 +2049,18 @@ namespace MediaBrowser.Controller.MediaEncoding var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupported = IsTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. // But it's still in ffmpeg mailing list. Disable it for now. - if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) { return GetOutputSizeParam(state, options, outputVideoCodec); } @@ -2030,8 +2070,8 @@ namespace MediaBrowser.Controller.MediaEncoding { // Adjust the size of graphical subtitles to fit the video stream. var videoStream = state.VideoStream; - var inputWidth = videoStream?.Width; - var inputHeight = videoStream?.Height; + var inputWidth = videoStream.Width; + var inputHeight = videoStream.Height; var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); if (width.HasValue && height.HasValue) @@ -2046,13 +2086,22 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(videoSizeParam) && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { - // For QSV, feed it into hardware encoder now + // upload graphical subtitle to QSV if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) { videoSizeParam += ",hwupload=extra_hw_frames=64"; } } + + if (!string.IsNullOrEmpty(videoSizeParam)) + { + // upload graphical subtitle to cuda + if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported) + { + videoSizeParam += ",hwupload_cuda"; + } + } } var mapPrefix = state.SubtitleStream.IsExternal ? @@ -2065,9 +2114,9 @@ namespace MediaBrowser.Controller.MediaEncoding // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference) // Always put the scaler before the overlay for better performance - var retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; + var retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; // When the input may or may not be hardware VAAPI decodable if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) @@ -2078,9 +2127,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first @@ -2093,9 +2142,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) @@ -2112,16 +2161,25 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isLinux) { - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""; } } else if (isNvdecDecoder && isNvencEncoder) { - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; + if (isCudaOverlaySupported && isCudaFormatConversionSupported) + { + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\""; + } + else + { + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; + } } return string.Format( @@ -2218,11 +2276,11 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase); var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupported = IsTonemappingSupported(state, options); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); - var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)) + var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)) || (isTonemappingSupportedOnQsv && isVppTonemappingSupported); var outputPixFmt = "format=nv12"; @@ -2273,15 +2331,23 @@ namespace MediaBrowser.Controller.MediaEncoding var outputWidth = width.Value; var outputHeight = height.Value; - var isTonemappingSupported = IsTonemappingSupported(state, options); + var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase); - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var outputPixFmt = string.Empty; if (isCudaFormatConversionSupported) { - outputPixFmt = "format=nv12"; - if (isTonemappingSupported && isTonemappingSupportedOnNvenc) + outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) + ? "format=yuv420p" + : "format=nv12"; + if ((isOpenclTonemappingSupported || isCudaTonemappingSupported) + && isTonemappingSupportedOnNvenc) { outputPixFmt = "format=p010"; } @@ -2559,16 +2625,21 @@ namespace MediaBrowser.Controller.MediaEncoding var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase); var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); + var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = OperatingSystem.IsLinux(); var isColorDepth10 = IsColorDepth10(state); - var isTonemappingSupported = IsTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder); + + var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder); var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2580,19 +2651,25 @@ namespace MediaBrowser.Controller.MediaEncoding var isScalingInAdvance = false; var isCudaDeintInAdvance = false; var isHwuploadCudaRequired = false; + var isNoTonemapFilterApplied = true; var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI. - if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) + if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) { - // Currently only with the use of NVENC decoder can we get a decent performance. - // Currently only the HEVC/H265 format is supported with NVDEC decoder. // NVIDIA Pascal and Turing or higher are recommended. // AMD Polaris and Vega or higher are recommended. // Intel Kaby Lake or newer is required. - if (isTonemappingSupported) + if (isOpenclTonemappingSupported) { + isNoTonemapFilterApplied = false; + var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); + if (!string.IsNullOrEmpty(inputHdrParams)) + { + filters.Add(inputHdrParams); + } + var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; if (options.TonemappingParam != 0) @@ -2664,7 +2741,11 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwdownload,format=p010"); } - if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder + || isCuvidHevcDecoder + || isCuvidVp9Decoder + || isSwDecoder + || isD3d11vaDecoder) { // Upload the HDR10 or HLG data to the OpenCL device, // use tonemap_opencl filter for tone mapping, @@ -2672,6 +2753,14 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload"); } + // Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl. + var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390); + if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase) + && !isBt2390SupportedInOpenclTonemap) + { + options.TonemappingAlgorithm = "hable"; + } + filters.Add( string.Format( CultureInfo.InvariantCulture, @@ -2683,7 +2772,11 @@ namespace MediaBrowser.Controller.MediaEncoding options.TonemappingParam, options.TonemappingRange)); - if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder + || isCuvidHevcDecoder + || isCuvidVp9Decoder + || isSwDecoder + || isD3d11vaDecoder) { filters.Add("hwdownload"); filters.Add("format=nv12"); @@ -2699,12 +2792,18 @@ namespace MediaBrowser.Controller.MediaEncoding // Reverse the data route from opencl to vaapi. filters.Add("hwmap=derive_device=vaapi:reverse=1"); } + + var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } } } // When the input may or may not be hardware VAAPI decodable. if ((isVaapiH264Encoder || isVaapiHevcEncoder) - && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))) + && !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); @@ -2812,6 +2911,61 @@ namespace MediaBrowser.Controller.MediaEncoding request.MaxHeight)); } + // Add Cuda tonemapping filter. + if (isNvdecDecoder && isCudaTonemappingSupported) + { + isNoTonemapFilterApplied = false; + var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); + if (!string.IsNullOrEmpty(inputHdrParams)) + { + filters.Add(inputHdrParams); + } + + var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) + ? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}" + : "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"; + + if (options.TonemappingParam != 0) + { + parameters += ":param={3}"; + } + + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + parameters += ":range={4}"; + } + + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + parameters, + options.TonemappingAlgorithm, + options.TonemappingPeak, + options.TonemappingDesat, + options.TonemappingParam, + options.TonemappingRange)); + + if (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) + { + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + } + + filters.Add("hwdownload"); + filters.Add("format=nv12"); + } + + var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } + } + // Add VPP tonemapping filter for VAAPI. // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options. if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv) @@ -2821,10 +2975,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // Another case is when using Nvenc decoder. - if (isNvdecDecoder && !isTonemappingSupported) + if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported) { var codec = videoStream.Codec; - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Assert 10-bit hardware decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) @@ -2833,7 +2987,10 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isCudaFormatConversionSupported) { - if (isLibX264Encoder || isLibX265Encoder || hasSubs) + if (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) { if (isNvencEncoder) { @@ -2860,7 +3017,11 @@ namespace MediaBrowser.Controller.MediaEncoding } // Assert 8-bit hardware decodable - else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs)) + else if (!isColorDepth10 + && (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))) { if (isNvencEncoder) { @@ -2881,7 +3042,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // Convert hw context from ocl to va. // For tonemapping and text subs burn-in. - if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) { filters.Add("scale_vaapi"); } @@ -2927,6 +3088,17 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload_cuda"); } + // If no tonemap filter is applied, + // tag the video range as SDR to prevent the encoder from encoding HDR video. + if (isNoTonemapFilterApplied) + { + var outputSdrParams = GetOutputSdrParams(null); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } + } + var output = string.Empty; if (filters.Count > 0) { @@ -2939,6 +3111,36 @@ namespace MediaBrowser.Controller.MediaEncoding return output; } + public static string GetInputHdrParams(string colorTransfer) + { + if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + { + // HLG + return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc"; + } + else + { + // HDR10 + return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; + } + } + + public static string GetOutputSdrParams(string tonemappingRange) + { + // SDR + if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv"; + } + + if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc"; + } + + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + } + /// <summary> /// Gets the number of threads. /// </summary> @@ -3104,7 +3306,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " " + videoDecoder; if (!IsCopyCodec(state.OutputVideoCodec) - && (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) + && videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase)) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -3113,7 +3315,7 @@ namespace MediaBrowser.Controller.MediaEncoding var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 + if (videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase) && width.HasValue && height.HasValue) { @@ -3409,8 +3611,13 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && IsVppTonemappingSupported(state, encodingOptions)) { - // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. - return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var isQsvEncoder = outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase); + if (isQsvEncoder) + { + // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. + return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + } } if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) @@ -3943,6 +4150,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream != null) { + if (videoStream.BitDepth.HasValue) + { + return videoStream.BitDepth.Value == 10; + } + if (!string.IsNullOrEmpty(videoStream.PixelFormat)) { result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase); @@ -3962,12 +4174,6 @@ namespace MediaBrowser.Controller.MediaEncoding return true; } } - - result = (videoStream.BitDepth ?? 8) == 10; - if (result) - { - return true; - } } return result; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index fa9f40d60..b09b7dba6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -422,7 +422,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { - return VideoStream?.Codec; + return VideoStream.Codec; } return OutputVideoCodec; @@ -440,7 +440,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (EncodingHelper.IsCopyCodec(OutputAudioCodec)) { - return AudioStream?.Codec; + return AudioStream.Codec; } return OutputAudioCodec; @@ -568,7 +568,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced; + return forceDeinterlaceIfSourceIsInterlaced; } public string[] GetRequestedProfiles(string codec) diff --git a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs new file mode 100644 index 000000000..7ce707b19 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.MediaEncoding +{ + /// <summary> + /// Enum FilterOptionType. + /// </summary> + public enum FilterOptionType + { + /// <summary> + /// The scale_cuda_format. + /// </summary> + ScaleCudaFormat = 0, + + /// <summary> + /// The tonemap_cuda_name. + /// </summary> + TonemapCudaName = 1, + + /// <summary> + /// The tonemap_opencl_bt2390. + /// </summary> + TonemapOpenclBt2390 = 2 + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index ff2456070..c5522bc3c 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { @@ -20,11 +19,6 @@ namespace MediaBrowser.Controller.MediaEncoding public interface IMediaEncoder : ITranscoderSupport { /// <summary> - /// Gets location of the discovered FFmpeg tool. - /// </summary> - FFmpegLocation EncoderLocation { get; } - - /// <summary> /// Gets the encoder path. /// </summary> /// <value>The encoder path.</value> @@ -55,9 +49,21 @@ namespace MediaBrowser.Controller.MediaEncoding /// Whether given filter is supported. /// </summary> /// <param name="filter">The filter.</param> + /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns> + bool SupportsFilter(string filter); + + /// <summary> + /// Whether filter is supported with the given option. + /// </summary> /// <param name="option">The option.</param> /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns> - bool SupportsFilter(string filter, string option); + bool SupportsFilterWithOption(FilterOptionType option); + + /// <summary> + /// Get the version of media encoder. + /// </summary> + /// <returns>The version of media encoder.</returns> + Version GetMediaEncoderVersion(); /// <summary> /// Extracts the audio image. diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index d8995ce74..0813a8e7d 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1306, SA1401 using System; using System.Collections.Generic; @@ -31,6 +31,21 @@ namespace MediaBrowser.Controller.Net new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>(); /// <summary> + /// The logger. + /// </summary> + protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger; + + protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + Logger = logger; + } + + /// <summary> /// Gets the type used for the messages sent to the client. /// </summary> /// <value>The type.</value> @@ -55,21 +70,6 @@ namespace MediaBrowser.Controller.Net protected abstract Task<TReturnDataType> GetDataToSend(); /// <summary> - /// The logger. - /// </summary> - protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger; - - protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger) - { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - Logger = logger; - } - - /// <summary> /// Processes the message. /// </summary> /// <param name="message">The message.</param> diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 5fa5834c8..c43acfb6d 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence /// <param name="key">The key.</param> /// <param name="userData">The user data.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken); /// <summary> diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 3eaf23515..5e671a725 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists ".zpl" }; - public Guid OwnerUserId { get; set; } - - public Share[] Shares { get; set; } - public Playlist() { Shares = Array.Empty<Share>(); } + public Guid OwnerUserId { get; set; } + + public Share[] Shares { get; set; } + [JsonIgnore] public bool IsFile => IsPlaylistFile(Path); - public static bool IsPlaylistFile(string path) - { - // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). - return System.IO.Path.HasExtension(path) && !Directory.Exists(path); - } - [JsonIgnore] public override string ContainingFolderPath { @@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists [JsonIgnore] public override bool SupportsCumulativeRunTimeTicks => true; + [JsonIgnore] + public override bool IsPreSorted => true; + + public string PlaylistMediaType { get; set; } + + [JsonIgnore] + public override string MediaType => PlaylistMediaType; + + [JsonIgnore] + private bool IsSharedItem + { + get + { + var path = Path; + + if (string.IsNullOrEmpty(path)) + { + return false; + } + + return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path); + } + } + + public static bool IsPlaylistFile(string path) + { + // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). + return System.IO.Path.HasExtension(path) && !Directory.Exists(path); + } + + public void SetMediaType(string value) + { + PlaylistMediaType = value; + } + public override double GetDefaultPrimaryImageAspectRatio() { return 1; @@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists return new[] { item }; } - [JsonIgnore] - public override bool IsPreSorted => true; - - public string PlaylistMediaType { get; set; } - - [JsonIgnore] - public override string MediaType => PlaylistMediaType; - - public void SetMediaType(string value) - { - PlaylistMediaType = value; - } - - [JsonIgnore] - private bool IsSharedItem - { - get - { - var path = Path; - - if (string.IsNullOrEmpty(path)) - { - return false; - } - - return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path); - } - } - public override bool IsVisible(User user) { if (!IsSharedItem) diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index 75286eadc..b95d00aa3 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -14,17 +14,17 @@ namespace MediaBrowser.Controller.Resolvers public interface IItemResolver { /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + ResolverPriority Priority { get; } + + /// <summary> /// Resolves the path. /// </summary> /// <param name="args">The args.</param> /// <returns>BaseItem.</returns> BaseItem ResolvePath(ItemResolveArgs args); - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - ResolverPriority Priority { get; } } public interface IMultiItemResolver @@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers public class MultiItemResolverResult { - public List<BaseItem> Items { get; set; } - - public List<FileSystemMetadata> ExtraFiles { get; set; } - public MultiItemResolverResult() { Items = new List<BaseItem>(); ExtraFiles = new List<FileSystemMetadata>(); } + + public List<BaseItem> Items { get; set; } + + public List<FileSystemMetadata> ExtraFiles { get; set; } } } diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 9e661cbe4..3330dd540 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles /// <summary> /// Searches the subtitles. /// </summary> + /// <param name="video">The video.</param> + /// <param name="language">Subtitle language.</param> + /// <param name="isPerfectMatch">Require perfect match.</param> + /// <param name="cancellationToken">CancellationToken to use for the operation.</param> + /// <returns>Subtitles, wrapped in task.</returns> Task<RemoteSubtitleInfo[]> SearchSubtitles( Video video, string language, @@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles /// <summary> /// Downloads the subtitles. /// </summary> + /// <param name="video">The video.</param> + /// <param name="subtitleId">Subtitle ID.</param> + /// <param name="cancellationToken">CancellationToken to use for the operation.</param> + /// <returns>A task.</returns> Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken); /// <summary> /// Downloads the subtitles. /// </summary> + /// <param name="video">The video.</param> + /// <param name="libraryOptions">Library options to use.</param> + /// <param name="subtitleId">Subtitle ID.</param> + /// <param name="cancellationToken">CancellationToken to use for the operation.</param> + /// <returns>A task.</returns> Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken); /// <summary> @@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles /// <summary> /// Deletes the subtitles. /// </summary> + /// <param name="item">Media item.</param> + /// <param name="index">Subtitle index.</param> + /// <returns>A task.</returns> Task DeleteSubtitles(BaseItem item, int index); /// <summary> /// Gets the providers. /// </summary> + /// <param name="item">The media item.</param> + /// <returns>Subtitles providers.</returns> SubtitleProviderInfo[] GetSupportedProviders(BaseItem item); } } diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 0f7c47e76..767d87d46 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles { public class SubtitleSearchRequest : IHasProviderIds { + public SubtitleSearchRequest() + { + SearchAllProviders = true; + ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + DisabledSubtitleFetchers = Array.Empty<string>(); + SubtitleFetcherOrder = Array.Empty<string>(); + } + public string Language { get; set; } public string TwoLetterISOLanguageName { get; set; } @@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles public string[] DisabledSubtitleFetchers { get; set; } public string[] SubtitleFetcherOrder { get; set; } - - public SubtitleSearchRequest() - { - SearchAllProviders = true; - ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - DisabledSubtitleFetchers = Array.Empty<string>(); - SubtitleFetcherOrder = Array.Empty<string>(); - } } } |
