diff options
| author | Claus Vium <cvium@users.noreply.github.com> | 2020-12-04 13:50:44 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-04 13:50:44 +0100 |
| commit | dca3f62ff85ba62af95831848718e9764d163306 (patch) | |
| tree | 5be2338b7ec18013fb18feb3325d3228b3e5da59 /MediaBrowser.Controller | |
| parent | eed1a40b1935e00e04f6b27c7d307bf110b83b31 (diff) | |
| parent | 9afd19b06e025992ee5159a08d7af8bd736a828e (diff) | |
Merge branch 'master' into PlugsVersionNumberFix
Diffstat (limited to 'MediaBrowser.Controller')
21 files changed, 392 insertions, 318 deletions
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 67aa7f338..085f769d0 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,5 +1,7 @@ -using System; +using System; using System.Linq; +using System.Threading; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -12,6 +14,8 @@ namespace MediaBrowser.Controller.BaseItemManager { private readonly IServerConfigurationManager _serverConfigurationManager; + private int _metadataRefreshConcurrency = 0; + /// <summary> /// Initializes a new instance of the <see cref="BaseItemManager"/> class. /// </summary> @@ -19,9 +23,17 @@ namespace MediaBrowser.Controller.BaseItemManager public BaseItemManager(IServerConfigurationManager serverConfigurationManager) { _serverConfigurationManager = serverConfigurationManager; + + _metadataRefreshConcurrency = GetMetadataRefreshConcurrency(); + SetupMetadataThrottler(); + + _serverConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; } /// <inheritdoc /> + public SemaphoreSlim MetadataRefreshThrottler { get; private set; } + + /// <inheritdoc /> public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name) { if (baseItem is Channel) @@ -82,5 +94,42 @@ namespace MediaBrowser.Controller.BaseItemManager return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); } + + /// <summary> + /// 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) + { + int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency(); + if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency) + { + _metadataRefreshConcurrency = newMetadataRefreshConcurrency; + SetupMetadataThrottler(); + } + } + + /// <summary> + /// Creates the metadata refresh throttler. + /// </summary> + private void SetupMetadataThrottler() + { + MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency); + } + + /// <summary> + /// Returns the metadata refresh concurrency. + /// </summary> + private int GetMetadataRefreshConcurrency() + { + var concurrency = _serverConfigurationManager.Configuration.LibraryMetadataRefreshConcurrency; + + if (concurrency <= 0) + { + concurrency = Environment.ProcessorCount; + } + + return concurrency; + } } } diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index ee4d3dcdc..e1f5d05a6 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Controller.Entities; +using System; +using System.Threading; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.BaseItemManager @@ -9,6 +11,11 @@ namespace MediaBrowser.Controller.BaseItemManager public interface IBaseItemManager { /// <summary> + /// Gets the semaphore used to limit the amount of concurrent metadata refreshes. + /// </summary> + SemaphoreSlim MetadataRefreshThrottler { get; } + + /// <summary> /// Is metadata fetcher enabled. /// </summary> /// <param name="baseItem">The base item.</param> diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 9a9d22d33..ddae7dbd3 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Channels /// </summary> /// <param name="id">The identifier.</param> /// <returns>ChannelFeatures.</returns> - ChannelFeatures GetChannelFeatures(string id); + ChannelFeatures GetChannelFeatures(Guid? id); /// <summary> /// Gets all channel features. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1b25fbdbb..d8fad3bfb 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1385,7 +1385,6 @@ namespace MediaBrowser.Controller.Entities new List<FileSystemMetadata>(); var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); - await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh if (ownedItemsChanged) { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 1e3825c6e..23f4c00c1 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; @@ -328,11 +329,11 @@ namespace MediaBrowser.Controller.Entities return; } - progress.Report(5); + progress.Report(ProgressHelpers.RetrievedChildren); if (recursive) { - ProviderManager.OnRefreshProgress(this, 5); + ProviderManager.OnRefreshProgress(this, ProgressHelpers.RetrievedChildren); } // Build a dictionary of the current children we have now by Id so we can compare quickly and easily @@ -353,11 +354,6 @@ namespace MediaBrowser.Controller.Entities { await currentChild.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); } - else - { - // metadata is up-to-date; make sure DB has correct images dimensions and hash - await LibraryManager.UpdateImagesAsync(currentChild).ConfigureAwait(false); - } continue; } @@ -393,11 +389,11 @@ namespace MediaBrowser.Controller.Entities validChildrenNeedGeneration = true; } - progress.Report(10); + progress.Report(ProgressHelpers.UpdatedChildItems); if (recursive) { - ProviderManager.OnRefreshProgress(this, 10); + ProviderManager.OnRefreshProgress(this, ProgressHelpers.UpdatedChildItems); } cancellationToken.ThrowIfCancellationRequested(); @@ -407,11 +403,13 @@ namespace MediaBrowser.Controller.Entities var innerProgress = new ActionableProgress<double>(); var folder = this; - innerProgress.RegisterAction(p => + innerProgress.RegisterAction(innerPercent => { - double newPct = 0.80 * p + 10; - progress.Report(newPct); - ProviderManager.OnRefreshProgress(folder, newPct); + var percent = ProgressHelpers.GetProgress(ProgressHelpers.UpdatedChildItems, ProgressHelpers.ScannedSubfolders, innerPercent); + + progress.Report(percent); + + ProviderManager.OnRefreshProgress(folder, percent); }); if (validChildrenNeedGeneration) @@ -425,11 +423,11 @@ namespace MediaBrowser.Controller.Entities if (refreshChildMetadata) { - progress.Report(90); + progress.Report(ProgressHelpers.ScannedSubfolders); if (recursive) { - ProviderManager.OnRefreshProgress(this, 90); + ProviderManager.OnRefreshProgress(this, ProgressHelpers.ScannedSubfolders); } var container = this as IMetadataContainer; @@ -437,13 +435,15 @@ namespace MediaBrowser.Controller.Entities var innerProgress = new ActionableProgress<double>(); var folder = this; - innerProgress.RegisterAction(p => + innerProgress.RegisterAction(innerPercent => { - double newPct = 0.10 * p + 90; - progress.Report(newPct); + var percent = ProgressHelpers.GetProgress(ProgressHelpers.ScannedSubfolders, ProgressHelpers.RefreshedMetadata, innerPercent); + + progress.Report(percent); + if (recursive) { - ProviderManager.OnRefreshProgress(folder, newPct); + ProviderManager.OnRefreshProgress(folder, percent); } }); @@ -458,55 +458,35 @@ namespace MediaBrowser.Controller.Entities validChildren = Children.ToList(); } - await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken); + await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken).ConfigureAwait(false); } } } - private async Task RefreshMetadataRecursive(List<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) + private Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - var numComplete = 0; - var count = children.Count; - double currentPercent = 0; - - foreach (var child in children) - { - cancellationToken.ThrowIfCancellationRequested(); - - var innerProgress = new ActionableProgress<double>(); - - // Avoid implicitly captured closure - var currentInnerPercent = currentPercent; - - innerProgress.RegisterAction(p => - { - double innerPercent = currentInnerPercent; - innerPercent += p / count; - progress.Report(innerPercent); - }); - - await RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken) - .ConfigureAwait(false); - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - currentPercent = percent; - - progress.Report(percent); - } + return RunTasks( + (baseItem, innerProgress) => RefreshChildMetadata(baseItem, refreshOptions, recursive && baseItem.IsFolder, innerProgress, cancellationToken), + children, + progress, + cancellationToken); } private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) { - var series = container as Series; - if (series != null) - { - await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - } + // limit the amount of concurrent metadata refreshes + await ProviderManager.RunMetadataRefresh( + async () => + { + var series = container as Series; + if (series != null) + { + await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + } - await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false); + await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false); + }, + cancellationToken).ConfigureAwait(false); } private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) @@ -521,12 +501,15 @@ namespace MediaBrowser.Controller.Entities { if (refreshOptions.RefreshItem(child)) { - await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + // limit the amount of concurrent metadata refreshes + await ProviderManager.RunMetadataRefresh( + async () => await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); } if (recursive && child is Folder folder) { - await folder.RefreshMetadataRecursive(folder.Children.ToList(), refreshOptions, true, progress, cancellationToken); + await folder.RefreshMetadataRecursive(folder.Children.ToList(), refreshOptions, true, progress, cancellationToken).ConfigureAwait(false); } } } @@ -539,39 +522,72 @@ namespace MediaBrowser.Controller.Entities /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) + private Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) { - var numComplete = 0; - var count = children.Count; - double currentPercent = 0; + return RunTasks( + (folder, innerProgress) => folder.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService), + children, + progress, + cancellationToken); + } - foreach (var child in children) + /// <summary> + /// Runs an action block on a list of children. + /// </summary> + /// <param name="task">The task to run for each child.</param> + /// <param name="children">The list of children.</param> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task RunTasks<T>(Func<T, IProgress<double>, Task> task, IList<T> children, IProgress<double> progress, CancellationToken cancellationToken) + { + var childrenCount = children.Count; + var childrenProgress = new double[childrenCount]; + + void UpdateProgress() { - cancellationToken.ThrowIfCancellationRequested(); + progress.Report(childrenProgress.Average()); + } - var innerProgress = new ActionableProgress<double>(); + var fanoutConcurrency = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency; + var parallelism = fanoutConcurrency == 0 ? Environment.ProcessorCount : fanoutConcurrency; - // Avoid implicitly captured closure - var currentInnerPercent = currentPercent; + var actionBlock = new ActionBlock<int>( + async i => + { + var innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(p => + innerProgress.RegisterAction(innerPercent => + { + // round the percent and only update progress if it changed to prevent excessive UpdateProgress calls + var innerPercentRounded = Math.Round(innerPercent); + if (childrenProgress[i] != innerPercentRounded) + { + childrenProgress[i] = innerPercentRounded; + UpdateProgress(); + } + }); + + await task(children[i], innerProgress).ConfigureAwait(false); + + childrenProgress[i] = 100; + + UpdateProgress(); + }, + new ExecutionDataflowBlockOptions { - double innerPercent = currentInnerPercent; - innerPercent += p / count; - progress.Report(innerPercent); + MaxDegreeOfParallelism = parallelism, + CancellationToken = cancellationToken, }); - await child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService) - .ConfigureAwait(false); + for (var i = 0; i < childrenCount; i++) + { + actionBlock.Post(i); + } - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - currentPercent = percent; + actionBlock.Complete(); - progress.Report(percent); - } + await actionBlock.Completion.ConfigureAwait(false); } /// <summary> @@ -1067,12 +1083,12 @@ namespace MediaBrowser.Controller.Entities return false; } - if (request.Genres.Length > 0) + if (request.Genres.Count > 0) { return false; } - if (request.GenreIds.Length > 0) + if (request.GenreIds.Count > 0) { return false; } @@ -1177,7 +1193,7 @@ namespace MediaBrowser.Controller.Entities return false; } - if (request.GenreIds.Length > 0) + if (request.GenreIds.Count > 0) { return false; } @@ -1768,5 +1784,45 @@ namespace MediaBrowser.Controller.Entities } } } + + /// <summary> + /// Contains constants used when reporting scan progress. + /// </summary> + private static class ProgressHelpers + { + /// <summary> + /// Reported after the folders immediate children are retrieved. + /// </summary> + public const int RetrievedChildren = 5; + + /// <summary> + /// Reported after add, updating, or deleting child items from the LibraryManager. + /// </summary> + public const int UpdatedChildItems = 10; + + /// <summary> + /// Reported once subfolders are scanned. + /// When scanning subfolders, the progress will be between [UpdatedItems, ScannedSubfolders]. + /// </summary> + public const int ScannedSubfolders = 50; + + /// <summary> + /// Reported once metadata is refreshed. + /// When refreshing metadata, the progress will be between [ScannedSubfolders, MetadataRefreshed]. + /// </summary> + public const int RefreshedMetadata = 100; + + /// <summary> + /// Gets the current progress given the previous step, next step, and progress in between. + /// </summary> + /// <param name="previousProgressStep">The previous progress step.</param> + /// <param name="nextProgressStep">The next progress step.</param> + /// <param name="currentProgress">The current progress step.</param> + /// <returns>The progress.</returns> + public static double GetProgress(int previousProgressStep, int nextProgressStep, double currentProgress) + { + return previousProgressStep + ((nextProgressStep - previousProgressStep) * (currentProgress / 100)); + } + } } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 904752a22..270217356 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Entities public string[] ExcludeInheritedTags { get; set; } - public string[] Genres { get; set; } + public IReadOnlyList<string> Genres { get; set; } public bool? IsSpecialSeason { get; set; } @@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.Entities public Guid[] StudioIds { get; set; } - public Guid[] GenreIds { get; set; } + public IReadOnlyList<Guid> GenreIds { get; set; } public ImageType[] ImageTypes { get; set; } @@ -162,7 +162,7 @@ namespace MediaBrowser.Controller.Entities public double? MinCommunityRating { get; set; } - public Guid[] ChannelIds { get; set; } + public IReadOnlyList<Guid> ChannelIds { get; set; } public int? ParentIndexNumber { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index a262fee15..4e33a6bbd 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -791,7 +791,7 @@ namespace MediaBrowser.Controller.Entities } // Apply genre filter - if (query.Genres.Length > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) + if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) { return false; } @@ -822,7 +822,7 @@ namespace MediaBrowser.Controller.Entities } // Apply genre filter - if (query.GenreIds.Length > 0 && !query.GenreIds.Any(id => + if (query.GenreIds.Count > 0 && !query.GenreIds.Any(id => { var genreItem = libraryManager.GetItemById(id); return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 07f381881..6320b01b8 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -143,26 +143,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The video3 D format.</value> public Video3DFormat? Video3DFormat { get; set; } - public string[] GetPlayableStreamFileNames() - { - var videoType = VideoType; - - if (videoType == VideoType.Iso && IsoType == Model.Entities.IsoType.BluRay) - { - videoType = VideoType.BluRay; - } - else if (videoType == VideoType.Iso && IsoType == Model.Entities.IsoType.Dvd) - { - videoType = VideoType.Dvd; - } - else - { - return Array.Empty<string>(); - } - - throw new NotImplementedException(); - } - /// <summary> /// Gets or sets the aspect ratio. /// </summary> @@ -415,31 +395,6 @@ namespace MediaBrowser.Controller.Entities return updateType; } - public static string[] QueryPlayableStreamFiles(string rootPath, VideoType videoType) - { - if (videoType == VideoType.Dvd) - { - return FileSystem.GetFiles(rootPath, new[] { ".vob" }, false, true) - .OrderByDescending(i => i.Length) - .ThenBy(i => i.FullName) - .Take(1) - .Select(i => i.FullName) - .ToArray(); - } - - if (videoType == VideoType.BluRay) - { - return FileSystem.GetFiles(rootPath, new[] { ".m2ts" }, false, true) - .OrderByDescending(i => i.Length) - .ThenBy(i => i.FullName) - .Take(1) - .Select(i => i.FullName) - .ToArray(); - } - - return Array.Empty<string>(); - } - /// <summary> /// Gets a value indicating whether [is3 D]. /// </summary> diff --git a/MediaBrowser.Controller/IResourceFileManager.cs b/MediaBrowser.Controller/IResourceFileManager.cs deleted file mode 100644 index 26f0424b7..000000000 --- a/MediaBrowser.Controller/IResourceFileManager.cs +++ /dev/null @@ -1,9 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Controller -{ - public interface IResourceFileManager - { - string GetResourcePath(string basePath, string virtualPath); - } -} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index ffbb147b0..2456da826 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { @@ -56,41 +57,42 @@ namespace MediaBrowser.Controller /// <summary> /// Gets the system info. /// </summary> - /// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param> + /// <param name="source">The originator of the request.</param> /// <returns>SystemInfo.</returns> - Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken = default); + SystemInfo GetSystemInfo(IPAddress source); - Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken = default); + PublicSystemInfo GetPublicSystemInfo(IPAddress address); /// <summary> - /// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request - /// to the API that should exist at the address. + /// Gets a URL specific for the request. /// </summary> - /// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param> - /// <returns>A list containing all the local IP addresses of the server.</returns> - Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken = default); + /// <param name="request">The <see cref="HttpRequest"/> instance.</param> + /// <param name="port">Optional port number.</param> + /// <returns>An accessible URL.</returns> + string GetSmartApiUrl(HttpRequest request, int? port = null); /// <summary> - /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured - /// IP address that can be found via <see cref="GetLocalIpAddresses"/>. HTTPS will be preferred when available. + /// Gets a URL specific for the request. /// </summary> - /// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param> - /// <returns>The server URL.</returns> - Task<string> GetLocalApiUrl(CancellationToken cancellationToken = default); + /// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param> + /// <param name="port">Optional port number.</param> + /// <returns>An accessible URL.</returns> + string GetSmartApiUrl(IPAddress remoteAddr, int? port = null); /// <summary> - /// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1) - /// over HTTP (not HTTPS). + /// Gets a URL specific for the request. /// </summary> - /// <returns>The API URL.</returns> - string GetLoopbackHttpApiUrl(); + /// <param name="hostname">The hostname used in the connection.</param> + /// <param name="port">Optional port number.</param> + /// <returns>An accessible URL.</returns> + string GetSmartApiUrl(string hostname, int? port = null); /// <summary> - /// Gets a local (LAN) URL that can be used to access the API. HTTPS will be preferred when available. + /// Gets a localhost URL that can be used to access the API using the loop-back IP address. + /// over HTTP (not HTTPS). /// </summary> - /// <param name="address">The IP address to use as the hostname in the URL.</param> /// <returns>The API URL.</returns> - string GetLocalApiUrl(IPAddress address); + string GetLoopbackHttpApiUrl(); /// <summary> /// Gets a local (LAN) URL that can be used to access the API. @@ -106,7 +108,7 @@ namespace MediaBrowser.Controller /// preferring the HTTPS port, if available. /// </param> /// <returns>The API URL.</returns> - string GetLocalApiUrl(ReadOnlySpan<char> hostname, string scheme = null, int? port = null); + string GetLocalApiUrl(string hostname, string scheme = null, int? port = null); /// <summary> /// Open a URL in an external browser window. diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index c7c79df76..24b101694 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -571,6 +571,10 @@ namespace MediaBrowser.Controller.Library string videoPath, string[] files); + void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason); + BaseItem GetParentItem(string parentId, Guid? userId); + + BaseItem GetParentItem(Guid? parentId, Guid? userId); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 9acc98dce..5f75df54e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -17,6 +17,7 @@ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/> + <PackageReference Include="System.Threading.Tasks.Dataflow" Version="5.0.0" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9a6f1231f..91a03e647 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -112,6 +112,16 @@ namespace MediaBrowser.Controller.MediaEncoding return _mediaEncoder.SupportsHwaccel("vaapi"); } + private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + { + var videoStream = state.VideoStream; + return IsColorDepth10(state) + && _mediaEncoder.SupportsHwaccel("opencl") + && options.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase); + } + /// <summary> /// Gets the name of the output video codec. /// </summary> @@ -380,25 +390,9 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetInputPathArgument(EncodingJobInfo state) { - var protocol = state.InputProtocol; var mediaPath = state.MediaPath ?? string.Empty; - string[] inputPath; - if (state.IsInputVideo - && !(state.VideoType == VideoType.Iso && state.IsoMount == null)) - { - inputPath = MediaEncoderHelpers.GetInputArgument( - _fileSystem, - mediaPath, - state.IsoMount, - state.PlayableStreamFileNames); - } - else - { - inputPath = new[] { mediaPath }; - } - - return _mediaEncoder.GetInputArgument(inputPath, protocol); + return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource); } /// <summary> @@ -468,6 +462,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions); if (!IsCopyCodec(outputVideoCodec)) { @@ -477,10 +472,23 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isVaapiDecoder) { - arg.Append("-hwaccel_output_format vaapi ") - .Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) - .Append(' '); + if (isTonemappingSupported) + { + arg.Append("-init_hw_device vaapi=va:") + .Append(encodingOptions.VaapiDevice) + .Append(' ') + .Append("-init_hw_device opencl=ocl@va ") + .Append("-hwaccel_device va ") + .Append("-hwaccel_output_format vaapi ") + .Append("-filter_hw_device ocl "); + } + else + { + arg.Append("-hwaccel_output_format vaapi ") + .Append("-vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(' '); + } } else if (!isVaapiDecoder && isVaapiEncoder) { @@ -529,13 +537,7 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) { - var isColorDepth10 = IsColorDepth10(state); - - if (isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && encodingOptions.EnableTonemapping - && !string.IsNullOrEmpty(state.VideoStream.VideoRange) - && state.VideoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + if (isTonemappingSupported) { arg.Append("-init_hw_device opencl=ocl:") .Append(encodingOptions.OpenclDevice) @@ -1866,6 +1868,19 @@ namespace MediaBrowser.Controller.MediaEncoding var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isTonemappingSupported = IsTonemappingSupported(state, options); + var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); + + // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. + // But it's still in ffmpeg mailing list. Disable it for now. + if (isTonemappingSupported && isTonemappingSupportedOnVaapi) + { + return GetOutputSizeParam(state, options, outputVideoCodec); + } + // Setup subtitle scaling if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) { @@ -1997,6 +2012,7 @@ namespace MediaBrowser.Controller.MediaEncoding public List<string> GetScalingFilters( EncodingJobInfo state, + EncodingOptions options, int? videoWidth, int? videoHeight, Video3DFormat? threedFormat, @@ -2035,6 +2051,19 @@ namespace MediaBrowser.Controller.MediaEncoding || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var isTonemappingSupported = IsTonemappingSupported(state, options); + var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !qsv_or_vaapi; + + var outputPixFmt = string.Empty; + if (isTonemappingSupported && isTonemappingSupportedOnVaapi) + { + outputPixFmt = "format=p010:out_range=limited"; + } + else + { + outputPixFmt = "format=nv12"; + } + if (!videoWidth.HasValue || outputWidth != videoWidth.Value || !videoHeight.HasValue @@ -2045,10 +2074,11 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add( string.Format( CultureInfo.InvariantCulture, - "{0}=w={1}:h={2}:format=nv12{3}", + "{0}=w={1}:h={2}{3}{4}", qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", outputWidth, outputHeight, + ":" + outputPixFmt, (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } else @@ -2056,8 +2086,9 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add( string.Format( CultureInfo.InvariantCulture, - "{0}=format=nv12{1}", + "{0}={1}{2}", qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", + outputPixFmt, (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } } @@ -2290,6 +2321,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isSwDecoder = string.IsNullOrEmpty(videoDecoder); var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; @@ -2300,6 +2332,10 @@ namespace MediaBrowser.Controller.MediaEncoding var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isColorDepth10 = IsColorDepth10(state); + var isTonemappingSupported = IsTonemappingSupported(state, options); + var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || 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 hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2311,18 +2347,14 @@ namespace MediaBrowser.Controller.MediaEncoding var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - if ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) - || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) + if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || isTonemappingSupportedOnVaapi) { // 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. - if (isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && options.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + // Intel Kaby Lake or newer is required. + if (isTonemappingSupported) { var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; @@ -2355,10 +2387,35 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("format=p010"); } - // Upload the HDR10 or HLG data to the OpenCL device, - // use tonemap_opencl filter for tone mapping, - // and then download the SDR data to memory. - filters.Add("hwupload"); + if (isNvdecHevcDecoder || isSwDecoder || isD3d11vaDecoder) + { + // Upload the HDR10 or HLG data to the OpenCL device, + // use tonemap_opencl filter for tone mapping, + // and then download the SDR data to memory. + filters.Add("hwupload"); + } + + if (isVaapiDecoder) + { + isScalingInAdvance = true; + filters.AddRange( + GetScalingFilters( + state, + options, + inputWidth, + inputHeight, + threeDFormat, + videoDecoder, + outputVideoCodec, + request.Width, + request.Height, + request.MaxWidth, + request.MaxHeight)); + + // hwmap the HDR data to opencl device by cl-va p010 interop. + filters.Add("hwmap"); + } + filters.Add( string.Format( CultureInfo.InvariantCulture, @@ -2369,33 +2426,46 @@ namespace MediaBrowser.Controller.MediaEncoding options.TonemappingPeak, options.TonemappingParam, options.TonemappingRange)); - filters.Add("hwdownload"); - if (isLibX264Encoder - || isLibX265Encoder - || hasGraphicalSubs - || (isNvdecHevcDecoder && isDeinterlaceHevc) - || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc)) + if (isNvdecHevcDecoder || isSwDecoder || isD3d11vaDecoder) { - filters.Add("format=nv12"); + filters.Add("hwdownload"); + } + + if (isSwDecoder || isD3d11vaDecoder) + { + if (isLibX264Encoder + || isLibX265Encoder + || hasGraphicalSubs + || (isNvdecHevcDecoder && isDeinterlaceHevc) + || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc)) + { + filters.Add("format=nv12"); + } + } + + if (isVaapiDecoder) + { + // Reverse the data route from opencl to vaapi. + filters.Add("hwmap=derive_device=vaapi:reverse=1"); } } } - // When the input may or may not be hardware VAAPI decodable - if (isVaapiH264Encoder || isVaapiHevcEncoder) + // When the input may or may not be hardware VAAPI decodable. + if ((isVaapiH264Encoder || isVaapiHevcEncoder) && !isTonemappingSupported && !isTonemappingSupportedOnVaapi) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); } - // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context + // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context. else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder)) { filters.Add("hwupload=extra_hw_frames=64"); } - // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first + // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first. else if (IsVaapiSupported(state) && isVaapiDecoder && (isLibX264Encoder || isLibX265Encoder)) { var codec = videoStream.Codec.ToLowerInvariant(); @@ -2422,7 +2492,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - // Add hardware deinterlace filter before scaling filter + // Add hardware deinterlace filter before scaling filter. if (isDeinterlaceH264) { if (isVaapiH264Encoder) @@ -2435,7 +2505,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - // Add software deinterlace filter before scaling filter + // Add software deinterlace filter before scaling filter. if ((isDeinterlaceH264 || isDeinterlaceHevc) && !isVaapiH264Encoder && !isVaapiHevcEncoder @@ -2467,6 +2537,7 @@ namespace MediaBrowser.Controller.MediaEncoding filters.AddRange( GetScalingFilters( state, + options, inputWidth, inputHeight, threeDFormat, @@ -2483,6 +2554,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasTextSubs) { + // Convert hw context from ocl to va. + // For tonemapping and text subs burn-in. + if (isTonemappingSupported && isTonemappingSupportedOnVaapi) + { + filters.Add("scale_vaapi"); + } + // Test passed on Intel and AMD gfx filters.Add("hwmap=mode=read+write"); filters.Add("format=nv12"); @@ -2579,18 +2657,10 @@ namespace MediaBrowser.Controller.MediaEncoding } } - public string GetProbeSizeArgument(int numInputFiles) - => numInputFiles > 1 ? "-probesize " + _configuration.GetFFmpegProbeSize() : string.Empty; - - public string GetAnalyzeDurationArgument(int numInputFiles) - => numInputFiles > 1 ? "-analyzeduration " + _configuration.GetFFmpegAnalyzeDuration() : string.Empty; - public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) { var inputModifier = string.Empty; - - var numInputFiles = state.PlayableStreamFileNames.Length > 0 ? state.PlayableStreamFileNames.Length : 1; - var probeSizeArgument = GetProbeSizeArgument(numInputFiles); + var probeSizeArgument = string.Empty; string analyzeDurationArgument; if (state.MediaSource.AnalyzeDurationMs.HasValue) @@ -2599,7 +2669,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - analyzeDurationArgument = GetAnalyzeDurationArgument(numInputFiles); + analyzeDurationArgument = string.Empty; } if (!string.IsNullOrEmpty(probeSizeArgument)) @@ -2783,32 +2853,6 @@ namespace MediaBrowser.Controller.MediaEncoding state.IsoType = mediaSource.IsoType; - if (mediaSource.VideoType.HasValue) - { - state.VideoType = mediaSource.VideoType.Value; - - if (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd) - { - state.PlayableStreamFileNames = Video.QueryPlayableStreamFiles(state.MediaPath, mediaSource.VideoType.Value).Select(Path.GetFileName).ToArray(); - } - else if (mediaSource.VideoType.Value == VideoType.Iso && state.IsoType == IsoType.BluRay) - { - state.PlayableStreamFileNames = Video.QueryPlayableStreamFiles(state.MediaPath, VideoType.BluRay).Select(Path.GetFileName).ToArray(); - } - else if (mediaSource.VideoType.Value == VideoType.Iso && state.IsoType == IsoType.Dvd) - { - state.PlayableStreamFileNames = Video.QueryPlayableStreamFiles(state.MediaPath, VideoType.Dvd).Select(Path.GetFileName).ToArray(); - } - else - { - state.PlayableStreamFileNames = Array.Empty<string>(); - } - } - else - { - state.PlayableStreamFileNames = Array.Empty<string>(); - } - if (mediaSource.Timestamp.HasValue) { state.InputTimestamp = mediaSource.Timestamp.Value; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 52794a69b..dacd6dea6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -33,10 +33,6 @@ namespace MediaBrowser.Controller.MediaEncoding public bool IsInputVideo { get; set; } - public IIsoMount IsoMount { get; set; } - - public string[] PlayableStreamFileNames { get; set; } - public string OutputAudioCodec { get; set; } public int? OutputVideoBitrate { get; set; } @@ -313,7 +309,6 @@ namespace MediaBrowser.Controller.MediaEncoding { TranscodingType = jobType; RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - PlayableStreamFileNames = Array.Empty<string>(); SupportedAudioCodecs = Array.Empty<string>(); SupportedVideoCodecs = Array.Empty<string>(); SupportedSubtitleCodecs = Array.Empty<string>(); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index f6bc1f4de..e7f042d2f 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -61,18 +62,18 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Extracts the video image. /// </summary> - Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); + Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); - Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken); + Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken); /// <summary> /// Extracts the video images on interval. /// </summary> Task ExtractVideoImagesOnInterval( - string[] inputFiles, + string inputFile, string container, MediaStream videoStream, - MediaProtocol protocol, + MediaSourceInfo mediaSource, Video3DFormat? threedFormat, TimeSpan interval, string targetDirectory, @@ -91,10 +92,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the input argument. /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="protocol">The protocol.</param> + /// <param name="inputFile">The input file.</param> + /// <param name="mediaSource">The mediaSource.</param> /// <returns>System.String.</returns> - string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol); + string GetInputArgument(string inputFile, MediaSourceInfo mediaSource); /// <summary> /// Gets the time parameter. diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index ce53c23ad..281d50372 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -13,38 +13,5 @@ namespace MediaBrowser.Controller.MediaEncoding /// </summary> public static class MediaEncoderHelpers { - /// <summary> - /// Gets the input argument. - /// </summary> - /// <param name="fileSystem">The file system.</param> - /// <param name="videoPath">The video path.</param> - /// <param name="isoMount">The iso mount.</param> - /// <param name="playableStreamFileNames">The playable stream file names.</param> - /// <returns>string[].</returns> - public static string[] GetInputArgument(IFileSystem fileSystem, string videoPath, IIsoMount isoMount, IReadOnlyCollection<string> playableStreamFileNames) - { - if (playableStreamFileNames.Count > 0) - { - if (isoMount == null) - { - return GetPlayableStreamFiles(fileSystem, videoPath, playableStreamFileNames); - } - - return GetPlayableStreamFiles(fileSystem, isoMount.MountedPath, playableStreamFileNames); - } - - return new[] { videoPath }; - } - - private static string[] GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames) - { - var allFiles = fileSystem - .GetFilePaths(rootPath, true) - .ToArray(); - - return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase))) - .Where(f => !string.IsNullOrEmpty(f)) - .ToArray(); - } } } diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs index 59729de49..2cb04bdc4 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs @@ -1,9 +1,7 @@ #pragma warning disable CS1591 -using System; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.MediaEncoding { @@ -14,14 +12,5 @@ namespace MediaBrowser.Controller.MediaEncoding public bool ExtractChapters { get; set; } public DlnaProfileType MediaType { get; set; } - - public IIsoMount MountedIso { get; set; } - - public string[] PlayableStreamFileNames { get; set; } - - public MediaInfoRequest() - { - PlayableStreamFileNames = Array.Empty<string>(); - } } } diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 0194c596f..93573e08e 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -58,5 +58,10 @@ namespace MediaBrowser.Controller.Net /// Gets or sets a value indicating whether the token is authenticated. /// </summary> public bool IsAuthenticated { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the request has a token. + /// </summary> + public bool HasToken { get; set; } } } diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index fbf2c5213..f6c592070 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Playlists /// <param name="itemIds">The item ids.</param> /// <param name="userId">The user identifier.</param> /// <returns>Task.</returns> - Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId); + Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId); /// <summary> /// Removes from playlist. diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 996ec27c0..0a4967223 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -46,6 +46,14 @@ namespace MediaBrowser.Controller.Providers Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken); /// <summary> + /// Runs multiple metadata refreshes concurrently. + /// </summary> + /// <param name="action">The action to run.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> + Task RunMetadataRefresh(Func<Task> action, CancellationToken cancellationToken); + + /// <summary> /// Saves the image. /// </summary> /// <param name="item">The item.</param> diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index ce58a60b9..d09852870 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading; @@ -54,7 +55,7 @@ namespace MediaBrowser.Controller.Session /// Gets or sets the playable media types. /// </summary> /// <value>The playable media types.</value> - public string[] PlayableMediaTypes + public IReadOnlyList<string> PlayableMediaTypes { get { @@ -230,7 +231,7 @@ namespace MediaBrowser.Controller.Session /// Gets or sets the supported commands. /// </summary> /// <value>The supported commands.</value> - public GeneralCommandType[] SupportedCommands + public IReadOnlyList<GeneralCommandType> SupportedCommands => Capabilities == null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands; public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory) |
