Bläddra i källkod

restored external subtitle support

Luke Pulverenti 11 år sedan
förälder
incheckning
013d5a467e
75 ändrade filer med 559 tillägg och 451 borttagningar
  1. 0 1
      MediaBrowser.Api/ConfigurationService.cs
  2. 2 2
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  3. 1 1
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  4. 45 8
      MediaBrowser.Controller/Entities/BaseItem.cs
  5. 3 2
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  6. 11 7
      MediaBrowser.Controller/Entities/Folder.cs
  7. 14 3
      MediaBrowser.Controller/Entities/IHasImages.cs
  8. 9 3
      MediaBrowser.Controller/Entities/IHasMetadata.cs
  9. 5 3
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  10. 52 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  11. 33 1
      MediaBrowser.Controller/Entities/TV/Season.cs
  12. 1 1
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  13. 12 3
      MediaBrowser.Controller/Entities/Video.cs
  14. 1 1
      MediaBrowser.Controller/Library/ItemUpdateType.cs
  15. 10 1
      MediaBrowser.Controller/Providers/DirectoryService.cs
  16. 1 1
      MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
  17. 2 1
      MediaBrowser.Controller/Providers/IHasChangeMonitor.cs
  18. 1 1
      MediaBrowser.Controller/Providers/ILocalImageProvider.cs
  19. 2 4
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  20. 2 0
      MediaBrowser.Model/Configuration/MetadataOptions.cs
  21. 0 5
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  22. 2 0
      MediaBrowser.Model/Dto/BaseItemDto.cs
  23. 17 17
      MediaBrowser.Model/Entities/MetadataProviders.cs
  24. 0 6
      MediaBrowser.Model/Providers/ImageProviderInfo.cs
  25. 5 0
      MediaBrowser.Model/Querying/ItemFields.cs
  26. 1 1
      MediaBrowser.Providers/All/InternalMetadataFolderImageProvider.cs
  27. 8 8
      MediaBrowser.Providers/All/LocalImageProvider.cs
  28. 5 1
      MediaBrowser.Providers/BaseXmlProvider.cs
  29. 5 3
      MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs
  30. 1 1
      MediaBrowser.Providers/Folders/CollectionFolderImageProvider.cs
  31. 1 1
      MediaBrowser.Providers/Folders/ImagesByNameImageProvider.cs
  32. 2 2
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  33. 39 32
      MediaBrowser.Providers/Manager/MetadataService.cs
  34. 42 20
      MediaBrowser.Providers/Manager/ProviderManager.cs
  35. 1 1
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  36. 40 22
      MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
  37. 97 68
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  38. 1 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  39. 1 1
      MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
  40. 1 1
      MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
  41. 3 3
      MediaBrowser.Providers/Music/AlbumMetadataService.cs
  42. 1 1
      MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
  43. 1 1
      MediaBrowser.Providers/Music/FanArtArtistProvider.cs
  44. 4 4
      MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
  45. 1 1
      MediaBrowser.Providers/Omdb/OmdbProvider.cs
  46. 10 1
      MediaBrowser.Providers/People/MovieDbPersonProvider.cs
  47. 2 11
      MediaBrowser.Providers/Savers/AlbumXmlSaver.cs
  48. 1 12
      MediaBrowser.Providers/Savers/ArtistXmlSaver.cs
  49. 2 11
      MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs
  50. 1 2
      MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
  51. 9 13
      MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
  52. 2 11
      MediaBrowser.Providers/Savers/FolderXmlSaver.cs
  53. 2 11
      MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs
  54. 1 9
      MediaBrowser.Providers/Savers/GameXmlSaver.cs
  55. 3 8
      MediaBrowser.Providers/Savers/MovieXmlSaver.cs
  56. 2 11
      MediaBrowser.Providers/Savers/SeasonXmlSaver.cs
  57. 1 9
      MediaBrowser.Providers/Savers/SeriesXmlSaver.cs
  58. 1 1
      MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs
  59. 1 53
      MediaBrowser.Providers/TV/EpisodeMetadataService.cs
  60. 1 1
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  61. 1 1
      MediaBrowser.Providers/TV/FanartSeriesProvider.cs
  62. 1 1
      MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs
  63. 1 22
      MediaBrowser.Providers/TV/SeasonMetadataService.cs
  64. 0 2
      MediaBrowser.Providers/TV/SeriesMetadataService.cs
  65. 1 1
      MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
  66. 1 1
      MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
  67. 19 3
      MediaBrowser.Providers/TV/TvdbPrescanTask.cs
  68. 1 1
      MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs
  69. 1 1
      MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
  70. 1 1
      MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
  71. 1 1
      MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
  72. 1 1
      MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
  73. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  74. 1 1
      Nuget/MediaBrowser.Common.nuspec
  75. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 0 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -143,7 +143,6 @@ namespace MediaBrowser.Api
                 DisableSaversForType(typeof(Game), config);
                 DisableSaversForType(typeof(GameSystem), config);
                 DisableSaversForType(typeof(Movie), config);
-                DisableSaversForType(typeof(Trailer), config);
                 DisableSaversForType(typeof(BoxSet), config);
                 DisableSaversForType(typeof(Book), config);
                 DisableSaversForType(typeof(Series), config);

+ 2 - 2
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.Entities
 
         public List<string> PhysicalLocationsList { get; set; }
 
-        protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(DirectoryService directoryService)
+        protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
         {
             return CreateResolveArgs().FileSystemChildren;
         }
@@ -119,7 +119,7 @@ namespace MediaBrowser.Controller.Entities
         /// Get the children of this folder from the actual file system
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        protected override IEnumerable<BaseItem> GetNonCachedChildren(DirectoryService directoryService)
+        protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
             return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
         }

+ 1 - 1
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Entities.Audio
         }
 
         private readonly Task _cachedTask = Task.FromResult(true);
-        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, DirectoryService directoryService)
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         {
             if (IsAccessedByName)
             {

+ 45 - 8
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -563,6 +563,8 @@ namespace MediaBrowser.Controller.Entities
         {
             var locationType = LocationType;
 
+            var requiresSave = false;
+
             if (IsFolder || Parent != null)
             {
                 options.DirectoryService = options.DirectoryService ?? new DirectoryService(Logger);
@@ -571,13 +573,34 @@ namespace MediaBrowser.Controller.Entities
                     GetFileSystemChildren(options.DirectoryService).ToList() :
                     new List<FileSystemInfo>();
 
-                await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false);
+                var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
+
+                if (ownedItemsChanged)
+                {
+                    requiresSave = true;
+                }
             }
 
+            var dateLastSaved = DateLastSaved;
+
             await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
+
+            // If it wasn't saved by the provider process, save now
+            if (requiresSave && dateLastSaved == DateLastSaved)
+            {
+                await UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+            }
         }
 
-        protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+        /// <summary>
+        /// Refreshes owned items such as trailers, theme videos, special features, etc.
+        /// Returns true or false indicating if changes were found.
+        /// </summary>
+        /// <param name="options"></param>
+        /// <param name="fileSystemChildren"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
             var themeSongsChanged = false;
 
@@ -605,13 +628,10 @@ namespace MediaBrowser.Controller.Entities
                 }
             }
 
-            if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
-            {
-                options.ForceSave = true;
-            }
+            return themeSongsChanged || themeVideosChanged || localTrailersChanged;
         }
 
-        protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren(DirectoryService directoryService)
+        protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
         {
             var path = ContainingFolderPath;
 
@@ -1205,7 +1225,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// Validates that images within the item are still on the file system
         /// </summary>
-        public bool ValidateImages(DirectoryService directoryService)
+        public bool ValidateImages(IDirectoryService directoryService)
         {
             var allDirectories = ImageInfos.Select(i => System.IO.Path.GetDirectoryName(i.Path)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
             var allFiles = allDirectories.SelectMany(directoryService.GetFiles).Select(i => i.FullName).ToList();
@@ -1390,5 +1410,22 @@ namespace MediaBrowser.Controller.Entities
                 ParentIndexNumber = ParentIndexNumber
             };
         }
+
+        /// <summary>
+        /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what.
+        /// </summary>
+        /// <returns>ItemUpdateType.</returns>
+        public virtual ItemUpdateType BeforeMetadataRefresh()
+        {
+            var updateType = ItemUpdateType.None;
+
+            if (string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Path))
+            {
+                Name = System.IO.Path.GetFileNameWithoutExtension(Path);
+                updateType = updateType | ItemUpdateType.MetadataEdit;
+            }
+
+            return updateType;
+        }
     }
 }

+ 3 - 2
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Entities
 
         public List<string> PhysicalLocationsList { get; set; }
 
-        protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(DirectoryService directoryService)
+        protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
         {
             return CreateResolveArgs().FileSystemChildren;
         }
@@ -119,8 +119,9 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
         /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
         /// <param name="refreshOptions">The refresh options.</param>
+        /// <param name="directoryService">The directory service.</param>
         /// <returns>Task.</returns>
-        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, DirectoryService directoryService)
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         {
             CreateResolveArgs();
             ResetDynamicChildren();

+ 11 - 7
MediaBrowser.Controller/Entities/Folder.cs

@@ -319,7 +319,7 @@ namespace MediaBrowser.Controller.Entities
             return ValidateChildrenWithCancellationSupport(progress, cancellationToken, recursive, true, metadataRefreshOptions, metadataRefreshOptions.DirectoryService);
         }
 
-        private async Task ValidateChildrenWithCancellationSupport(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, DirectoryService directoryService)
+        private async Task ValidateChildrenWithCancellationSupport(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -373,7 +373,7 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="refreshOptions">The refresh options.</param>
         /// <param name="directoryService">The directory service.</param>
         /// <returns>Task.</returns>
-        protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, DirectoryService directoryService)
+        protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         {
             var locationType = LocationType;
 
@@ -593,7 +593,7 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        private async Task ValidateSubFolders(IList<Folder> children, DirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken)
+        private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken)
         {
             var list = children;
             var childCount = list.Count;
@@ -679,7 +679,7 @@ namespace MediaBrowser.Controller.Entities
         /// Get the children of this folder from the actual file system
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        protected virtual IEnumerable<BaseItem> GetNonCachedChildren(DirectoryService directoryService)
+        protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
             return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(directoryService), this);
         }
@@ -927,17 +927,21 @@ namespace MediaBrowser.Controller.Entities
             return item;
         }
 
-        protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+        protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
+            var changesFound = false;
+
             if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
             {
                 if (RefreshLinkedChildren(fileSystemChildren))
                 {
-                    options.ForceSave = true;
+                    changesFound = true;
                 }
             }
 
-            return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken);
+            var baseHasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+
+            return baseHasChanges || changesFound;
         }
 
         /// <summary>

+ 14 - 3
MediaBrowser.Controller/Entities/IHasImages.cs

@@ -1,8 +1,8 @@
-using System.IO;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities
@@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// Validates the images and returns true or false indicating if any were removed.
         /// </summary>
-        bool ValidateImages(DirectoryService directoryService);
+        bool ValidateImages(IDirectoryService directoryService);
 
         /// <summary>
         /// Gets a value indicating whether this instance is owned item.
@@ -166,5 +166,16 @@ namespace MediaBrowser.Controller.Entities
         {
             item.SetImagePath(imageType, 0, file);
         }
+
+        /// <summary>
+        /// Sets the image path.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="file">The file.</param>
+        public static void SetImagePath(this IHasImages item, ImageType imageType, string file)
+        {
+            item.SetImagePath(imageType, new FileInfo(file));
+        }
     }
 }

+ 9 - 3
MediaBrowser.Controller/Entities/IHasMetadata.cs

@@ -1,9 +1,9 @@
-using System;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -49,5 +49,11 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what.
+        /// </summary>
+        /// <returns>ItemUpdateType.</returns>
+        ItemUpdateType BeforeMetadataRefresh();
     }
 }

+ 5 - 3
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -96,9 +96,9 @@ namespace MediaBrowser.Controller.Entities.Movies
             return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
         }
 
-        protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+        protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+            var hasChanges = await base.RefreshedOwnedItems(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
@@ -108,9 +108,11 @@ namespace MediaBrowser.Controller.Entities.Movies
 
                 if (specialFeaturesChanged)
                 {
-                    options.ForceSave = true;
+                    hasChanges = true;
                 }
             }
+
+            return hasChanges;
         }
 
         private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)

+ 52 - 1
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -1,5 +1,7 @@
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -260,5 +262,54 @@ namespace MediaBrowser.Controller.Entities.TV
 
             return id;
         }
+
+        public override ItemUpdateType BeforeMetadataRefresh()
+        {
+            var updateType = base.BeforeMetadataRefresh();
+
+            var locationType = LocationType;
+            if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+            {
+                if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
+                {
+                    IndexNumber = IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(Path, Parent is Season);
+
+                    // If a change was made record it
+                    if (IndexNumber.HasValue)
+                    {
+                        updateType = updateType | ItemUpdateType.MetadataImport;
+                    }
+                }
+
+                if (!IndexNumberEnd.HasValue && !string.IsNullOrEmpty(Path))
+                {
+                    IndexNumberEnd = IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(Path);
+
+                    // If a change was made record it
+                    if (IndexNumberEnd.HasValue)
+                    {
+                        updateType = updateType | ItemUpdateType.MetadataImport;
+                    }
+                }
+            }
+
+            if (!ParentIndexNumber.HasValue)
+            {
+                var season = Season;
+
+                if (season != null)
+                {
+                    ParentIndexNumber = season.IndexNumber;
+                }
+
+                // If a change was made record it
+                if (ParentIndexNumber.HasValue)
+                {
+                    updateType = updateType | ItemUpdateType.MetadataImport;
+                }
+            }
+
+            return updateType;
+        }
     }
 }

+ 33 - 1
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
@@ -243,9 +244,40 @@ namespace MediaBrowser.Controller.Entities.TV
             }
         }
 
+        /// <summary>
+        /// Gets the lookup information.
+        /// </summary>
+        /// <returns>SeasonInfo.</returns>
         public SeasonInfo GetLookupInfo()
         {
             return GetItemLookupInfo<SeasonInfo>();
         }
+
+        /// <summary>
+        /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what.
+        /// </summary>
+        /// <returns>ItemUpdateType.</returns>
+        public override ItemUpdateType BeforeMetadataRefresh()
+        {
+            var updateType = base.BeforeMetadataRefresh();
+
+            var locationType = LocationType;
+
+            if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+            {
+                if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
+                {
+                    IndexNumber = IndexNumber ?? TVUtils.GetSeasonNumberFromPath(Path);
+
+                    // If a change was made record it
+                    if (IndexNumber.HasValue)
+                    {
+                        updateType = updateType | ItemUpdateType.MetadataImport;
+                    }
+                }
+            }
+
+            return updateType;
+        }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities
         /// Get the children of this folder from the actual file system
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        protected override IEnumerable<BaseItem> GetNonCachedChildren(DirectoryService directoryService)
+        protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
             return base.GetNonCachedChildren(directoryService).Concat(LibraryManager.RootFolder.VirtualChildren);
         }

+ 12 - 3
MediaBrowser.Controller/Entities/Video.cs

@@ -27,8 +27,15 @@ namespace MediaBrowser.Controller.Entities
             PlayableStreamFileNames = new List<string>();
             AdditionalPartIds = new List<Guid>();
             Tags = new List<string>();
+            SubtitleFiles = new List<string>();
         }
 
+        /// <summary>
+        /// Gets or sets the subtitle paths.
+        /// </summary>
+        /// <value>The subtitle paths.</value>
+        public List<string> SubtitleFiles { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance has subtitles.
         /// </summary>
@@ -156,9 +163,9 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+        protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+            var hasChanges = await base.RefreshedOwnedItems(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
@@ -169,9 +176,11 @@ namespace MediaBrowser.Controller.Entities
 
                 if (additionalPartsChanged)
                 {
-                    options.ForceSave = true;
+                    hasChanges = true;
                 }
             }
+
+            return hasChanges;
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Controller/Library/ItemUpdateType.cs

@@ -5,7 +5,7 @@ namespace MediaBrowser.Controller.Library
     [Flags]
     public enum ItemUpdateType
     {
-        Unspecified = 1,
+        None = 1,
         MetadataImport = 2,
         ImageUpdate = 4,
         MetadataDownload = 8,

+ 10 - 1
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -6,7 +6,16 @@ using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.Controller.Providers
 {
-    public class DirectoryService
+    public interface IDirectoryService
+    {
+        List<FileSystemInfo> GetFileSystemEntries(string path);
+        IEnumerable<FileInfo> GetFiles(string path);
+        IEnumerable<DirectoryInfo> GetDirectories(string path);
+        FileInfo GetFile(string path);
+        DirectoryInfo GetDirectory(string path);
+    }
+
+    public class DirectoryService : IDirectoryService
     {
         private readonly ILogger _logger;
 

+ 1 - 1
MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs

@@ -12,6 +12,6 @@ namespace MediaBrowser.Controller.Providers
     public interface ICustomMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ICustomMetadataProvider
         where TItemType : IHasMetadata
     {
-        Task<ItemUpdateType> FetchAsync(TItemType item, CancellationToken cancellationToken);
+        Task<ItemUpdateType> FetchAsync(TItemType item, IDirectoryService directoryService, CancellationToken cancellationToken);
     }
 }

+ 2 - 1
MediaBrowser.Controller/Providers/IHasChangeMonitor.cs

@@ -9,8 +9,9 @@ namespace MediaBrowser.Controller.Providers
         /// Determines whether the specified item has changed.
         /// </summary>
         /// <param name="item">The item.</param>
+        /// <param name="directoryService">The directory service.</param>
         /// <param name="date">The date.</param>
         /// <returns><c>true</c> if the specified item has changed; otherwise, <c>false</c>.</returns>
-        bool HasChanged(IHasMetadata item, DateTime date);
+        bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date);
     }
 }

+ 1 - 1
MediaBrowser.Controller/Providers/ILocalImageProvider.cs

@@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Providers
 
     public interface ILocalImageFileProvider : ILocalImageProvider
     {
-        List<LocalImageInfo> GetImages(IHasImages item, DirectoryService directoryService);
+        List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService);
     }
 
     public class LocalImageInfo

+ 2 - 4
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Logging;
-using System;
+using System;
 
 namespace MediaBrowser.Controller.Providers
 {
@@ -17,13 +16,12 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         [Obsolete]
         public bool ForceSave { get; set; }
-
-        public DirectoryService DirectoryService { get; set; }
     }
 
     public class ImageRefreshOptions
     {
         public ImageRefreshMode ImageRefreshMode { get; set; }
+        public IDirectoryService DirectoryService { get; set; }
 
         public ImageRefreshOptions()
         {

+ 2 - 0
MediaBrowser.Model/Configuration/MetadataOptions.cs

@@ -15,6 +15,7 @@ namespace MediaBrowser.Model.Configuration
         public ImageOption[] ImageOptions { get; set; }
 
         public string[] DisabledMetadataSavers { get; set; }
+        public string[] LocalMetadataReaders { get; set; }
 
         public MetadataOptions()
             : this(3, 1280)
@@ -35,6 +36,7 @@ namespace MediaBrowser.Model.Configuration
 
             ImageOptions = imageOptions.ToArray();
             DisabledMetadataSavers = new string[] { };
+            LocalMetadataReaders = new string[] { };
         }
 
         public int GetLimit(ImageType type)

+ 0 - 5
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -265,11 +265,6 @@ namespace MediaBrowser.Model.Configuration
 
             MetadataOptions = options.ToArray();
         }
-
-        public MetadataOptions GetMetadataOptions(string type)
-        {
-            return MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase));
-        }
     }
 
     public enum ImageSavingConvention

+ 2 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -32,6 +32,8 @@ namespace MediaBrowser.Model.Dto
         /// <value>The date created.</value>
         public DateTime? DateCreated { get; set; }
 
+        public DateTime? DateLastMediaAdded { get; set; }
+        
         public int? AirsBeforeSeasonNumber { get; set; }
         public int? AirsAfterSeasonNumber { get; set; }
         public int? AirsBeforeEpisodeNumber { get; set; }

+ 17 - 17
MediaBrowser.Model/Entities/MetadataProviders.cs

@@ -6,40 +6,40 @@ namespace MediaBrowser.Model.Entities
     /// </summary>
     public enum MetadataProviders
     {
-        Gamesdb,
+        Gamesdb = 1,
         /// <summary>
         /// The imdb
         /// </summary>
-        Imdb,
+        Imdb = 2,
         /// <summary>
         /// The TMDB
         /// </summary>
-        Tmdb,
+        Tmdb = 3,
         /// <summary>
         /// The TVDB
         /// </summary>
-        Tvdb,
+        Tvdb = 4,
         /// <summary>
         /// The tvcom
         /// </summary>
-        Tvcom,
+        Tvcom = 5,
         /// <summary>
         /// The rotten tomatoes
         /// </summary>
-        RottenTomatoes,
+        RottenTomatoes = 6,
         /// <summary>
         /// Tmdb Collection Id
         /// </summary>
-        TmdbCollection,
-        MusicBrainzAlbum,
-        MusicBrainzAlbumArtist,
-        MusicBrainzArtist,
-        MusicBrainzReleaseGroup,
-        Zap2It,
-        NesBox,
-        NesBoxRom,
-        TvRage,
-        AudioDbArtist,
-        AudioDbAlbum
+        TmdbCollection = 7,
+        MusicBrainzAlbum = 8,
+        MusicBrainzAlbumArtist = 9,
+        MusicBrainzArtist = 10,
+        MusicBrainzReleaseGroup = 11,
+        Zap2It = 12,
+        NesBox = 13,
+        NesBoxRom = 14,
+        TvRage = 15,
+        AudioDbArtist = 16,
+        AudioDbAlbum = 17
     }
 }

+ 0 - 6
MediaBrowser.Model/Providers/ImageProviderInfo.cs

@@ -10,11 +10,5 @@
         /// </summary>
         /// <value>The name.</value>
         public string Name { get; set; }
-
-        /// <summary>
-        /// Gets or sets the order.
-        /// </summary>
-        /// <value>The order.</value>
-        public int Order { get; set; }
     }
 }

+ 5 - 0
MediaBrowser.Model/Querying/ItemFields.cs

@@ -41,6 +41,11 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         DateCreated,
 
+        /// <summary>
+        /// The date last media added
+        /// </summary>
+        DateLastMediaAdded,
+
         /// <summary>
         /// Item display preferences
         /// </summary>

+ 1 - 1
MediaBrowser.Providers/All/InternalMetadataFolderImageProvider.cs

@@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.All
             }
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService)
         {
             var path = _config.ApplicationPaths.GetInternalMetadataPath(item.Id);
 

+ 8 - 8
MediaBrowser.Providers/All/LocalImageProvider.cs

@@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.All
             return false;
         }
 
-        private IEnumerable<FileSystemInfo> GetFiles(IHasImages item, bool includeDirectories, DirectoryService directoryService)
+        private IEnumerable<FileSystemInfo> GetFiles(IHasImages item, bool includeDirectories, IDirectoryService directoryService)
         {
             if (item.LocationType != LocationType.FileSystem)
             {
@@ -77,7 +77,7 @@ namespace MediaBrowser.Providers.All
                 .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase));
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService)
         {
             var files = GetFiles(item, true, directoryService).ToList();
 
@@ -88,12 +88,12 @@ namespace MediaBrowser.Providers.All
             return list;
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, string path, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, string path, IDirectoryService directoryService)
         {
             return GetImages(item, new[] { path }, directoryService);
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService)
         {
             var files = paths.SelectMany(directoryService.GetFiles)
                .Where(i =>
@@ -113,7 +113,7 @@ namespace MediaBrowser.Providers.All
             return list;
         }
 
-        private void PopulateImages(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, bool supportParentSeriesFiles, DirectoryService directoryService)
+        private void PopulateImages(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, bool supportParentSeriesFiles, IDirectoryService directoryService)
         {
             var imagePrefix = string.Empty;
 
@@ -185,7 +185,7 @@ namespace MediaBrowser.Providers.All
             }
         }
 
-        private void PopulateBackdrops(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix, DirectoryService directoryService)
+        private void PopulateBackdrops(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix, IDirectoryService directoryService)
         {
             PopulateBackdrops(images, files, imagePrefix, "backdrop", "backdrop", ImageType.Backdrop);
 
@@ -212,7 +212,7 @@ namespace MediaBrowser.Providers.All
             }
         }
 
-        private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images, DirectoryService directoryService)
+        private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images, IDirectoryService directoryService)
         {
             var imageFiles = directoryService.GetFiles(path)
                 .Where(i =>
@@ -262,7 +262,7 @@ namespace MediaBrowser.Providers.All
         }
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images, DirectoryService directoryService)
+        private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images, IDirectoryService directoryService)
         {
             var seasonNumber = season.IndexNumber;
 

+ 5 - 1
MediaBrowser.Providers/BaseXmlProvider.cs

@@ -39,6 +39,10 @@ namespace MediaBrowser.Providers
             {
                 result.HasMetadata = false;
             }
+            catch (DirectoryNotFoundException)
+            {
+                result.HasMetadata = false;
+            }
             finally
             {
                 XmlProviderUtils.XmlParsingResourcePool.Release();
@@ -56,7 +60,7 @@ namespace MediaBrowser.Providers
 
         protected abstract FileInfo GetXmlFile(ItemInfo info);
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path });
 

+ 5 - 3
MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs

@@ -85,10 +85,12 @@ namespace MediaBrowser.Providers.BoxSets
 
         private BoxSet GetItem(RootObject obj)
         {
-            var item = new BoxSet();
+            var item = new BoxSet
+            {
+                Name = obj.name, 
+                Overview = obj.overview
+            };
 
-            item.Name = obj.name;
-            item.Overview = obj.overview;
             item.SetProviderId(MetadataProviders.Tmdb, obj.id.ToString(_enUs));
 
             return item;

+ 1 - 1
MediaBrowser.Providers/Folders/CollectionFolderImageProvider.cs

@@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.Folders
             }
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService)
         {
             var collectionFolder = (CollectionFolder)item;
 

+ 1 - 1
MediaBrowser.Providers/Folders/ImagesByNameImageProvider.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.Folders
             }
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService)
         {
             var name = _fileSystem.GetValidFilename(item.Name);
 

+ 2 - 2
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Manager
             _fileSystem = fileSystem;
         }
 
-        public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers, DirectoryService directoryService)
+        public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService)
         {
             var hasChanges = item.ValidateImages(directoryService);
 
@@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Manager
 
         public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
         {
-            var result = new RefreshResult { UpdateType = ItemUpdateType.Unspecified };
+            var result = new RefreshResult { UpdateType = ItemUpdateType.None };
 
             var providers = GetImageProviders(item, imageProviders).ToList();
 

+ 39 - 32
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -4,11 +4,11 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.Manager
             var itemOfType = (TItemType)item;
             var config = ProviderManager.GetMetadataOptions(item);
 
-            var updateType = ItemUpdateType.Unspecified;
+            var updateType = ItemUpdateType.None;
             var refreshResult = GetLastResult(item.Id);
             refreshResult.LastErrorMessage = string.Empty;
             refreshResult.LastStatus = ProviderRefreshStatus.Success;
@@ -106,7 +106,7 @@ namespace MediaBrowser.Providers.Manager
 
                 if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue)
                 {
-                    updateType = updateType | BeforeMetadataRefresh(itemOfType);
+                    updateType = updateType | item.BeforeMetadataRefresh();
                 }
 
                 if (providers.Count > 0)
@@ -138,15 +138,10 @@ namespace MediaBrowser.Providers.Manager
 
             updateType = updateType | BeforeSave(itemOfType);
 
-            var providersHadChanges = updateType > ItemUpdateType.Unspecified;
+            var providersHadChanges = updateType > ItemUpdateType.None;
 
             if (refreshOptions.ForceSave || providersHadChanges)
             {
-                if (string.IsNullOrEmpty(item.Name))
-                {
-                    throw new InvalidOperationException(item.GetType().Name + " has no name: " + item.Path);
-                }
-
                 // Save to database
                 await SaveItem(itemOfType, updateType, cancellationToken);
             }
@@ -157,16 +152,6 @@ namespace MediaBrowser.Providers.Manager
             }
         }
 
-        /// <summary>
-        /// Befores the metadata refresh.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>ItemUpdateType.</returns>
-        protected virtual ItemUpdateType BeforeMetadataRefresh(TItemType item)
-        {
-            return ItemUpdateType.Unspecified;
-        }
-
         /// <summary>
         /// Befores the save.
         /// </summary>
@@ -174,7 +159,7 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>ItemUpdateType.</returns>
         protected virtual ItemUpdateType BeforeSave(TItemType item)
         {
-            return ItemUpdateType.Unspecified;
+            return ItemUpdateType.None;
         }
 
         /// <summary>
@@ -198,15 +183,37 @@ namespace MediaBrowser.Providers.Manager
                 var currentItem = item;
 
                 var providersWithChanges = providers.OfType<IHasChangeMonitor>()
-                    .Where(i => i.HasChanged(currentItem, currentItem.DateLastSaved))
+                    .Where(i => i.HasChanged(currentItem, options.DirectoryService, currentItem.DateLastSaved))
+                    .Cast<IMetadataProvider<TItemType>>()
                     .ToList();
 
-                // If local providers are the only ones with changes, then just run those
-                if (providersWithChanges.All(i => i is ILocalMetadataProvider))
+                if (providersWithChanges.Count == 0)
+                {
+                    providers = new List<IMetadataProvider<TItemType>>();
+                }
+                else
                 {
-                    providers = providersWithChanges.Count == 0 ?
-                        new List<IMetadataProvider<TItemType>>() :
-                        providers.Where(i => i is ILocalMetadataProvider).ToList();
+                    providers = providers.Where(i =>
+                    {
+                        // If any provider reports a change, always run local ones as well
+                        if (i is ILocalMetadataProvider)
+                        {
+                            return true;
+                        }
+
+                        var anyRemoteProvidersChanged = providersWithChanges.OfType<IRemoteMetadataProvider>()
+                            .Any();
+
+                        // If any remote providers changed, run them all so that priorities can be honored
+                        if (i is IRemoteMetadataProvider)
+                        {
+                            return anyRemoteProvidersChanged;
+                        }
+
+                        // Run custom providers if they report a change or any remote providers change
+                        return anyRemoteProvidersChanged || providersWithChanges.Contains(i);
+
+                    }).ToList();
                 }
             }
 
@@ -227,7 +234,7 @@ namespace MediaBrowser.Providers.Manager
                 var currentItem = item;
 
                 providers = providers.OfType<IHasChangeMonitor>()
-                    .Where(i => i.HasChanged(currentItem, dateLastImageRefresh.Value))
+                    .Where(i => i.HasChanged(currentItem, options.DirectoryService, dateLastImageRefresh.Value))
                     .Cast<IImageProvider>()
                     .ToList();
             }
@@ -249,7 +256,7 @@ namespace MediaBrowser.Providers.Manager
         {
             var refreshResult = new RefreshResult
             {
-                UpdateType = ItemUpdateType.Unspecified,
+                UpdateType = ItemUpdateType.None,
                 Providers = providers.Select(i => i.GetType().FullName.GetMD5()).ToList()
             };
 
@@ -316,26 +323,26 @@ namespace MediaBrowser.Providers.Manager
                 await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
             }
 
-            if (refreshResult.UpdateType > ItemUpdateType.Unspecified)
+            if (refreshResult.UpdateType > ItemUpdateType.None)
             {
                 MergeData(temp, item, item.LockedFields, true, true);
             }
 
             foreach (var provider in providers.OfType<ICustomMetadataProvider<TItemType>>())
             {
-                await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false);
+                await RunCustomProvider(provider, item, options.DirectoryService, refreshResult, cancellationToken).ConfigureAwait(false);
             }
 
             return refreshResult;
         }
 
-        private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, RefreshResult refreshResult, CancellationToken cancellationToken)
+        private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, IDirectoryService directoryService, RefreshResult refreshResult, CancellationToken cancellationToken)
         {
             Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
 
             try
             {
-                refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false);
+                refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, directoryService, cancellationToken).ConfigureAwait(false);
             }
             catch (OperationCanceledException)
             {

+ 42 - 20
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -279,8 +279,7 @@ namespace MediaBrowser.Providers.Manager
         {
             return GetRemoteImageProviders(item).Select(i => new ImageProviderInfo
             {
-                Name = i.Name,
-                Order = GetOrder(item, i)
+                Name = i.Name
             });
         }
 
@@ -298,21 +297,39 @@ namespace MediaBrowser.Providers.Manager
                     return false;
                 }
 
-            }).OrderBy(i => GetOrder(item, i));
+            }).OrderBy(GetOrder);
         }
 
         public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(IHasMetadata item)
             where T : IHasMetadata
         {
-            return GetMetadataProvidersInternal<T>(item, false);
+            var options = GetMetadataOptions(item);
+
+            return GetMetadataProvidersInternal<T>(item, options, false);
         }
 
-        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, bool includeDisabled)
+        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, MetadataOptions options, bool includeDisabled)
             where T : IHasMetadata
         {
             return _metadataProviders.OfType<IMetadataProvider<T>>()
                 .Where(i => CanRefresh(i, item, includeDisabled))
-                .OrderBy(i => GetOrder(item, i));
+                .OrderBy(i =>
+                {
+                    // See if there's a user-defined order
+                    if (i is ILocalMetadataProvider)
+                    {
+                        var index = Array.IndexOf(options.LocalMetadataReaders, i.Name);
+
+                        if (index != -1)
+                        {
+                            return index;
+                        }
+                    }
+
+                    // Not configured. Just return some high number to put it at the end.
+                    return 100;
+                })
+                .ThenBy(GetOrder);
         }
 
         private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item)
@@ -354,10 +371,9 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// Gets the order.
         /// </summary>
-        /// <param name="item">The item.</param>
         /// <param name="provider">The provider.</param>
         /// <returns>System.Int32.</returns>
-        private int GetOrder(IHasImages item, IImageProvider provider)
+        private int GetOrder(IImageProvider provider)
         {
             var hasOrder = provider as IHasOrder;
 
@@ -372,19 +388,18 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// Gets the order.
         /// </summary>
-        /// <param name="item">The item.</param>
         /// <param name="provider">The provider.</param>
         /// <returns>System.Int32.</returns>
-        private int GetOrder(IHasMetadata item, IMetadataProvider provider)
+        private int GetOrder(IMetadataProvider provider)
         {
             var hasOrder = provider as IHasOrder;
 
-            if (hasOrder == null)
+            if (hasOrder != null)
             {
-                return 0;
+                return hasOrder.Order;
             }
 
-            return hasOrder.Order;
+            return 0;
         }
 
         public IEnumerable<MetadataPluginSummary> GetAllMetadataPlugins()
@@ -432,6 +447,8 @@ namespace MediaBrowser.Providers.Manager
                 Parent = new Folder()
             };
 
+            var options = GetMetadataOptions(dummy);
+
             var summary = new MetadataPluginSummary
             {
                 ItemType = typeof(T).Name
@@ -439,7 +456,7 @@ namespace MediaBrowser.Providers.Manager
 
             var imageProviders = GetImageProviders(dummy).ToList();
 
-            AddMetadataPlugins(summary.Plugins, dummy);
+            AddMetadataPlugins(summary.Plugins, dummy, options);
             AddImagePlugins(summary.Plugins, dummy, imageProviders);
 
             summary.SupportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
@@ -450,10 +467,10 @@ namespace MediaBrowser.Providers.Manager
             return summary;
         }
 
-        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item)
+        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, MetadataOptions options)
             where T : IHasMetadata
         {
-            var providers = GetMetadataProvidersInternal<T>(item, true).ToList();
+            var providers = GetMetadataProvidersInternal<T>(item, options, true).ToList();
 
             // Locals
             list.AddRange(providers.Where(i => (i is ILocalMetadataProvider)).Select(i => new MetadataPlugin
@@ -496,16 +513,21 @@ namespace MediaBrowser.Providers.Manager
             }));
         }
 
-        private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
-
         public MetadataOptions GetMetadataOptions(IHasMetadata item)
         {
             var type = item.GetType().Name;
+
+            if (item is Trailer)
+            {
+                type = typeof(Movie).Name;
+            }
+
             return ConfigurationManager.Configuration.MetadataOptions
                 .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
                 new MetadataOptions();
         }
-        
+
+        private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
         /// <summary>
         /// Saves the metadata.
         /// </summary>
@@ -517,7 +539,7 @@ namespace MediaBrowser.Providers.Manager
             foreach (var saver in _savers.Where(i => IsSaverEnabledForItem(i, item, updateType, true)))
             {
                 _logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
-                
+
                 var fileSaver = saver as IMetadataFileSaver;
 
                 if (fileSaver != null)

+ 1 - 1
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -159,7 +159,7 @@ namespace MediaBrowser.Providers.MediaInfo
             return item.LocationType == LocationType.FileSystem && item is Audio;
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return item.DateModified > date;
         }

+ 40 - 22
MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs

@@ -13,10 +13,11 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Serialization;
 using System;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Serialization;
+using System.Linq;
 
 namespace MediaBrowser.Providers.MediaInfo
 {
@@ -46,47 +47,47 @@ namespace MediaBrowser.Providers.MediaInfo
             get { return "ffprobe"; }
         }
 
-        public Task<ItemUpdateType> FetchAsync(Episode item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Episode item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(MusicVideo item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(MusicVideo item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(Movie item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Movie item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(AdultVideo item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(AdultVideo item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(LiveTvVideoRecording item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(LiveTvVideoRecording item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Trailer item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(Video item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Video item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
-            return FetchVideoInfo(item, cancellationToken);
+            return FetchVideoInfo(item, directoryService, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(Audio item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Audio item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
             return FetchAudioInfo(item, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
             return FetchAudioInfo(item, cancellationToken);
         }
@@ -103,8 +104,8 @@ namespace MediaBrowser.Providers.MediaInfo
             _json = json;
         }
 
-        private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.Unspecified);
-        public Task<ItemUpdateType> FetchVideoInfo<T>(T item, CancellationToken cancellationToken)
+        private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
+        public Task<ItemUpdateType> FetchVideoInfo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
             where T : Video
         {
             if (item.LocationType != LocationType.FileSystem)
@@ -124,7 +125,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json);
 
-            return prober.ProbeVideo(item, cancellationToken);
+            return prober.ProbeVideo(item, directoryService, cancellationToken);
         }
 
         public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)
@@ -140,9 +141,26 @@ namespace MediaBrowser.Providers.MediaInfo
             return prober.Probe(item, cancellationToken);
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
-            return item.DateModified > date;
+            if (item.DateModified > date)
+            {
+                return true;
+            }
+
+            if (item.LocationType == LocationType.FileSystem)
+            {
+                var video = item as Video;
+
+                if (video != null)
+                {
+                    var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json);
+
+                    return !video.SubtitleFiles.SequenceEqual(prober.GetSubtitleFiles(video, directoryService).Select(i => i.FullName).OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
+                }
+            }
+
+            return false;
         }
 
         public int Order

+ 97 - 68
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
 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;
@@ -45,7 +46,7 @@ namespace MediaBrowser.Providers.MediaInfo
             _json = json;
         }
 
-        public async Task<ItemUpdateType> ProbeVideo<T>(T item, CancellationToken cancellationToken)
+        public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
             where T : Video
         {
             var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
@@ -72,7 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 cancellationToken.ThrowIfCancellationRequested();
 
-                await Fetch(item, cancellationToken, result, isoMount).ConfigureAwait(false);
+                await Fetch(item, cancellationToken, result, isoMount, directoryService).ConfigureAwait(false);
 
             }
             finally
@@ -125,7 +126,7 @@ namespace MediaBrowser.Providers.MediaInfo
             return result;
         }
 
-        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount)
+        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, IDirectoryService directoryService)
         {
             if (data.format != null)
             {
@@ -148,7 +149,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken);
             }
 
-            AddExternalSubtitles(video, mediaStreams);
+            AddExternalSubtitles(video, mediaStreams, directoryService);
 
             FetchWtvInfo(video, data);
 
@@ -342,76 +343,104 @@ namespace MediaBrowser.Providers.MediaInfo
             }
         }
 
+        public IEnumerable<FileInfo> GetSubtitleFiles(Video video, IDirectoryService directoryService)
+        {
+            var containingPath = video.ContainingFolderPath;
+
+            if (string.IsNullOrEmpty(containingPath))
+            {
+                throw new ArgumentException(string.Format("Cannot search for items that don't have a path: {0} {1}", video.Name, video.Id));
+            }
+
+            var files = directoryService.GetFiles(containingPath);
+
+            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
+
+            return files.Where(i =>
+            {
+                if (!i.Attributes.HasFlag(FileAttributes.Directory) &&
+                    SubtitleExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase))
+                {
+                    var fullName = i.FullName;
+
+                    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
+
+                    if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
+                    if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
+                }
+
+                return false;
+            });
+        }
+
         /// <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)
+        private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService)
         {
-            //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 files = GetSubtitleFiles(video, directoryService);
+
+            var startIndex = currentStreams.Count;
+            var streams = new List<MediaStream>();
+
+            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
+
+            foreach (var file in files)
+            {
+                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
+                    });
+                }
+            }
+
+            video.SubtitleFiles = streams.Select(i => i.Path).OrderBy(i => i).ToList();
+
+            currentStreams.AddRange(streams);
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -126,7 +126,7 @@ namespace MediaBrowser.Providers.MediaInfo
             return item.LocationType == LocationType.FileSystem && item is Video;
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return item.DateModified > date;
         }

+ 1 - 1
MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs

@@ -345,7 +345,7 @@ namespace MediaBrowser.Providers.Movies
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (!_config.Configuration.EnableFanArtUpdates)
             {

+ 1 - 1
MediaBrowser.Providers/Movies/MovieDbImageProvider.cs

@@ -208,7 +208,7 @@ namespace MediaBrowser.Providers.Movies
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return MovieDbProvider.Current.HasChanged(item, date);
         }

+ 3 - 3
MediaBrowser.Providers/Music/AlbumMetadataService.cs

@@ -99,7 +99,7 @@ namespace MediaBrowser.Providers.Music
 
         private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IEnumerable<Audio> songs)
         {
-            var updateType = ItemUpdateType.Unspecified;
+            var updateType = ItemUpdateType.None;
             
             var albumArtist = songs
                 .Select(i => i.AlbumArtist)
@@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.Music
 
         private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IEnumerable<Audio> songs)
         {
-            var updateType = ItemUpdateType.Unspecified;
+            var updateType = ItemUpdateType.None;
 
             var currentList = item.Artists.ToList();
 
@@ -137,7 +137,7 @@ namespace MediaBrowser.Providers.Music
 
         private ItemUpdateType SetDateFromSongs(MusicAlbum item, List<Audio> songs)
         {
-            var updateType = ItemUpdateType.Unspecified;
+            var updateType = ItemUpdateType.None;
 
             var date = songs.Select(i => i.PremiereDate)
                             .FirstOrDefault(i => i.HasValue);

+ 1 - 1
MediaBrowser.Providers/Music/FanArtAlbumProvider.cs

@@ -357,7 +357,7 @@ namespace MediaBrowser.Providers.Music
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (!_config.Configuration.EnableFanArtUpdates)
             {

+ 1 - 1
MediaBrowser.Providers/Music/FanArtArtistProvider.cs

@@ -374,7 +374,7 @@ namespace MediaBrowser.Providers.Music
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (!_config.Configuration.EnableFanArtUpdates)
             {

+ 4 - 4
MediaBrowser.Providers/Omdb/OmdbItemProvider.cs

@@ -27,18 +27,18 @@ namespace MediaBrowser.Providers.Omdb
             get { return "IMDb via The Open Movie Database"; }
         }
 
-        public Task<ItemUpdateType> FetchAsync(Series item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Series item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
             return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
         }
 
-        public Task<ItemUpdateType> FetchAsync(Movie item, CancellationToken cancellationToken)
+        public Task<ItemUpdateType> FetchAsync(Movie item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
             return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
         }
 
-        private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.Unspecified);
-        public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken)
+        private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
+        public Task<ItemUpdateType> FetchAsync(Trailer item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
             if (item.IsLocalTrailer)
             {

+ 1 - 1
MediaBrowser.Providers/Omdb/OmdbProvider.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.Omdb
 
             if (string.IsNullOrEmpty(imdbId))
             {
-                return ItemUpdateType.Unspecified;
+                return ItemUpdateType.None;
             }
 
             var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;

+ 10 - 1
MediaBrowser.Providers/People/MovieDbPersonProvider.cs

@@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.People
                 return;
             }
 
-            var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images", MovieDbProvider.ApiKey, id);
+            var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", MovieDbProvider.ApiKey, id);
 
             using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
             {
@@ -267,6 +267,14 @@ namespace MediaBrowser.Providers.People
             public List<Profile> profiles { get; set; }
         }
 
+        public class ExternalIds
+        {
+            public string imdb_id { get; set; }
+            public string freebase_mid { get; set; }
+            public string freebase_id { get; set; }
+            public int tvrage_id { get; set; }
+        }
+
         public class PersonResult
         {
             public bool adult { get; set; }
@@ -283,6 +291,7 @@ namespace MediaBrowser.Providers.People
             public string profile_path { get; set; }
             public Credits credits { get; set; }
             public Images images { get; set; }
+            public ExternalIds external_ids { get; set; }
         }
 
         #endregion

+ 2 - 11
MediaBrowser.Providers/Savers/AlbumXmlSaver.cs

@@ -1,25 +1,16 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
     class AlbumXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public AlbumXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 1 - 12
MediaBrowser.Providers/Savers/ArtistXmlSaver.cs

@@ -1,11 +1,7 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Providers.Music;
-using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
@@ -15,13 +11,6 @@ namespace MediaBrowser.Providers.Savers
 {
     class ArtistXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public ArtistXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 2 - 11
MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs

@@ -1,25 +1,16 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
     public class BoxSetXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public BoxSetXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 1 - 2
MediaBrowser.Providers/Savers/ChannelXmlSaver.cs

@@ -1,12 +1,11 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {

+ 9 - 13
MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs

@@ -1,24 +1,28 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Security;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
     public class EpisodeXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
         private readonly IItemRepository _itemRepository;
 
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+        public EpisodeXmlSaver(IItemRepository itemRepository)
+        {
+            _itemRepository = itemRepository;
+        }
+
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
@@ -53,14 +57,6 @@ namespace MediaBrowser.Providers.Savers
             }
         }
 
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        public EpisodeXmlSaver(IServerConfigurationManager config, IItemRepository itemRepository)
-        {
-            _config = config;
-            _itemRepository = itemRepository;
-        }
-
         /// <summary>
         /// Saves the specified item.
         /// </summary>

+ 2 - 11
MediaBrowser.Providers/Savers/FolderXmlSaver.cs

@@ -1,27 +1,18 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
     public class FolderXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public FolderXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 2 - 11
MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs

@@ -1,25 +1,16 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
 using System.Security;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
     public class GameSystemXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public GameSystemXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 1 - 9
MediaBrowser.Providers/Savers/GameXmlSaver.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
@@ -16,13 +15,6 @@ namespace MediaBrowser.Providers.Savers
     /// </summary>
     public class GameXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public GameXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 3 - 8
MediaBrowser.Providers/Savers/MovieXmlSaver.cs

@@ -1,18 +1,15 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Security;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
@@ -21,12 +18,10 @@ namespace MediaBrowser.Providers.Savers
     /// </summary>
     public class MovieXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
         private readonly IItemRepository _itemRepository;
 
-        public MovieXmlSaver(IServerConfigurationManager config, IItemRepository itemRepository)
+        public MovieXmlSaver(IItemRepository itemRepository)
         {
-            _config = config;
             _itemRepository = itemRepository;
         }
 

+ 2 - 11
MediaBrowser.Providers/Savers/SeasonXmlSaver.cs

@@ -1,25 +1,16 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
 using System.Threading;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Providers.Savers
 {
     public class SeasonXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public SeasonXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 1 - 9
MediaBrowser.Providers/Savers/SeriesXmlSaver.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
@@ -13,13 +12,6 @@ namespace MediaBrowser.Providers.Savers
 {
     public class SeriesXmlSaver : IMetadataFileSaver
     {
-        private readonly IServerConfigurationManager _config;
-
-        public SeriesXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-
         public string Name
         {
             get

+ 1 - 1
MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs

@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.TV
             return item is Episode && item.LocationType == LocationType.FileSystem;
         }
 
-        public List<LocalImageInfo> GetImages(IHasImages item, DirectoryService directoryService)
+        public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService)
         {
             var parentPath = Path.GetDirectoryName(item.Path);
 

+ 1 - 53
MediaBrowser.Providers/TV/EpisodeMetadataService.cs

@@ -1,26 +1,19 @@
 using MediaBrowser.Common.IO;
 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 MediaBrowser.Providers.Manager;
 using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.TV
 {
     public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo>
     {
-        private readonly ILibraryManager _libraryManager;
-
-        public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+        public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem)
             : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
-            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -70,50 +63,5 @@ namespace MediaBrowser.Providers.TV
                 target.IndexNumberEnd = source.IndexNumberEnd;
             }
         }
-
-        protected override ItemUpdateType BeforeMetadataRefresh(Episode item)
-        {
-            var updateType = base.BeforeMetadataRefresh(item);
-
-            var locationType = item.LocationType;
-            if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
-            {
-                var currentIndexNumber = item.IndexNumber;
-                var currentIndexNumberEnd = item.IndexNumberEnd;
-                var currentParentIndexNumber = item.ParentIndexNumber;
-
-                var filename = Path.GetFileName(item.Path);
-
-                item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season);
-                item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(item.Path);
-
-                if (!item.ParentIndexNumber.HasValue)
-                {
-                    var season = item.Season;
-
-                    if (season != null)
-                    {
-                        item.ParentIndexNumber = season.IndexNumber;
-                    }
-                }
-
-                if ((currentIndexNumber ?? -1) != (item.IndexNumber ?? -1))
-                {
-                    updateType = updateType | ItemUpdateType.MetadataImport;
-                }
-
-                if ((currentIndexNumberEnd ?? -1) != (item.IndexNumberEnd ?? -1))
-                {
-                    updateType = updateType | ItemUpdateType.MetadataImport;
-                }
-
-                if ((currentParentIndexNumber ?? -1) != (item.ParentIndexNumber ?? -1))
-                {
-                    updateType = updateType | ItemUpdateType.MetadataImport;
-                }
-            }
-
-            return updateType;
-        }
     }
 }

+ 1 - 1
MediaBrowser.Providers/TV/FanArtSeasonProvider.cs

@@ -275,7 +275,7 @@ namespace MediaBrowser.Providers.TV
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (!_config.Configuration.EnableFanArtUpdates)
             {

+ 1 - 1
MediaBrowser.Providers/TV/FanartSeriesProvider.cs

@@ -423,7 +423,7 @@ namespace MediaBrowser.Providers.TV
             }
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (!_config.Configuration.EnableFanArtUpdates)
             {

+ 1 - 1
MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs

@@ -203,7 +203,7 @@ namespace MediaBrowser.Providers.TV
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return MovieDbSeriesProvider.Current.HasChanged(item, date);
         }

+ 1 - 22
MediaBrowser.Providers/TV/SeasonMetadataService.cs

@@ -1,25 +1,19 @@
 using MediaBrowser.Common.IO;
 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 MediaBrowser.Providers.Manager;
 using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.TV
 {
     public class SeasonMetadataService : MetadataService<Season, SeasonInfo>
     {
-        private readonly ILibraryManager _libraryManager;
-
-        public SeasonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+        public SeasonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem)
             : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
-            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -34,20 +28,5 @@ namespace MediaBrowser.Providers.TV
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
         }
-
-        protected override ItemUpdateType BeforeMetadataRefresh(Season item)
-        {
-            var updateType = base.BeforeMetadataRefresh(item);
-
-            var currentIndexNumber = item.IndexNumber;
-
-            item.IndexNumber = item.IndexNumber ?? TVUtils.GetSeasonNumberFromPath(item.Path);
-
-            if ((currentIndexNumber ?? -1) != (item.IndexNumber ?? -1))
-            {
-                updateType = updateType | ItemUpdateType.MetadataImport;
-            }
-            return updateType;
-        }
     }
 }

+ 0 - 2
MediaBrowser.Providers/TV/SeriesMetadataService.cs

@@ -8,8 +8,6 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using System.Collections.Generic;
 using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.TV
 {

+ 1 - 1
MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs

@@ -192,7 +192,7 @@ namespace MediaBrowser.Providers.TV
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (item.LocationType != LocationType.Virtual)
             {

+ 1 - 1
MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs

@@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.TV
             return Task.FromResult(result);
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             // Only enable for virtual items
             if (item.LocationType != LocationType.Virtual)

+ 19 - 3
MediaBrowser.Providers/TV/TvdbPrescanTask.cs

@@ -98,8 +98,19 @@ namespace MediaBrowser.Providers.TV
 
             string newUpdateTime;
 
-            var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
-
+            var existingDirectories = Directory.EnumerateDirectories(path)
+                .Select(Path.GetFileName)
+                .ToList();
+
+            var seriesIdsInLibrary = _libraryManager.RootFolder.RecursiveChildren
+               .OfType<Series>()
+               .Select(i => i.GetProviderId(MetadataProviders.Tvdb))
+               .Where(i => !string.IsNullOrEmpty(i))
+               .ToList();
+
+            var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase)
+                .ToList();
+            
             // If this is our first time, update all series
             if (string.IsNullOrEmpty(lastUpdateTime))
             {
@@ -116,6 +127,8 @@ namespace MediaBrowser.Providers.TV
                     newUpdateTime = GetUpdateTime(stream);
                 }
 
+                existingDirectories.AddRange(missingSeries);
+
                 await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false);
             }
             else
@@ -130,7 +143,10 @@ namespace MediaBrowser.Providers.TV
 
                 var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue;
 
-                await UpdateSeries(seriesToUpdate.Item1, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false);
+                var listToUpdate = seriesToUpdate.Item1.ToList();
+                listToUpdate.AddRange(missingSeries);
+
+                await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false);
             }
 
             File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);

+ 1 - 1
MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs

@@ -340,7 +340,7 @@ namespace MediaBrowser.Providers.TV
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (item.LocationType != LocationType.Virtual)
             {

+ 1 - 1
MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs

@@ -337,7 +337,7 @@ namespace MediaBrowser.Providers.TV
             });
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             if (!_config.Configuration.EnableTvDbUpdates)
             {

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs

@@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             get { return 0; }
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1;
         }

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs

@@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             get { return 0; }
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 6;
         }

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs

@@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             get { return 0; }
         }
 
-        public bool HasChanged(IHasMetadata item, DateTime date)
+        public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         {
             return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 3;
         }

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.324</version>
+        <version>3.0.325</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.324" />
+            <dependency id="MediaBrowser.Common" version="3.0.325" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="SimpleInjector" version="2.4.1" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.324</version>
+        <version>3.0.325</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.324</version>
+        <version>3.0.325</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.324" />
+            <dependency id="MediaBrowser.Common" version="3.0.325" />
         </dependencies>
     </metadata>
     <files>