Browse Source

abstract preferred metadata language per item

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
44c0eba39d
30 tập tin đã thay đổi với 186 bổ sung80 xóa
  1. 2 1
      MediaBrowser.Controller/Entities/AdultVideo.cs
  2. 23 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  3. 4 1
      MediaBrowser.Controller/Entities/Book.cs
  4. 3 1
      MediaBrowser.Controller/Entities/Game.cs
  5. 0 1
      MediaBrowser.Controller/Entities/Genre.cs
  6. 6 0
      MediaBrowser.Controller/Entities/IHasImages.cs
  7. 15 0
      MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs
  8. 3 1
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  9. 3 1
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  10. 3 1
      MediaBrowser.Controller/Entities/TV/Series.cs
  11. 3 1
      MediaBrowser.Controller/Entities/Trailer.cs
  12. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  13. 1 1
      MediaBrowser.Model/Dto/BaseItemDto.cs
  14. 1 1
      MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
  15. 1 3
      MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
  16. 3 4
      MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
  17. 15 16
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  18. 31 10
      MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
  19. 8 0
      MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
  20. 1 1
      MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
  21. 1 1
      MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
  22. 1 1
      MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
  23. 1 1
      MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
  24. 4 6
      MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
  25. 4 6
      MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
  26. 34 13
      MediaBrowser.Providers/TV/TvdbPrescanTask.cs
  27. 8 6
      MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
  28. 1 1
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  29. 2 1
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  30. 3 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 2 - 1
MediaBrowser.Controller/Entities/AdultVideo.cs

@@ -1,7 +1,8 @@
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class AdultVideo : Video
+    public class AdultVideo : Video, IHasPreferredMetadataLanguage
     {
+        public string PreferredMetadataLanguage { get; set; }
     }
 }

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

@@ -956,6 +956,29 @@ namespace MediaBrowser.Controller.Entities
             return Id.ToString();
         }
 
+        /// <summary>
+        /// Gets the preferred metadata language.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        public virtual string GetPreferredMetadataLanguage()
+        {
+            string lang = null;
+
+            var hasLang = this as IHasPreferredMetadataLanguage;
+
+            if (hasLang != null)
+            {
+                lang = hasLang.PreferredMetadataLanguage;
+            }
+
+            if (string.IsNullOrEmpty(lang))
+            {
+                lang = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+            }
+
+            return lang;
+        }
+
         /// <summary>
         /// Determines if a given user has access to this item
         /// </summary>

+ 4 - 1
MediaBrowser.Controller/Entities/Book.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class Book : BaseItem, IHasTags
+    public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage
     {
         public override string MediaType
         {
@@ -12,6 +12,7 @@ namespace MediaBrowser.Controller.Entities
                 return Model.Entities.MediaType.Book;
             }
         }
+
         /// <summary>
         /// Gets or sets the tags.
         /// </summary>
@@ -20,6 +21,8 @@ namespace MediaBrowser.Controller.Entities
 
         public string SeriesName { get; set; }
 
+        public string PreferredMetadataLanguage { get; set; }
+
         /// <summary>
         /// 
         /// </summary>

+ 3 - 1
MediaBrowser.Controller/Entities/Game.cs

@@ -5,13 +5,15 @@ using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasLanguage, IHasScreenshots
+    public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasLanguage, IHasScreenshots, IHasPreferredMetadataLanguage
     {
         public List<Guid> SoundtrackIds { get; set; }
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
 
+        public string PreferredMetadataLanguage { get; set; }
+
         public Game()
         {
             MultiPartGameFiles = new List<string>();

+ 0 - 1
MediaBrowser.Controller/Entities/Genre.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Model.Dto;
-using System;
 using System.Collections.Generic;
 using System.Runtime.Serialization;
 

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

@@ -75,6 +75,12 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value>The primary image path.</value>
         string PrimaryImagePath { get; set; }
+
+        /// <summary>
+        /// Gets the preferred metadata language.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        string GetPreferredMetadataLanguage();
     }
 
     public static class HasImagesExtensions

+ 15 - 0
MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs

@@ -0,0 +1,15 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+    /// <summary>
+    /// Interface IHasPreferredMetadataLanguage
+    /// </summary>
+    public interface IHasPreferredMetadataLanguage
+    {
+        /// <summary>
+        /// Gets or sets the preferred metadata language.
+        /// </summary>
+        /// <value>The preferred metadata language.</value>
+        string PreferredMetadataLanguage { get; set; }
+    }
+}

+ 3 - 1
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Entities.Movies
     /// <summary>
     /// Class BoxSet
     /// </summary>
-    public class BoxSet : Folder, IHasTrailers, IHasTags
+    public class BoxSet : Folder, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage
     {
         public BoxSet()
         {
@@ -31,6 +31,8 @@ namespace MediaBrowser.Controller.Entities.Movies
         /// <value>The tags.</value>
         public List<string> Tags { get; set; }
 
+        public string PreferredMetadataLanguage { get; set; }
+
         protected override bool GetBlockUnratedValue(UserConfiguration config)
         {
             return config.BlockUnratedMovies;

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

@@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Movies
     /// <summary>
     /// Class Movie
     /// </summary>
-    public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags
+    public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage
     {
         public List<Guid> SpecialFeatureIds { get; set; }
 
@@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities.Movies
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
+
+        public string PreferredMetadataLanguage { get; set; }
         
         public Movie()
         {

+ 3 - 1
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities.TV
     /// <summary>
     /// Class Series
     /// </summary>
-    public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags
+    public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage
     {
         public List<Guid> SpecialFeatureIds { get; set; }
         public List<Guid> SoundtrackIds { get; set; }
@@ -223,5 +223,7 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             return config.BlockUnratedSeries;
         }
+
+        public string PreferredMetadataLanguage { get; set; }
     }
 }

+ 3 - 1
MediaBrowser.Controller/Entities/Trailer.cs

@@ -9,9 +9,11 @@ namespace MediaBrowser.Controller.Entities
     /// <summary>
     /// Class Trailer
     /// </summary>
-    public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags
+    public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage
     {
         public List<Guid> SoundtrackIds { get; set; }
+
+        public string PreferredMetadataLanguage { get; set; }
         
         public Trailer()
         {

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

@@ -88,6 +88,7 @@
     <Compile Include="Entities\IHasImages.cs" />
     <Compile Include="Entities\IHasLanguage.cs" />
     <Compile Include="Entities\IHasMediaStreams.cs" />
+    <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" />
     <Compile Include="Entities\IHasProductionLocations.cs" />
     <Compile Include="Entities\IHasScreenshots.cs" />
     <Compile Include="Entities\IHasSoundtracks.cs" />

+ 1 - 1
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -88,7 +88,7 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// <value>The path.</value>
         public string Path { get; set; }
-
+        
         /// <summary>
         /// Gets or sets the official rating.
         /// </summary>

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

@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.Movies
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 

+ 1 - 3
MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs

@@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
                 RatingType = RatingType.Score
             }));
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 
@@ -123,8 +123,6 @@ namespace MediaBrowser.Providers.Movies
         /// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
         private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, IHasImages item)
         {
-            var language = _config.Configuration.PreferredMetadataLanguage;
-
             return images.posters ?? new List<MovieDbProvider.Poster>();
         }
 

+ 3 - 4
MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -65,13 +64,13 @@ namespace MediaBrowser.Providers.Movies
 
                 var tmdbImageUrl = tmdbSettings.images.base_url + "original";
 
-                return GetImages(images, tmdbImageUrl);
+                return GetImages(images, item.GetPreferredMetadataLanguage(), tmdbImageUrl);
             }
 
             return new List<RemoteImageInfo>();
         }
 
-        private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string baseImageUrl)
+        private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string preferredLanguage, string baseImageUrl)
         {
             var list = new List<RemoteImageInfo>();
 
@@ -88,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
                 }));
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = preferredLanguage;
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 

+ 15 - 16
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -318,7 +318,7 @@ namespace MediaBrowser.Providers.Movies
             var year = item.ProductionYear ?? yearInName;
 
             Logger.Info("MovieDbProvider: Finding id for item: " + name);
-            string language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
+            var language = item.GetPreferredMetadataLanguage().ToLower();
 
             //if we are a boxset - look at our first child
             var boxset = item as BoxSet;
@@ -502,7 +502,7 @@ namespace MediaBrowser.Providers.Movies
         {
             // Id could be ImdbId or TmdbId
 
-            var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var dataFilePath = GetDataFilePath(item);
 
@@ -538,7 +538,7 @@ namespace MediaBrowser.Providers.Movies
 
             if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item))
             {
-                dataFilePath = GetDataFilePath(item, tmdbId);
+                dataFilePath = GetDataFilePath(item, tmdbId, language);
 
                 if (!string.IsNullOrEmpty(dataFilePath))
                 {
@@ -555,17 +555,16 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="id">The id.</param>
         /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param>
         /// <param name="dataPath">The data path.</param>
+        /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        internal async Task DownloadMovieInfo(string id, bool isBoxSet, string dataPath, CancellationToken cancellationToken)
+        internal async Task DownloadMovieInfo(string id, bool isBoxSet, string dataPath, string preferredMetadataLanguage, CancellationToken cancellationToken)
         {
-            var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
-
-            var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
+            var mainResult = await FetchMainResult(id, isBoxSet, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
 
             if (mainResult == null) return;
 
-            var dataFilePath = Path.Combine(dataPath, language + ".json");
+            var dataFilePath = Path.Combine(dataPath, preferredMetadataLanguage + ".json");
 
             Directory.CreateDirectory(dataPath);
 
@@ -593,16 +592,14 @@ namespace MediaBrowser.Providers.Movies
                 return null;
             }
 
-            return GetDataFilePath(item, id);
+            return GetDataFilePath(item, id, item.GetPreferredMetadataLanguage());
         }
 
-        internal string GetDataFilePath(BaseItem item, string tmdbId)
+        internal string GetDataFilePath(BaseItem item, string tmdbId, string preferredLanguage)
         {
-            var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
-
             var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, item is BoxSet, tmdbId);
 
-            path = Path.Combine(path, language + ".json");
+            path = Path.Combine(path, preferredLanguage + ".json");
 
             return path;
         }
@@ -838,15 +835,17 @@ namespace MediaBrowser.Providers.Movies
 
             // genres
             // Movies get this from imdb
-            if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres))
+            var genres = movieData.genres ?? new List<GenreItem>();
+            if (!movie.LockedFields.Contains(MetadataFields.Genres))
             {
                 // Only grab them if a boxset or there are no genres.
                 // For movies and trailers we'll use imdb via omdb
-                if (!(movie is Movie) || movie.Genres.Count == 0)
+                // But omdb data is for english users only so fetch if language is not english
+                if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase))
                 {
                     movie.Genres.Clear();
 
-                    foreach (var genre in movieData.genres.Select(g => g.name))
+                    foreach (var genre in genres.Select(g => g.name))
                     {
                         movie.AddGenre(genre);
                     }

+ 31 - 10
MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs

@@ -2,7 +2,10 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using System;
@@ -37,6 +40,7 @@ namespace MediaBrowser.Providers.Movies
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _json;
         private readonly IFileSystem _fileSystem;
+        private readonly ILibraryManager _libraryManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
@@ -45,13 +49,14 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="config">The config.</param>
         /// <param name="json">The json.</param>
-        public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
+        public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem, ILibraryManager libraryManager)
         {
             _logger = logger;
             _httpClient = httpClient;
             _config = config;
             _json = json;
             _fileSystem = fileSystem;
+            _libraryManager = libraryManager;
         }
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -196,15 +201,30 @@ namespace MediaBrowser.Providers.Movies
             var list = ids.ToList();
             var numComplete = 0;
 
+            // Gather all movies into a lookup by tmdb id
+            var allMovies = _libraryManager.RootFolder.RecursiveChildren
+                .Where(i => i is Movie || i is Trailer)
+                .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)))
+                .ToLookup(i => i.GetProviderId(MetadataProviders.Tmdb));
+
             foreach (var id in list)
             {
-                try
-                {
-                    await UpdateMovie(id, isBoxSet, moviesDataPath, cancellationToken).ConfigureAwait(false);
-                }
-                catch (Exception ex)
+                // Find the preferred language(s) for the movie in the library
+                var languages = allMovies[id]
+                    .Select(i => i.GetPreferredMetadataLanguage())
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+
+                foreach (var language in languages)
                 {
-                    _logger.ErrorException("Error updating tmdb movie id {0}", ex, id);
+                    try
+                    {
+                        await UpdateMovie(id, isBoxSet, moviesDataPath, language, cancellationToken).ConfigureAwait(false);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error updating tmdb movie id {0}, language {1}", ex, id, language);
+                    }
                 }
 
                 numComplete++;
@@ -222,17 +242,18 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="id">The id.</param>
         /// <param name="isBoxSet">if set to <c>true</c> [is box set].</param>
         /// <param name="dataPath">The data path.</param>
+        /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        private Task UpdateMovie(string id, bool isBoxSet, string dataPath, CancellationToken cancellationToken)
+        private Task UpdateMovie(string id, bool isBoxSet, string dataPath, string preferredMetadataLanguage, CancellationToken cancellationToken)
         {
-            _logger.Info("Updating movie from tmdb " + id);
+            _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
 
             var itemDataPath = Path.Combine(dataPath, id);
 
             Directory.CreateDirectory(dataPath);
 
-            return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, itemDataPath, cancellationToken);
+            return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, itemDataPath, preferredMetadataLanguage, cancellationToken);
         }
 
         class Result

+ 8 - 0
MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs

@@ -202,6 +202,14 @@ namespace MediaBrowser.Providers.Movies
 
         private bool ShouldFetchGenres(BaseItem item)
         {
+            var lang = item.GetPreferredMetadataLanguage();
+
+            // The data isn't localized and so can only be used for english users
+            if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
             // Only fetch if other providers didn't get anything
             if (item is Trailer)
             {

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

@@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.Music
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 

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

@@ -72,7 +72,7 @@ namespace MediaBrowser.Providers.Music
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 

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

@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.TV
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 

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

@@ -71,7 +71,7 @@ namespace MediaBrowser.Providers.TV
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
+            var language = item.GetPreferredMetadataLanguage();
 
             var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
 

+ 4 - 6
MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs

@@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.TV
 
                 try
                 {
-                    var result = GetImages(path, season.IndexNumber.Value, cancellationToken);
+                    var result = GetImages(path, item.GetPreferredMetadataLanguage(), season.IndexNumber.Value, cancellationToken);
 
                     return Task.FromResult(result);
                 }
@@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.TV
             return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
         }
 
-        private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, int seasonNumber, CancellationToken cancellationToken)
+        private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, int seasonNumber, CancellationToken cancellationToken)
         {
             var settings = new XmlReaderSettings
             {
@@ -123,13 +123,11 @@ namespace MediaBrowser.Providers.TV
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+            var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
 
             return list.OrderByDescending(i =>
                 {
-                    if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+                    if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
                     {
                         return 3;
                     }

+ 4 - 6
MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs

@@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.TV
 
                 try
                 {
-                    var result = GetImages(path, cancellationToken);
+                    var result = GetImages(path, item.GetPreferredMetadataLanguage(), cancellationToken);
 
                     return Task.FromResult(result);
                 }
@@ -77,7 +77,7 @@ namespace MediaBrowser.Providers.TV
             return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
         }
 
-        private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, CancellationToken cancellationToken)
+        private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
         {
             var settings = new XmlReaderSettings
             {
@@ -122,13 +122,11 @@ namespace MediaBrowser.Providers.TV
                 }
             }
 
-            var language = _config.Configuration.PreferredMetadataLanguage;
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+            var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
 
             return list.OrderByDescending(i =>
             {
-                if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
                 {
                     return 3;
                 }

+ 34 - 13
MediaBrowser.Providers/TV/TvdbPrescanTask.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using System;
@@ -45,6 +46,7 @@ namespace MediaBrowser.Providers.TV
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
+        private readonly ILibraryManager _libraryManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
@@ -52,12 +54,13 @@ namespace MediaBrowser.Providers.TV
         /// <param name="logger">The logger.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="config">The config.</param>
-        public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem)
+        public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager)
         {
             _logger = logger;
             _httpClient = httpClient;
             _config = config;
             _fileSystem = fileSystem;
+            _libraryManager = libraryManager;
         }
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -273,19 +276,36 @@ namespace MediaBrowser.Providers.TV
             var list = seriesIds.ToList();
             var numComplete = 0;
 
+            // Gather all series into a lookup by tvdb id
+            var allSeries = _libraryManager.RootFolder.RecursiveChildren
+                .OfType<Series>()
+                .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
+                .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb));
+
             foreach (var seriesId in list)
             {
-                try
-                {
-                    await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, cancellationToken).ConfigureAwait(false);
-                }
-                catch (HttpException ex)
+                // Find the preferred language(s) for the movie in the library
+                var languages = allSeries[seriesId]
+                    .Select(i => i.GetPreferredMetadataLanguage())
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+
+                foreach (var language in languages)
                 {
-                    // Already logged at lower levels, but don't fail the whole operation, unless timed out
-                    // We have to fail this to make it run again otherwise new episode data could potentially be missing
-                    if (ex.IsTimedOut)
+                    try
                     {
-                        throw;
+                        await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false);
+                    }
+                    catch (HttpException ex)
+                    {
+                        _logger.ErrorException("Error updating tvdb series id {0}, language {1}", ex, seriesId, language);
+                        
+                        // Already logged at lower levels, but don't fail the whole operation, unless timed out
+                        // We have to fail this to make it run again otherwise new episode data could potentially be missing
+                        if (ex.IsTimedOut)
+                        {
+                            throw;
+                        }
                     }
                 }
 
@@ -304,17 +324,18 @@ namespace MediaBrowser.Providers.TV
         /// <param name="id">The id.</param>
         /// <param name="seriesDataPath">The series data path.</param>
         /// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
+        /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken)
+        private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
         {
-            _logger.Info("Updating series " + id);
+            _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
 
             seriesDataPath = Path.Combine(seriesDataPath, id);
 
             Directory.CreateDirectory(seriesDataPath);
 
-            return TvdbSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, cancellationToken);
+            return TvdbSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
         }
     }
 }

+ 8 - 6
MediaBrowser.Providers/TV/TvdbSeriesProvider.cs

@@ -248,13 +248,13 @@ namespace MediaBrowser.Providers.TV
                 .Select(Path.GetFileName)
                 .ToList();
 
-            var seriesXmlFilename = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() + ".xml";
+            var seriesXmlFilename = series.GetPreferredMetadataLanguage().ToLower() + ".xml";
 
             // Only download if not already there
             // The prescan task will take care of updates so we don't need to re-download here
             if (!files.Contains("banners.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains("actors.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains(seriesXmlFilename, StringComparer.OrdinalIgnoreCase))
             {
-                await DownloadSeriesZip(seriesId, seriesDataPath, null, cancellationToken).ConfigureAwait(false);
+                await DownloadSeriesZip(seriesId, seriesDataPath, null, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
             }
 
             // Have to check this here since we prevent the normal enforcement through ProviderManager
@@ -285,11 +285,13 @@ namespace MediaBrowser.Providers.TV
         /// </summary>
         /// <param name="seriesId">The series id.</param>
         /// <param name="seriesDataPath">The series data path.</param>
+        /// <param name="lastTvDbUpdateTime">The last tv database update time.</param>
+        /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken)
+        internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
         {
-            var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage);
+            var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, preferredMetadataLanguage);
 
             using (var zipStream = await HttpClient.Get(new HttpRequestOptions
             {
@@ -319,7 +321,7 @@ namespace MediaBrowser.Providers.TV
                 await SanitizeXmlFile(file).ConfigureAwait(false);
             }
 
-            await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, ConfigurationManager.Configuration.PreferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
+            await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
         }
 
         private void DeleteXmlFiles(string path)
@@ -852,7 +854,7 @@ namespace MediaBrowser.Providers.TV
                                 if (!string.IsNullOrWhiteSpace(val))
                                 {
                                     // Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
-                                    if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(ConfigurationManager.Configuration.PreferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)))
+                                    if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(item.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase)))
                                     {
                                         var vals = val
                                             .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)

+ 1 - 1
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -378,7 +378,7 @@ namespace MediaBrowser.Server.Implementations.Providers
                 providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
             }
 
-            var preferredLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+            var preferredLanguage = item.GetPreferredMetadataLanguage();
 
             var tasks = providers.Select(i => Task.Run(async () =>
             {

+ 2 - 1
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -417,7 +417,6 @@ namespace MediaBrowser.WebDashboard.Api
             var files = new[]
                             {
                                 "thirdparty/jquerymobile-1.4.0/jquery.mobile-1.4.0.min.css",
-                                //"thirdparty/jqm-icon-pack-3.0/font-awesome/jqm-icon-pack-3.0.0-fa.css" + versionString,
                                 "css/all.css" + versionString
                             };
 
@@ -561,6 +560,8 @@ namespace MediaBrowser.WebDashboard.Api
             await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false);
             await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.0/jquery.mobile-1.4.0.min.js", newLineBytes).ConfigureAwait(false);
 
+            //await AppendResource(memoryStream, "thirdparty/jquery.infinite-scroll-helper.min.js", newLineBytes).ConfigureAwait(false);
+
             var versionString = string.Format("window.dashboardVersion='{0}';", _appHost.ApplicationVersion);
             var versionBytes = Encoding.UTF8.GetBytes(versionString);
 

+ 3 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -481,6 +481,9 @@
     <Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\thirdparty\jquery.infinite-scroll-helper.min.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\ajax-loader.gif">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>