diff options
9 files changed, 164 insertions, 89 deletions
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1c03e11dd..8c2895701 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -146,7 +146,6 @@ <Compile Include="Providers\FanartBaseProvider.cs" /> <Compile Include="Providers\IImageEnhancer.cs" /> <Compile Include="Providers\ImagesByNameProvider.cs" /> - <Compile Include="Providers\MediaInfo\BaseFFMpegImageProvider.cs" /> <Compile Include="Providers\MediaInfo\BaseFFMpegProvider.cs" /> <Compile Include="Providers\MediaInfo\FFMpegAudioImageProvider.cs" /> <Compile Include="Providers\MediaInfo\BaseFFProbeProvider.cs" /> diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index 3a4f3cae7..f0a960ce1 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -735,16 +735,16 @@ namespace MediaBrowser.Controller.MediaInfo /// <summary> /// Extracts an image from an Audio file and returns a Task whose result indicates whether it was successful or not /// </summary> - /// <param name="input">The input.</param> + /// <param name="inputPath">The input path.</param> /// <param name="outputPath">The output path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> /// <exception cref="System.ArgumentNullException">input</exception> - public async Task<bool> ExtractImage(Audio input, string outputPath, CancellationToken cancellationToken) + public async Task<bool> ExtractAudioImage(string inputPath, string outputPath, CancellationToken cancellationToken) { - if (input == null) + if (string.IsNullOrEmpty(inputPath)) { - throw new ArgumentNullException("input"); + throw new ArgumentNullException("inputPath"); } if (string.IsNullOrEmpty(outputPath)) @@ -759,7 +759,7 @@ namespace MediaBrowser.Controller.MediaInfo CreateNoWindow = true, UseShellExecute = false, FileName = FFMpegPath, - Arguments = string.Format("-i {0} -threads 0 -v quiet -f image2 \"{1}\"", GetInputArgument(input), outputPath), + Arguments = string.Format("-i {0} -threads 0 -v quiet -f image2 \"{1}\"", GetFileInputArgument(inputPath), outputPath), WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = false } @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.MediaInfo return true; } - _logger.Error("ffmpeg audio image extraction failed for {0}", input.Path); + _logger.Error("ffmpeg audio image extraction failed for {0}", inputPath); return false; } @@ -1040,6 +1040,16 @@ namespace MediaBrowser.Controller.MediaInfo } /// <summary> + /// Gets the file input argument. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>System.String.</returns> + private string GetFileInputArgument(string path) + { + return string.Format("file:\"{0}\"", path); + } + + /// <summary> /// Gets the input argument. /// </summary> /// <param name="item">The item.</param> diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegImageProvider.cs deleted file mode 100644 index 902fdca27..000000000 --- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegImageProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.Controller.Providers.MediaInfo -{ - public abstract class BaseFFMpegImageProvider<T> : BaseFFMpegProvider<T> - where T : BaseItem - { - protected BaseFFMpegImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) - { - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Last; } - } - } -} diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs index 20f59b22d..6edd28b24 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs @@ -2,9 +2,9 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.Threading.Tasks; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Controller.Providers.MediaInfo { diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs index e752a863d..68e552d3c 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs @@ -1,21 +1,23 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Concurrent; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Controller.Providers.MediaInfo { /// <summary> /// Uses ffmpeg to create video images /// </summary> - public class FFMpegAudioImageProvider : BaseFFMpegImageProvider<Audio> + public class FFMpegAudioImageProvider : BaseFFMpegProvider<Audio> { - public FFMpegAudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) + public FFMpegAudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) { } @@ -25,67 +27,101 @@ namespace MediaBrowser.Controller.Providers.MediaInfo protected static readonly Task<bool> TrueTaskResult = Task.FromResult(true); /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Last; } + } + + /// <summary> + /// The _locks + /// </summary> + private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); + + /// <summary> + /// Gets the lock. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns>System.Object.</returns> + private SemaphoreSlim GetLock(string filename) + { + return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + /// <summary> /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// </summary> /// <param name="item">The item.</param> /// <param name="force">if set to <c>true</c> [force].</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> - public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - var audio = (Audio)item; + var success = ProviderRefreshStatus.Success; - if (string.IsNullOrEmpty(audio.PrimaryImagePath)) + if (force || string.IsNullOrEmpty(item.PrimaryImagePath)) { - // First try to use the parent's image - audio.PrimaryImagePath = audio.ResolveArgs.Parent.PrimaryImagePath; + var album = item.ResolveArgs.Parent as MusicAlbum; + + if (album != null) + { + // First try to use the parent's image + item.PrimaryImagePath = item.ResolveArgs.Parent.PrimaryImagePath; + } // If it's still empty see if there's an embedded image - if (string.IsNullOrEmpty(audio.PrimaryImagePath)) + if (force || string.IsNullOrEmpty(item.PrimaryImagePath)) { + var audio = (Audio)item; + if (audio.MediaStreams != null && audio.MediaStreams.Any(s => s.Type == MediaStreamType.Video)) { - var filename = item.Id + "_" + item.DateModified.Ticks + "_primary"; + var filename = album != null && string.IsNullOrEmpty(audio.Album + album.DateModified.Ticks) ? (audio.Id.ToString() + audio.DateModified.Ticks) : audio.Album; - var path = Kernel.Instance.FFMpegManager.AudioImageCache.GetResourcePath(filename, ".jpg"); + var path = Kernel.Instance.FFMpegManager.AudioImageCache.GetResourcePath(filename + "_primary", ".jpg"); if (!Kernel.Instance.FFMpegManager.AudioImageCache.ContainsFilePath(path)) { - return ExtractImage(audio, path, cancellationToken); - } - - // Image is already in the cache - audio.PrimaryImagePath = path; - } + var semaphore = GetLock(path); - } - } + // Acquire a lock + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - SetLastRefreshed(item, DateTime.UtcNow); - return TrueTaskResult; - } + // Check again + if (!Kernel.Instance.FFMpegManager.AudioImageCache.ContainsFilePath(path)) + { + try + { + var imageSucceeded = await Kernel.Instance.FFMpegManager.ExtractAudioImage(audio.Path, path, cancellationToken).ConfigureAwait(false); - /// <summary> - /// Extracts the image. - /// </summary> - /// <param name="audio">The audio.</param> - /// <param name="path">The path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - private async Task<bool> ExtractImage(Audio audio, string path, CancellationToken cancellationToken) - { - var success = await Kernel.Instance.FFMpegManager.ExtractImage(audio, path, cancellationToken).ConfigureAwait(false); + if (!imageSucceeded) + { + success = ProviderRefreshStatus.Failure; + } + } + finally + { + semaphore.Release(); + } + } + else + { + semaphore.Release(); + } + } - if (success) - { - audio.PrimaryImagePath = path; - SetLastRefreshed(audio, DateTime.UtcNow); - } - else - { - SetLastRefreshed(audio, DateTime.UtcNow, ProviderRefreshStatus.Failure); + if (success == ProviderRefreshStatus.Success) + { + // Image is already in the cache + audio.PrimaryImagePath = path; + } + } + } } + SetLastRefreshed(item, DateTime.UtcNow, success); return true; } } diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs index 3d258fe66..9dec93a8c 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// <summary> /// Uses ffmpeg to create video images /// </summary> - public class FfMpegVideoImageProvider : BaseFFMpegImageProvider<Video> + public class FfMpegVideoImageProvider : BaseFFMpegProvider<Video> { /// <summary> /// The _iso manager @@ -30,6 +30,15 @@ namespace MediaBrowser.Controller.Providers.MediaInfo : base(logManager, configurationManager) { _isoManager = isoManager; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Last; } } /// <summary> @@ -80,7 +89,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// <returns>Task{System.Boolean}.</returns> public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(item.PrimaryImagePath)) + if (force || string.IsNullOrEmpty(item.PrimaryImagePath)) { var video = (Video)item; diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs index a7cc4985b..e5f57d704 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo // There's several values in tags may or may not be present FetchStudios(audio, tags, "organization"); FetchStudios(audio, tags, "ensemble"); - FetchStudios(audio, tags, "publisher"); + FetchPublishers(audio, tags, "publisher"); } /// <summary> @@ -169,6 +169,22 @@ namespace MediaBrowser.Controller.Providers.MediaInfo } /// <summary> + /// Fetches the publishers. + /// </summary> + /// <param name="audio">The audio.</param> + /// <param name="tags">The tags.</param> + /// <param name="tagName">Name of the tag.</param> + private void FetchPublishers(Audio audio, Dictionary<string, string> tags, string tagName) + { + var val = GetDictionaryValue(tags, tagName); + + if (!string.IsNullOrEmpty(val)) + { + audio.AddPublishers(val.Split(new[] { '/', '|' }, StringSplitOptions.RemoveEmptyEntries)); + } + } + + /// <summary> /// Gets the genres from the tags collection /// </summary> /// <param name="audio">The audio.</param> diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index a57eb8eaa..9bc47a025 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -126,6 +126,9 @@ namespace MediaBrowser.Server.Implementations.Library } } + private ConcurrentDictionary<string, UserRootFolder> _userRootFolders = + new ConcurrentDictionary<string, UserRootFolder>(); + /// <summary> /// Initializes a new instance of the <see cref="LibraryManager" /> class. /// </summary> @@ -485,6 +488,16 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> + /// Gets the user root folder. + /// </summary> + /// <param name="userRootPath">The user root path.</param> + /// <returns>UserRootFolder.</returns> + public UserRootFolder GetUserRootFolder(string userRootPath) + { + return _userRootFolders.GetOrAdd(userRootPath, key => Kernel.ItemRepository.RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder))) as UserRootFolder ?? (UserRootFolder)ResolvePath(userRootPath)); + } + + /// <summary> /// Gets a Person /// </summary> /// <param name="name">The name.</param> @@ -739,15 +752,32 @@ namespace MediaBrowser.Server.Implementations.Library // Start by just validating the children of the root, but go no further await RootFolder.ValidateChildren(new Progress<double> { }, cancellationToken, recursive: false); - // Validate only the collection folders for each user, just to make them available as quickly as possible - var userCollectionFolderTasks = _userManager.Users.AsParallel().Select(user => user.ValidateCollectionFolders(new Progress<double> { }, cancellationToken)); - await Task.WhenAll(userCollectionFolderTasks).ConfigureAwait(false); + foreach (var folder in _userManager.Users.Select(u => u.RootFolder).Distinct()) + { + await ValidateCollectionFolders(folder, cancellationToken).ConfigureAwait(false); + } // Now validate the entire media library await RootFolder.ValidateChildren(progress, cancellationToken, recursive: true).ConfigureAwait(false); } /// <summary> + /// Validates only the collection folders for a User and goes no further + /// </summary> + /// <param name="userRootFolder">The user root folder.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ValidateCollectionFolders(UserRootFolder userRootFolder, CancellationToken cancellationToken) + { + _logger.Info("Validating collection folders within {0}", userRootFolder.Path); + await userRootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + await userRootFolder.ValidateChildren(new Progress<double> { }, cancellationToken, recursive: false).ConfigureAwait(false); + } + + /// <summary> /// Gets the default view. /// </summary> /// <returns>IEnumerable{VirtualFolderInfo}.</returns> diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index df6e06174..dced1ce28 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -136,26 +136,24 @@ namespace MediaBrowser.Server.Implementations.Providers } var supportedProvidersHash = string.Join("+", supportedProviders.Select(i => i.GetType().Name)).GetMD5(); - bool providersChanged; + bool providersChanged = false; item.ProviderData.TryGetValue(SupportedProvidersKey, out supportedProvidersInfo); - if (supportedProvidersInfo == null) - { - // First time - supportedProvidersInfo = new BaseProviderInfo { ProviderId = SupportedProvidersKey, FileSystemStamp = supportedProvidersHash }; - providersChanged = force = true; - } - else + if (supportedProvidersInfo != null) { // Force refresh if the supported providers have changed providersChanged = force = force || supportedProvidersInfo.FileSystemStamp != supportedProvidersHash; + + // If providers have changed, clear provider info and update the supported providers hash + if (providersChanged) + { + _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name); + item.ProviderData.Clear(); + } } - // If providers have changed, clear provider info and update the supported providers hash if (providersChanged) { - _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name); - item.ProviderData.Clear(); supportedProvidersInfo.FileSystemStamp = supportedProvidersHash; } |
