diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-05 23:39:16 -0500 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-05 23:39:16 -0500 |
| commit | 821a3d29a228feaa3ac4d36c58ee478a405e0481 (patch) | |
| tree | 48925fa069c1b57cc3c126b58194740c191359c1 /MediaBrowser.Providers | |
| parent | 64eb8c82a3e82d84ac827aa35a55fdface9ac783 (diff) | |
converted movie providers to new system
Diffstat (limited to 'MediaBrowser.Providers')
58 files changed, 1270 insertions, 4224 deletions
diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs index 3b6439b4b4..40f9fded5a 100644 --- a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs +++ b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs @@ -5,11 +5,10 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Movies; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.AdultVideos { - class AdultVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<AdultVideo> + class AdultVideoXmlProvider : BaseXmlProvider<AdultVideo> { private readonly ILogger _logger; @@ -19,41 +18,14 @@ namespace MediaBrowser.Providers.AdultVideos _logger = logger; } - public async Task<MetadataResult<AdultVideo>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(AdultVideo item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<AdultVideo>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - result.Item = new AdultVideo(); - - new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); - result.HasMetadata = true; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new MovieXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return MovieXmlProvider.GetXmlFileInfo(path, FileSystem); + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } } } diff --git a/MediaBrowser.Providers/All/LocalImageProvider.cs b/MediaBrowser.Providers/All/LocalImageProvider.cs index 089cfe549c..daae58ff6a 100644 --- a/MediaBrowser.Providers/All/LocalImageProvider.cs +++ b/MediaBrowser.Providers/All/LocalImageProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.All if (locationType == LocationType.FileSystem) { // Episode has it's own provider - if (item is Episode || item is Audio) + if (item.IsOwnedItem || item is Episode || item is Audio) { return false; } diff --git a/MediaBrowser.Providers/BaseXmlProvider.cs b/MediaBrowser.Providers/BaseXmlProvider.cs index 68b0034807..521198e969 100644 --- a/MediaBrowser.Providers/BaseXmlProvider.cs +++ b/MediaBrowser.Providers/BaseXmlProvider.cs @@ -3,32 +3,81 @@ using MediaBrowser.Controller.Providers; using System; using System.IO; using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Providers { - public abstract class BaseXmlProvider: IHasChangeMonitor + public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor + where T : IHasMetadata, new() { - protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4); - protected IFileSystem FileSystem; + public async Task<MetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult<T>(); + + var file = GetXmlFile(info); + + if (file == null) + { + return result; + } + + var path = file.FullName; + + await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + result.Item = new T(); + + Fetch(result.Item, path, cancellationToken); + result.HasMetadata = true; + } + catch (FileNotFoundException) + { + result.HasMetadata = false; + } + finally + { + XmlProviderUtils.XmlParsingResourcePool.Release(); + } + + return result; + } + + protected abstract void Fetch(T item, string path, CancellationToken cancellationToken); + protected BaseXmlProvider(IFileSystem fileSystem) { FileSystem = fileSystem; } - protected abstract FileInfo GetXmlFile(string path); + protected abstract FileInfo GetXmlFile(ItemInfo info); public bool HasChanged(IHasMetadata item, DateTime date) { - var file = GetXmlFile(item.Path); + var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path }); + + if (file == null) + { + return false; + } return FileSystem.GetLastWriteTimeUtc(file) > date; } - public bool HasLocalMetadata(IHasMetadata item) + public string Name { - return GetXmlFile(item.Path).Exists; + get + { + return "Media Browser Xml"; + } } } + + static class XmlProviderUtils + { + internal static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4); + } } diff --git a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs index eee6f3b487..a214dff8c1 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs @@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.BoxSets { /// <summary> /// Class BoxSetXmlProvider. /// </summary> - public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet> + public class BoxSetXmlProvider : BaseXmlProvider<BoxSet> { private readonly ILogger _logger; @@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.BoxSets _logger = logger; } - public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(BoxSet item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<BoxSet>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new BoxSet(); - - new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "collection.xml")); + return new FileInfo(Path.Combine(info.Path, "collection.xml")); } } } diff --git a/MediaBrowser.Providers/CollectionFolderImageProvider.cs b/MediaBrowser.Providers/CollectionFolderImageProvider.cs deleted file mode 100644 index e4ea36dd12..0000000000 --- a/MediaBrowser.Providers/CollectionFolderImageProvider.cs +++ /dev/null @@ -1,56 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MediaBrowser.Providers -{ - public class CollectionFolderImageProvider : ImageFromMediaLocationProvider - { - public CollectionFolderImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager, fileSystem) - { - } - - public override bool Supports(BaseItem item) - { - return item is CollectionFolder && item.LocationType == LocationType.FileSystem; - } - - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Second; } - } - - protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension) - { - return item.PhysicalLocations - .Select(i => GetImageFromLocation(i, filenameWithoutExtension)) - .FirstOrDefault(i => i != null); - } - - protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items) - { - var files = items.SelectMany(i => i.PhysicalLocations) - .Select(i => new DirectoryInfo(i)) - .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) - .Where(i => - { - var ext = i.Extension; - - return !string.IsNullOrEmpty(ext) && - BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - }) - .ToList(); - - return GetFileSystemStamp(files); - } - } -} diff --git a/MediaBrowser.Providers/FolderProviderFromXml.cs b/MediaBrowser.Providers/FolderProviderFromXml.cs deleted file mode 100644 index 31e4bc8bcf..0000000000 --- a/MediaBrowser.Providers/FolderProviderFromXml.cs +++ /dev/null @@ -1,93 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers -{ - /// <summary> - /// Provides metadata for Folders and all subclasses by parsing folder.xml - /// </summary> - public class FolderProviderFromXml : BaseMetadataProvider - { - private readonly IFileSystem _fileSystem; - - public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - _fileSystem = fileSystem; - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item.IsFolder && item.LocationType == LocationType.FileSystem; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - - private const string XmlFileName = "folder.xml"; - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); - - if (xml == null) - { - return false; - } - - return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved; - } - - /// <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="providerInfo">The provider information.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); - - if (metadataFile != null) - { - var path = metadataFile.FullName; - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - new BaseItemXmlParser<Folder>(Logger).Fetch((Folder)item, path, cancellationToken); - } - finally - { - XmlParsingResourcePool.Release(); - } - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - } -} diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs new file mode 100644 index 0000000000..cc688d91ac --- /dev/null +++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Folders +{ + public class FolderMetadataService : MetadataService<Folder, ItemId> + { + private readonly ILibraryManager _libraryManager; + + public FolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Folder source, Folder target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(Folder item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + + public override int Order + { + get + { + // Make sure the type-specific services get picked first + return 10; + } + } + } +} diff --git a/MediaBrowser.Providers/Folders/FolderXmlProvider.cs b/MediaBrowser.Providers/Folders/FolderXmlProvider.cs new file mode 100644 index 0000000000..2fc6a82906 --- /dev/null +++ b/MediaBrowser.Providers/Folders/FolderXmlProvider.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System.IO; +using System.Threading; + +namespace MediaBrowser.Providers.Folders +{ + /// <summary> + /// Provides metadata for Folders and all subclasses by parsing folder.xml + /// </summary> + public class FolderXmlProvider : BaseXmlProvider<Folder> + { + private readonly ILogger _logger; + + public FolderXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(Folder item, string path, CancellationToken cancellationToken) + { + new BaseItemXmlParser<Folder>(_logger).Fetch(item, path, cancellationToken); + } + + protected override FileInfo GetXmlFile(ItemInfo info) + { + return new FileInfo(Path.Combine(info.Path, "folder.xml")); + } + } +} diff --git a/MediaBrowser.Providers/UserRootFolderNameProvider.cs b/MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs index 551dd42576..043e32d119 100644 --- a/MediaBrowser.Providers/UserRootFolderNameProvider.cs +++ b/MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs @@ -7,7 +7,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace MediaBrowser.Providers +namespace MediaBrowser.Providers.Folders { public class UserRootFolderNameProvider : BaseMetadataProvider { diff --git a/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs b/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs index 61fb791e61..3e74f4c329 100644 --- a/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs +++ b/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Games { - public class GameSystemXmlProvider : BaseXmlProvider, ILocalMetadataProvider<GameSystem> + public class GameSystemXmlProvider : BaseXmlProvider<GameSystem> { private readonly ILogger _logger; @@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Games _logger = logger; } - public async Task<MetadataResult<GameSystem>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(GameSystem item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<GameSystem>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new GameSystem(); - - new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "gamesystem.xml")); + return new FileInfo(Path.Combine(info.Path, "gamesystem.xml")); } } } diff --git a/MediaBrowser.Providers/Games/GameXmlProvider.cs b/MediaBrowser.Providers/Games/GameXmlProvider.cs index e2a67de8d4..644fe3e429 100644 --- a/MediaBrowser.Providers/Games/GameXmlProvider.cs +++ b/MediaBrowser.Providers/Games/GameXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Games { - public class GameXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Game> + public class GameXmlProvider : BaseXmlProvider<Game> { private readonly ILogger _logger; @@ -18,57 +17,29 @@ namespace MediaBrowser.Providers.Games _logger = logger; } - public async Task<MetadataResult<Game>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(Game item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<Game>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new Game(); - - new GameXmlParser(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new GameXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - var fileInfo = FileSystem.GetFileSystemInfo(path); + var fileInfo = FileSystem.GetFileSystemInfo(info.Path); var directoryInfo = fileInfo as DirectoryInfo; if (directoryInfo == null) { - directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path)); + directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path)); } var directoryPath = directoryInfo.FullName; - var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml"); + var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml"); var file = new FileInfo(specificFile); - return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml")); + return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml")); } } } diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs deleted file mode 100644 index bc58f31789..0000000000 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ /dev/null @@ -1,637 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers -{ - /// <summary> - /// Provides images for all types by looking for standard images - folder, backdrop, logo, etc. - /// </summary> - public class ImageFromMediaLocationProvider : BaseMetadataProvider - { - protected readonly IFileSystem FileSystem; - - public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - FileSystem = fileSystem; - } - - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - if (item.LocationType == LocationType.FileSystem) - { - if (item.ResolveArgs.IsDirectory) - { - return true; - } - - return item.IsInMixedFolder && item.Parent != null && !(item is Episode); - } - if (item.LocationType == LocationType.Virtual) - { - var season = item as Season; - - if (season != null) - { - var series = season.Series; - - if (series != null && series.LocationType == LocationType.FileSystem) - { - return true; - } - } - } - - return false; - } - - protected override IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item) - { - var season = item as Season; - if (season != null) - { - var list = new List<BaseItem>(); - - if (season.LocationType == LocationType.FileSystem) - { - list.Add(season); - } - - var series = season.Series; - if (series != null && series.LocationType == LocationType.FileSystem) - { - list.Add(series); - } - - return list; - } - - return base.GetItemsForFileStampComparison(item); - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - - /// <summary> - /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes - /// </summary> - /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnFileSystemStampChange - { - get - { - return true; - } - } - - /// <summary> - /// Gets the filestamp extensions. - /// </summary> - /// <value>The filestamp extensions.</value> - protected override string[] FilestampExtensions - { - get - { - return BaseItem.SupportedImageExtensions; - } - } - - /// <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, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Make sure current image paths still exist - item.ValidateImages(); - - cancellationToken.ThrowIfCancellationRequested(); - - var args = GetResolveArgsContainingImages(item); - - PopulateBaseItemImages(item, args); - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return TrueTaskResult; - } - - private ItemResolveArgs GetResolveArgsContainingImages(BaseItem item) - { - if (item.LocationType != LocationType.FileSystem) - { - return null; - } - - if (item.IsInMixedFolder) - { - if (item.Parent == null) - { - return item.ResolveArgs; - } - return item.Parent.ResolveArgs; - } - - return item.ResolveArgs; - } - - /// <summary> - /// Gets the image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - /// <param name="filenameWithoutExtension">The filename without extension.</param> - /// <returns>FileSystemInfo.</returns> - protected virtual FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension) - { - if (string.IsNullOrEmpty(item.MetaLocation)) - { - return null; - } - - return BaseItem.SupportedImageExtensions - .Select(i => args.GetMetaFileByPath(GetFullImagePath(item, args, filenameWithoutExtension, i))) - .FirstOrDefault(i => i != null); - } - - protected virtual string GetFullImagePath(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension, string extension) - { - var path = item.MetaLocation; - - if (item.IsInMixedFolder) - { - var pathFilenameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path); - - // If the image filename and path file name match, just look for an image using the same full path as the item - if (string.Equals(pathFilenameWithoutExtension, filenameWithoutExtension)) - { - return Path.ChangeExtension(item.Path, extension); - } - - return Path.Combine(path, pathFilenameWithoutExtension + "-" + filenameWithoutExtension + extension); - } - - return Path.Combine(path, filenameWithoutExtension + extension); - } - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - /// <summary> - /// Fills in image paths based on files win the folder - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - private void PopulateBaseItemImages(BaseItem item, ItemResolveArgs args) - { - PopulatePrimaryImage(item, args); - - // Logo Image - var image = GetImage(item, args, "logo"); - - if (image != null) - { - item.SetImagePath(ImageType.Logo, image.FullName); - } - - // Clearart - image = GetImage(item, args, "clearart"); - - if (image != null) - { - item.SetImagePath(ImageType.Art, image.FullName); - } - - // Disc - image = GetImage(item, args, "disc") ?? - GetImage(item, args, "cdart"); - - if (image != null) - { - item.SetImagePath(ImageType.Disc, image.FullName); - } - - // Box Image - image = GetImage(item, args, "box"); - - if (image != null) - { - item.SetImagePath(ImageType.Box, image.FullName); - } - - // BoxRear Image - image = GetImage(item, args, "boxrear"); - - if (image != null) - { - item.SetImagePath(ImageType.BoxRear, image.FullName); - } - - // Thumbnail Image - image = GetImage(item, args, "menu"); - - if (image != null) - { - item.SetImagePath(ImageType.Menu, image.FullName); - } - - PopulateBanner(item, args); - PopulateThumb(item, args); - - // Backdrop Image - PopulateBackdrops(item, args); - PopulateScreenshots(item, args); - } - - private void PopulatePrimaryImage(BaseItem item, ItemResolveArgs args) - { - // Primary Image - var image = GetImage(item, args, "folder") ?? - GetImage(item, args, "poster") ?? - GetImage(item, args, "cover") ?? - GetImage(item, args, "default"); - - // Support plex/xbmc convention - if (image == null && item is Series) - { - image = GetImage(item, args, "show"); - } - - // Support plex/xbmc convention - if (image == null) - { - // Supprt xbmc conventions - var season = item as Season; - if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem) - { - image = GetSeasonImageFromSeriesFolder(season, "-poster"); - } - } - - // Support plex/xbmc convention - if (image == null && (item is Movie || item is MusicVideo || item is AdultVideo)) - { - image = GetImage(item, args, "movie"); - } - - // Look for a file with the same name as the item - if (image == null && !string.IsNullOrEmpty(item.Path)) - { - var name = Path.GetFileNameWithoutExtension(item.Path); - - if (!string.IsNullOrEmpty(name)) - { - image = GetImage(item, args, name) ?? - GetImage(item, args, name + "-poster"); - } - } - - if (image != null) - { - item.SetImagePath(ImageType.Primary, image.FullName); - } - } - - /// <summary> - /// Populates the banner. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - private void PopulateBanner(BaseItem item, ItemResolveArgs args) - { - // Banner Image - var image = GetImage(item, args, "banner"); - - if (image == null) - { - // Supprt xbmc conventions - var season = item as Season; - if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem) - { - image = GetSeasonImageFromSeriesFolder(season, "-banner"); - } - } - - if (image != null) - { - item.SetImagePath(ImageType.Banner, image.FullName); - } - } - - /// <summary> - /// Populates the thumb. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - private void PopulateThumb(BaseItem item, ItemResolveArgs args) - { - // Thumbnail Image - var image = GetImage(item, args, "thumb") ?? - GetImage(item, args, "landscape"); - - if (image == null) - { - // Supprt xbmc conventions - var season = item as Season; - if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem) - { - image = GetSeasonImageFromSeriesFolder(season, "-landscape"); - } - } - - if (image != null) - { - item.SetImagePath(ImageType.Thumb, image.FullName); - } - - } - - /// <summary> - /// Populates the backdrops. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - private void PopulateBackdrops(BaseItem item, ItemResolveArgs args) - { - var backdropFiles = new List<string>(); - - PopulateBackdrops(item, args, backdropFiles, "backdrop", "backdrop"); - - // Support {name}-fanart.ext - if (!string.IsNullOrEmpty(item.Path)) - { - var name = Path.GetFileNameWithoutExtension(item.Path); - - if (!string.IsNullOrEmpty(name)) - { - var image = GetImage(item, args, name + "-fanart"); - - if (image != null) - { - backdropFiles.Add(image.FullName); - } - } - } - - // Support plex/xbmc conventions - PopulateBackdrops(item, args, backdropFiles, "fanart", "fanart-"); - PopulateBackdrops(item, args, backdropFiles, "background", "background-"); - PopulateBackdrops(item, args, backdropFiles, "art", "art-"); - - var season = item as Season; - if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem) - { - var image = GetSeasonImageFromSeriesFolder(season, "-fanart"); - - if (image != null) - { - backdropFiles.Add(image.FullName); - } - } - - if (item.LocationType == LocationType.FileSystem) - { - PopulateBackdropsFromExtraFanart(args, backdropFiles); - } - - if (backdropFiles.Count > 0) - { - item.BackdropImagePaths = backdropFiles; - } - } - - private FileSystemInfo GetSeasonImageFromSeriesFolder(Season season, string imageSuffix) - { - var series = season.Series; - var seriesFolderArgs = series.ResolveArgs; - - var seasonNumber = season.IndexNumber; - - string filename = null; - FileSystemInfo image; - - if (seasonNumber.HasValue) - { - var seasonMarker = seasonNumber.Value == 0 - ? "-specials" - : seasonNumber.Value.ToString("00", _usCulture); - - // Get this one directly from the file system since we have to go up a level - filename = "season" + seasonMarker + imageSuffix; - - image = GetImage(series, seriesFolderArgs, filename); - - if (image != null && image.Exists) - { - return image; - } - } - - var previousFilename = filename; - - // Try using the season name - filename = season.Name.ToLower().Replace(" ", string.Empty) + imageSuffix; - - if (!string.Equals(previousFilename, filename)) - { - image = GetImage(series, seriesFolderArgs, filename); - - if (image != null && image.Exists) - { - return image; - } - } - - return null; - } - - /// <summary> - /// Populates the backdrops from extra fanart. - /// </summary> - /// <param name="args">The args.</param> - /// <param name="backdrops">The backdrops.</param> - private void PopulateBackdropsFromExtraFanart(ItemResolveArgs args, List<string> backdrops) - { - if (!args.IsDirectory) - { - return; - } - - if (args.ContainsFileSystemEntryByName("extrafanart")) - { - var path = Path.Combine(args.Path, "extrafanart"); - - var imageFiles = Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly) - .Where(i => - { - var extension = Path.GetExtension(i); - - if (string.IsNullOrEmpty(extension)) - { - return false; - } - - return BaseItem.SupportedImageExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); - }) - .ToList(); - - backdrops.AddRange(imageFiles); - } - } - - /// <summary> - /// Populates the backdrops. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - /// <param name="backdropFiles">The backdrop files.</param> - /// <param name="filename">The filename.</param> - /// <param name="numberedSuffix">The numbered suffix.</param> - private void PopulateBackdrops(BaseItem item, ItemResolveArgs args, List<string> backdropFiles, string filename, string numberedSuffix) - { - var image = GetImage(item, args, filename); - - if (image != null) - { - backdropFiles.Add(image.FullName); - } - - var unfound = 0; - for (var i = 1; i <= 20; i++) - { - // Backdrop Image - image = GetImage(item, args, numberedSuffix + i); - - if (image != null) - { - backdropFiles.Add(image.FullName); - } - else - { - unfound++; - - if (unfound >= 3) - { - break; - } - } - } - } - - /// <summary> - /// Populates the screenshots. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - private void PopulateScreenshots(BaseItem item, ItemResolveArgs args) - { - // Screenshot Image - var image = GetImage(item, args, "screenshot"); - - var screenshotFiles = new List<string>(); - - if (image != null) - { - screenshotFiles.Add(image.FullName); - } - - var unfound = 0; - for (var i = 1; i <= 20; i++) - { - // Screenshot Image - image = GetImage(item, args, "screenshot" + i); - - if (image != null) - { - screenshotFiles.Add(image.FullName); - } - else - { - unfound++; - - if (unfound >= 3) - { - break; - } - } - } - - if (screenshotFiles.Count > 0) - { - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots != null) - { - hasScreenshots.ScreenshotImagePaths = screenshotFiles; - } - } - } - - protected FileSystemInfo GetImageFromLocation(string path, string filenameWithoutExtension) - { - try - { - var files = new DirectoryInfo(path) - .EnumerateFiles() - .Where(i => - { - var fileName = Path.GetFileNameWithoutExtension(i.FullName); - - if (!string.Equals(fileName, filenameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - var ext = i.Extension; - - return !string.IsNullOrEmpty(ext) && - BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - }) - .ToList(); - - return BaseItem.SupportedImageExtensions - .Select(ext => files.FirstOrDefault(i => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase))) - .FirstOrDefault(file => file != null); - } - catch (DirectoryNotFoundException) - { - return null; - } - } - } -} diff --git a/MediaBrowser.Providers/ImagesByNameProvider.cs b/MediaBrowser.Providers/ImagesByNameProvider.cs deleted file mode 100644 index f634170269..0000000000 --- a/MediaBrowser.Providers/ImagesByNameProvider.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Linq; - -namespace MediaBrowser.Providers -{ - /// <summary> - /// Provides images for generic types by looking for standard images in the IBN - /// </summary> - public class ImagesByNameProvider : ImageFromMediaLocationProvider - { - public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager, fileSystem) - { - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - // Only run for these generic types since we are expensive in file i/o - return item is ICollectionFolder; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get - { - return MetadataProviderPriority.Last; - } - } - - /// <summary> - /// Gets the location. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>System.String.</returns> - protected string GetLocation(BaseItem item) - { - var name = FileSystem.GetValidFilename(item.Name); - - return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name); - } - - /// <summary> - /// Gets the image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - /// <param name="filenameWithoutExtension">The filename without extension.</param> - /// <returns>FileSystemInfo.</returns> - protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension) - { - var location = GetLocation(item); - - return GetImageFromLocation(location, filenameWithoutExtension); - } - - protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items) - { - var location = GetLocation(items.First()); - - try - { - var files = new DirectoryInfo(location) - .EnumerateFiles("*", SearchOption.TopDirectoryOnly) - .Where(i => - { - var ext = i.Extension; - - return !string.IsNullOrEmpty(ext) && - BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - }) - .ToList(); - - return GetFileSystemStamp(files); - } - catch (DirectoryNotFoundException) - { - // User doesn't have the folder. No need to log or blow up - - return Guid.Empty; - } - } - } -} diff --git a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs index 701cc9f85f..af3de824dd 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.LiveTv { - public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider<LiveTvChannel> + public class ChannelXmlProvider : BaseXmlProvider<LiveTvChannel> { private readonly ILogger _logger; @@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.LiveTv _logger = logger; } - public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(LiveTvChannel item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<LiveTvChannel>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new LiveTvChannel(); - - new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "channel.xml")); + return new FileInfo(Path.Combine(info.Path, "channel.xml")); } } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 52aaf412a6..2a4ef75970 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -379,14 +379,19 @@ namespace MediaBrowser.Providers.Manager if (saveLocally) { - if (item.IsInMixedFolder && !(item is Episode)) + if (item is Episode) + { + path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension); + } + + else if (item.IsInMixedFolder) { path = GetSavePathForItemInMixedFolder(item, type, filename, extension); } if (string.IsNullOrEmpty(path)) { - path = Path.Combine(item.MetaLocation, filename + extension); + path = Path.Combine(item.ContainingFolderPath, filename + extension); } } @@ -468,7 +473,7 @@ namespace MediaBrowser.Providers.Manager return new[] { - Path.Combine(item.MetaLocation, "fanart" + extension) + Path.Combine(item.ContainingFolderPath, "fanart" + extension) }; } @@ -483,8 +488,8 @@ namespace MediaBrowser.Providers.Manager return new[] { - Path.Combine(item.MetaLocation, "extrafanart", extraFanartFilename + extension), - Path.Combine(item.MetaLocation, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension) + Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension), + Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension) }; } @@ -519,10 +524,10 @@ namespace MediaBrowser.Providers.Manager if (item is MusicAlbum || item is MusicArtist) { - return new[] { Path.Combine(item.MetaLocation, "folder" + extension) }; + return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) }; } - return new[] { Path.Combine(item.MetaLocation, "poster" + extension) }; + return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) }; } if (type == ImageType.Banner) @@ -561,7 +566,7 @@ namespace MediaBrowser.Providers.Manager return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) }; } - return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) }; + return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) }; } // All other paths are the same diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 3fb1e7074b..f2fa4dc294 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -93,8 +93,6 @@ namespace MediaBrowser.Providers.Manager /// <returns>Task.</returns> private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken) { - _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); - try { var images = provider.GetSupportedImages(item); @@ -103,6 +101,8 @@ namespace MediaBrowser.Providers.Manager { if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType)) { + _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); + var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false); if (response.HasImage) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index c817180c51..e0272bc7bc 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -88,12 +88,16 @@ namespace MediaBrowser.Providers.Manager // Next run metadata providers if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) { - updateType = updateType | BeforeMetadataRefresh(itemOfType); - var providers = GetProviders(item, refreshResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList(); + if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue) + { + updateType = updateType | BeforeMetadataRefresh(itemOfType); + } + if (providers.Count > 0) { + var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false); updateType = updateType | result.UpdateType; @@ -145,7 +149,7 @@ namespace MediaBrowser.Providers.Manager { var type = item.GetType().Name; return ServerConfigurationManager.Configuration.MetadataOptions - .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? + .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? _defaultOptions; } @@ -278,9 +282,11 @@ namespace MediaBrowser.Providers.Manager { Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); + var itemInfo = new ItemInfo { Path = item.Path, IsInMixedFolder = item.IsInMixedFolder }; + try { - var localItem = await provider.GetMetadata(item.Path, cancellationToken).ConfigureAwait(false); + var localItem = await provider.GetMetadata(itemInfo, cancellationToken).ConfigureAwait(false); if (localItem.HasMetadata) { @@ -327,7 +333,7 @@ namespace MediaBrowser.Providers.Manager { await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false); } - + return refreshResult; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index dbfc97d517..030b3cbd90 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -48,12 +48,6 @@ namespace MediaBrowser.Providers.Manager /// <value>The configuration manager.</value> private IServerConfigurationManager ConfigurationManager { get; set; } - /// <summary> - /// Gets the list of currently registered metadata prvoiders - /// </summary> - /// <value>The metadata providers enumerable.</value> - private BaseMetadataProvider[] MetadataProviders { get; set; } - private IImageProvider[] ImageProviders { get; set; } private readonly IFileSystem _fileSystem; @@ -86,15 +80,12 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Adds the metadata providers. /// </summary> - /// <param name="providers">The providers.</param> /// <param name="imageProviders">The image providers.</param> /// <param name="metadataServices">The metadata services.</param> /// <param name="metadataProviders">The metadata providers.</param> /// <param name="metadataSavers">The metadata savers.</param> - public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers) + public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers) { - MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); - ImageProviders = imageProviders.ToArray(); _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); @@ -111,174 +102,8 @@ namespace MediaBrowser.Providers.Manager return service.RefreshMetadata(item, options, cancellationToken); } - return ((BaseItem)item).RefreshMetadataDirect(cancellationToken, options.ForceSave, options.ReplaceAllMetadata); - } - - /// <summary> - /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <returns>Task{System.Boolean}.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - ItemUpdateType? result = null; - - cancellationToken.ThrowIfCancellationRequested(); - - var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; - - var providerHistories = item.DateLastSaved == default(DateTime) ? - new List<BaseProviderInfo>() : - _providerRepo.GetProviderHistory(item.Id).ToList(); - - // Run the normal providers sequentially in order of priority - foreach (var provider in MetadataProviders) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!ProviderSupportsItem(provider, item)) - { - continue; - } - - // Skip if internet providers are currently disabled - if (provider.RequiresInternet && !enableInternetProviders) - { - continue; - } - - // Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running - // This is the case for the fan art provider which depends on the movie and tv providers having run before them - if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata) - { - continue; - } - - var providerInfo = providerHistories.FirstOrDefault(i => i.ProviderId == provider.Id); - - if (providerInfo == null) - { - providerInfo = new BaseProviderInfo - { - ProviderId = provider.Id - }; - providerHistories.Add(providerInfo); - } - - try - { - if (!force && !provider.NeedsRefresh(item, providerInfo)) - { - continue; - } - } - catch (Exception ex) - { - _logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path); - } - - var updateType = await FetchAsync(provider, item, providerInfo, force, cancellationToken).ConfigureAwait(false); - - if (updateType.HasValue) - { - if (result.HasValue) - { - result = result.Value | updateType.Value; - } - else - { - result = updateType; - } - } - } - - if (result.HasValue || force) - { - await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken); - } - - return result; - } - - /// <summary> - /// Providers the supports item. - /// </summary> - /// <param name="provider">The provider.</param> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool ProviderSupportsItem(BaseMetadataProvider provider, BaseItem item) - { - try - { - return provider.Supports(item); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name); - return false; - } - } - - /// <summary> - /// Fetches metadata and returns true or false indicating if any work that requires persistence was done - /// </summary> - /// <param name="provider">The provider.</param> - /// <param name="item">The item.</param> - /// <param name="providerInfo">The provider information.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, BaseProviderInfo providerInfo, bool force, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); - - try - { - var changed = await provider.FetchAsync(item, force, providerInfo, cancellationToken).ConfigureAwait(false); - - if (changed) - { - return provider.ItemUpdateType; - } - - return null; - } - catch (OperationCanceledException ex) - { - _logger.Debug("{0} canceled for {1}", provider.GetType().Name, item.Name); - - // If the outer cancellation token is the one that caused the cancellation, throw it - if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken) - { - throw; - } - - return null; - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed refreshing {1} {2}", ex, provider.GetType().Name, item.Name, item.Path ?? string.Empty); - - provider.SetLastRefreshed(item, DateTime.UtcNow, providerInfo, ProviderRefreshStatus.Failure); - - return ItemUpdateType.Unspecified; - } + _logger.Error("Unable to find a metadata service for item of type " + item.GetType().Name); + return Task.FromResult(true); } /// <summary> @@ -328,9 +153,6 @@ namespace MediaBrowser.Providers.Manager await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); } } - - // If this is ever used for something other than metadata we can add a file type param - item.ResolveArgs.AddMetadataFile(path); } finally { @@ -517,6 +339,15 @@ namespace MediaBrowser.Providers.Manager return false; } + // If this restriction is ever lifted, movie xml providers will have to be updated to prevent owned items like trailers from reading those files + if (item.IsOwnedItem) + { + if (provider is ILocalMetadataProvider || provider is IRemoteMetadataProvider) + { + return false; + } + } + return true; } @@ -581,6 +412,7 @@ namespace MediaBrowser.Providers.Manager list.Add(GetPluginSummary<AdultVideo>()); list.Add(GetPluginSummary<MusicVideo>()); + list.Add(GetPluginSummary<Video>()); list.Add(GetPluginSummary<LiveTvChannel>()); list.Add(GetPluginSummary<LiveTvProgram>()); @@ -678,6 +510,8 @@ namespace MediaBrowser.Providers.Manager { foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType))) { + _logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name); + var fileSaver = saver as IMetadataFileSaver; if (fileSaver != null) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index ad8465009d..2c710693e0 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -65,11 +65,13 @@ </ItemGroup> <ItemGroup> <Compile Include="AdultVideos\AdultVideoMetadataService.cs" /> + <Compile Include="AdultVideos\AdultVideoXmlProvider.cs" /> <Compile Include="All\LocalImageProvider.cs" /> <Compile Include="Books\BookMetadataService.cs" /> <Compile Include="BoxSets\BoxSetMetadataService.cs" /> <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" /> <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" /> + <Compile Include="Folders\FolderMetadataService.cs" /> <Compile Include="GameGenres\GameGenreMetadataService.cs" /> <Compile Include="Games\GameMetadataService.cs" /> <Compile Include="Games\GameSystemMetadataService.cs" /> @@ -83,33 +85,34 @@ <Compile Include="Manager\ProviderManager.cs" /> <Compile Include="Manager\MetadataService.cs" /> <Compile Include="BaseXmlProvider.cs" /> - <Compile Include="CollectionFolderImageProvider.cs" /> - <Compile Include="FolderProviderFromXml.cs" /> + <Compile Include="Folders\FolderXmlProvider.cs" /> <Compile Include="Games\GameXmlParser.cs" /> <Compile Include="Games\GameXmlProvider.cs" /> <Compile Include="Games\GameSystemXmlProvider.cs" /> - <Compile Include="ImageFromMediaLocationProvider.cs" /> - <Compile Include="ImagesByNameProvider.cs" /> + <Compile Include="MediaInfo\FFProbeAudioInfo.cs" /> <Compile Include="MediaInfo\FFProbeHelpers.cs" /> <Compile Include="MediaInfo\FFProbeProvider.cs" /> <Compile Include="MediaInfo\FFProbeVideoInfo.cs" /> + <Compile Include="Movies\TrailerMetadataService.cs" /> + <Compile Include="Movies\GenericMovieDbInfo.cs" /> <Compile Include="Movies\MovieDbSearch.cs" /> + <Compile Include="Movies\MovieMetadataService.cs" /> <Compile Include="Movies\MovieXmlProvider.cs" /> + <Compile Include="Movies\TmdbSettings.cs" /> + <Compile Include="Movies\TrailerXmlProvider.cs" /> <Compile Include="MusicGenres\MusicGenreImageProvider.cs" /> <Compile Include="GameGenres\GameGenreImageProvider.cs" /> <Compile Include="Genres\GenreImageProvider.cs" /> <Compile Include="ImagesByName\ImageUtils.cs" /> <Compile Include="MediaInfo\AudioImageProvider.cs" /> - <Compile Include="MediaInfo\BaseFFProbeProvider.cs" /> - <Compile Include="MediaInfo\FFProbeAudioInfoProvider.cs" /> - <Compile Include="MediaInfo\FFProbeVideoInfoProvider.cs" /> <Compile Include="MediaInfo\VideoImageProvider.cs" /> <Compile Include="BoxSets\BoxSetXmlProvider.cs" /> - <Compile Include="Movies\ManualMovieDbImageProvider.cs" /> - <Compile Include="Movies\ManualFanartMovieImageProvider.cs" /> + <Compile Include="Movies\MovieDbImageProvider.cs" /> + <Compile Include="Movies\FanartMovieImageProvider.cs" /> <Compile Include="MusicGenres\MusicGenreMetadataService.cs" /> <Compile Include="Music\AlbumMetadataService.cs" /> <Compile Include="Music\ArtistMetadataService.cs" /> + <Compile Include="Music\AudioMetadataService.cs" /> <Compile Include="Music\LastfmArtistProvider.cs" /> <Compile Include="Music\MusicBrainzArtistProvider.cs" /> <Compile Include="Music\MusicVideoMetadataService.cs" /> @@ -119,12 +122,8 @@ <Compile Include="People\MovieDbPersonImageProvider.cs" /> <Compile Include="Movies\MovieUpdatesPrescanTask.cs" /> <Compile Include="Movies\MovieXmlParser.cs" /> - <Compile Include="Movies\FanArtMovieProvider.cs" /> <Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" /> - <Compile Include="Movies\MovieDbImagesProvider.cs" /> <Compile Include="Movies\MovieDbProvider.cs" /> - <Compile Include="Movies\MovieProviderFromXml.cs" /> - <Compile Include="Movies\OpenMovieDatabaseProvider.cs" /> <Compile Include="Music\AlbumXmlProvider.cs" /> <Compile Include="Music\ArtistXmlProvider.cs" /> <Compile Include="Music\FanArtUpdatesPrescanTask.cs" /> @@ -177,9 +176,10 @@ <Compile Include="TV\SeriesXmlProvider.cs" /> <Compile Include="TV\SeriesXmlParser.cs" /> <Compile Include="TV\TvdbPrescanTask.cs" /> - <Compile Include="UserRootFolderNameProvider.cs" /> + <Compile Include="Folders\UserRootFolderNameProvider.cs" /> <Compile Include="Users\UserMetadataService.cs" /> - <Compile Include="VirtualItemImageValidator.cs" /> + <Compile Include="Videos\VideoMetadataService.cs" /> + <Compile Include="Years\YearMetadataService.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index ad3211650a..20ce952db7 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -1,16 +1,13 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; +using MediaBrowser.Model.IO; using System; -using System.Collections.Concurrent; -using System.IO; -using System.Linq; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -19,207 +16,83 @@ namespace MediaBrowser.Providers.MediaInfo /// <summary> /// Uses ffmpeg to create video images /// </summary> - public class AudioImageProvider : BaseMetadataProvider + public class AudioImageProvider : IDynamicImageProvider, IHasChangeMonitor { - /// <summary> - /// The _locks - /// </summary> - private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); - - /// <summary> - /// The _media encoder - /// </summary> + private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; + private readonly IServerConfigurationManager _config; - /// <summary> - /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class. - /// </summary> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="mediaEncoder">The media encoder.</param> - public AudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder) - : base(logManager, configurationManager) + public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config) { + _isoManager = isoManager; _mediaEncoder = mediaEncoder; + _config = config; } /// <summary> - /// Gets a value indicating whether [refresh on version change]. + /// The null mount task result /// </summary> - /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } + protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null); /// <summary> - /// Gets the provider version. + /// Mounts the iso if needed. /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{IIsoMount}.</returns> + protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken) { - get + if (item.VideoType == VideoType.Iso) { - return "1"; + return _isoManager.Mount(item.Path, cancellationToken); } - } - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item.LocationType == LocationType.FileSystem && item is Audio; + return NullMountTaskResult; } - /// <summary> - /// Override this to return the date that should be compared to the last refresh date - /// to determine if this provider should be re-fetched. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>DateTime.</returns> - protected override DateTime CompareDate(BaseItem item) + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) { - return item.DateModified; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Last; } - } - - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } + return new List<ImageType> { ImageType.Primary }; } - /// <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 async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - item.ValidateImages(); - var audio = (Audio)item; - if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.HasEmbeddedImage) + // Can't extract if we didn't find a video stream in the file + if (!audio.HasEmbeddedImage) { - try - { - await CreateImagesForSong(audio, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error extracting image for {0}", ex, item.Name); - } + return Task.FromResult(new DynamicImageResponse { HasImage = false }); } - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; + return GetVideoImage((Audio)item, cancellationToken); } - /// <summary> - /// Creates the images for song. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.InvalidOperationException">Can't extract an image unless the audio file has an embedded image.</exception> - private async Task CreateImagesForSong(Audio item, CancellationToken cancellationToken) + public async Task<DynamicImageResponse> GetVideoImage(Audio item, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false); - var path = GetAudioImagePath(item); - - if (!File.Exists(path)) + return new DynamicImageResponse { - var semaphore = GetLock(path); - - // Acquire a lock - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - // Check again - if (!File.Exists(path)) - { - try - { - var parentPath = Path.GetDirectoryName(path); - - Directory.CreateDirectory(parentPath); - - await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, path, cancellationToken).ConfigureAwait(false); - } - finally - { - semaphore.Release(); - } - } - else - { - semaphore.Release(); - } - } - - // Image is already in the cache - item.SetImagePath(ImageType.Primary, path); + Format = ImageFormat.Jpg, + HasImage = true, + Stream = stream + }; } - /// <summary> - /// Gets the audio image path. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>System.String.</returns> - private string GetAudioImagePath(Audio item) + public string Name { - var album = item.Parent as MusicAlbum; - - var filename = item.Album ?? string.Empty; - filename += item.Artists.FirstOrDefault() ?? string.Empty; - filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; - - filename = filename.GetMD5() + ".jpg"; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(AudioImagesPath, prefix, filename); + get { return "Embedded Image"; } } - /// <summary> - /// Gets the audio images data path. - /// </summary> - /// <value>The audio images data path.</value> - public string AudioImagesPath + public bool Supports(IHasImages item) { - get - { - return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-audio-images"); - } + return item.LocationType == LocationType.FileSystem && item is Audio; } - /// <summary> - /// Gets the lock. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>SemaphoreSlim.</returns> - private SemaphoreSlim GetLock(string filename) + public bool HasChanged(IHasMetadata item, DateTime date) { - return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + return item.DateModified > date; } } } diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs deleted file mode 100644 index ad4630dcf6..0000000000 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ /dev/null @@ -1,145 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.MediaInfo; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.MediaInfo -{ - /// <summary> - /// Provides a base class for extracting media information through ffprobe - /// </summary> - /// <typeparam name="T"></typeparam> - public abstract class BaseFFProbeProvider<T> : BaseMetadataProvider - where T : BaseItem, IHasMediaStreams - { - protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer) - : base(logManager, configurationManager) - { - JsonSerializer = jsonSerializer; - MediaEncoder = mediaEncoder; - } - - protected readonly IMediaEncoder MediaEncoder; - protected readonly IJsonSerializer JsonSerializer; - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item.LocationType == LocationType.FileSystem && item is T; - } - - /// <summary> - /// Override this to return the date that should be compared to the last refresh date - /// to determine if this provider should be re-fetched. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>DateTime.</returns> - protected override DateTime CompareDate(BaseItem item) - { - return item.DateModified; - } - - /// <summary> - /// The null mount task result - /// </summary> - protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null); - - /// <summary> - /// Gets the provider version. - /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion - { - get - { - return "ffmpeg20131209"; - } - } - - /// <summary> - /// Gets a value indicating whether [refresh on version change]. - /// </summary> - /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } - - /// <summary> - /// Gets the media info. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="isoMount">The iso mount.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{MediaInfoResult}.</returns> - /// <exception cref="System.ArgumentNullException">inputPath - /// or - /// cache</exception> - protected async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var type = InputType.File; - var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath }; - - var video = item as Video; - - if (video != null) - { - inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); - } - - return await MediaEncoder.GetMediaInfo(inputPath, type, item is Audio, cancellationToken).ConfigureAwait(false); - } - - /// <summary> - /// Mounts the iso if needed. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>IsoMount.</returns> - protected virtual Task<IIsoMount> MountIsoIfNeeded(T item, CancellationToken cancellationToken) - { - return NullMountTaskResult; - } - - /// <summary> - /// Called when [pre fetch]. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="mount">The mount.</param> - protected virtual void OnPreFetch(T item, IIsoMount mount) - { - - } - } -} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index bae719eea0..4fc92ddeb0 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -1,41 +1,36 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Providers.MediaInfo { - /// <summary> - /// Extracts audio information using ffprobe - /// </summary> - public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio> + class FFProbeAudioInfo { + private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; - public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer, IItemRepository itemRepo) - : base(logManager, configurationManager, mediaEncoder, jsonSerializer) + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo) { + _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; } - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken) + where T : Audio { - var myItem = (Audio)item; - - OnPreFetch(myItem, null); - - var result = await GetMediaInfo(item, null, cancellationToken).ConfigureAwait(false); + var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -43,11 +38,19 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - await Fetch(myItem, cancellationToken, result).ConfigureAwait(false); + await Fetch(item, cancellationToken, result).ConfigureAwait(false); - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return ItemUpdateType.MetadataImport; + } - return true; + private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + const InputType type = InputType.File; + var inputPath = new[] { item.Path }; + + return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); } /// <summary> @@ -82,7 +85,7 @@ namespace MediaBrowser.Providers.MediaInfo // If we got something, parse it if (!string.IsNullOrEmpty(duration)) { - audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks; + audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; } } } @@ -277,6 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo return null; } - } + } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 1f3723653e..d55a42d115 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -18,10 +19,14 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.MediaInfo { public class FFProbeProvider : ICustomMetadataProvider<Episode>, - ICustomMetadataProvider<MusicVideo>, - ICustomMetadataProvider<Movie>, - ICustomMetadataProvider<AdultVideo>, - ICustomMetadataProvider<LiveTvVideoRecording>, + ICustomMetadataProvider<MusicVideo>, + ICustomMetadataProvider<Movie>, + ICustomMetadataProvider<AdultVideo>, + ICustomMetadataProvider<LiveTvVideoRecording>, + ICustomMetadataProvider<LiveTvAudioRecording>, + ICustomMetadataProvider<Trailer>, + ICustomMetadataProvider<Video>, + ICustomMetadataProvider<Audio>, IHasChangeMonitor { private readonly ILogger _logger; @@ -30,7 +35,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IItemRepository _itemRepo; private readonly IBlurayExaminer _blurayExaminer; private readonly ILocalizationManager _localization; - + public string Name { get { return "ffprobe"; } @@ -61,6 +66,26 @@ namespace MediaBrowser.Providers.MediaInfo return FetchVideoInfo(item, cancellationToken); } + public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public Task<ItemUpdateType> FetchAsync(Video item, CancellationToken cancellationToken) + { + return FetchVideoInfo(item, cancellationToken); + } + + public Task<ItemUpdateType> FetchAsync(Audio item, CancellationToken cancellationToken) + { + return FetchAudioInfo(item, cancellationToken); + } + + public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, CancellationToken cancellationToken) + { + return FetchAudioInfo(item, cancellationToken); + } + public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization) { _logger = logger; @@ -95,6 +120,19 @@ namespace MediaBrowser.Providers.MediaInfo return prober.ProbeVideo(item, cancellationToken); } + public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken) + where T : Audio + { + if (item.LocationType != LocationType.FileSystem) + { + return _cachedTask; + } + + var prober = new FFProbeAudioInfo(_mediaEncoder, _itemRepo); + + return prober.Probe(item, cancellationToken); + } + public bool HasChanged(IHasMetadata item, DateTime date) { return item.DateModified > date; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 8adb75839d..9073441e44 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -321,69 +321,69 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="currentStreams">The current streams.</param> private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams) { - var useParent = !video.ResolveArgs.IsDirectory; - - if (useParent && video.Parent == null) - { - return; - } - - var fileSystemChildren = useParent - ? video.Parent.ResolveArgs.FileSystemChildren - : video.ResolveArgs.FileSystemChildren; - - var startIndex = currentStreams.Count; - var streams = new List<MediaStream>(); - - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); - - foreach (var file in fileSystemChildren - .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase))) - { - var fullName = file.FullName; - - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); - - // If the subtitle file matches the video file name - if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') - }); - } - else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - // Support xbmc naming conventions - 300.spanish.srt - var language = fileNameWithoutExtension.Split('.').LastOrDefault(); - - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var culture = _localization.GetCultures() - .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); - - if (culture != null) - { - language = culture.ThreeLetterISOLanguageName; - } - - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), - Language = language - }); - } - } - - currentStreams.AddRange(streams); + //var useParent = !video.ResolveArgs.IsDirectory; + + //if (useParent && video.Parent == null) + //{ + // return; + //} + + //var fileSystemChildren = useParent + // ? video.Parent.ResolveArgs.FileSystemChildren + // : video.ResolveArgs.FileSystemChildren; + + //var startIndex = currentStreams.Count; + //var streams = new List<MediaStream>(); + + //var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + //foreach (var file in fileSystemChildren + // .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase))) + //{ + // var fullName = file.FullName; + + // var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); + + // // If the subtitle file matches the video file name + // if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + // { + // streams.Add(new MediaStream + // { + // Index = startIndex++, + // Type = MediaStreamType.Subtitle, + // IsExternal = true, + // Path = fullName, + // Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') + // }); + // } + // else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) + // { + // // Support xbmc naming conventions - 300.spanish.srt + // var language = fileNameWithoutExtension.Split('.').LastOrDefault(); + + // // Try to translate to three character code + // // Be flexible and check against both the full and three character versions + // var culture = _localization.GetCultures() + // .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); + + // if (culture != null) + // { + // language = culture.ThreeLetterISOLanguageName; + // } + + // streams.Add(new MediaStream + // { + // Index = startIndex++, + // Type = MediaStreamType.Subtitle, + // IsExternal = true, + // Path = fullName, + // Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), + // Language = language + // }); + // } + //} + + //currentStreams.AddRange(streams); } /// <summary> diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs deleted file mode 100644 index 8d69e6ba23..0000000000 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs +++ /dev/null @@ -1,686 +0,0 @@ -using DvdLib.Ifo; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Localization; -using MediaBrowser.Controller.MediaInfo; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.MediaInfo -{ - /// <summary> - /// Extracts video information using ffprobe - /// </summary> - public class FFProbeVideoInfoProvider : BaseFFProbeProvider<Video> - { - private readonly IItemRepository _itemRepo; - - public FFProbeVideoInfoProvider(IIsoManager isoManager, IBlurayExaminer blurayExaminer, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, ILocalizationManager localization, IItemRepository itemRepo) - : base(logManager, configurationManager, mediaEncoder, jsonSerializer) - { - if (isoManager == null) - { - throw new ArgumentNullException("isoManager"); - } - if (blurayExaminer == null) - { - throw new ArgumentNullException("blurayExaminer"); - } - - _blurayExaminer = blurayExaminer; - _localization = localization; - _itemRepo = itemRepo; - _isoManager = isoManager; - } - - /// <summary> - /// Gets or sets the bluray examiner. - /// </summary> - /// <value>The bluray examiner.</value> - private readonly IBlurayExaminer _blurayExaminer; - - /// <summary> - /// The _iso manager - /// </summary> - private readonly IIsoManager _isoManager; - - private readonly ILocalizationManager _localization; - - /// <summary> - /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes - /// </summary> - /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnFileSystemStampChange - { - get - { - // Need this in case external subtitle files change - return true; - } - } - - /// <summary> - /// Gets the filestamp extensions. - /// </summary> - /// <value>The filestamp extensions.</value> - protected override string[] FilestampExtensions - { - get - { - return new[] { ".srt", ".ssa", ".ass" }; - } - } - - public override MetadataProviderPriority Priority - { - get - { - return MetadataProviderPriority.Second; - } - } - - /// <summary> - /// Supports video files and dvd structures - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - if (item.LocationType != LocationType.FileSystem) - { - return false; - } - - var video = item as Video; - - if (video != null) - { - if (video.VideoType == VideoType.Iso) - { - return _isoManager.CanMount(item.Path); - } - - return video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Dvd || video.VideoType == VideoType.BluRay; - } - - return false; - } - - /// <summary> - /// Called when [pre fetch]. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="mount">The mount.</param> - protected override void OnPreFetch(Video item, IIsoMount mount) - { - if (item.VideoType == VideoType.Iso) - { - item.IsoType = DetermineIsoType(mount); - } - - if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd)) - { - FetchFromDvdLib(item, mount); - } - - base.OnPreFetch(item, mount); - } - - private void FetchFromDvdLib(Video item, IIsoMount mount) - { - var path = mount == null ? item.Path : mount.MountedPath; - var dvd = new Dvd(path); - - item.RunTimeTicks = dvd.Titles.Select(GetRuntime).Max(); - - var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault(); - - uint? titleNumber = null; - - if (primaryTitle != null) - { - titleNumber = primaryTitle.TitleNumber; - } - - item.PlayableStreamFileNames = GetPrimaryPlaylistVobFiles(item, mount, titleNumber) - .Select(Path.GetFileName) - .ToList(); - } - - private long GetRuntime(Title title) - { - return title.ProgramChains - .Select(i => (TimeSpan)i.PlaybackTime) - .Select(i => i.Ticks) - .Sum(); - } - - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - var video = (Video)item; - - var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false); - - try - { - OnPreFetch(video, isoMount); - - // If we didn't find any satisfying the min length, just take them all - if (video.VideoType == VideoType.Dvd || (video.IsoType.HasValue && video.IsoType == IsoType.Dvd)) - { - if (video.PlayableStreamFileNames.Count == 0) - { - Logger.Error("No playable vobs found in dvd structure, skipping ffprobe."); - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - } - - var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - FFProbeHelpers.NormalizeFFProbeResult(result); - - cancellationToken.ThrowIfCancellationRequested(); - - await Fetch(video, force, providerInfo, cancellationToken, result, isoMount).ConfigureAwait(false); - - } - finally - { - if (isoMount != null) - { - isoMount.Dispose(); - } - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - /// <summary> - /// Mounts the iso if needed. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>IsoMount.</returns> - protected override Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken) - { - if (item.VideoType == VideoType.Iso) - { - return _isoManager.Mount(item.Path, cancellationToken); - } - - return base.MountIsoIfNeeded(item, cancellationToken); - } - - /// <summary> - /// Determines the type of the iso. - /// </summary> - /// <param name="isoMount">The iso mount.</param> - /// <returns>System.Nullable{IsoType}.</returns> - private IsoType? DetermineIsoType(IIsoMount isoMount) - { - var folders = Directory.EnumerateDirectories(isoMount.MountedPath).Select(Path.GetFileName).ToList(); - - if (folders.Contains("video_ts", StringComparer.OrdinalIgnoreCase)) - { - return IsoType.Dvd; - } - if (folders.Contains("bdmv", StringComparer.OrdinalIgnoreCase)) - { - return IsoType.BluRay; - } - - return null; - } - - private IEnumerable<string> GetPrimaryPlaylistVobFiles(Video video, IIsoMount isoMount, uint? titleNumber) - { - // min size 300 mb - const long minPlayableSize = 314572800; - - var root = isoMount != null ? isoMount.MountedPath : video.Path; - - // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size - // Once we reach a file that is at least the minimum, return all subsequent ones - var allVobs = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories) - .Where(file => string.Equals(Path.GetExtension(file), ".vob", StringComparison.OrdinalIgnoreCase)) - .ToList(); - - // If we didn't find any satisfying the min length, just take them all - if (allVobs.Count == 0) - { - Logger.Error("No vobs found in dvd structure."); - return new List<string>(); - } - - if (titleNumber.HasValue) - { - var prefix = string.Format("VTS_0{0}_", titleNumber.Value.ToString(UsCulture)); - var vobs = allVobs.Where(i => Path.GetFileName(i).StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); - - if (vobs.Count > 0) - { - return vobs; - } - - Logger.Debug("Could not determine vob file list for {0} using DvdLib. Will scan using file sizes.", video.Path); - } - - var files = allVobs - .SkipWhile(f => new FileInfo(f).Length < minPlayableSize) - .ToList(); - - // If we didn't find any satisfying the min length, just take them all - if (files.Count == 0) - { - Logger.Warn("Vob size filter resulted in zero matches. Taking all vobs."); - files = allVobs; - } - - // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file - if (files.Count > 0) - { - var parts = Path.GetFileNameWithoutExtension(files[0]).Split('_'); - - if (parts.Length == 3) - { - var title = parts[1]; - - files = files.TakeWhile(f => - { - var fileParts = Path.GetFileNameWithoutExtension(f).Split('_'); - - return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); - - }).ToList(); - - // If this resulted in not getting any vobs, just take them all - if (files.Count == 0) - { - Logger.Warn("Vob filename filter resulted in zero matches. Taking all vobs."); - files = allVobs; - } - } - } - - return files; - } - - /// <summary> - /// Fetches the specified video. - /// </summary> - /// <param name="video">The video.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="data">The data.</param> - /// <param name="isoMount">The iso mount.</param> - /// <returns>Task.</returns> - protected async Task Fetch(Video video, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount) - { - if (data.format != null) - { - // For dvd's this may not always be accurate, so don't set the runtime if the item already has one - var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0; - - if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration)) - { - video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, UsCulture)).Ticks; - } - } - - var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams; - - var chapters = data.Chapters ?? new List<ChapterInfo>(); - - if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) - { - var inputPath = isoMount != null ? isoMount.MountedPath : video.Path; - FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken); - } - - AddExternalSubtitles(video, mediaStreams); - - FetchWtvInfo(video, force, data); - - video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270); - - if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) - { - AddDummyChapters(video, chapters); - } - - var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - - video.VideoBitRate = videoStream == null ? null : videoStream.BitRate; - video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; - - video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); - - await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false); - - var videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed; - - await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); - - // Only save chapters if forcing, if the video changed, or if there are not already any saved ones - if (force || videoFileChanged || _itemRepo.GetChapter(video.Id, 0) == null) - { - await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); - } - } - - /// <summary> - /// Fetches the WTV info. - /// </summary> - /// <param name="video">The video.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="data">The data.</param> - private void FetchWtvInfo(Video video, bool force, InternalMediaInfoResult data) - { - if (data.format == null || data.format.tags == null) - { - return; - } - - if (force || video.Genres.Count == 0) - { - if (!video.LockedFields.Contains(MetadataFields.Genres)) - { - var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); - - if (!string.IsNullOrEmpty(genres)) - { - video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()) - .ToList(); - } - } - } - - if (force || string.IsNullOrEmpty(video.Overview)) - { - if (!video.LockedFields.Contains(MetadataFields.Overview)) - { - var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); - - if (!string.IsNullOrWhiteSpace(overview)) - { - video.Overview = overview; - } - } - } - - if (force || string.IsNullOrEmpty(video.OfficialRating)) - { - var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); - - if (!string.IsNullOrWhiteSpace(officialRating)) - { - if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) - { - video.OfficialRating = officialRating; - } - } - } - - if (force || video.People.Count == 0) - { - if (!video.LockedFields.Contains(MetadataFields.Cast)) - { - var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); - - if (!string.IsNullOrEmpty(people)) - { - video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor }) - .ToList(); - } - } - } - - if (force || !video.ProductionYear.HasValue) - { - var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); - - if (!string.IsNullOrWhiteSpace(year)) - { - int val; - - if (int.TryParse(year, NumberStyles.Integer, UsCulture, out val)) - { - video.ProductionYear = val; - } - } - } - } - - /// <summary> - /// Adds the external subtitles. - /// </summary> - /// <param name="video">The video.</param> - /// <param name="currentStreams">The current streams.</param> - private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams) - { - var useParent = !video.ResolveArgs.IsDirectory; - - if (useParent && video.Parent == null) - { - return; - } - - var fileSystemChildren = useParent - ? video.Parent.ResolveArgs.FileSystemChildren - : video.ResolveArgs.FileSystemChildren; - - var startIndex = currentStreams.Count; - var streams = new List<MediaStream>(); - - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); - - foreach (var file in fileSystemChildren - .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && FilestampExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase))) - { - var fullName = file.FullName; - - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); - - // If the subtitle file matches the video file name - if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') - }); - } - else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - // Support xbmc naming conventions - 300.spanish.srt - var language = fileNameWithoutExtension.Split('.').LastOrDefault(); - - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var culture = _localization.GetCultures() - .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); - - if (culture != null) - { - language = culture.ThreeLetterISOLanguageName; - } - - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), - Language = language - }); - } - } - - currentStreams.AddRange(streams); - } - - /// <summary> - /// The dummy chapter duration - /// </summary> - private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; - - /// <summary> - /// Adds the dummy chapters. - /// </summary> - /// <param name="video">The video.</param> - /// <param name="chapters">The chapters.</param> - private void AddDummyChapters(Video video, List<ChapterInfo> chapters) - { - var runtime = video.RunTimeTicks ?? 0; - - if (runtime < 0) - { - throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime)); - } - - if (runtime < _dummyChapterDuration) - { - return; - } - - long currentChapterTicks = 0; - var index = 1; - - // Limit to 100 chapters just in case there's some incorrect metadata here - while (currentChapterTicks < runtime && index < 100) - { - chapters.Add(new ChapterInfo - { - Name = "Chapter " + index, - StartPositionTicks = currentChapterTicks - }); - - index++; - currentChapterTicks += _dummyChapterDuration; - } - } - - /// <summary> - /// Fetches the bd info. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="chapters">The chapters.</param> - /// <param name="mediaStreams">The media streams.</param> - /// <param name="inputPath">The input path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, string inputPath, CancellationToken cancellationToken) - { - var video = (Video)item; - - var result = GetBDInfo(inputPath); - - cancellationToken.ThrowIfCancellationRequested(); - - int? currentHeight = null; - int? currentWidth = null; - int? currentBitRate = null; - - var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); - - // Grab the values that ffprobe recorded - if (videoStream != null) - { - currentBitRate = videoStream.BitRate; - currentWidth = videoStream.Width; - currentHeight = videoStream.Height; - } - - // Fill video properties from the BDInfo result - Fetch(video, mediaStreams, result, chapters); - - videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); - - // Use the ffprobe values if these are empty - if (videoStream != null) - { - videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate; - videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width; - videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height; - } - } - - /// <summary> - /// Determines whether the specified num is empty. - /// </summary> - /// <param name="num">The num.</param> - /// <returns><c>true</c> if the specified num is empty; otherwise, <c>false</c>.</returns> - private bool IsEmpty(int? num) - { - return !num.HasValue || num.Value == 0; - } - - /// <summary> - /// Fills video properties from the VideoStream of the largest playlist - /// </summary> - /// <param name="video">The video.</param> - /// <param name="mediaStreams">The media streams.</param> - /// <param name="stream">The stream.</param> - /// <param name="chapters">The chapters.</param> - private void Fetch(Video video, List<MediaStream> mediaStreams, BlurayDiscInfo stream, List<ChapterInfo> chapters) - { - // Check all input for null/empty/zero - - mediaStreams.Clear(); - mediaStreams.AddRange(stream.MediaStreams); - - video.MainFeaturePlaylistName = stream.PlaylistName; - - if (stream.RunTimeTicks.HasValue && stream.RunTimeTicks.Value > 0) - { - video.RunTimeTicks = stream.RunTimeTicks; - } - - video.PlayableStreamFileNames = stream.Files.ToList(); - - if (stream.Chapters != null) - { - chapters.Clear(); - - chapters.AddRange(stream.Chapters.Select(c => new ChapterInfo - { - StartPositionTicks = TimeSpan.FromSeconds(c).Ticks - - })); - } - } - - /// <summary> - /// Gets information about the longest playlist on a bdrom - /// </summary> - /// <param name="path">The path.</param> - /// <returns>VideoStream.</returns> - private BlurayDiscInfo GetBDInfo(string path) - { - return _blurayExaminer.GetDiscInfo(path); - } - } -} diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 70b849e1c3..31d44f4ec8 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.MediaInfo { - public class VideoImageProvider : IDynamicImageProvider + public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor { private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; @@ -26,34 +26,6 @@ namespace MediaBrowser.Providers.MediaInfo } /// <summary> - /// Qualifieses for extraction. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool QualifiesForExtraction(Video item) - { - // No support for this - if (item.VideoType == VideoType.HdDvd) - { - return false; - } - - // Can't extract from iso's if we weren't unable to determine iso type - if (item.VideoType == VideoType.Iso && !item.IsoType.HasValue) - { - return false; - } - - // Can't extract if we didn't find a video stream in the file - if (!item.DefaultVideoStreamIndex.HasValue) - { - return false; - } - - return true; - } - - /// <summary> /// The null mount task result /// </summary> protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null); @@ -81,7 +53,27 @@ namespace MediaBrowser.Providers.MediaInfo public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - return GetVideoImage((Video)item, cancellationToken); + var video = (Video)item; + + // No support for this + if (video.VideoType == VideoType.HdDvd) + { + return Task.FromResult(new DynamicImageResponse { HasImage = false }); + } + + // Can't extract from iso's if we weren't unable to determine iso type + if (video.VideoType == VideoType.Iso && !video.IsoType.HasValue) + { + return Task.FromResult(new DynamicImageResponse { HasImage = false }); + } + + // Can't extract if we didn't find a video stream in the file + if (!video.DefaultVideoStreamIndex.HasValue) + { + return Task.FromResult(new DynamicImageResponse { HasImage = false }); + } + + return GetVideoImage(video, cancellationToken); } public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) @@ -133,5 +125,10 @@ namespace MediaBrowser.Providers.MediaInfo return item.LocationType == LocationType.FileSystem && item is Video; } + + public bool HasChanged(IHasMetadata item, DateTime date) + { + return item.DateModified > date; + } } } diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs deleted file mode 100644 index 9fc57c5426..0000000000 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ /dev/null @@ -1,356 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Providers; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Net; -using System.Net; -using MediaBrowser.Providers.Music; - -namespace MediaBrowser.Providers.Movies -{ - /// <summary> - /// Class FanArtMovieProvider - /// </summary> - class FanartMovieProvider : BaseMetadataProvider - { - /// <summary> - /// Gets the HTTP client. - /// </summary> - /// <value>The HTTP client.</value> - protected IHttpClient HttpClient { get; private set; } - - /// <summary> - /// The _provider manager - /// </summary> - private readonly IProviderManager _providerManager; - - internal static FanartMovieProvider Current { get; private set; } - private readonly IFileSystem _fileSystem; - - /// <summary> - /// Initializes a new instance of the <see cref="FanartMovieProvider" /> class. - /// </summary> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="providerManager">The provider manager.</param> - /// <exception cref="System.ArgumentNullException">httpClient</exception> - public FanartMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - if (httpClient == null) - { - throw new ArgumentNullException("httpClient"); - } - HttpClient = httpClient; - _providerManager = providerManager; - _fileSystem = fileSystem; - Current = this; - } - - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } - } - - /// <summary> - /// Gets a value indicating whether [refresh on version change]. - /// </summary> - /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } - - /// <summary> - /// Gets the provider version. - /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion - { - get - { - return "13"; - } - } - - public override MetadataProviderPriority Priority - { - get - { - return MetadataProviderPriority.Fifth; - } - } - - /// <summary> - /// The fan art base URL - /// </summary> - protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1"; - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - var trailer = item as Trailer; - - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } - - return item is Movie || item is MusicVideo; - } - - /// <summary> - /// Needses the refresh internal. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="providerInfo">The provider info.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) - { - return false; - } - - return base.NeedsRefreshInternal(item, providerInfo); - } - - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var id = item.GetProviderId(MetadataProviders.Tmdb); - - if (!string.IsNullOrEmpty(id)) - { - // Process images - var xmlPath = GetFanartXmlPath(id); - - var fileInfo = new FileInfo(xmlPath); - - return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; - } - - return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); - } - - /// <summary> - /// Gets the movie data path. - /// </summary> - /// <param name="appPaths">The app paths.</param> - /// <param name="tmdbId">The TMDB id.</param> - /// <returns>System.String.</returns> - internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId) - { - var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId); - - return dataPath; - } - - /// <summary> - /// Gets the movie data path. - /// </summary> - /// <param name="appPaths">The app paths.</param> - /// <returns>System.String.</returns> - internal static string GetMoviesDataPath(IApplicationPaths appPaths) - { - var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies"); - - return dataPath; - } - - /// <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 async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var movieId = item.GetProviderId(MetadataProviders.Tmdb); - - if (!string.IsNullOrEmpty(movieId)) - { - var xmlPath = GetFanartXmlPath(movieId); - - // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates - if (!File.Exists(xmlPath)) - { - await DownloadMovieXml(movieId, cancellationToken).ConfigureAwait(false); - } - - var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false); - - await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - public string GetFanartXmlPath(string tmdbId) - { - var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId); - return Path.Combine(movieDataPath, "fanart.xml"); - } - - /// <summary> - /// Downloads the movie XML. - /// </summary> - /// <param name="tmdbId">The TMDB id.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId); - - var xmlPath = GetFanartXmlPath(tmdbId); - - Directory.CreateDirectory(Path.GetDirectoryName(xmlPath)); - - using (var response = await HttpClient.Get(new HttpRequestOptions - { - Url = url, - ResourcePool = FanartArtistProvider.FanArtResourcePool, - CancellationToken = cancellationToken - - }).ConfigureAwait(false)) - { - using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await response.CopyToAsync(xmlFileStream).ConfigureAwait(false); - } - } - } - - private readonly Task _cachedTask = Task.FromResult(true); - internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken) - { - var path = GetFanartXmlPath(tmdbId); - - var fileInfo = _fileSystem.GetFileSystemInfo(path); - - if (fileInfo.Exists) - { - if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) - { - return _cachedTask; - } - } - - return DownloadMovieXml(tmdbId, cancellationToken); - } - - private async Task FetchImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions(); - - if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary)) - { - await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo)) - { - await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art)) - { - await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (options.IsEnabled(ImageType.Disc) && !item.HasImage(ImageType.Disc)) - { - await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner)) - { - await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb)) - { - await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var backdropLimit = options.GetLimit(ImageType.Backdrop); - - if (backdropLimit > 0 && - item.BackdropImagePaths.Count < backdropLimit) - { - foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) - { - await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken) - .ConfigureAwait(false); - - if (item.BackdropImagePaths.Count >= backdropLimit) break; - } - } - } - - private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) - { - foreach (var image in images.Where(i => i.Type == type)) - { - try - { - await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false); - break; - } - catch (HttpException ex) - { - // Sometimes fanart has bad url's in their xml - if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) - { - continue; - } - break; - } - } - } - } -} diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs index 47948455b0..600ba1925e 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs @@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.Movies return; } - var path = FanartMovieProvider.GetMoviesDataPath(_config.CommonApplicationPaths); + var path = FanartMovieImageProvider.GetMoviesDataPath(_config.CommonApplicationPaths); Directory.CreateDirectory(path); @@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Movies { _logger.Info("Updating movie " + id); - await FanartMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false); + await FanartMovieImageProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false); numComplete++; double percent = numComplete; diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index 70aaec526c..e0ed0ec250 100644 --- a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -7,6 +8,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Music; using System; using System.Collections.Generic; using System.Globalization; @@ -16,22 +18,27 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; -using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Movies { - public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder + public class FanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder { private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; - public ManualFanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) + private const string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1"; + + internal static FanartMovieImageProvider Current; + + public FanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) { _config = config; _httpClient = httpClient; _fileSystem = fileSystem; + + Current = this; } public string Name @@ -86,9 +93,9 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(movieId)) { - await FanartMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false); + await EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false); - var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(movieId); + var xmlPath = GetFanartXmlPath(movieId); try { @@ -344,7 +351,7 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(id)) { // Process images - var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(id); + var xmlPath = GetFanartXmlPath(id); var fileInfo = new FileInfo(xmlPath); @@ -353,5 +360,85 @@ namespace MediaBrowser.Providers.Movies return false; } + + /// <summary> + /// Gets the movie data path. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="tmdbId">The TMDB id.</param> + /// <returns>System.String.</returns> + internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId) + { + var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId); + + return dataPath; + } + + /// <summary> + /// Gets the movie data path. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <returns>System.String.</returns> + internal static string GetMoviesDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies"); + + return dataPath; + } + + public string GetFanartXmlPath(string tmdbId) + { + var movieDataPath = GetMovieDataPath(_config.ApplicationPaths, tmdbId); + return Path.Combine(movieDataPath, "fanart.xml"); + } + + /// <summary> + /// Downloads the movie XML. + /// </summary> + /// <param name="tmdbId">The TMDB id.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId); + + var xmlPath = GetFanartXmlPath(tmdbId); + + Directory.CreateDirectory(Path.GetDirectoryName(xmlPath)); + + using (var response = await _httpClient.Get(new HttpRequestOptions + { + Url = url, + ResourcePool = FanartArtistProvider.FanArtResourcePool, + CancellationToken = cancellationToken + + }).ConfigureAwait(false)) + { + using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await response.CopyToAsync(xmlFileStream).ConfigureAwait(false); + } + } + } + + private readonly Task _cachedTask = Task.FromResult(true); + internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken) + { + var path = GetFanartXmlPath(tmdbId); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + { + return _cachedTask; + } + } + + return DownloadMovieXml(tmdbId, cancellationToken); + } } } diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs new file mode 100644 index 0000000000..d06d12a496 --- /dev/null +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -0,0 +1,245 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Movies +{ + public class GenericMovieDbInfo<T> + where T : Video, new() + { + private readonly ILogger _logger; + private readonly IJsonSerializer _jsonSerializer; + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public GenericMovieDbInfo(ILogger logger, IJsonSerializer jsonSerializer) + { + _logger = logger; + _jsonSerializer = jsonSerializer; + } + + public async Task<MetadataResult<T>> GetMetadata(ItemId itemId, CancellationToken cancellationToken) + { + var result = new MetadataResult<T>(); + + var tmdbId = itemId.GetProviderId(MetadataProviders.Tmdb); + var imdbId = itemId.GetProviderId(MetadataProviders.Imdb); + + // Don't search for music video id's because it is very easy to misidentify. + if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId) && typeof(T) != typeof(MusicVideo)) + { + tmdbId = await new MovieDbSearch(_logger, _jsonSerializer) + .FindMovieId(itemId, cancellationToken).ConfigureAwait(false); + } + + if (!string.IsNullOrEmpty(tmdbId) || !string.IsNullOrEmpty(imdbId)) + { + cancellationToken.ThrowIfCancellationRequested(); + + result.Item = await FetchMovieData(tmdbId, imdbId, itemId.MetadataLanguage, itemId.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + + result.HasMetadata = result.Item != null; + } + + return result; + } + + /// <summary> + /// Fetches the movie data. + /// </summary> + /// <param name="tmdbId">The TMDB identifier.</param> + /// <param name="imdbId">The imdb identifier.</param> + /// <param name="language">The language.</param> + /// <param name="preferredCountryCode">The preferred country code.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{`0}.</returns> + private async Task<T> FetchMovieData(string tmdbId, string imdbId, string language, string preferredCountryCode, CancellationToken cancellationToken) + { + string dataFilePath = null; + MovieDbProvider.CompleteMovieData movieInfo = null; + + // Id could be ImdbId or TmdbId + if (string.IsNullOrEmpty(tmdbId)) + { + movieInfo = await MovieDbProvider.Current.FetchMainResult(imdbId, language, cancellationToken).ConfigureAwait(false); + if (movieInfo == null) return null; + + tmdbId = movieInfo.id.ToString(_usCulture); + + dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _jsonSerializer.SerializeToFile(movieInfo, dataFilePath); + } + + await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); + + dataFilePath = dataFilePath ?? MovieDbProvider.Current.GetDataFilePath(tmdbId, language); + movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(dataFilePath); + + var item = new T(); + + ProcessMainInfo(item, preferredCountryCode, movieInfo); + + return item; + } + + /// <summary> + /// Processes the main info. + /// </summary> + /// <param name="movie">The movie.</param> + /// <param name="preferredCountryCode">The preferred country code.</param> + /// <param name="movieData">The movie data.</param> + private void ProcessMainInfo(T movie, string preferredCountryCode, MovieDbProvider.CompleteMovieData movieData) + { + movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name; + + // Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException. + movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null; + movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null; + + movie.HomePageUrl = movieData.homepage; + + var hasBudget = movie as IHasBudget; + if (hasBudget != null) + { + hasBudget.Budget = movieData.budget; + hasBudget.Revenue = movieData.revenue; + } + + if (!string.IsNullOrEmpty(movieData.tagline)) + { + var hasTagline = movie as IHasTaglines; + if (hasTagline != null) + { + hasTagline.Taglines.Clear(); + hasTagline.AddTagline(movieData.tagline); + } + } + + movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture)); + movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id); + + if (movieData.belongs_to_collection != null) + { + movie.SetProviderId(MetadataProviders.TmdbCollection, + movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture)); + + var movieItem = movie as Movie; + + if (movieItem != null) + { + movieItem.TmdbCollectionName = movieData.belongs_to_collection.name; + } + } + else + { + movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry + } + + float rating; + string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture); + + if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating)) + { + movie.CommunityRating = rating; + } + + movie.VoteCount = movieData.vote_count; + + //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match + if (movieData.releases != null && movieData.releases.countries != null) + { + var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country(); + var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country(); + var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new MovieDbProvider.Country(); + + var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-"; + movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification) + ? ratingPrefix + ourRelease.certification + : !string.IsNullOrEmpty(usRelease.certification) + ? usRelease.certification + : !string.IsNullOrEmpty(minimunRelease.certification) + ? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification + : null; + } + + if (movieData.release_date.Year != 1) + { + //no specific country release info at all + movie.PremiereDate = movieData.release_date.ToUniversalTime(); + movie.ProductionYear = movieData.release_date.Year; + } + + //studios + if (movieData.production_companies != null) + { + movie.Studios.Clear(); + + foreach (var studio in movieData.production_companies.Select(c => c.name)) + { + movie.AddStudio(studio); + } + } + + // genres + // Movies get this from imdb + var genres = movieData.genres ?? new List<MovieDbProvider.GenreItem>(); + + foreach (var genre in genres.Select(g => g.name)) + { + movie.AddGenre(genre); + } + + //Actors, Directors, Writers - all in People + //actors come from cast + if (movieData.casts != null && movieData.casts.cast != null) + { + foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order }); + } + + //and the rest from crew + if (movieData.casts != null && movieData.casts.crew != null) + { + foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department }); + } + + if (movieData.keywords != null && movieData.keywords.keywords != null) + { + var hasTags = movie as IHasKeywords; + if (hasTags != null) + { + hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList(); + } + } + + if (movieData.trailers != null && movieData.trailers.youtube != null && + movieData.trailers.youtube.Count > 0) + { + var hasTrailers = movie as IHasTrailers; + if (hasTrailers != null) + { + hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl + { + Url = string.Format("http://www.youtube.com/watch?v={0}", i.source), + IsDirectLink = false, + Name = i.name, + VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition + + }).ToList(); + } + } + } + + } +} diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs index bc4652e2c5..be2f7ad614 100644 --- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs @@ -15,12 +15,12 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { - class ManualMovieDbImageProvider : IRemoteImageProvider, IHasOrder + class MovieDbImageProvider : IRemoteImageProvider, IHasOrder { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; - public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) + public MovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; @@ -168,9 +168,17 @@ namespace MediaBrowser.Providers.Movies private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer, CancellationToken cancellationToken) { - await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false); + var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); + var language = item.GetPreferredMetadataLanguage(); + + if (string.IsNullOrEmpty(tmdbId)) + { + return null; + } + + await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); - var path = MovieDbProvider.Current.GetDataFilePath(item); + var path = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); if (!string.IsNullOrEmpty(path)) { diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs deleted file mode 100644 index 9ef2e1bca1..0000000000 --- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs +++ /dev/null @@ -1,247 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Providers; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Movies -{ - /// <summary> - /// Class MovieDbImagesProvider - /// </summary> - public class MovieDbImagesProvider : BaseMetadataProvider - { - /// <summary> - /// The _provider manager - /// </summary> - private readonly IProviderManager _providerManager; - - private readonly IFileSystem _fileSystem; - - /// <summary> - /// Initializes a new instance of the <see cref="MovieDbImagesProvider"/> class. - /// </summary> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="providerManager">The provider manager.</param> - public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - _providerManager = providerManager; - _fileSystem = fileSystem; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Fourth; } - } - - /// <summary> - /// Supports the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - var trailer = item as Trailer; - - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } - - // Don't support local trailers - return item is Movie || item is MusicVideo; - } - - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } - } - - /// <summary> - /// Gets a value indicating whether [requires internet]. - /// </summary> - /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value> - public override bool RequiresInternet - { - get - { - return true; - } - } - - /// <summary> - /// Gets a value indicating whether [refresh on version change]. - /// </summary> - /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } - - /// <summary> - /// Gets the provider version. - /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion - { - get - { - return "3"; - } - } - - /// <summary> - /// Needses the refresh internal. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="providerInfo">The provider info.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) - { - return false; - } - - var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions(); - - // Don't refresh if we already have both poster and backdrop and we're not refreshing images - if (item.HasImage(ImageType.Primary) && - item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop) && - !item.LockedFields.Contains(MetadataFields.Images)) - { - return false; - } - - return base.NeedsRefreshInternal(item, providerInfo); - } - - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var path = MovieDbProvider.Current.GetDataFilePath(item); - - if (!string.IsNullOrEmpty(path)) - { - var fileInfo = new FileInfo(path); - - if (fileInfo.Exists) - { - return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; - } - } - - return false; - } - - /// <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 async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false); - await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - /// <summary> - /// Processes the images. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="images">The images.</param> - /// <param name="cancellationToken">The cancellation token</param> - /// <returns>Task.</returns> - private async Task ProcessImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var eligiblePosters = images - .Where(i => i.Type == ImageType.Primary) - .ToList(); - - // poster - if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images)) - { - var poster = eligiblePosters[0]; - - var url = poster.Url; - - var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken - - }).ConfigureAwait(false); - - await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Primary, null, cancellationToken) - .ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions(); - - var eligibleBackdrops = images - .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= options.GetMinWidth(ImageType.Backdrop)) - .ToList(); - - var backdropLimit = options.GetLimit(ImageType.Backdrop); - - // backdrops - only download if earlier providers didn't find any (fanart) - if (eligibleBackdrops.Count > 0 && - options.IsEnabled(ImageType.Backdrop) && - item.BackdropImagePaths.Count < backdropLimit && - !item.LockedFields.Contains(MetadataFields.Backdrops)) - { - for (var i = 0; i < eligibleBackdrops.Count; i++) - { - var url = eligibleBackdrops[i].Url; - - var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken - - }).ConfigureAwait(false); - - await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, cancellationToken) - .ConfigureAwait(false); - - if (item.BackdropImagePaths.Count >= backdropLimit) - { - break; - } - } - } - } - } -} diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 3adc682fca..47b92235b8 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -5,16 +5,11 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; -using MediaBrowser.Providers.Savers; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; @@ -23,114 +18,55 @@ namespace MediaBrowser.Providers.Movies /// <summary> /// Class MovieDbProvider /// </summary> - public class MovieDbProvider : BaseMetadataProvider, IDisposable + public class MovieDbProvider : IRemoteMetadataProvider<Movie>, IDisposable { - protected static CultureInfo EnUs = new CultureInfo("en-US"); - - protected readonly IProviderManager ProviderManager; - - /// <summary> - /// The movie db - /// </summary> internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1); internal static MovieDbProvider Current { get; private set; } - /// <summary> - /// Gets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - protected IJsonSerializer JsonSerializer { get; private set; } - - /// <summary> - /// Gets the HTTP client. - /// </summary> - /// <value>The HTTP client.</value> - protected IHttpClient HttpClient { get; private set; } + private readonly IJsonSerializer _jsonSerializer; + private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _configurationManager; + private readonly ILogger _logger; - /// <summary> - /// Initializes a new instance of the <see cref="MovieDbProvider" /> class. - /// </summary> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="providerManager">The provider manager.</param> - public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - JsonSerializer = jsonSerializer; - HttpClient = httpClient; - ProviderManager = providerManager; + public MovieDbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger) + { + _jsonSerializer = jsonSerializer; + _httpClient = httpClient; _fileSystem = fileSystem; + _configurationManager = configurationManager; + _logger = logger; Current = this; } - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) + public Task<MetadataResult<Movie>> GetMetadata(ItemId id, CancellationToken cancellationToken) { - if (dispose) - { - MovieDbResourcePool.Dispose(); - } + return GetItemMetadata<Movie>(id, cancellationToken); } - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority + public Task<MetadataResult<T>> GetItemMetadata<T>(ItemId id, CancellationToken cancellationToken) + where T : Video, new () { - get { return MetadataProviderPriority.Third; } + var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer); + + return movieDb.GetMetadata(id, cancellationToken); } - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) + public string Name { - var trailer = item as Trailer; - - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } - - // Don't support local trailers - return item is Movie || item is MusicVideo; + get { return "TheMovieDb"; } } /// <summary> - /// Gets a value indicating whether [requires internet]. + /// Releases unmanaged and - optionally - managed resources. /// </summary> - /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value> - public override bool RequiresInternet - { - get - { - return true; - } - } - - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } - - protected override string ProviderVersion + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) { - get + if (dispose) { - return "3"; + MovieDbResourcePool.Dispose(); } } @@ -170,7 +106,7 @@ namespace MediaBrowser.Providers.Movies }).ConfigureAwait(false)) { - _tmdbSettings = JsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json); + _tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json); return _tmdbSettings; } @@ -187,30 +123,6 @@ namespace MediaBrowser.Providers.Movies internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) - { - return true; - } - - return base.NeedsRefreshInternal(item, providerInfo); - } - - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var path = GetDataFilePath(item); - - if (!string.IsNullOrEmpty(path)) - { - var fileInfo = new FileInfo(path); - - return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; - } - - return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); - } - /// <summary> /// Gets the movie data path. /// </summary> @@ -232,121 +144,6 @@ namespace MediaBrowser.Providers.Movies } /// <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 async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var id = item.GetProviderId(MetadataProviders.Tmdb); - - if (string.IsNullOrEmpty(id)) - { - id = item.GetProviderId(MetadataProviders.Imdb); - } - - // Don't search for music video id's because it is very easy to misidentify. - if (string.IsNullOrEmpty(id) && !(item is MusicVideo)) - { - id = await new MovieDbSearch(Logger, JsonSerializer) - .FindMovieId(GetId(item), cancellationToken).ConfigureAwait(false); - } - - if (!string.IsNullOrEmpty(id)) - { - cancellationToken.ThrowIfCancellationRequested(); - - await FetchMovieData(item, id, force, cancellationToken).ConfigureAwait(false); - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - private ItemId GetId(IHasMetadata item) - { - return new ItemId - { - MetadataCountryCode = item.GetPreferredMetadataCountryCode(), - MetadataLanguage = item.GetPreferredMetadataLanguage(), - Name = item.Name, - ProviderIds = item.ProviderIds - }; - } - - /// <summary> - /// Determines whether [has alt meta] [the specified item]. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns> - internal static bool HasAltMeta(BaseItem item) - { - var path = MovieXmlSaver.GetMovieSavePath((Video)item); - - if (item.LocationType == LocationType.FileSystem) - { - // If mixed with multiple movies in one folder, resolve args won't have the file system children - return item.ResolveArgs.ContainsMetaFileByName(Path.GetFileName(path)) || File.Exists(path); - } - - return false; - } - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - /// <summary> - /// Fetches the movie data. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="id">The id.</param> - /// <param name="isForcedRefresh">if set to <c>true</c> [is forced refresh].</param> - /// <param name="cancellationToken">The cancellation token</param> - /// <returns>Task.</returns> - private async Task FetchMovieData(BaseItem item, string id, bool isForcedRefresh, CancellationToken cancellationToken) - { - // Id could be ImdbId or TmdbId - - var language = item.GetPreferredMetadataLanguage(); - - var dataFilePath = GetDataFilePath(item); - - var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); - - if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath)) - { - var mainResult = await FetchMainResult(id, language, cancellationToken).ConfigureAwait(false); - - if (mainResult == null) return; - - tmdbId = mainResult.id.ToString(_usCulture); - - dataFilePath = GetDataFilePath(tmdbId, language); - - var directory = Path.GetDirectoryName(dataFilePath); - - Directory.CreateDirectory(directory); - - JsonSerializer.SerializeToFile(mainResult, dataFilePath); - } - - if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item)) - { - dataFilePath = GetDataFilePath(tmdbId, language); - - if (!string.IsNullOrEmpty(dataFilePath)) - { - var mainResult = JsonSerializer.DeserializeFromFile<CompleteMovieData>(dataFilePath); - - ProcessMainInfo(item, mainResult); - } - } - } - - /// <summary> /// Downloads the movie info. /// </summary> /// <param name="id">The id.</param> @@ -363,60 +160,49 @@ namespace MediaBrowser.Providers.Movies Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); - JsonSerializer.SerializeToFile(mainResult, dataFilePath); + _jsonSerializer.SerializeToFile(mainResult, dataFilePath); } private readonly Task _cachedTask = Task.FromResult(true); - internal Task EnsureMovieInfo(BaseItem item, CancellationToken cancellationToken) + internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken) { - var path = GetDataFilePath(item); - - if (string.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(tmdbId)) + { + throw new ArgumentNullException("tmdbId"); + } + if (string.IsNullOrEmpty(language)) { - return _cachedTask; + throw new ArgumentNullException("language"); } - + + var path = GetDataFilePath(tmdbId, language); + var fileInfo = _fileSystem.GetFileSystemInfo(path); if (fileInfo.Exists) { // If it's recent or automatic updates are enabled, don't re-download - if ((ConfigurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + if ((_configurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) { return _cachedTask; } } - var id = item.GetProviderId(MetadataProviders.Tmdb); - - if (string.IsNullOrEmpty(id)) - { - return _cachedTask; - } - - return DownloadMovieInfo(id, item.GetPreferredMetadataLanguage(), cancellationToken); + return DownloadMovieInfo(tmdbId, language, cancellationToken); } - /// <summary> - /// Gets the data file path. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>System.String.</returns> - internal string GetDataFilePath(BaseItem item) + internal string GetDataFilePath(string tmdbId, string preferredLanguage) { - var id = item.GetProviderId(MetadataProviders.Tmdb); - - if (string.IsNullOrEmpty(id)) + if (string.IsNullOrEmpty(tmdbId)) { - return null; + throw new ArgumentNullException("tmdbId"); + } + if (string.IsNullOrEmpty(preferredLanguage)) + { + throw new ArgumentNullException("preferredLanguage"); } - return GetDataFilePath(id, item.GetPreferredMetadataLanguage()); - } - - private string GetDataFilePath(string tmdbId, string preferredLanguage) - { - var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId); + var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId); var filename = string.Format("all-{0}.json", preferredLanguage ?? string.Empty); @@ -431,7 +217,7 @@ namespace MediaBrowser.Providers.Movies /// <param name="language">The language.</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>Task{CompleteMovieData}.</returns> - private async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken) + internal async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken) { var url = string.Format(GetMovieInfo3, id, ApiKey); @@ -461,7 +247,7 @@ namespace MediaBrowser.Providers.Movies }).ConfigureAwait(false)) { - mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json); + mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json); } cancellationToken.ThrowIfCancellationRequested(); @@ -470,7 +256,7 @@ namespace MediaBrowser.Providers.Movies { if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) { - Logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English..."); + _logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English..."); url = string.Format(GetMovieInfo3, id, ApiKey) + "&include_image_language=en,null&language=en"; @@ -482,12 +268,12 @@ namespace MediaBrowser.Providers.Movies }).ConfigureAwait(false)) { - mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json); + mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json); } if (String.IsNullOrEmpty(mainResult.overview)) { - Logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")"); + _logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")"); return null; } } @@ -495,183 +281,6 @@ namespace MediaBrowser.Providers.Movies return mainResult; } - /// <summary> - /// Processes the main info. - /// </summary> - /// <param name="movie">The movie.</param> - /// <param name="movieData">The movie data.</param> - private void ProcessMainInfo(BaseItem movie, CompleteMovieData movieData) - { - if (!movie.LockedFields.Contains(MetadataFields.Name)) - { - movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name; - } - if (!movie.LockedFields.Contains(MetadataFields.Overview)) - { - // Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException. - movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null; - movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null; - } - movie.HomePageUrl = movieData.homepage; - - var hasBudget = movie as IHasBudget; - if (hasBudget != null) - { - hasBudget.Budget = movieData.budget; - hasBudget.Revenue = movieData.revenue; - } - - if (!string.IsNullOrEmpty(movieData.tagline)) - { - var hasTagline = movie as IHasTaglines; - if (hasTagline != null) - { - hasTagline.Taglines.Clear(); - hasTagline.AddTagline(movieData.tagline); - } - } - - movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture)); - movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id); - - if (movieData.belongs_to_collection != null) - { - movie.SetProviderId(MetadataProviders.TmdbCollection, - movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture)); - - var movieItem = movie as Movie; - - if (movieItem != null) - { - movieItem.TmdbCollectionName = movieData.belongs_to_collection.name; - } - } - else - { - movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry - } - - float rating; - string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture); - - // tmdb appears to have unified their numbers to always report "7.3" regardless of country - // so I removed the culture-specific processing here because it was not working for other countries -ebr - // Movies get this from imdb - if (!(movie is Movie) && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating)) - { - movie.CommunityRating = rating; - } - - // Movies get this from imdb - if (!(movie is Movie)) - { - movie.VoteCount = movieData.vote_count; - } - - var preferredCountryCode = movie.GetPreferredMetadataCountryCode(); - - //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match - if (movieData.releases != null && movieData.releases.countries != null) - { - var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country(); - var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country(); - var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country(); - - if (!movie.LockedFields.Contains(MetadataFields.OfficialRating)) - { - var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-"; - movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification) - ? ratingPrefix + ourRelease.certification - : !string.IsNullOrEmpty(usRelease.certification) - ? usRelease.certification - : !string.IsNullOrEmpty(minimunRelease.certification) - ? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification - : null; - } - } - - if (movieData.release_date.Year != 1) - { - //no specific country release info at all - movie.PremiereDate = movieData.release_date.ToUniversalTime(); - movie.ProductionYear = movieData.release_date.Year; - } - - //studios - if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios)) - { - movie.Studios.Clear(); - - foreach (var studio in movieData.production_companies.Select(c => c.name)) - { - movie.AddStudio(studio); - } - } - - // genres - // Movies get this from imdb - var genres = movieData.genres ?? new List<GenreItem>(); - if (!movie.LockedFields.Contains(MetadataFields.Genres)) - { - // Only grab them if a boxset or there are no genres. - // For movies and trailers we'll use imdb via omdb - // But omdb data is for english users only so fetch if language is not english - if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase)) - { - movie.Genres.Clear(); - - foreach (var genre in genres.Select(g => g.name)) - { - movie.AddGenre(genre); - } - } - } - - if (!movie.LockedFields.Contains(MetadataFields.Cast)) - { - movie.People.Clear(); - - //Actors, Directors, Writers - all in People - //actors come from cast - if (movieData.casts != null && movieData.casts.cast != null) - { - foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order }); - } - - //and the rest from crew - if (movieData.casts != null && movieData.casts.crew != null) - { - foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department }); - } - } - - if (movieData.keywords != null && movieData.keywords.keywords != null && !movie.LockedFields.Contains(MetadataFields.Keywords)) - { - var hasTags = movie as IHasKeywords; - if (hasTags != null) - { - hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList(); - } - } - - if (movieData.trailers != null && movieData.trailers.youtube != null && - movieData.trailers.youtube.Count > 0) - { - var hasTrailers = movie as IHasTrailers; - if (hasTrailers != null) - { - hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl - { - Url = string.Format("http://www.youtube.com/watch?v={0}", i.source), - IsDirectLink = false, - Name = i.name, - VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition - - }).ToList(); - } - } - } - private DateTime _lastRequestDate = DateTime.MinValue; /// <summary> @@ -695,7 +304,7 @@ namespace MediaBrowser.Providers.Movies _lastRequestDate = DateTime.Now; - return await HttpClient.Get(options).ConfigureAwait(false); + return await _httpClient.Get(options).ConfigureAwait(false); } finally { @@ -897,18 +506,5 @@ namespace MediaBrowser.Providers.Movies public Keywords keywords { get; set; } public Trailers trailers { get; set; } } - - internal class TmdbImageSettings - { - public List<string> backdrop_sizes { get; set; } - public string base_url { get; set; } - public List<string> poster_sizes { get; set; } - public List<string> profile_sizes { get; set; } - } - - internal class TmdbSettingsResult - { - public TmdbImageSettings images { get; set; } - } } } diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs new file mode 100644 index 0000000000..cc513b3959 --- /dev/null +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Movies +{ + public class MovieMetadataService : MetadataService<Movie, ItemId> + { + private readonly ILibraryManager _libraryManager; + + public MovieMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Movie source, Movie target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(Movie item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs deleted file mode 100644 index 7ae6dffc79..0000000000 --- a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs +++ /dev/null @@ -1,112 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Providers.Savers; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Movies -{ - /// <summary> - /// Class MovieProviderFromXml - /// </summary> - public class MovieProviderFromXml : BaseMetadataProvider - { - private readonly IItemRepository _itemRepo; - private readonly IFileSystem _fileSystem; - - public MovieProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - _itemRepo = itemRepo; - _fileSystem = fileSystem; - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - if (item.LocationType != LocationType.FileSystem) - { - return false; - } - - var trailer = item as Trailer; - - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } - - // Check parent for null to avoid running this against things like video backdrops - return item is Video && !(item is Episode) && item.Parent != null; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var savePath = MovieXmlSaver.GetMovieSavePath((Video)item); - - var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath); - - if (!xml.Exists) - { - return false; - } - - return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved; - } - - /// <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 async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var video = (Video)item; - - var path = MovieXmlSaver.GetMovieSavePath(video); - - if (File.Exists(path)) - { - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - new MovieXmlParser(Logger).FetchAsync(video, path, cancellationToken); - } - finally - { - XmlParsingResourcePool.Release(); - } - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - - return true; - } - } -} diff --git a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs index 82250f2b58..8eabc0a2dd 100644 --- a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { - public class MovieXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Movie> + public class MovieXmlProvider : BaseXmlProvider<Movie> { private readonly ILogger _logger; @@ -18,61 +17,34 @@ namespace MediaBrowser.Providers.Movies _logger = logger; } - public async Task<MetadataResult<Movie>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(Movie item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<Movie>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - result.Item = new Movie(); - - new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); - result.HasMetadata = true; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new MovieXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return GetXmlFileInfo(path, FileSystem); + return GetXmlFileInfo(info, FileSystem); } - public static FileInfo GetXmlFileInfo(string path, IFileSystem _fileSystem) + public static FileInfo GetXmlFileInfo(ItemInfo info, IFileSystem fileSystem) { - var fileInfo = _fileSystem.GetFileSystemInfo(path); + var fileInfo = fileSystem.GetFileSystemInfo(info.Path); var directoryInfo = fileInfo as DirectoryInfo; if (directoryInfo == null) { - directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path)); + directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path)); } var directoryPath = directoryInfo.FullName; - var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml"); + var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml"); var file = new FileInfo(specificFile); - return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml")); + return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml")); } } } diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs deleted file mode 100644 index 9b4f17a86f..0000000000 --- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs +++ /dev/null @@ -1,285 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Movies -{ - public class OpenMovieDatabaseProvider : BaseMetadataProvider - { - private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); - - /// <summary> - /// Gets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - protected IJsonSerializer JsonSerializer { get; private set; } - - /// <summary> - /// Gets the HTTP client. - /// </summary> - /// <value>The HTTP client.</value> - protected IHttpClient HttpClient { get; private set; } - - public OpenMovieDatabaseProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient) - : base(logManager, configurationManager) - { - JsonSerializer = jsonSerializer; - HttpClient = httpClient; - } - - /// <summary> - /// Gets the provider version. - /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion - { - get - { - return "13"; - } - } - - /// <summary> - /// Gets a value indicating whether [requires internet]. - /// </summary> - /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value> - public override bool RequiresInternet - { - get - { - return true; - } - } - - /// <summary> - /// Gets a value indicating whether [refresh on version change]. - /// </summary> - /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value> - protected override bool RefreshOnVersionChange - { - get - { - return true; - } - } - - /// <summary> - /// Supports the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - var trailer = item as Trailer; - - // Don't support local trailers - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } - - return item is Movie || item is MusicVideo || item is Series; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get - { - // Run after moviedb and xml providers - return MetadataProviderPriority.Fifth; - } - } - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - var imdbId = item.GetProviderId(MetadataProviders.Imdb); - - if (string.IsNullOrEmpty(imdbId)) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId; - - var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam); - - using (var stream = await HttpClient.Get(new HttpRequestOptions - { - Url = url, - ResourcePool = _resourcePool, - CancellationToken = cancellationToken - - }).ConfigureAwait(false)) - { - var result = JsonSerializer.DeserializeFromStream<RootObject>(stream); - - var hasCriticRating = item as IHasCriticRating; - if (hasCriticRating != null) - { - // Seeing some bogus RT data on omdb for series, so filter it out here - // RT doesn't even have tv series - int tomatoMeter; - - if (!string.IsNullOrEmpty(result.tomatoMeter) - && int.TryParse(result.tomatoMeter, NumberStyles.Integer, UsCulture, out tomatoMeter) - && tomatoMeter >= 0) - { - hasCriticRating.CriticRating = tomatoMeter; - } - - if (!string.IsNullOrEmpty(result.tomatoConsensus) - && !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase) - && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase)) - { - hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus); - } - } - - int voteCount; - - if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, UsCulture, out voteCount) - && voteCount >= 0) - { - item.VoteCount = voteCount; - } - - float imdbRating; - - if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, UsCulture, out imdbRating) - && imdbRating >= 0) - { - item.CommunityRating = imdbRating; - } - - if (!string.IsNullOrEmpty(result.Website) - && !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase)) - { - item.HomePageUrl = result.Website; - } - - ParseAdditionalMetadata(item, result); - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - private void ParseAdditionalMetadata(BaseItem item, RootObject result) - { - // Grab series genres because imdb data is better than tvdb. Leave movies alone - // But only do it if english is the preferred language because this data will not be localized - if (!item.LockedFields.Contains(MetadataFields.Genres) && - ShouldFetchGenres(item) && - !string.IsNullOrWhiteSpace(result.Genre) && - !string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase)) - { - item.Genres.Clear(); - - foreach (var genre in result.Genre - .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(i => i.Trim()) - .Where(i => !string.IsNullOrWhiteSpace(i))) - { - item.AddGenre(genre); - } - } - - var hasMetascore = item as IHasMetascore; - if (hasMetascore != null) - { - float metascore; - - if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, UsCulture, out metascore) && metascore >= 0) - { - hasMetascore.Metascore = metascore; - } - } - - var hasAwards = item as IHasAwards; - if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) && - !string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase)) - { - hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards); - } - } - - private bool ShouldFetchGenres(BaseItem item) - { - var lang = item.GetPreferredMetadataLanguage(); - - // The data isn't localized and so can only be used for english users - if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Only fetch if other providers didn't get anything - if (item is Trailer) - { - return item.Genres.Count == 0; - } - - return item is Series || item is Movie; - } - - protected class RootObject - { - public string Title { get; set; } - public string Year { get; set; } - public string Rated { get; set; } - public string Released { get; set; } - public string Runtime { get; set; } - public string Genre { get; set; } - public string Director { get; set; } - public string Writer { get; set; } - public string Actors { get; set; } - public string Plot { get; set; } - public string Poster { get; set; } - public string imdbRating { get; set; } - public string imdbVotes { get; set; } - public string imdbID { get; set; } - public string Type { get; set; } - public string tomatoMeter { get; set; } - public string tomatoImage { get; set; } - public string tomatoRating { get; set; } - public string tomatoReviews { get; set; } - public string tomatoFresh { get; set; } - public string tomatoRotten { get; set; } - public string tomatoConsensus { get; set; } - public string tomatoUserMeter { get; set; } - public string tomatoUserRating { get; set; } - public string tomatoUserReviews { get; set; } - public string DVD { get; set; } - public string BoxOffice { get; set; } - public string Production { get; set; } - public string Website { get; set; } - public string Response { get; set; } - - public string Language { get; set; } - public string Country { get; set; } - public string Awards { get; set; } - public string Metascore { get; set; } - } - } -} diff --git a/MediaBrowser.Providers/Movies/TmdbSettings.cs b/MediaBrowser.Providers/Movies/TmdbSettings.cs new file mode 100644 index 0000000000..59e8f7cef6 --- /dev/null +++ b/MediaBrowser.Providers/Movies/TmdbSettings.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Movies +{ + internal class TmdbImageSettings + { + public List<string> backdrop_sizes { get; set; } + public string base_url { get; set; } + public List<string> poster_sizes { get; set; } + public List<string> profile_sizes { get; set; } + } + + internal class TmdbSettingsResult + { + public TmdbImageSettings images { get; set; } + } +} diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs new file mode 100644 index 0000000000..ad9cf52311 --- /dev/null +++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Movies +{ + public class TrailerMetadataService : MetadataService<Trailer, ItemId> + { + private readonly ILibraryManager _libraryManager; + + public TrailerMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Trailer source, Trailer target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(Trailer item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs b/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs new file mode 100644 index 0000000000..52704b151c --- /dev/null +++ b/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System.IO; +using System.Threading; + +namespace MediaBrowser.Providers.Movies +{ + public class TrailerXmlProvider : BaseXmlProvider<Trailer> + { + private readonly ILogger _logger; + + public TrailerXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(Trailer item, string path, CancellationToken cancellationToken) + { + new MovieXmlParser(_logger).Fetch(item, path, cancellationToken); + } + + protected override FileInfo GetXmlFile(ItemInfo info) + { + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); + } + } +} diff --git a/MediaBrowser.Providers/Music/AlbumXmlProvider.cs b/MediaBrowser.Providers/Music/AlbumXmlProvider.cs index 7c7de61829..06a1ed121d 100644 --- a/MediaBrowser.Providers/Music/AlbumXmlProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - class AlbumXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicAlbum> + class AlbumXmlProvider : BaseXmlProvider<MusicAlbum> { private readonly ILogger _logger; @@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(MusicAlbum item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<MusicAlbum>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new MusicAlbum(); - - new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "album.xml")); + return new FileInfo(Path.Combine(info.Path, "album.xml")); } } } diff --git a/MediaBrowser.Providers/Music/ArtistXmlProvider.cs b/MediaBrowser.Providers/Music/ArtistXmlProvider.cs index 30c38bdaa6..921cbc8b94 100644 --- a/MediaBrowser.Providers/Music/ArtistXmlProvider.cs +++ b/MediaBrowser.Providers/Music/ArtistXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - class ArtistXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicArtist> + class ArtistXmlProvider : BaseXmlProvider<MusicArtist> { private readonly ILogger _logger; @@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(MusicArtist item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<MusicArtist>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new MusicArtist(); - - new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "artist.xml")); + return new FileInfo(Path.Combine(info.Path, "artist.xml")); } } } diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs new file mode 100644 index 0000000000..c056fdabfe --- /dev/null +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -0,0 +1,53 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class AudioMetadataService : MetadataService<Audio, ItemId> + { + private readonly ILibraryManager _libraryManager; + + public AudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Audio source, Audio target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + if (replaceData || target.Artists.Count == 0) + { + target.Artists = source.Artists; + } + + if (replaceData || string.IsNullOrEmpty(target.Album)) + { + target.Album = source.Album; + } + } + + protected override Task SaveItem(Audio item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs index dcfe0d89ea..5bdc2cdd01 100644 --- a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs +++ b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs @@ -5,11 +5,10 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Movies; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - class MusicVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicVideo> + class MusicVideoXmlProvider : BaseXmlProvider<MusicVideo> { private readonly ILogger _logger; @@ -19,42 +18,14 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - public async Task<MetadataResult<MusicVideo>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(MusicVideo item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<MusicVideo>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var item = new MusicVideo(); - - new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken); - result.HasMetadata = true; - result.Item = item; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return MovieXmlProvider.GetXmlFileInfo(path, FileSystem); + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } } } diff --git a/MediaBrowser.Providers/People/PersonXmlProvider.cs b/MediaBrowser.Providers/People/PersonXmlProvider.cs index c23458f686..0996615d3b 100644 --- a/MediaBrowser.Providers/People/PersonXmlProvider.cs +++ b/MediaBrowser.Providers/People/PersonXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.People { - public class PersonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Person> + public class PersonXmlProvider : BaseXmlProvider<Person> { private readonly ILogger _logger; @@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.People _logger = logger; } - public async Task<MetadataResult<Person>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(Person item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<Person>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var person = new Person(); - - new BaseItemXmlParser<Person>(_logger).Fetch(person, path, cancellationToken); - result.HasMetadata = true; - result.Item = person; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new BaseItemXmlParser<Person>(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "person.xml")); + return new FileInfo(Path.Combine(info.Path, "person.xml")); } } } diff --git a/MediaBrowser.Providers/ProviderUtils.cs b/MediaBrowser.Providers/ProviderUtils.cs index 61fe19a613..ecefb72c40 100644 --- a/MediaBrowser.Providers/ProviderUtils.cs +++ b/MediaBrowser.Providers/ProviderUtils.cs @@ -93,7 +93,10 @@ namespace MediaBrowser.Providers { if (replaceData || !target.RunTimeTicks.HasValue) { - target.RunTimeTicks = source.RunTimeTicks; + if (!(target is Audio) && !(target is Video)) + { + target.RunTimeTicks = source.RunTimeTicks; + } } } @@ -159,6 +162,11 @@ namespace MediaBrowser.Providers MergeAlbumArtist(source, target, lockedFields, replaceData); MergeBudget(source, target, lockedFields, replaceData); + MergeMetascore(source, target, lockedFields, replaceData); + MergeCriticRating(source, target, lockedFields, replaceData); + MergeAwards(source, target, lockedFields, replaceData); + MergeTaglines(source, target, lockedFields, replaceData); + MergeTrailers(source, target, lockedFields, replaceData); if (mergeMetadataSettings) { @@ -218,5 +226,80 @@ namespace MediaBrowser.Providers } } } + + private static void MergeMetascore(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + { + var sourceCast = source as IHasMetascore; + var targetCast = target as IHasMetascore; + + if (sourceCast != null && targetCast != null) + { + if (replaceData || !targetCast.Metascore.HasValue) + { + targetCast.Metascore = sourceCast.Metascore; + } + } + } + + private static void MergeAwards(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + { + var sourceCast = source as IHasAwards; + var targetCast = target as IHasAwards; + + if (sourceCast != null && targetCast != null) + { + if (replaceData || string.IsNullOrEmpty(targetCast.AwardSummary)) + { + targetCast.AwardSummary = sourceCast.AwardSummary; + } + } + } + + private static void MergeCriticRating(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + { + var sourceCast = source as IHasCriticRating; + var targetCast = target as IHasCriticRating; + + if (sourceCast != null && targetCast != null) + { + if (replaceData || !targetCast.CriticRating.HasValue) + { + targetCast.CriticRating = sourceCast.CriticRating; + } + + if (replaceData || string.IsNullOrEmpty(targetCast.CriticRatingSummary)) + { + targetCast.CriticRatingSummary = sourceCast.CriticRatingSummary; + } + } + } + + private static void MergeTaglines(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + { + var sourceCast = source as IHasTaglines; + var targetCast = target as IHasTaglines; + + if (sourceCast != null && targetCast != null) + { + if (replaceData || targetCast.Taglines.Count == 0) + { + targetCast.Taglines = sourceCast.Taglines; + } + } + } + + private static void MergeTrailers(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + { + var sourceCast = source as IHasTrailers; + var targetCast = target as IHasTrailers; + + if (sourceCast != null && targetCast != null) + { + if (replaceData || targetCast.RemoteTrailers.Count == 0) + { + targetCast.RemoteTrailers = sourceCast.RemoteTrailers; + } + } + } } } diff --git a/MediaBrowser.Providers/RefreshIntrosTask.cs b/MediaBrowser.Providers/RefreshIntrosTask.cs index b20a6e331d..bfe7e7609f 100644 --- a/MediaBrowser.Providers/RefreshIntrosTask.cs +++ b/MediaBrowser.Providers/RefreshIntrosTask.cs @@ -90,20 +90,14 @@ namespace MediaBrowser.Providers } var dbItem = _libraryManager.GetItemById(item.Id); - var isNewItem = false; if (dbItem != null) { - dbItem.ResetResolveArgs(item.ResolveArgs); item = dbItem; } - else - { - isNewItem = true; - } // Force the save if it's a new item - await item.RefreshMetadata(cancellationToken, isNewItem).ConfigureAwait(false); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Providers/Savers/GameXmlSaver.cs b/MediaBrowser.Providers/Savers/GameXmlSaver.cs index a6225b58c1..6138195173 100644 --- a/MediaBrowser.Providers/Savers/GameXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/GameXmlSaver.cs @@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.Savers return Path.ChangeExtension(item.Path, ".xml"); } - return Path.Combine(item.MetaLocation, "game.xml"); + return Path.Combine(item.ContainingFolderPath, "game.xml"); } } } diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index e5ba1aefd0..15fdc67529 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -50,16 +50,9 @@ namespace MediaBrowser.Providers.Savers // If new metadata has been downloaded and save local is on if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) { - var trailer = item as Trailer; - - // Don't support local trailers - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } var video = item as Video; // Check parent for null to avoid running this against things like video backdrops - return video != null && !(item is Episode) && video.Parent != null; + return video != null && !(item is Episode) && !video.IsOwnedItem; } return false; @@ -145,7 +138,7 @@ namespace MediaBrowser.Providers.Savers return Path.ChangeExtension(item.Path, ".xml"); } - return Path.Combine(item.MetaLocation, "movie.xml"); + return Path.Combine(item.ContainingFolderPath, "movie.xml"); } } } diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 2b6edaf081..ddb89bd42a 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -102,8 +103,10 @@ namespace MediaBrowser.Providers.TV var currentIndexNumberEnd = item.IndexNumberEnd; var currentParentIndexNumber = item.ParentIndexNumber; - item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season); - item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(item.Path); + var filename = Path.GetFileName(item.Path); + + item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(filename, item.Parent is Season); + item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(filename); if (!item.ParentIndexNumber.HasValue) { diff --git a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs index b8d88f5e65..b1f8ef9767 100644 --- a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs @@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class EpisodeXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Episode> + public class EpisodeXmlProvider : BaseXmlProvider<Episode> { private readonly ILogger _logger; @@ -18,43 +17,16 @@ namespace MediaBrowser.Providers.TV _logger = logger; } - public async Task<MetadataResult<Episode>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(Episode item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<Episode>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - result.Item = new Episode(); - - new EpisodeXmlParser(_logger).Fetch(result.Item, path, cancellationToken); - result.HasMetadata = true; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new EpisodeXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - var metadataPath = Path.GetDirectoryName(path); + var metadataPath = Path.GetDirectoryName(info.Path); metadataPath = Path.Combine(metadataPath, "metadata"); - var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(path), ".xml")); + var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(info.Path), ".xml")); return new FileInfo(metadataFile); } diff --git a/MediaBrowser.Providers/TV/SeasonXmlProvider.cs b/MediaBrowser.Providers/TV/SeasonXmlProvider.cs index 9dcc9fe4f2..f9fe451207 100644 --- a/MediaBrowser.Providers/TV/SeasonXmlProvider.cs +++ b/MediaBrowser.Providers/TV/SeasonXmlProvider.cs @@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { /// <summary> /// Class SeriesProviderFromXml /// </summary> - public class SeasonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Season> + public class SeasonXmlProvider : BaseXmlProvider<Season> { private readonly ILogger _logger; @@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV _logger = logger; } - public async Task<MetadataResult<Season>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(Season item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<Season>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var person = new Season(); - - new BaseItemXmlParser<Season>(_logger).Fetch(person, path, cancellationToken); - result.HasMetadata = true; - result.Item = person; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new BaseItemXmlParser<Season>(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "season.xml")); + return new FileInfo(Path.Combine(info.Path, "season.xml")); } } } diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs index 6d2ece19c7..dc06857ce3 100644 --- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs +++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.TV await new MissingEpisodeProvider(_logger, _config).Run(seriesGroups, cancellationToken).ConfigureAwait(false); var numComplete = 0; - + foreach (var series in seriesList) { cancellationToken.ThrowIfCancellationRequested(); @@ -171,7 +172,9 @@ namespace MediaBrowser.Providers.TV { foreach (var series in group) { - await series.RefreshMetadata(cancellationToken, true) + await series.RefreshMetadata(new MetadataRefreshOptions + { + }, cancellationToken) .ConfigureAwait(false); await series.ValidateChildren(new Progress<double>(), cancellationToken, true) @@ -438,7 +441,9 @@ namespace MediaBrowser.Providers.TV await season.AddChild(episode, cancellationToken).ConfigureAwait(false); - await episode.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await episode.RefreshMetadata(new MetadataRefreshOptions + { + }, cancellationToken).ConfigureAwait(false); } /// <summary> @@ -464,7 +469,9 @@ namespace MediaBrowser.Providers.TV }; await series.AddChild(season, cancellationToken).ConfigureAwait(false); - await season.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await season.RefreshMetadata(new MetadataRefreshOptions + { + }, cancellationToken).ConfigureAwait(false); return season; } diff --git a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs index 8f0c631368..4dfaa39257 100644 --- a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs +++ b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs @@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { /// <summary> /// Class SeriesProviderFromXml /// </summary> - public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Series> + public class SeriesXmlProvider : BaseXmlProvider<Series> { private readonly ILogger _logger; @@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV _logger = logger; } - public async Task<MetadataResult<Series>> GetMetadata(string path, CancellationToken cancellationToken) + protected override void Fetch(Series item, string path, CancellationToken cancellationToken) { - path = GetXmlFile(path).FullName; - - var result = new MetadataResult<Series>(); - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var person = new Series(); - - new SeriesXmlParser(_logger).Fetch(person, path, cancellationToken); - result.HasMetadata = true; - result.Item = person; - } - catch (FileNotFoundException) - { - result.HasMetadata = false; - } - finally - { - XmlParsingResourcePool.Release(); - } - - return result; - } - - public string Name - { - get { return "Media Browser Xml"; } + new SeriesXmlParser(_logger).Fetch(item, path, cancellationToken); } - protected override FileInfo GetXmlFile(string path) + protected override FileInfo GetXmlFile(ItemInfo info) { - return new FileInfo(Path.Combine(path, "series.xml")); + return new FileInfo(Path.Combine(info.Path, "series.xml")); } } } diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs index 5523b8ab3a..178c7a265a 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs @@ -185,7 +185,14 @@ namespace MediaBrowser.Providers.TV var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); var success = false; var usingAbsoluteData = false; - var episode = new Episode(); + + var episode = new Episode + { + IndexNumber = id.IndexNumber, + ParentIndexNumber = id.ParentIndexNumber, + IndexNumberEnd = id.IndexNumberEnd + }; + try { FetchMainEpisodeInfo(episode, file, cancellationToken); diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs new file mode 100644 index 0000000000..9aa4ba1387 --- /dev/null +++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Videos +{ + public class VideoMetadataService : MetadataService<Video, ItemId> + { + private readonly ILibraryManager _libraryManager; + + public VideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Video source, Video target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(Video item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + + public override int Order + { + get + { + // Make sure the type-specific services get picked first + return 10; + } + } + } +} diff --git a/MediaBrowser.Providers/VirtualItemImageValidator.cs b/MediaBrowser.Providers/VirtualItemImageValidator.cs deleted file mode 100644 index 892275d385..0000000000 --- a/MediaBrowser.Providers/VirtualItemImageValidator.cs +++ /dev/null @@ -1,57 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers -{ - public class VirtualItemImageValidator : BaseMetadataProvider - { - public VirtualItemImageValidator(ILogManager logManager, IServerConfigurationManager configurationManager) - : base(logManager, configurationManager) - { - } - - public override bool Supports(BaseItem item) - { - var locationType = item.LocationType; - - // The regular provider will get virtual seasons - if (item.LocationType == LocationType.Virtual) - { - var season = item as Season; - - if (season != null) - { - var series = season.Series; - - if (series != null && series.LocationType == LocationType.FileSystem) - { - return false; - } - } - } - - return locationType == LocationType.Virtual || - locationType == LocationType.Remote; - } - - public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - item.ValidateImages(); - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return TrueTaskResult; - } - - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - } -} diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs new file mode 100644 index 0000000000..01e5111778 --- /dev/null +++ b/MediaBrowser.Providers/Years/YearMetadataService.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Years +{ + public class YearMetadataService : MetadataService<Year, ItemId> + { + private readonly ILibraryManager _libraryManager; + + public YearMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Year source, Year target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(Year item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} |
