فهرست منبع

Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser

Conflicts:
	MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
Luke Pulverenti 11 سال پیش
والد
کامیت
ffc4db4128

+ 4 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -32,6 +32,7 @@ namespace MediaBrowser.Controller.Entities
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             LockedFields = new List<MetadataFields>();
             ImageInfos = new List<ItemImageInfo>();
+            Identities = new List<IItemIdentity>();
         }
 
         /// <summary>
@@ -244,6 +245,9 @@ namespace MediaBrowser.Controller.Entities
 
         public bool IsUnidentified { get; set; }
 
+        [IgnoreDataMember]
+        public List<IItemIdentity> Identities { get; set; }
+
         /// <summary>
         /// Gets or sets the locked fields.
         /// </summary>

+ 6 - 0
MediaBrowser.Controller/Entities/IHasMetadata.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
@@ -55,5 +56,10 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value><c>true</c> if this instance is unidentified; otherwise, <c>false</c>.</value>
         bool IsUnidentified { get; set; }
+
+        /// <summary>
+        /// Gets the item identities.
+        /// </summary>
+        List<IItemIdentity> Identities { get; set; }
     }
 }

+ 2 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -225,6 +225,8 @@
     <Compile Include="Security\IEncryptionManager.cs" />
     <Compile Include="Subtitles\ISubtitleManager.cs" />
     <Compile Include="Subtitles\ISubtitleProvider.cs" />
+    <Compile Include="Providers\ItemIdentifier.cs" />
+    <Compile Include="Providers\ItemIdentities.cs" />
     <Compile Include="Providers\ItemLookupInfo.cs" />
     <Compile Include="Providers\MetadataRefreshOptions.cs" />
     <Compile Include="Providers\NameParser.cs" />

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

@@ -54,11 +54,13 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="metadataServices">The metadata services.</param>
+        /// <param name="identityProviders">The identity providers.</param>
+        /// <param name="identityConverters">The identity converters.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="savers">The savers.</param>
         /// <param name="imageSavers">The image savers.</param>
         /// <param name="externalIds">The external ids.</param>
-        void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
+        void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IItemIdentityProvider> identityProviders, IEnumerable<IItemIdentityConverter> identityConverters, IEnumerable<IMetadataProvider> metadataProviders,
             IEnumerable<IMetadataSaver> savers,
             IEnumerable<IImageSaver> imageSavers,
             IEnumerable<IExternalId> externalIds);
@@ -136,5 +138,12 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{HttpResponseInfo}.</returns>
         Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
+
+        IEnumerable<IItemIdentityProvider<TLookupInfo, TIdentity>> GetItemIdentityProviders<TLookupInfo, TIdentity>()
+            where TLookupInfo : ItemLookupInfo
+            where TIdentity : IItemIdentity;
+
+        IEnumerable<IItemIdentityConverter<TIdentity>> GetItemIdentityConverters<TIdentity>()
+            where TIdentity : IItemIdentity;
     }
 }

+ 73 - 0
MediaBrowser.Controller/Providers/ItemIdentifier.cs

@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public class ItemIdentifier<TLookupInfo, TIdentity>
+        where TLookupInfo : ItemLookupInfo
+        where TIdentity : IItemIdentity
+    {
+        public async Task<IEnumerable<TIdentity>> FindIdentities(TLookupInfo item, IProviderManager providerManager, CancellationToken cancellationToken)
+        {
+            var providers = providerManager.GetItemIdentityProviders<TLookupInfo, TIdentity>();
+            var converters = providerManager.GetItemIdentityConverters<TIdentity>();
+
+            var identities = new List<IdentityPair>();
+
+            foreach (var provider in providers)
+            {
+                var result = new IdentityPair
+                {
+                    Identity = await provider.FindIdentity(item),
+                    Order = provider.Order
+                };
+
+                if (!Equals(result.Identity, default(TIdentity)))
+                {
+                    identities.Add(result);
+                }
+            }
+
+            var convertersAvailable = new List<IItemIdentityConverter<TIdentity>>(converters);
+            bool changesMade;
+
+            do
+            {
+                changesMade = false;
+
+                for (int i = convertersAvailable.Count - 1; i >= 0; i--)
+                {
+                    var converter = convertersAvailable[i];
+                    var input = identities.FirstOrDefault(id => id.Identity.Type == converter.SourceType);
+                    var existing = identities.Where(id => id.Identity.Type == converter.ResultType);
+
+                    if (input != null && !existing.Any(id => id.Order <= converter.Order))
+                    {
+                        var result = new IdentityPair
+                        {
+                            Identity = await converter.Convert(input.Identity).ConfigureAwait(false),
+                            Order = converter.Order
+                        };
+
+                        if (!Equals(result.Identity, default(TIdentity)))
+                        {
+                            identities.Add(result);
+                            convertersAvailable.RemoveAt(i);
+                            changesMade = true;
+                        }
+                    }
+                }
+            } while (changesMade);
+
+            return identities.OrderBy(id => id.Order).Select(id => id.Identity);
+        }
+
+        private class IdentityPair
+        {
+            public TIdentity Identity;
+            public int Order;
+        }
+    }
+}

+ 43 - 0
MediaBrowser.Controller/Providers/ItemIdentities.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface IItemIdentity
+    {
+        string Type { get; }
+    }
+
+    public interface IHasIdentities<out TIdentity>
+        where TIdentity : IItemIdentity
+    {
+        IEnumerable<TIdentity> Identities { get; }
+
+        Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken);
+    }
+
+    public interface IItemIdentityProvider : IHasOrder { }
+
+    public interface IItemIdentityProvider<in TLookupInfo, TIdentity> : IItemIdentityProvider
+        where TLookupInfo : ItemLookupInfo
+        where TIdentity : IItemIdentity
+    {
+        Task<TIdentity> FindIdentity(TLookupInfo info);
+    }
+
+    public interface IItemIdentityConverter : IHasOrder { }
+
+    public interface IItemIdentityConverter<TIdentity> : IItemIdentityConverter
+        where TIdentity : IItemIdentity
+    {
+        Task<TIdentity> Convert(TIdentity identity);
+
+        string SourceType { get; }
+
+        string ResultType { get; }
+    }
+}

+ 72 - 4
MediaBrowser.Controller/Providers/ItemLookupInfo.cs

@@ -1,4 +1,7 @@
-using MediaBrowser.Model.Entities;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 
@@ -96,8 +99,10 @@ namespace MediaBrowser.Controller.Providers
         public string Path { get; set; }
     }
 
-    public class EpisodeInfo : ItemLookupInfo
+    public class EpisodeInfo : ItemLookupInfo, IHasIdentities<EpisodeIdentity>
     {
+        private List<EpisodeIdentity> _identities = new List<EpisodeIdentity>();
+
         public Dictionary<string, string> SeriesProviderIds { get; set; }
 
         public int? IndexNumberEnd { get; set; }
@@ -107,6 +112,27 @@ namespace MediaBrowser.Controller.Providers
         {
             SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
+
+        public IEnumerable<EpisodeIdentity> Identities
+        {
+            get { return _identities; }
+        }
+
+        public async Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken)
+        {
+            var identifier = new ItemIdentifier<EpisodeInfo, EpisodeIdentity>();
+            _identities = (await identifier.FindIdentities(this, providerManager, cancellationToken)).ToList();
+        }
+    }
+
+    public class EpisodeIdentity : IItemIdentity
+    {
+        public string Type { get; set; }
+
+        public string SeriesId { get; set; }
+        public int? SeasonIndex { get; set; }
+        public int IndexNumber { get; set; }
+        public int? IndexNumberEnd { get; set; }
     }
 
     public class SongInfo : ItemLookupInfo
@@ -116,9 +142,29 @@ namespace MediaBrowser.Controller.Providers
         public List<string> Artists { get; set; }
     }
 
-    public class SeriesInfo : ItemLookupInfo
+    public class SeriesInfo : ItemLookupInfo, IHasIdentities<SeriesIdentity>
     {
+        private List<SeriesIdentity> _identities = new List<SeriesIdentity>();
+
         public int? AnimeSeriesIndex { get; set; }
+
+        public IEnumerable<SeriesIdentity> Identities
+        {
+            get { return _identities; }
+        }
+
+        public async Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken)
+        {
+            var identifier = new ItemIdentifier<SeriesInfo, SeriesIdentity>();
+            _identities = (await identifier.FindIdentities(this, providerManager, cancellationToken)).ToList();
+        }
+    }
+
+    public class SeriesIdentity : IItemIdentity
+    {
+        public string Type { get; set; }
+
+        public string Id { get; set; }
     }
 
     public class PersonLookupInfo : ItemLookupInfo
@@ -151,8 +197,10 @@ namespace MediaBrowser.Controller.Providers
         public string SeriesName { get; set; }
     }
 
-    public class SeasonInfo : ItemLookupInfo
+    public class SeasonInfo : ItemLookupInfo, IHasIdentities<SeasonIdentity>
     {
+        private List<SeasonIdentity> _identities = new List<SeasonIdentity>();
+
         public Dictionary<string, string> SeriesProviderIds { get; set; }
         public int? AnimeSeriesIndex { get; set; }
 
@@ -160,5 +208,25 @@ namespace MediaBrowser.Controller.Providers
         {
             SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
+
+        public IEnumerable<SeasonIdentity> Identities
+        {
+            get { return _identities; }
+        }
+
+        public async Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken)
+        {
+            var identifier = new ItemIdentifier<SeasonInfo, SeasonIdentity>();
+            _identities = (await identifier.FindIdentities(this, providerManager, cancellationToken)).ToList();
+        }
+    }
+
+    public class SeasonIdentity : IItemIdentity
+    {
+        public string Type { get; set; }
+
+        public string SeriesId { get; set; }
+
+        public int SeasonIndex { get; set; }
     }
 }

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

@@ -104,6 +104,9 @@ namespace MediaBrowser.Providers.Manager
                 refreshResult.AddStatus(ProviderRefreshStatus.Failure, ex.Message);
             }
 
+            // Identify item
+            TIdType id = null;
+
             // Next run metadata providers
             if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
             {
@@ -120,7 +123,8 @@ namespace MediaBrowser.Providers.Manager
 
                 if (providers.Count > 0)
                 {
-                    var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
+                    id = await CreateInitialLookupInfo(itemOfType, cancellationToken).ConfigureAwait(false);
+                    var result = await RefreshWithProviders(itemOfType, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
 
                     updateType = updateType | result.UpdateType;
                     refreshResult.AddStatus(result.Status, result.ErrorMessage);
@@ -129,6 +133,13 @@ namespace MediaBrowser.Providers.Manager
                 }
             }
 
+            if (id == null)
+            {
+                id = await CreateInitialLookupInfo(itemOfType, cancellationToken).ConfigureAwait(false);
+            }
+
+            MergeIdentities(itemOfType, id);
+
             // Next run remote image providers, but only if local image providers didn't throw an exception
             if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
             {
@@ -162,6 +173,15 @@ namespace MediaBrowser.Providers.Manager
             }
         }
 
+        private void MergeIdentities(TItemType item, TIdType id)
+        {
+            var hasIdentity = id as IHasIdentities<IItemIdentity>;
+            if (hasIdentity != null)
+            {
+                item.Identities = hasIdentity.Identities.ToList();
+            }
+        }
+
         /// <summary>
         /// Befores the save.
         /// </summary>
@@ -259,7 +279,7 @@ namespace MediaBrowser.Providers.Manager
             return item is TItemType;
         }
 
-        protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List<IMetadataProvider> providers, ItemImageProvider imageService, CancellationToken cancellationToken)
+        protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, TIdType id, MetadataRefreshOptions options, List<IMetadataProvider> providers, ItemImageProvider imageService, CancellationToken cancellationToken)
         {
             var refreshResult = new RefreshResult
             {
@@ -280,7 +300,7 @@ namespace MediaBrowser.Providers.Manager
             // If replacing all metadata, run internet providers first
             if (options.ReplaceAllMetadata)
             {
-                await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
+                await ExecuteRemoteProviders(item, temp, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
             }
 
             var hasLocalMetadata = false;
@@ -333,7 +353,7 @@ namespace MediaBrowser.Providers.Manager
             // Local metadata is king - if any is found don't run remote providers
             if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh))
             {
-                await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
+                await ExecuteRemoteProviders(item, temp, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
             }
 
             if (refreshResult.UpdateType > ItemUpdateType.None)
@@ -374,10 +394,8 @@ namespace MediaBrowser.Providers.Manager
             return new TItemType();
         }
 
-        private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken)
+        private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, TIdType id, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken)
         {
-            TIdType id = null;
-
             var unidentifiedCount = 0;
             var identifiedCount = 0;
 
@@ -386,11 +404,7 @@ namespace MediaBrowser.Providers.Manager
                 var providerName = provider.GetType().Name;
                 Logger.Debug("Running {0} for {1}", providerName, item.Path ?? item.Name);
 
-                if (id == null)
-                {
-                    id = item.GetLookupInfo();
-                }
-                else
+                if (id != null)
                 {
                     MergeNewData(temp, id);
                 }
@@ -435,6 +449,19 @@ namespace MediaBrowser.Providers.Manager
             }
         }
 
+        private async Task<TIdType> CreateInitialLookupInfo(TItemType item, CancellationToken cancellationToken)
+        {
+            var info = item.GetLookupInfo();
+            
+            var hasIdentity = info as IHasIdentities<IItemIdentity>;
+            if (hasIdentity != null)
+            {
+                await hasIdentity.FindIdentities(ProviderManager, cancellationToken).ConfigureAwait(false);
+            }
+
+            return info;
+        }
+
         private void MergeNewData(TItemType source, TIdType lookupInfo)
         {
             // Copy new provider id's that may have been obtained

+ 23 - 3
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -53,6 +53,8 @@ namespace MediaBrowser.Providers.Manager
         private readonly IFileSystem _fileSystem;
 
         private IMetadataService[] _metadataServices = { };
+        private IItemIdentityProvider[] _identityProviders = { };
+        private IItemIdentityConverter[] _identityConverters = { };
         private IMetadataProvider[] _metadataProviders = { };
         private IEnumerable<IMetadataSaver> _savers;
         private IImageSaver[] _imageSavers;
@@ -81,17 +83,22 @@ namespace MediaBrowser.Providers.Manager
         /// </summary>
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="metadataServices">The metadata services.</param>
+        /// <param name="identityProviders">The identity providers.</param>
+        /// <param name="identityConverters">The identity converters.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="metadataSavers">The metadata savers.</param>
         /// <param name="imageSavers">The image savers.</param>
         /// <param name="externalIds">The external ids.</param>
-        public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
-            IEnumerable<IImageSaver> imageSavers,
-            IEnumerable<IExternalId> externalIds)
+        public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
+                             IEnumerable<IItemIdentityProvider> identityProviders, IEnumerable<IItemIdentityConverter> identityConverters,
+                             IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
+                             IEnumerable<IImageSaver> imageSavers, IEnumerable<IExternalId> externalIds)
         {
             ImageProviders = imageProviders.ToArray();
 
             _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
+            _identityProviders = identityProviders.ToArray();
+            _identityConverters = identityConverters.ToArray();
             _metadataProviders = metadataProviders.ToArray();
             _savers = metadataSavers.ToArray();
             _imageSavers = imageSavers.ToArray();
@@ -257,6 +264,19 @@ namespace MediaBrowser.Providers.Manager
                 .ThenBy(GetDefaultOrder);
         }
 
+        public IEnumerable<IItemIdentityProvider<TLookupInfo, TIdentity>> GetItemIdentityProviders<TLookupInfo, TIdentity>()
+            where TLookupInfo : ItemLookupInfo
+            where TIdentity : IItemIdentity
+        {
+            return _identityProviders.OfType<IItemIdentityProvider<TLookupInfo, TIdentity>>();
+        }
+
+        public IEnumerable<IItemIdentityConverter<TIdentity>> GetItemIdentityConverters<TIdentity>()
+            where TIdentity : IItemIdentity
+        {
+            return _identityConverters.OfType<IItemIdentityConverter<TIdentity>>();
+        }
+
         private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item, bool includeDisabled)
         {
             var options = GetMetadataOptions(item);

+ 1 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -205,6 +205,7 @@
     <Compile Include="TV\SeriesMetadataService.cs" />
     <Compile Include="TV\TvdbEpisodeImageProvider.cs" />
     <Compile Include="People\TvdbPersonImageProvider.cs" />
+    <Compile Include="TV\TvdbSeasonIdentityProvider.cs" />
     <Compile Include="TV\TvdbSeasonImageProvider.cs" />
     <Compile Include="TV\TvdbSeriesImageProvider.cs" />
     <Compile Include="TV\SeasonMetadataService.cs" />

+ 41 - 23
MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.TV
     /// <summary>
     /// Class RemoteEpisodeProvider
     /// </summary>
-    class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasChangeMonitor
+    class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IItemIdentityProvider<EpisodeInfo, EpisodeIdentity>, IHasChangeMonitor
     {
         internal static TvdbEpisodeProvider Current;
         private readonly IFileSystem _fileSystem;
@@ -40,19 +40,18 @@ namespace MediaBrowser.Providers.TV
         {
             var list = new List<RemoteSearchResult>();
 
-            string seriesTvdbId;
-            searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
+            var identity = searchInfo.Identities.FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString()) ?? await FindIdentity(searchInfo).ConfigureAwait(false);
 
-            if (!string.IsNullOrEmpty(seriesTvdbId))
+            if (identity != null)
             {
-                await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesTvdbId, searchInfo.MetadataLanguage,
+                await TvdbSeriesProvider.Current.EnsureSeriesInfo(identity.SeriesId, searchInfo.MetadataLanguage,
                         cancellationToken).ConfigureAwait(false);
 
-                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesTvdbId);
+                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.SeriesId);
 
                 try
                 {
-                    var item = FetchEpisodeData(searchInfo, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
+                    var item = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
 
                     if (item != null)
                     {
@@ -83,20 +82,19 @@ namespace MediaBrowser.Providers.TV
             get { return "TheTVDB"; }
         }
 
-        public Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
+        public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
         {
-            string seriesTvdbId;
-            searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
+            var identity = searchInfo.Identities.FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString()) ?? await FindIdentity(searchInfo).ConfigureAwait(false);
 
             var result = new MetadataResult<Episode>();
 
-            if (!string.IsNullOrEmpty(seriesTvdbId))
+            if (identity != null)
             {
-                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesTvdbId);
+                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.SeriesId);
 
                 try
                 {
-                    result.Item = FetchEpisodeData(searchInfo, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
+                    result.Item = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
                     result.HasMetadata = result.Item != null;
                 }
                 catch (FileNotFoundException)
@@ -105,7 +103,7 @@ namespace MediaBrowser.Providers.TV
                 }
             }
 
-            return Task.FromResult(result);
+            return result;
         }
 
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
@@ -211,18 +209,14 @@ namespace MediaBrowser.Providers.TV
         /// </summary>
         /// <param name="id">The identifier.</param>
         /// <param name="seriesDataPath">The series data path.</param>
+        /// <param name="seriesProviderIds"></param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.Boolean}.</returns>
-        private Episode FetchEpisodeData(EpisodeInfo id, string seriesDataPath, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
+        private Episode FetchEpisodeData(EpisodeInfo id, EpisodeIdentity identity, string seriesDataPath, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
         {
-            if (id.IndexNumber == null)
-            {
-                return null;
-            }
-
-            var episodeNumber = id.IndexNumber.Value;
+            var episodeNumber = identity.IndexNumber;
             var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0;
-            var seasonNumber = id.ParentIndexNumber + seasonOffset;
+            var seasonNumber = identity.SeasonIndex + seasonOffset;
 
             if (seasonNumber == null)
             {
@@ -263,7 +257,7 @@ namespace MediaBrowser.Providers.TV
                 usingAbsoluteData = true;
             }
 
-            var end = id.IndexNumberEnd ?? episodeNumber;
+            var end = identity.IndexNumberEnd ?? episodeNumber;
             episodeNumber++;
 
             while (episodeNumber <= end)
@@ -726,5 +720,29 @@ namespace MediaBrowser.Providers.TV
         {
             throw new NotImplementedException();
         }
+
+        public Task<EpisodeIdentity> FindIdentity(EpisodeInfo info)
+        {
+            string seriesTvdbId;
+            info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
+
+            if (string.IsNullOrEmpty(seriesTvdbId) || info.IndexNumber == null)
+            {
+                return Task.FromResult<EpisodeIdentity>(null);
+            }
+
+            var id = new EpisodeIdentity
+            {
+                Type = MetadataProviders.Tvdb.ToString(),
+                SeriesId = seriesTvdbId,
+                SeasonIndex = info.ParentIndexNumber,
+                IndexNumber = info.IndexNumber.Value,
+                IndexNumberEnd = info.IndexNumberEnd
+            };
+
+            return Task.FromResult(id);
+        }
+
+        public int Order { get { return 0; } }
     }
 }

+ 29 - 0
MediaBrowser.Providers/TV/TvdbSeasonIdentityProvider.cs

@@ -0,0 +1,29 @@
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Providers.TV
+{
+    public class TvdbSeasonIdentityProvider : IItemIdentityProvider<SeasonInfo, SeasonIdentity>
+    {
+        public Task<SeasonIdentity> FindIdentity(SeasonInfo info)
+        {
+            string tvdbSeriesId;
+            if (!info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out tvdbSeriesId) || string.IsNullOrEmpty(tvdbSeriesId) || info.IndexNumber == null)
+            {
+                return Task.FromResult<SeasonIdentity>(null);
+            }
+
+            var result = new SeasonIdentity
+            {
+                Type = MetadataProviders.Tvdb.ToString(),
+                SeriesId = tvdbSeriesId,
+                SeasonIndex = info.IndexNumber.Value
+            };
+
+            return Task.FromResult(result);
+        }
+
+        public int Order { get { return 0; } }
+    }
+}

+ 3 - 2
MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs

@@ -65,7 +65,8 @@ namespace MediaBrowser.Providers.TV
             var season = (Season)item;
             var series = season.Series;
 
-            var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tvdb) : null;
+            var identity = season.Identities.OfType<SeasonIdentity>().FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString());
+            var seriesId = identity != null ? identity.SeriesId : null;
 
             if (!string.IsNullOrEmpty(seriesId) && season.IndexNumber.HasValue)
             {
@@ -78,7 +79,7 @@ namespace MediaBrowser.Providers.TV
 
                 try
                 {
-                    int seasonNumber = AdjustForSeriesOffset(series, season.IndexNumber.Value);
+                    int seasonNumber = AdjustForSeriesOffset(series, identity.SeasonIndex);
                     return GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, cancellationToken);
                 }
                 catch (FileNotFoundException)

+ 24 - 2
MediaBrowser.Providers/TV/TvdbSeriesProvider.cs

@@ -23,7 +23,7 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
+    public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IItemIdentityProvider<SeriesInfo, SeriesIdentity>, IHasOrder
     {
         private const string TvdbSeriesOffset = "TvdbSeriesOffset";
         private const string TvdbSeriesOffsetFormat = "{0}-{1}";
@@ -72,7 +72,15 @@ namespace MediaBrowser.Providers.TV
 
             if (string.IsNullOrEmpty(seriesId))
             {
-                seriesId = await FindSeries(itemId.Name, cancellationToken).ConfigureAwait(false);
+                seriesId = itemId.Identities
+                                 .Where(id => id.Type == MetadataProviders.Tvdb.ToString())
+                                 .Select(id => id.Id)
+                                 .FirstOrDefault();
+
+                if (string.IsNullOrEmpty(seriesId))
+                {
+                    seriesId = await FindSeries(itemId.Name, cancellationToken).ConfigureAwait(false);
+                }
 
                 if (string.IsNullOrEmpty(seriesId))
                 {
@@ -1121,6 +1129,20 @@ namespace MediaBrowser.Providers.TV
             get { return "TheTVDB"; }
         }
 
+        public async Task<SeriesIdentity> FindIdentity(SeriesInfo info)
+        {
+            string tvdbId;
+            if (!info.ProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out tvdbId))
+                tvdbId = await FindSeries(info.Name, CancellationToken.None);
+
+            if (!string.IsNullOrEmpty(tvdbId))
+            {
+                return new SeriesIdentity {Type = MetadataProviders.Tvdb.ToString(), Id = tvdbId};
+            }
+
+            return null;
+        }
+
         public int Order
         {
             get

+ 8 - 4
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -722,10 +722,14 @@ namespace MediaBrowser.ServerApplication
                                     GetExports<IBaseItemComparer>(),
                                     GetExports<ILibraryPostScanTask>());
 
-            ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
-                                    GetExports<IMetadataSaver>(),
-                                    GetExports<IImageSaver>(),
-                                    GetExports<IExternalId>());
+            ProviderManager.AddParts(GetExports<IImageProvider>(),
+                                     GetExports<IMetadataService>(),
+                                     GetExports<IItemIdentityProvider>(),
+                                     GetExports<IItemIdentityConverter>(),
+                                     GetExports<IMetadataProvider>(),
+                                     GetExports<IMetadataSaver>(),
+                                     GetExports<IImageSaver>(),
+                                     GetExports<IExternalId>());
 
             SeriesOrderManager.AddParts(GetExports<ISeriesOrderProvider>());
 

+ 3 - 1
MediaBrowser.sln

@@ -1,6 +1,8 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
+# Visual Studio 2013
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"