diff options
94 files changed, 1671 insertions, 5363 deletions
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 79b2651d5..17520ba1c 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -75,15 +75,7 @@ namespace MediaBrowser.Api.Library if (locationType != LocationType.Remote && locationType != LocationType.Virtual) { - try - { - return c.PhysicalLocations; - } - catch (Exception ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, c.Path); - } - + return c.PhysicalLocations; } return new List<string>(); diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index 584bbf9a1..a7a58dbfb 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -331,21 +331,8 @@ namespace MediaBrowser.Api { if (item.Parent is AggregateFolder) { - return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => - { - - try - { - return i.LocationType == LocationType.FileSystem && - i.PhysicalLocations.Contains(item.Path); - } - catch (Exception ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path); - return false; - } - - }); + return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.LocationType == LocationType.FileSystem && + i.PhysicalLocations.Contains(item.Path)); } return item; diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs index f81cfa1f6..475d7bc54 100644 --- a/MediaBrowser.Controller/Entities/AdultVideo.cs +++ b/MediaBrowser.Controller/Entities/AdultVideo.cs @@ -3,6 +3,10 @@ namespace MediaBrowser.Controller.Entities { public class AdultVideo : Video, IHasPreferredMetadataLanguage { + /// <summary> + /// Gets or sets the preferred metadata language. + /// </summary> + /// <value>The preferred metadata language.</value> public string PreferredMetadataLanguage { get; set; } /// <summary> diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 302842e7e..ef455846e 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -1,7 +1,11 @@ -using System; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities { @@ -11,6 +15,11 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class AggregateFolder : Folder { + public AggregateFolder() + { + PhysicalLocationsList = new List<string>(); + } + /// <summary> /// We don't support manual shortcuts /// </summary> @@ -36,6 +45,60 @@ namespace MediaBrowser.Controller.Entities get { return _virtualChildren; } } + [IgnoreDataMember] + public override IEnumerable<string> PhysicalLocations + { + get + { + return PhysicalLocationsList; + } + } + + public List<string> PhysicalLocationsList { get; set; } + + protected override IEnumerable<FileSystemInfo> GetFileSystemChildren() + { + return CreateResolveArgs().FileSystemChildren; + } + + private ItemResolveArgs CreateResolveArgs() + { + var path = ContainingFolderPath; + + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager) + { + FileInfo = new DirectoryInfo(path), + Path = path, + Parent = Parent + }; + + // Gather child folder and files + if (args.IsDirectory) + { + var isPhysicalRoot = args.IsPhysicalRoot; + + // When resolving the root, we need it's grandchildren (children of user views) + var flattenFolderDepth = isPhysicalRoot ? 2 : 0; + + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + + // Need to remove subpaths that may have been resolved from shortcuts + // Example: if \\server\movies exists, then strip out \\server\movies\action + if (isPhysicalRoot) + { + var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys); + + fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName); + } + + args.FileSystemDictionary = fileSystemDictionary; + } + + PhysicalLocationsList = args.PhysicalLocations.ToList(); + + return args; + } + /// <summary> /// Adds the virtual child. /// </summary> diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 650a9bad0..de5516e29 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,12 +1,10 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -103,6 +101,35 @@ namespace MediaBrowser.Controller.Entities protected internal bool IsOffline { get; set; } /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + [IgnoreDataMember] + public virtual string ContainingFolderPath + { + get + { + if (IsFolder) + { + return Path; + } + + return System.IO.Path.GetDirectoryName(Path); + } + } + + [IgnoreDataMember] + public bool IsOwnedItem + { + get + { + // Local trailer, special feature, theme video, etc. + // An item that belongs to another item but is not part of the Parent-Child tree + return !IsFolder && Parent == null; + } + } + + /// <summary> /// Gets or sets the type of the location. /// </summary> /// <value>The type of the location.</value> @@ -190,19 +217,6 @@ namespace MediaBrowser.Controller.Entities public List<MetadataFields> LockedFields { get; set; } /// <summary> - /// Should be overridden to return the proper folder where metadata lives - /// </summary> - /// <value>The meta location.</value> - [IgnoreDataMember] - public virtual string MetaLocation - { - get - { - return Path ?? ""; - } - } - - /// <summary> /// Gets the type of the media. /// </summary> /// <value>The type of the media.</value> @@ -215,160 +229,19 @@ namespace MediaBrowser.Controller.Entities } } - /// <summary> - /// The _resolve args - /// </summary> - private ItemResolveArgs _resolveArgs; - /// <summary> - /// We attach these to the item so that we only ever have to hit the file system once - /// (this includes the children of the containing folder) - /// </summary> - /// <value>The resolve args.</value> [IgnoreDataMember] - public ItemResolveArgs ResolveArgs + public virtual IEnumerable<string> PhysicalLocations { get { - if (_resolveArgs == null) - { - try - { - _resolveArgs = CreateResolveArgs(); - } - catch (IOException ex) - { - Logger.ErrorException("Error creating resolve args for {0}", ex, Path); - - IsOffline = true; + var locationType = LocationType; - throw; - } - } - - return _resolveArgs; - } - set - { - _resolveArgs = value; - } - } - - [IgnoreDataMember] - public IEnumerable<string> PhysicalLocations - { - get - { - return ResolveArgs.PhysicalLocations; - } - } - - /// <summary> - /// Resets the resolve args. - /// </summary> - /// <param name="pathInfo">The path info.</param> - public void ResetResolveArgs(FileSystemInfo pathInfo) - { - ResetResolveArgs(CreateResolveArgs(pathInfo)); - } - - /// <summary> - /// Resets the resolve args. - /// </summary> - public void ResetResolveArgs() - { - _resolveArgs = null; - } - - /// <summary> - /// Resets the resolve args. - /// </summary> - /// <param name="args">The args.</param> - public void ResetResolveArgs(ItemResolveArgs args) - { - _resolveArgs = args; - } - - /// <summary> - /// Creates ResolveArgs on demand - /// </summary> - /// <param name="pathInfo">The path info.</param> - /// <returns>ItemResolveArgs.</returns> - /// <exception cref="System.IO.IOException">Unable to retrieve file system info for + path</exception> - protected internal virtual ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null) - { - var path = Path; - - var locationType = LocationType; - - if (locationType == LocationType.Remote || - locationType == LocationType.Virtual) - { - return new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager); - } - - var isDirectory = false; - - if (UseParentPathToCreateResolveArgs) - { - path = System.IO.Path.GetDirectoryName(path); - isDirectory = true; - } - - pathInfo = pathInfo ?? (isDirectory ? new DirectoryInfo(path) : FileSystem.GetFileSystemInfo(path)); - - if (pathInfo == null || !pathInfo.Exists) - { - throw new IOException("Unable to retrieve file system info for " + path); - } - - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager) - { - FileInfo = pathInfo, - Path = path, - Parent = Parent - }; - - // Gather child folder and files - if (args.IsDirectory) - { - var isPhysicalRoot = args.IsPhysicalRoot; - - // When resolving the root, we need it's grandchildren (children of user views) - var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - - var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); - - // Need to remove subpaths that may have been resolved from shortcuts - // Example: if \\server\movies exists, then strip out \\server\movies\action - if (isPhysicalRoot) + if (locationType != LocationType.Remote && locationType != LocationType.Virtual) { - var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys); - - fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName); + return new string[] { }; } - args.FileSystemDictionary = fileSystemDictionary; - } - - //update our dates - EntityResolutionHelper.EnsureDates(FileSystem, this, args, false); - - IsOffline = false; - - return args; - } - - /// <summary> - /// Some subclasses will stop resolving at a directory and point their Path to a file within. This will help ensure the on-demand resolve args are identical to the - /// original ones. - /// </summary> - /// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value> - [IgnoreDataMember] - protected virtual bool UseParentPathToCreateResolveArgs - { - get - { - return false; + return new[] { Path }; } } @@ -583,122 +456,93 @@ namespace MediaBrowser.Controller.Entities /// Loads local trailers from the file system /// </summary> /// <returns>List{Video}.</returns> - private IEnumerable<Trailer> LoadLocalTrailers() - { - ItemResolveArgs resolveArgs; - - try - { - resolveArgs = ResolveArgs; - - if (!resolveArgs.IsDirectory) - { - return new List<Trailer>(); - } - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return new List<Trailer>(); - } - - var files = new List<FileSystemInfo>(); - - var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName); - - // Path doesn't exist. No biggie - if (folder != null) - { - try - { - files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles()); - } - catch (IOException ex) - { - Logger.ErrorException("Error loading trailers for {0}", ex, Name); - } - } - - // Support xbmc trailers (-trailer suffix on video file names) - files.AddRange(resolveArgs.FileSystemChildren.Where(i => - { - try - { - if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) - { - if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - catch (IOException ex) - { - Logger.ErrorException("Error accessing path {0}", ex, i.FullName); - } - - return false; - })); - - return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => - { - // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.GetItemById(video.Id) as Trailer; - - if (dbItem != null) - { - dbItem.ResetResolveArgs(video.ResolveArgs); - video = dbItem; - } - - return video; - - }).ToList(); + private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren) + { + return new List<Trailer>(); + //ItemResolveArgs resolveArgs; + + //try + //{ + // resolveArgs = ResolveArgs; + + // if (!resolveArgs.IsDirectory) + // { + // return new List<Trailer>(); + // } + //} + //catch (IOException ex) + //{ + // Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); + // return new List<Trailer>(); + //} + + //var files = new List<FileSystemInfo>(); + + //var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName); + + //// Path doesn't exist. No biggie + //if (folder != null) + //{ + // try + // { + // files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles()); + // } + // catch (IOException ex) + // { + // Logger.ErrorException("Error loading trailers for {0}", ex, Name); + // } + //} + + //// Support xbmc trailers (-trailer suffix on video file names) + //files.AddRange(resolveArgs.FileSystemChildren.Where(i => + //{ + // try + // { + // if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) + // { + // if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) + // { + // return true; + // } + // } + // } + // catch (IOException ex) + // { + // Logger.ErrorException("Error accessing path {0}", ex, i.FullName); + // } + + // return false; + //})); + + //return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => + //{ + // // Try to retrieve it from the db. If we don't find it, use the resolved version + // var dbItem = LibraryManager.GetItemById(video.Id) as Trailer; + + // if (dbItem != null) + // { + // video = dbItem; + // } + + // return video; + + //}).ToList(); } /// <summary> /// Loads the theme songs. /// </summary> /// <returns>List{Audio.Audio}.</returns> - private IEnumerable<Audio.Audio> LoadThemeSongs() + private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren) { - ItemResolveArgs resolveArgs; - - try - { - resolveArgs = ResolveArgs; - - if (!resolveArgs.IsDirectory) - { - return new List<Audio.Audio>(); - } - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return new List<Audio.Audio>(); - } - - var files = new List<FileSystemInfo>(); - - var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName); - - // Path doesn't exist. No biggie - if (folder != null) - { - try - { - files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles()); - } - catch (IOException ex) - { - Logger.ErrorException("Error loading theme songs for {0}", ex, Name); - } - } + var files = fileSystemChildren.OfType<DirectoryInfo>() + .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) + .ToList(); // Support plex/xbmc convention - files.AddRange(resolveArgs.FileSystemChildren - .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name)) + files.AddRange(fileSystemChildren.OfType<FileInfo>() + .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) ); return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio => @@ -708,7 +552,6 @@ namespace MediaBrowser.Controller.Entities if (dbItem != null) { - dbItem.ResetResolveArgs(audio.ResolveArgs); audio = dbItem; } @@ -720,44 +563,11 @@ namespace MediaBrowser.Controller.Entities /// Loads the video backdrops. /// </summary> /// <returns>List{Video}.</returns> - private IEnumerable<Video> LoadThemeVideos() + private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren) { - ItemResolveArgs resolveArgs; - - try - { - resolveArgs = ResolveArgs; - - if (!resolveArgs.IsDirectory) - { - return new List<Video>(); - } - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return new List<Video>(); - } - - var folder = resolveArgs.GetFileSystemEntryByName(ThemeVideosFolderName); - - // Path doesn't exist. No biggie - if (folder == null) - { - return new List<Video>(); - } - - IEnumerable<FileSystemInfo> files; - - try - { - files = new DirectoryInfo(folder.FullName).EnumerateFiles(); - } - catch (IOException ex) - { - Logger.ErrorException("Error loading video backdrops for {0}", ex, Name); - return new List<Video>(); - } + var files = fileSystemChildren.OfType<DirectoryInfo>() + .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)); return LibraryManager.ResolvePaths<Video>(files, null).Select(item => { @@ -766,7 +576,6 @@ namespace MediaBrowser.Controller.Entities if (dbItem != null) { - dbItem.ResetResolveArgs(item.ResolveArgs); item = dbItem; } @@ -774,9 +583,9 @@ namespace MediaBrowser.Controller.Entities }).ToList(); } - public Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool resetResolveArgs = true) + public Task RefreshMetadata(CancellationToken cancellationToken) { - return RefreshMetadata(new MetadataRefreshOptions { ResetResolveArgs = resetResolveArgs }, cancellationToken); + return RefreshMetadata(new MetadataRefreshOptions(), cancellationToken); } /// <summary> @@ -785,35 +594,24 @@ namespace MediaBrowser.Controller.Entities /// <param name="options">The options.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>true if a provider reports we changed</returns> - public async Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken) + public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken) { - if (options.ResetResolveArgs) + var locationType = LocationType; + + if (IsFolder || Parent != null) { - // Reload this - ResetResolveArgs(); - } + var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? + GetFileSystemChildren().ToList() : + new List<FileSystemInfo>(); - await BeforeRefreshMetadata(options, cancellationToken).ConfigureAwait(false); + await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false); + } await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false); - - return false; } - private readonly Task _cachedTask = Task.FromResult(true); - protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken) + protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - return _cachedTask; - } - - [Obsolete] - public virtual async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) - { - // Refresh for the item - var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh); - - cancellationToken.ThrowIfCancellationRequested(); - var themeSongsChanged = false; var themeVideosChanged = false; @@ -825,102 +623,83 @@ namespace MediaBrowser.Controller.Entities var hasThemeMedia = this as IHasThemeMedia; if (hasThemeMedia != null) { - themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); + if (!IsInMixedFolder) + { + themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); + themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + } } var hasTrailers = this as IHasTrailers; if (hasTrailers != null) { - localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); + localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); } } - - cancellationToken.ThrowIfCancellationRequested(); - - // Get the result from the item task - var updateReason = await itemRefreshTask.ConfigureAwait(false); - - var changed = updateReason.HasValue; - - if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged) + + if (themeSongsChanged || themeVideosChanged || localTrailersChanged) { - cancellationToken.ThrowIfCancellationRequested(); - - await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false); + options.ForceSave = true; } + } - return changed; + protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren() + { + var path = ContainingFolderPath; + + return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly); } - private async Task<bool> RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) + private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadLocalTrailers().ToList(); + var newItems = LoadLocalTrailers(fileSystemChildren).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = forceSave, - ReplaceAllMetadata = forceRefresh, - ResetResolveArgs = false + var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken)); - }, cancellationToken)); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); item.LocalTrailerIds = newItemIds; - return itemsChanged || results.Contains(true); + return itemsChanged; } - private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) + private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newThemeVideos = LoadThemeVideos().ToList(); + var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList(); var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList(); var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); - var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = forceSave, - ReplaceAllMetadata = forceRefresh, - ResetResolveArgs = false - - }, cancellationToken)); + var tasks = newThemeVideos.Select(i => i.RefreshMetadata(options, cancellationToken)); - var results = await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); item.ThemeVideoIds = newThemeVideoIds; - return themeVideosChanged || results.Contains(true); + return themeVideosChanged; } /// <summary> /// Refreshes the theme songs. /// </summary> - private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) + private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newThemeSongs = LoadThemeSongs().ToList(); + var newThemeSongs = LoadThemeSongs(fileSystemChildren).ToList(); var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList(); var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); - var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = forceSave, - ReplaceAllMetadata = forceRefresh, - ResetResolveArgs = false - - }, cancellationToken)); + var tasks = newThemeSongs.Select(i => i.RefreshMetadata(options, cancellationToken)); - var results = await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); item.ThemeSongIds = newThemeSongIds; - return themeSongsChanged || results.Contains(true); + return themeSongsChanged; } /// <summary> @@ -1655,27 +1434,8 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("imagePath"); } - var locationType = LocationType; - - if (locationType == LocationType.Remote || - locationType == LocationType.Virtual) - { - return FileSystem.GetLastWriteTimeUtc(imagePath); - } - - var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath); - - // If we didn't the metafile entry, check the Season - if (metaFileEntry == null) - { - if (Parent != null) - { - metaFileEntry = Parent.ResolveArgs.GetMetaFileByPath(imagePath); - } - } - // See if we can avoid a file system lookup by looking for the file in ResolveArgs - return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry); + return FileSystem.GetLastWriteTimeUtc(imagePath); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 298941378..28ccf687c 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -29,25 +29,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The preferred metadata country code.</value> public string PreferredMetadataCountryCode { get; set; } - /// <summary> - /// - /// </summary> - public override string MetaLocation - { - get - { - return System.IO.Path.GetDirectoryName(Path); - } - } - - protected override bool UseParentPathToCreateResolveArgs - { - get - { - return !IsInMixedFolder; - } - } - public Book() { Tags = new List<string>(); diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 6220bc4d5..9c6b60969 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -1,4 +1,6 @@ -using System; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,6 +16,11 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class CollectionFolder : Folder, ICollectionFolder { + public CollectionFolder() + { + PhysicalLocationsList = new List<string>(); + } + /// <summary> /// Gets a value indicating whether this instance is virtual folder. /// </summary> @@ -42,6 +49,60 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public override IEnumerable<string> PhysicalLocations + { + get + { + return PhysicalLocationsList; + } + } + + public List<string> PhysicalLocationsList { get; set; } + + protected override IEnumerable<FileSystemInfo> GetFileSystemChildren() + { + return CreateResolveArgs().FileSystemChildren; + } + + private ItemResolveArgs CreateResolveArgs() + { + var path = ContainingFolderPath; + + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager) + { + FileInfo = new DirectoryInfo(path), + Path = path, + Parent = Parent + }; + + // Gather child folder and files + if (args.IsDirectory) + { + var isPhysicalRoot = args.IsPhysicalRoot; + + // When resolving the root, we need it's grandchildren (children of user views) + var flattenFolderDepth = isPhysicalRoot ? 2 : 0; + + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + + // Need to remove subpaths that may have been resolved from shortcuts + // Example: if \\server\movies exists, then strip out \\server\movies\action + if (isPhysicalRoot) + { + var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys); + + fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName); + } + + args.FileSystemDictionary = fileSystemDictionary; + } + + PhysicalLocationsList = args.PhysicalLocations.ToList(); + + return args; + } + // Cache this since it will be used a lot /// <summary> /// The null task result @@ -59,13 +120,14 @@ namespace MediaBrowser.Controller.Entities /// <returns>Task.</returns> protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) { + CreateResolveArgs(); ResetDynamicChildren(); return NullTaskResult; } private List<LinkedChild> _linkedChildren; - + /// <summary> /// Our children are actually just references to the ones in the physical root... /// </summary> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 02da2fe61..63a1c2bab 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; @@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Entities public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeVideoIds { get; set; } - + public Folder() { LinkedChildren = new List<LinkedChild>(); @@ -379,7 +380,7 @@ namespace MediaBrowser.Controller.Entities } catch (IOException ex) { - nonCachedChildren = new BaseItem[] {}; + nonCachedChildren = new BaseItem[] { }; Logger.ErrorException("Error getting file system entries for {0}", ex, Path); } @@ -402,8 +403,6 @@ namespace MediaBrowser.Controller.Entities if (currentChildren.TryGetValue(child.Id, out currentChild)) { - currentChild.ResetResolveArgs(child.ResolveArgs); - //existing item - check if it has changed if (currentChild.HasChanged(child)) { @@ -411,7 +410,7 @@ namespace MediaBrowser.Controller.Entities if (currentChildLocationType != LocationType.Remote && currentChildLocationType != LocationType.Virtual) { - EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false); + currentChild.DateModified = child.DateModified; } currentChild.IsInMixedFolder = child.IsInMixedFolder; @@ -539,8 +538,7 @@ namespace MediaBrowser.Controller.Entities await child.RefreshMetadata(new MetadataRefreshOptions { ForceSave = currentTuple.Item2, - ReplaceAllMetadata = forceRefreshMetadata, - ResetResolveArgs = false + ReplaceAllMetadata = forceRefreshMetadata }, cancellationToken).ConfigureAwait(false); } @@ -581,16 +579,6 @@ namespace MediaBrowser.Controller.Entities }); await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); - - try - { - // Some folder providers are unable to refresh until children have been refreshed. - await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false); - } - catch (IOException ex) - { - Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); - } } else { @@ -661,14 +649,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>IEnumerable{BaseItem}.</returns> protected virtual IEnumerable<BaseItem> GetNonCachedChildren() { - var resolveArgs = ResolveArgs; - - if (resolveArgs == null || resolveArgs.FileSystemDictionary == null) - { - Logger.Error("ResolveArgs null for {0}", Path); - } - - return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this); + return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this); } /// <summary> @@ -914,43 +895,29 @@ namespace MediaBrowser.Controller.Entities return item; } - protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken) + protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { if (SupportsShortcutChildren && LocationType == LocationType.FileSystem) { - RefreshLinkedChildren(); + if (RefreshLinkedChildren(fileSystemChildren)) + { + options.ForceSave = true; + } } - return base.BeforeRefreshMetadata(options, cancellationToken); + return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken); } /// <summary> /// Refreshes the linked children. /// </summary> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool RefreshLinkedChildren() + private bool RefreshLinkedChildren(IEnumerable<FileSystemInfo> fileSystemChildren) { - ItemResolveArgs resolveArgs; - - try - { - resolveArgs = ResolveArgs; - - if (!resolveArgs.IsDirectory) - { - return false; - } - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return false; - } - var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList(); var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList(); - var newShortcutLinks = resolveArgs.FileSystemChildren + var newShortcutLinks = fileSystemChildren .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName)) .Select(i => { @@ -1058,7 +1025,7 @@ namespace MediaBrowser.Controller.Entities return this; } - if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) + if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) { return this; } diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index da95b7c44..1b5176362 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -80,17 +80,6 @@ namespace MediaBrowser.Controller.Entities public string GameSystem { get; set; } /// <summary> - /// - /// </summary> - public override string MetaLocation - { - get - { - return System.IO.Path.GetDirectoryName(Path); - } - } - - /// <summary> /// Gets or sets a value indicating whether this instance is multi part. /// </summary> /// <value><c>true</c> if this instance is multi part; otherwise, <c>false</c>.</value> @@ -101,17 +90,6 @@ namespace MediaBrowser.Controller.Entities /// </summary> public List<string> MultiPartGameFiles { get; set; } - /// <summary> - /// - /// </summary> - protected override bool UseParentPathToCreateResolveArgs - { - get - { - return !IsInMixedFolder; - } - } - public override string GetUserDataKey() { var id = this.GetProviderId(MetadataProviders.Gamesdb); diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index 1aa299c2a..51f25979c 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -99,6 +99,12 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The backdrop image paths.</value> List<string> BackdropImagePaths { get; set; } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + bool IsOwnedItem { get; } } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index a4e885337..41a1969d6 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.Movies public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeVideoIds { get; set; } - + /// <summary> /// Gets or sets the preferred metadata country code. /// </summary> @@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities.Movies public string PreferredMetadataCountryCode { get; set; } public string PreferredMetadataLanguage { get; set; } - + public Movie() { SpecialFeatureIds = new List<Guid>(); @@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies public List<Guid> LocalTrailerIds { get; set; } public List<string> Keywords { get; set; } - + public List<MediaUrl> RemoteTrailers { get; set; } /// <summary> @@ -103,88 +103,48 @@ namespace MediaBrowser.Controller.Entities.Movies return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey(); } - /// <summary> - /// Overrides the base implementation to refresh metadata for special features - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="forceSave">if set to <c>true</c> [is new item].</param> - /// <param name="forceRefresh">if set to <c>true</c> [force].</param> - /// <returns>Task{System.Boolean}.</returns> - public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) + protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - // Kick off a task to refresh the main item - var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); - - var specialFeaturesChanged = false; + await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); // Must have a parent to have special features // In other words, it must be part of the Parent/Child tree if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder) { - specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); - } + var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - return specialFeaturesChanged || result; + if (specialFeaturesChanged) + { + options.ForceSave = true; + } + } } - private async Task<bool> RefreshSpecialFeatures(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) + private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadSpecialFeatures().ToList(); + var newItems = LoadSpecialFeatures(fileSystemChildren).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = forceSave, - ReplaceAllMetadata = forceRefresh, - ResetResolveArgs = false - - }, cancellationToken)); + var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken)); - var results = await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); SpecialFeatureIds = newItemIds; - return itemsChanged || results.Contains(true); + return itemsChanged; } /// <summary> /// Loads the special features. /// </summary> /// <returns>IEnumerable{Video}.</returns> - private IEnumerable<Video> LoadSpecialFeatures() + private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren) { - FileSystemInfo folder; - - try - { - folder = ResolveArgs.GetFileSystemEntryByName("extras") ?? - ResolveArgs.GetFileSystemEntryByName("specials"); - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return new List<Video>(); - } - - // Path doesn't exist. No biggie - if (folder == null) - { - return new List<Video>(); - } - - IEnumerable<FileSystemInfo> files; - - try - { - files = new DirectoryInfo(folder.FullName).EnumerateFiles(); - } - catch (IOException ex) - { - Logger.ErrorException("Error loading special features for {0}", ex, Name); - return new List<Video>(); - } + var files = fileSystemChildren.OfType<DirectoryInfo>() + .Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)); return LibraryManager.ResolvePaths<Video>(files, null).Select(video => { @@ -193,7 +153,6 @@ namespace MediaBrowser.Controller.Entities.Movies if (dbItem != null) { - dbItem.ResetResolveArgs(video.ResolveArgs); video = dbItem; } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 3bdfc3c44..73726a4e2 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -12,28 +12,6 @@ namespace MediaBrowser.Controller.Entities.TV public class Episode : Video { /// <summary> - /// Episodes have a special Metadata folder - /// </summary> - /// <value>The meta location.</value> - [IgnoreDataMember] - public override string MetaLocation - { - get - { - return System.IO.Path.Combine(Parent.Path, "metadata"); - } - } - - [IgnoreDataMember] - protected override bool UseParentPathToCreateResolveArgs - { - get - { - return false; - } - } - - /// <summary> /// Gets the season in which it aired. /// </summary> /// <value>The aired season.</value> diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 2d781118e..744416560 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -132,34 +132,6 @@ namespace MediaBrowser.Controller.Entities.TV } /// <summary> - /// Add files from the metadata folder to ResolveArgs - /// </summary> - /// <param name="args">The args.</param> - public static void AddMetadataFiles(ItemResolveArgs args) - { - var folder = args.GetFileSystemEntryByName("metadata"); - - if (folder != null) - { - args.AddMetadataFiles(new DirectoryInfo(folder.FullName).EnumerateFiles()); - } - } - - /// <summary> - /// Creates ResolveArgs on demand - /// </summary> - /// <param name="pathInfo">The path info.</param> - /// <returns>ItemResolveArgs.</returns> - protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null) - { - var args = base.CreateResolveArgs(pathInfo); - - AddMetadataFiles(args); - - return args; - } - - /// <summary> /// Creates the name of the sort. /// </summary> /// <returns>System.String.</returns> diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 552a517de..efb3c393b 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -111,20 +111,6 @@ namespace MediaBrowser.Controller.Entities.TV }; } - /// <summary> - /// Creates ResolveArgs on demand - /// </summary> - /// <param name="pathInfo">The path info.</param> - /// <returns>ItemResolveArgs.</returns> - protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null) - { - var args = base.CreateResolveArgs(pathInfo); - - Season.AddMetadataFiles(args); - - return args; - } - [IgnoreDataMember] public bool ContainsEpisodesWithoutSeasonFolders { diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index f429a2677..d6d193442 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -89,33 +89,6 @@ namespace MediaBrowser.Controller.Entities } } - /// <summary> - /// Should be overridden to return the proper folder where metadata lives - /// </summary> - /// <value>The meta location.</value> - [IgnoreDataMember] - public override string MetaLocation - { - get - { - if (!IsLocalTrailer) - { - return System.IO.Path.GetDirectoryName(Path); - } - - return base.MetaLocation; - } - } - - /// <summary> - /// Needed because the resolver stops at the trailer folder and we find the video inside. - /// </summary> - /// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value> - protected override bool UseParentPathToCreateResolveArgs - { - get { return !IsLocalTrailer; } - } - public override string GetUserDataKey() { var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 9c9466766..de78068b3 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -84,33 +84,23 @@ namespace MediaBrowser.Controller.Entities /// <value>The aspect ratio.</value> public string AspectRatio { get; set; } - /// <summary> - /// Should be overridden to return the proper folder where metadata lives - /// </summary> - /// <value>The meta location.</value> [IgnoreDataMember] - public override string MetaLocation + public override string ContainingFolderPath { get { - return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path; - } - } + if (IsMultiPart) + { + return System.IO.Path.GetDirectoryName(Path); + } - /// <summary> - /// Needed because the resolver stops at the movie folder and we find the video inside. - /// </summary> - /// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value> - protected override bool UseParentPathToCreateResolveArgs - { - get - { - if (IsInMixedFolder) + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd || + VideoType == VideoType.HdDvd) { - return false; + return Path; } - return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart; + return base.ContainingFolderPath; } } @@ -159,106 +149,73 @@ namespace MediaBrowser.Controller.Entities } } - /// <summary> - /// Overrides the base implementation to refresh metadata for local trailers - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="forceSave">if set to <c>true</c> [is new item].</param> - /// <param name="forceRefresh">if set to <c>true</c> [force].</param> - /// <returns>true if a provider reports we changed</returns> - public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) + protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - // Kick off a task to refresh the main item - var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); - - var additionalPartsChanged = false; + await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); // Must have a parent to have additional parts // In other words, it must be part of the Parent/Child tree // The additional parts won't have additional parts themselves if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null) { - try - { - additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); - } - catch (IOException ex) + var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + + if (additionalPartsChanged) { - Logger.ErrorException("Error loading additional parts for {0}.", ex, Name); + options.ForceSave = true; } } - - return additionalPartsChanged || result; } /// <summary> /// Refreshes the additional parts. /// </summary> + /// <param name="options">The options.</param> + /// <param name="fileSystemChildren">The file system children.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="forceSave">if set to <c>true</c> [force save].</param> - /// <param name="forceRefresh">if set to <c>true</c> [force refresh].</param> - /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param> /// <returns>Task{System.Boolean}.</returns> - private async Task<bool> RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) + private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadAdditionalParts().ToList(); + var newItems = LoadAdditionalParts(fileSystemChildren).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = forceSave, - ReplaceAllMetadata = forceRefresh + var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken)); - }, cancellationToken)); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); AdditionalPartIds = newItemIds; - return itemsChanged || results.Contains(true); + return itemsChanged; } /// <summary> /// Loads the additional parts. /// </summary> /// <returns>IEnumerable{Video}.</returns> - private IEnumerable<Video> LoadAdditionalParts() + private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren) { IEnumerable<FileSystemInfo> files; var path = Path; - if (string.IsNullOrEmpty(path)) - { - throw new ApplicationException(string.Format("Item {0} has a null path.", Name ?? Id.ToString())); - } - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) { - var parentPath = System.IO.Path.GetDirectoryName(path); - - if (string.IsNullOrEmpty(parentPath)) + files = fileSystemChildren.Where(i => { - throw new IOException("Unable to get parent path info from " + path); - } + if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name); + } - files = new DirectoryInfo(parentPath) - .EnumerateDirectories() - .Where(i => !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name)); + return false; + }); } else { - var resolveArgs = ResolveArgs; - - if (resolveArgs == null) - { - throw new IOException("ResolveArgs are null for " + path); - } - - files = resolveArgs.FileSystemChildren.Where(i => + files = fileSystemChildren.Where(i => { if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { @@ -276,7 +233,6 @@ namespace MediaBrowser.Controller.Entities if (dbItem != null) { - dbItem.ResetResolveArgs(video.ResolveArgs); video = dbItem; } diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index c45c14239..5d6d850f0 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -196,77 +196,6 @@ namespace MediaBrowser.Controller.Library } /// <summary> - /// Store these to reduce disk access in Resolvers - /// </summary> - /// <value>The metadata file dictionary.</value> - private Dictionary<string, FileSystemInfo> MetadataFileDictionary { get; set; } - - /// <summary> - /// Gets the metadata files. - /// </summary> - /// <value>The metadata files.</value> - public IEnumerable<FileSystemInfo> MetadataFiles - { - get - { - if (MetadataFileDictionary != null) - { - return MetadataFileDictionary.Values; - } - - return new FileSystemInfo[] { }; - } - } - - /// <summary> - /// Adds the metadata file. - /// </summary> - /// <param name="path">The path.</param> - /// <exception cref="System.IO.FileNotFoundException"></exception> - public void AddMetadataFile(string path) - { - var file = new FileInfo(path); - - if (!file.Exists) - { - throw new FileNotFoundException(path); - } - - AddMetadataFile(file); - } - - /// <summary> - /// Adds the metadata file. - /// </summary> - /// <param name="fileInfo">The file info.</param> - public void AddMetadataFile(FileSystemInfo fileInfo) - { - AddMetadataFiles(new[] { fileInfo }); - } - - /// <summary> - /// Adds the metadata files. - /// </summary> - /// <param name="files">The files.</param> - /// <exception cref="System.ArgumentNullException"></exception> - public void AddMetadataFiles(IEnumerable<FileSystemInfo> files) - { - if (files == null) - { - throw new ArgumentNullException(); - } - - if (MetadataFileDictionary == null) - { - MetadataFileDictionary = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase); - } - foreach (var file in files) - { - MetadataFileDictionary[file.Name] = file; - } - } - - /// <summary> /// Gets the name of the file system entry by. /// </summary> /// <param name="name">The name.</param> @@ -320,16 +249,6 @@ namespace MediaBrowser.Controller.Library { throw new ArgumentNullException(); } - - if (MetadataFileDictionary != null) - { - FileSystemInfo entry; - - if (MetadataFileDictionary.TryGetValue(System.IO.Path.GetFileName(path), out entry)) - { - return entry; - } - } return GetFileSystemEntryByPath(path); } @@ -346,16 +265,6 @@ namespace MediaBrowser.Controller.Library { throw new ArgumentNullException(); } - - if (MetadataFileDictionary != null) - { - FileSystemInfo entry; - - if (MetadataFileDictionary.TryGetValue(name, out entry)) - { - return entry; - } - } return GetFileSystemEntryByName(name); } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index 1bff5647b..668c3a414 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -19,6 +19,6 @@ namespace MediaBrowser.Controller.LiveTv bool IsParentalAllowed(User user); - Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); + Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 7bd6d824a..5d3d9dbca 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <typeparam name="T"></typeparam> public class BaseItemXmlParser<T> - where T : BaseItem, new() + where T : BaseItem { /// <summary> /// The logger @@ -422,11 +422,7 @@ namespace MediaBrowser.Controller.Providers int runtime; if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out runtime)) { - // For audio and video don't replace ffmpeg data - if (!(item is Video || item is Audio)) - { - item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; - } + item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } } break; diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index 3b499b487..6818fa52b 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -2,13 +2,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -146,19 +141,6 @@ namespace MediaBrowser.Controller.Providers providerInfo.LastRefreshed = value; providerInfo.LastRefreshStatus = status; providerInfo.ProviderVersion = providerVersion; - - // Save the file system stamp for future comparisons - if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem) - { - try - { - providerInfo.FileStamp = GetCurrentFileSystemStamp(item); - } - catch (IOException ex) - { - Logger.ErrorException("Error getting file stamp for {0}", ex, item.Path); - } - } } /// <summary> @@ -233,12 +215,6 @@ namespace MediaBrowser.Controller.Providers return true; } - if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem && - HasFileSystemStampChanged(item, providerInfo)) - { - return true; - } - if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion)) { return true; @@ -264,17 +240,6 @@ namespace MediaBrowser.Controller.Providers } /// <summary> - /// Determines if the item's file system stamp has changed from the last time the provider refreshed - /// </summary> - /// <param name="item">The item.</param> - /// <param name="providerInfo">The provider info.</param> - /// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns> - protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo) - { - return GetCurrentFileSystemStamp(item) != providerInfo.FileStamp; - } - - /// <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> @@ -301,159 +266,5 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <value>The priority.</value> public abstract MetadataProviderPriority Priority { get; } - - /// <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 virtual bool RefreshOnFileSystemStampChange - { - get - { - return false; - } - } - - protected virtual string[] FilestampExtensions - { - get { return new string[] { }; } - } - - /// <summary> - /// Determines if the parent's file system stamp should be used for comparison - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - protected virtual bool UseParentFileSystemStamp(BaseItem item) - { - // True when the current item is just a file - return !item.ResolveArgs.IsDirectory; - } - - protected virtual IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item) - { - if (UseParentFileSystemStamp(item) && item.Parent != null) - { - return new[] { item.Parent }; - } - - return new[] { item }; - } - - /// <summary> - /// Gets the item's current file system stamp - /// </summary> - /// <param name="item">The item.</param> - /// <returns>Guid.</returns> - private Guid GetCurrentFileSystemStamp(BaseItem item) - { - return GetFileSystemStamp(GetItemsForFileStampComparison(item)); - } - - private Dictionary<string, string> _fileStampExtensionsDictionary; - - private Dictionary<string, string> FileStampExtensionsDictionary - { - get - { - return _fileStampExtensionsDictionary ?? - (_fileStampExtensionsDictionary = - FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase)); - } - } - - /// <summary> - /// Gets the file system stamp. - /// </summary> - /// <param name="items">The items.</param> - /// <returns>Guid.</returns> - protected virtual Guid GetFileSystemStamp(IEnumerable<BaseItem> items) - { - var sb = new StringBuilder(); - - var extensions = FileStampExtensionsDictionary; - var numExtensions = FilestampExtensions.Length; - - foreach (var item in items) - { - // If there's no path or the item is a file, there's nothing to do - if (item.LocationType == LocationType.FileSystem) - { - var resolveArgs = item.ResolveArgs; - - if (resolveArgs.IsDirectory) - { - // Record the name of each file - // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order - AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions); - AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions); - } - } - } - - var stamp = sb.ToString(); - - if (string.IsNullOrEmpty(stamp)) - { - return Guid.Empty; - } - - return stamp.GetMD5(); - } - - private static readonly Dictionary<string, string> FoldersToMonitor = new[] { "extrafanart", "extrathumbs" } - .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - - protected Guid GetFileSystemStamp(IEnumerable<FileSystemInfo> files) - { - var sb = new StringBuilder(); - - var extensions = FileStampExtensionsDictionary; - var numExtensions = FilestampExtensions.Length; - - // Record the name of each file - // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order - AddFiles(sb, files, extensions, numExtensions); - - return sb.ToString().GetMD5(); - } - - /// <summary> - /// Adds the files. - /// </summary> - /// <param name="sb">The sb.</param> - /// <param name="files">The files.</param> - /// <param name="extensions">The extensions.</param> - /// <param name="numExtensions">The num extensions.</param> - private void AddFiles(StringBuilder sb, IEnumerable<FileSystemInfo> files, Dictionary<string, string> extensions, int numExtensions) - { - foreach (var file in files - .OrderBy(f => f.Name)) - { - try - { - if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory) - { - if (FoldersToMonitor.ContainsKey(file.Name)) - { - sb.Append(file.Name); - - var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); - AddFiles(sb, children, extensions, numExtensions); - } - } - - // It's a file - else if (numExtensions == 0 || extensions.ContainsKey(file.Extension)) - { - sb.Append(file.Name); - } - } - catch (IOException ex) - { - Logger.ErrorException("Error accessing file attributes for {0}", ex, file.FullName); - } - } - } } } diff --git a/MediaBrowser.Controller/Providers/IHasMetadata.cs b/MediaBrowser.Controller/Providers/IHasMetadata.cs index 3fba73a28..19ab00e68 100644 --- a/MediaBrowser.Controller/Providers/IHasMetadata.cs +++ b/MediaBrowser.Controller/Providers/IHasMetadata.cs @@ -39,5 +39,11 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns> bool IsSaveLocalMetadataEnabled(); + + /// <summary> + /// Gets a value indicating whether this instance is in mixed folder. + /// </summary> + /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value> + bool IsInMixedFolder { get; } } } diff --git a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs index 62b208b59..91ef22b2c 100644 --- a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs @@ -5,12 +5,6 @@ namespace MediaBrowser.Controller.Providers { public interface ILocalMetadataProvider : IMetadataProvider { - /// <summary> - /// Determines whether [has local metadata] [the specified item]. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns> - bool HasLocalMetadata(IHasMetadata item); } public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider @@ -19,9 +13,16 @@ namespace MediaBrowser.Controller.Providers /// <summary> /// Gets the metadata. /// </summary> - /// <param name="path">The path.</param> + /// <param name="info">The information.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{MetadataResult{`0}}.</returns> - Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken); + Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken); + } + + public class ItemInfo + { + public string Path { get; set; } + + public bool IsInMixedFolder { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 7019623ee..369b58256 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -25,15 +25,6 @@ namespace MediaBrowser.Controller.Providers Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken); /// <summary> - /// Executes the metadata providers. - /// </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> - Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false); - - /// <summary> /// Saves the image. /// </summary> /// <param name="item">The item.</param> @@ -60,12 +51,11 @@ namespace MediaBrowser.Controller.Providers /// <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="savers">The savers.</param> - void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, + void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> savers); /// <summary> diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 27de50ef8..83f1a12d9 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -16,17 +16,6 @@ namespace MediaBrowser.Controller.Providers /// </summary> [Obsolete] public bool ForceSave { get; set; } - - /// <summary> - /// TODO: deprecate. Keeping this for now, for api compatibility - /// </summary> - [Obsolete] - public bool ResetResolveArgs { get; set; } - - public MetadataRefreshOptions() - { - ResetResolveArgs = true; - } } public class ImageRefreshOptions diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 4d409ff7d..af3c62396 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -153,12 +153,6 @@ namespace MediaBrowser.Model.Dto public int? VoteCount { get; set; } /// <summary> - /// Gets or sets the original run time ticks. - /// </summary> - /// <value>The original run time ticks.</value> - public long? OriginalRunTimeTicks { get; set; } - - /// <summary> /// Gets or sets the cumulative run time ticks. /// </summary> /// <value>The cumulative run time ticks.</value> diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 406957daa..e16da857b 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -72,11 +72,6 @@ namespace MediaBrowser.Model.Querying Settings, /// <summary> - /// The original run time ticks - /// </summary> - OriginalRunTimeTicks, - - /// <summary> /// The item overview /// </summary> Overview, diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs index 3b6439b4b..40f9fded5 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 089cfe549..daae58ff6 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 68b003480..521198e96 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 eee6f3b48..a214dff8c 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 e4ea36dd1..000000000 --- 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 31e4bc8bc..000000000 --- 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 000000000..cc688d91a --- /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 000000000..2fc6a8290 --- /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 551dd4257..043e32d11 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 61fb791e6..3e74f4c32 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 e2a67de8d..644fe3e42 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 bc58f3178..000000000 --- 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 f63417026..000000000 --- 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 701cc9f85..af3de824d 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 52aaf412a..2a4ef7597 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 3fb1e7074..f2fa4dc29 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 c817180c5..e0272bc7b 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 dbfc97d51..030b3cbd9 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 ad8465009..2c710693e 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 ad3211650..20ce952db 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 ad4630dcf..000000000 --- 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 bae719eea..4fc92ddeb 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 1f3723653..d55a42d11 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 8adb75839..9073441e4 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 8d69e6ba2..000000000 --- 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 70b849e1c..31d44f4ec 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 9fc57c542..000000000 --- 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 47948455b..600ba1925 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 70aaec526..e0ed0ec25 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 000000000..d06d12a49 --- /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 bc4652e2c..be2f7ad61 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 9ef2e1bca..000000000 --- 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 3adc682fc..47b92235b 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 000000000..cc513b395 --- /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 7ae6dffc7..000000000 --- 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 82250f2b5..8eabc0a2d 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 9b4f17a86..000000000 --- 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 000000000..59e8f7cef --- /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 000000000..ad9cf5231 --- /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 000000000..52704b151 --- /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 7c7de6182..06a1ed121 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 30c38bdaa..921cbc8b9 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 000000000..c056fdabf --- /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 dcfe0d89e..5bdc2cdd0 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 c23458f68..0996615d3 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 61fe19a61..ecefb72c4 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 b20a6e331..bfe7e7609 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 a6225b58c..613819517 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 e5ba1aefd..15fdc6752 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 2b6edaf08..ddb89bd42 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 b8d88f5e6..b1f8ef976 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 9dcc9fe4f..f9fe45120 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 6d2ece19c..dc06857ce 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 8f0c63136..4dfaa3925 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 5523b8ab3..178c7a265 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 000000000..9aa4ba138 --- /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 892275d38..000000000 --- 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 000000000..01e511177 --- /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); + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 32d4c7708..7dacfacc2 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -273,21 +273,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (item.LocationType == LocationType.FileSystem) { - return collections.Where(i => - { - - try - { - return i.LocationType == LocationType.FileSystem && - i.PhysicalLocations.Contains(item.Path); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path); - return false; - } - - }).Cast<T>(); + return collections.Where(i => i.LocationType == LocationType.FileSystem && + i.PhysicalLocations.Contains(item.Path)).Cast<T>(); } } diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 38f5cb62a..f1f9048a9 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -162,18 +162,7 @@ namespace MediaBrowser.Server.Implementations.IO .Children .OfType<Folder>() .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) - .SelectMany(f => - { - try - { - return f.PhysicalLocations; - } - catch (IOException) - { - return new string[] { }; - } - - }) + .SelectMany(f => f.PhysicalLocations) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(i => i) .ToList(); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 8294bfed6..813d279ab 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -833,9 +833,6 @@ namespace MediaBrowser.Server.Implementations.Library (item as MusicArtist).IsAccessedByName = true; } - // Set this now so we don't cause additional file system access during provider executions - item.ResetResolveArgs(fileInfo); - return new Tuple<bool, T>(isNew, item); } @@ -1113,6 +1110,7 @@ namespace MediaBrowser.Server.Implementations.Library cancellationToken.ThrowIfCancellationRequested(); await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false).ConfigureAwait(false); + var b = true; } /// <summary> @@ -1244,7 +1242,6 @@ namespace MediaBrowser.Server.Implementations.Library if (dbItem != null) { - dbItem.ResetResolveArgs(video.ResolveArgs); video = dbItem; } } @@ -1383,6 +1380,8 @@ namespace MediaBrowser.Server.Implementations.Library item.DateLastSaved = DateTime.UtcNow; + _logger.Debug("Saving {0} to database.", item.Path ?? item.Name); + await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false); UpdateItemInLibraryCache(item); @@ -1479,16 +1478,7 @@ namespace MediaBrowser.Server.Implementations.Library return true; } - try - { - - return i.PhysicalLocations.Contains(item.Path); - } - catch (IOException ex) - { - _logger.ErrorException("Error getting resolve args for {0}", ex, i.Path); - return false; - } + return i.PhysicalLocations.Contains(item.Path); }) .Select(i => i.CollectionType) .Where(i => !string.IsNullOrEmpty(i)) diff --git a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs index 4ce5f11d4..ad2db5abb 100644 --- a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs +++ b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs @@ -23,8 +23,6 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="fileSystem">The file system.</param> public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem) { - item.ResetResolveArgs(args); - // If the resolver didn't specify this if (string.IsNullOrEmpty(item.Path)) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 3d6f7e66a..998895cbf 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -184,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies private void SetProviderIdFromPath(Video item) { //we need to only look at the name of this actual item (not parents) - var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.MetaLocation); + var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath); var id = justName.GetAttributeValue("tmdbid"); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index c426fed25..70280d8d3 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -47,17 +47,5 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV return null; } - - /// <summary> - /// Sets the initial item values. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - protected override void SetInitialItemValues(Season item, ItemResolveArgs args) - { - base.SetInitialItemValues(item, args); - - Season.AddMetadataFiles(args); - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 1bd975944..625274da9 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -92,8 +92,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { base.SetInitialItemValues(item, args); - Season.AddMetadataFiles(args); - SetProviderIdFromPath(item, args.Path); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 9501d2d12..94b6cdd9e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -326,13 +326,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.Name = channelInfo.Name; } - // Set this now so we don't cause additional file system access during provider executions - item.ResetResolveArgs(fileInfo); - await item.RefreshMetadata(new MetadataRefreshOptions { - ForceSave = isNew, - ResetResolveArgs = false + ForceSave = isNew }, cancellationToken); @@ -391,8 +387,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv await item.RefreshMetadata(new MetadataRefreshOptions { - ForceSave = isNew, - ResetResolveArgs = false + ForceSave = isNew }, cancellationToken); @@ -448,8 +443,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv await item.RefreshMetadata(new MetadataRefreshOptions { - ForceSave = isNew, - ResetResolveArgs = false + ForceSave = isNew }, cancellationToken); diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 323a63674..d7858222e 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -501,7 +501,7 @@ namespace MediaBrowser.ServerApplication GetExports<ILibraryPostScanTask>(), GetExports<IPeoplePrescanTask>()); - ProviderManager.AddParts(GetExports<BaseMetadataProvider>(), GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(), + ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(), GetExports<IMetadataSaver>()); ImageProcessor.AddParts(GetExports<IImageEnhancer>()); @@ -627,7 +627,7 @@ namespace MediaBrowser.ServerApplication list.Add(typeof(IServerApplicationHost).Assembly); // Include composable parts in the Providers assembly - list.Add(typeof(ImagesByNameProvider).Assembly); + list.Add(typeof(ProviderUtils).Assembly); // Common implementations list.Add(typeof(TaskManager).Assembly); diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 744debbcd..0c5360b49 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -237,4 +237,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal |
