Переглянути джерело

abstract preferred metadata language per item

Luke Pulverenti 11 роки тому
батько
коміт
44c0eba39d
30 змінених файлів з 186 додано та 80 видалено
  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>