aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs114
-rw-r--r--MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/RemoteSeasonProvider.cs209
-rw-r--r--MediaBrowser.Providers/TV/RemoteSeriesProvider.cs101
-rw-r--r--MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs114
-rw-r--r--MediaBrowser.Providers/TV/TvdbPrescanTask.cs116
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs219
7 files changed, 697 insertions, 182 deletions
diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs
index 287980fc7..a2f7b153f 100644
--- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs
+++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
@@ -21,6 +22,12 @@ namespace MediaBrowser.Api.DefaultTheme
{
[ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public Guid UserId { get; set; }
+
+ [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string ComedyGenre { get; set; }
+
+ [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string RomanceGenre { get; set; }
}
[Route("/MBT/DefaultTheme/Movies", "GET")]
@@ -31,6 +38,19 @@ namespace MediaBrowser.Api.DefaultTheme
[ApiMember(Name = "FamilyRating", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string FamilyRating { get; set; }
+
+ [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string ComedyGenre { get; set; }
+
+ [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string RomanceGenre { get; set; }
+ }
+
+ [Route("/MBT/DefaultTheme/Home", "GET")]
+ public class GetHomeView : IReturn<HomeView>
+ {
+ [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public Guid UserId { get; set; }
}
public class DefaultThemeService : BaseApiService
@@ -51,6 +71,39 @@ namespace MediaBrowser.Api.DefaultTheme
_localization = localization;
}
+ public object Get(GetHomeView request)
+ {
+ var result = GetHomeView(request).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ private async Task<HomeView> GetHomeView(GetHomeView request)
+ {
+ var user = _userManager.GetUserById(request.UserId);
+
+ var allItems = user.RootFolder.GetRecursiveChildren(user)
+ .ToList();
+
+ var itemsWithBackdrops = allItems.Where(i => i.BackdropImagePaths.Count > 0).ToList();
+
+ var view = new HomeView();
+
+ var fields = new List<ItemFields>();
+
+ var eligibleSpotlightItems = itemsWithBackdrops
+ .Where(i => i is Game || i is Movie || i is Series || i is MusicArtist);
+
+ var spotlightItemTasks = FilterItemsForBackdropDisplay(eligibleSpotlightItems)
+ .OrderBy(i => Guid.NewGuid())
+ .Take(50)
+ .Select(i => _dtoService.GetBaseItemDto(i, fields, user));
+
+ view.SpotlightItems = await Task.WhenAll(spotlightItemTasks).ConfigureAwait(false);
+
+ return view;
+ }
+
public object Get(GetTvView request)
{
var result = GetTvView(request).Result;
@@ -72,10 +125,9 @@ namespace MediaBrowser.Api.DefaultTheme
var fields = new List<ItemFields>();
- var spotlightItemTasks = seriesWithBackdrops
- .OrderByDescending(i => GetResolution(i, i.BackdropImagePaths[0]))
- .Take(60)
+ var spotlightItemTasks = FilterItemsForBackdropDisplay(seriesWithBackdrops)
.OrderBy(i => Guid.NewGuid())
+ .Take(50)
.Select(i => _dtoService.GetBaseItemDto(i, fields, user));
view.SpotlightItems = await Task.WhenAll(spotlightItemTasks).ConfigureAwait(false);
@@ -88,6 +140,25 @@ namespace MediaBrowser.Api.DefaultTheme
.Take(3)
.ToArray();
+ var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+ var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+
+ view.RomanceItems = seriesWithBackdrops
+ .Where(i => i.Genres.Any(romanceGenres.ContainsKey))
+ .OrderBy(i => Guid.NewGuid())
+ .Select(i => GetItemStub(i, ImageType.Backdrop))
+ .Where(i => i != null)
+ .Take(3)
+ .ToArray();
+
+ view.ComedyItems = seriesWithBackdrops
+ .Where(i => i.Genres.Any(comedyGenres.ContainsKey))
+ .OrderBy(i => Guid.NewGuid())
+ .Select(i => GetItemStub(i, ImageType.Backdrop))
+ .Where(i => i != null)
+ .Take(3)
+ .ToArray();
+
view.ActorItems = await GetActors(series).ConfigureAwait(false);
return view;
@@ -137,10 +208,9 @@ namespace MediaBrowser.Api.DefaultTheme
var fields = new List<ItemFields>();
- var spotlightItemTasks = itemsWithBackdrops
- .OrderByDescending(i => GetResolution(i, i.BackdropImagePaths[0]))
- .Take(60)
+ var spotlightItemTasks = FilterItemsForBackdropDisplay(itemsWithBackdrops)
.OrderBy(i => Guid.NewGuid())
+ .Take(50)
.Select(i => _dtoService.GetBaseItemDto(i, fields, user));
view.SpotlightItems = await Task.WhenAll(spotlightItemTasks).ConfigureAwait(false);
@@ -178,9 +248,10 @@ namespace MediaBrowser.Api.DefaultTheme
.Take(3)
.ToArray();
- var romanceGenres = new[] { "romance" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+ var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+ var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
- view.RomanticItems = moviesWithBackdrops
+ view.RomanceItems = moviesWithBackdrops
.Where(i => i.Genres.Any(romanceGenres.ContainsKey))
.OrderBy(i => Guid.NewGuid())
.Select(i => GetItemStub(i, ImageType.Backdrop))
@@ -188,6 +259,14 @@ namespace MediaBrowser.Api.DefaultTheme
.Take(3)
.ToArray();
+ view.ComedyItems = moviesWithBackdrops
+ .Where(i => i.Genres.Any(comedyGenres.ContainsKey))
+ .OrderBy(i => Guid.NewGuid())
+ .Select(i => GetItemStub(i, ImageType.Backdrop))
+ .Where(i => i != null)
+ .Take(3)
+ .ToArray();
+
view.HDItems = hdMovies
.Where(i => i.BackdropImagePaths.Count > 0)
.OrderBy(i => Guid.NewGuid())
@@ -209,6 +288,25 @@ namespace MediaBrowser.Api.DefaultTheme
return view;
}
+ private IEnumerable<BaseItem> FilterItemsForBackdropDisplay(IEnumerable<BaseItem> items)
+ {
+ var tuples = items
+ .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, i.BackdropImagePaths[0])))
+ .Where(i => i.Item2 > 0)
+ .ToList();
+
+ var topItems = tuples
+ .Where(i => i.Item2 >= 1920)
+ .ToList();
+
+ if (topItems.Count >= 10)
+ {
+ return topItems.Select(i => i.Item1);
+ }
+
+ return tuples.Select(i => i.Item1);
+ }
+
private double GetResolution(BaseItem item, string path)
{
try
diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
index 3e01ad5be..f6c0c9208 100644
--- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
+++ b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
@@ -1,5 +1,4 @@
-using System.Linq;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -10,6 +9,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Globalization;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -46,7 +46,7 @@ namespace MediaBrowser.Providers.Movies
{
get
{
- return "8";
+ return "9";
}
}
diff --git a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs
index 1ea2c1db5..de9e5c018 100644
--- a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs
@@ -5,10 +5,9 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
using System;
using System.IO;
-using System.Net;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
@@ -77,7 +76,7 @@ namespace MediaBrowser.Providers.TV
return ItemUpdateType.ImageUpdate;
}
}
-
+
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
@@ -98,7 +97,7 @@ namespace MediaBrowser.Providers.TV
{
get
{
- return "1";
+ return "2";
}
}
@@ -119,7 +118,7 @@ namespace MediaBrowser.Providers.TV
return imagesFileInfo.LastWriteTimeUtc;
}
}
-
+
return base.CompareDate(item);
}
@@ -138,22 +137,21 @@ namespace MediaBrowser.Providers.TV
var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
- if (!string.IsNullOrEmpty(seriesId))
+ var seasonNumber = season.IndexNumber;
+
+ if (!string.IsNullOrEmpty(seriesId) && seasonNumber.HasValue)
{
// Process images
var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
- var imagesFileInfo = new FileInfo(imagesXmlPath);
-
- if (imagesFileInfo.Exists)
+ try
{
- if (!season.HasImage(ImageType.Primary) || !season.HasImage(ImageType.Banner) || season.BackdropImagePaths.Count == 0)
- {
- var xmlDoc = new XmlDocument();
- xmlDoc.Load(imagesXmlPath);
-
- await FetchImages(season, xmlDoc, cancellationToken).ConfigureAwait(false);
- }
+ var fanartData = FetchFanartXmlData(imagesXmlPath, seasonNumber.Value, cancellationToken);
+ await DownloadImages(item, fanartData, ConfigurationManager.Configuration.MaxBackdrops, cancellationToken).ConfigureAwait(false);
+ }
+ catch (FileNotFoundException)
+ {
+ // No biggie. Not all series have images
}
SetLastRefreshed(item, DateTime.UtcNow);
@@ -163,86 +161,169 @@ namespace MediaBrowser.Providers.TV
return false;
}
- /// <summary>
- /// Fetches the images.
- /// </summary>
- /// <param name="season">The season.</param>
- /// <param name="images">The images.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task FetchImages(Season season, XmlDocument images, CancellationToken cancellationToken)
+ private async Task DownloadImages(BaseItem item, FanartXmlData data, int backdropLimit, CancellationToken cancellationToken)
{
- var seasonNumber = season.IndexNumber ?? -1;
+ if (!item.HasImage(ImageType.Primary))
+ {
+ var url = data.LanguagePoster ?? data.Poster;
+ if (!string.IsNullOrEmpty(url))
+ {
+ url = TVUtils.BannerUrl + url;
- if (seasonNumber == -1)
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
+
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
{
- return;
+ var url = data.LanguageBanner ?? data.Banner;
+ if (!string.IsNullOrEmpty(url))
+ {
+ url = TVUtils.BannerUrl + url;
+
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
}
- if (!season.HasImage(ImageType.Primary))
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count == 0)
{
- var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
- images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='en']");
- if (n != null)
+ var bdNo = item.BackdropImagePaths.Count;
+
+ foreach (var backdrop in data.Backdrops)
{
- n = n.SelectSingleNode("./BannerPath");
+ var url = TVUtils.BannerUrl + backdrop;
- if (n != null)
- {
- var url = TVUtils.BannerUrl + n.InnerText;
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
+ .ConfigureAwait(false);
- await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
- .ConfigureAwait(false);
- }
+ bdNo++;
+
+ if (item.BackdropImagePaths.Count >= backdropLimit) break;
}
}
+ }
+
+ private FanartXmlData FetchFanartXmlData(string bannersXmlPath, int seasonNumber, CancellationToken cancellationToken)
+ {
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !season.HasImage(ImageType.Banner))
+ var data = new FanartXmlData();
+
+ using (var streamReader = new StreamReader(bannersXmlPath, Encoding.UTF8))
{
- var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
- images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='en']");
- if (n != null)
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
{
- n = n.SelectSingleNode("./BannerPath");
- if (n != null)
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
{
- try
- {
- var url = TVUtils.BannerUrl + n.InnerText;
+ cancellationToken.ThrowIfCancellationRequested();
- await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
- .ConfigureAwait(false);
- }
- catch (HttpException ex)
+ if (reader.NodeType == XmlNodeType.Element)
{
- Logger.ErrorException("Error downloading season banner for {0}", ex, season.Path);
-
- // Sometimes banners will come up not found even though they're reported in tvdb xml
- if (ex.StatusCode.HasValue && ex.StatusCode.Value != HttpStatusCode.NotFound)
+ switch (reader.Name)
{
- throw;
+ case "Banner":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ FetchInfoFromBannerNode(data, subtree, seasonNumber);
+ }
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
}
}
}
}
}
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && season.BackdropImagePaths.Count == 0)
+ return data;
+ }
+
+ private void FetchInfoFromBannerNode(FanartXmlData data, XmlReader reader, int seasonNumber)
+ {
+ reader.MoveToContent();
+
+ string bannerType = null;
+ string bannerType2 = null;
+ string url = null;
+ int? bannerSeason = null;
+
+ while (reader.Read())
{
- var n = images.SelectSingleNode("//Banner[BannerType='fanart'][Season='" + seasonNumber + "']");
- if (n != null)
+ if (reader.NodeType == XmlNodeType.Element)
{
- n = n.SelectSingleNode("./BannerPath");
- if (n != null)
+ switch (reader.Name)
{
- var url = TVUtils.BannerUrl + n.InnerText;
+ case "BannerType":
+ {
+ bannerType = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
+
+ case "BannerType2":
+ {
+ bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
+
+ case "BannerPath":
+ {
+ url = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
- await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, 0, cancellationToken)
- .ConfigureAwait(false);
+ case "Season":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ bannerSeason = int.Parse(val);
+ }
+ break;
+ }
+
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ if (!string.IsNullOrEmpty(url) && bannerSeason.HasValue && bannerSeason.Value == seasonNumber)
+ {
+ if (string.Equals(bannerType, "season", StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
+ {
+ data.Poster = url;
+ }
+ else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
+ {
+ data.Banner = url;
}
}
+ else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
+ {
+ data.Backdrops.Add(url);
+ }
}
}
+
}
}
diff --git a/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs b/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
index 34be77a9e..b79e50e20 100644
--- a/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
@@ -247,10 +247,7 @@ namespace MediaBrowser.Providers.TV
if (!series.LockedFields.Contains(MetadataFields.Cast))
{
- var actorsDoc = new XmlDocument();
- actorsDoc.Load(actorsXmlPath);
-
- FetchActors(series, actorsDoc, seriesDoc);
+ FetchActors(series, actorsXmlPath, cancellationToken);
}
}
}
@@ -384,8 +381,11 @@ namespace MediaBrowser.Providers.TV
}
}
}
+
series.OfficialRating = doc.SafeGetString("//ContentRating");
- if (!series.LockedFields.Contains(MetadataFields.Genres))
+
+ // Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
+ if (!series.LockedFields.Contains(MetadataFields.Genres) && (series.Genres.Count == 0 || !string.Equals(ConfigurationManager.Configuration.PreferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)))
{
string g = doc.SafeGetString("//Genre");
@@ -429,32 +429,95 @@ namespace MediaBrowser.Providers.TV
/// Fetches the actors.
/// </summary>
/// <param name="series">The series.</param>
- /// <param name="actorsDoc">The actors doc.</param>
- /// <param name="seriesDoc">The seriesDoc.</param>
- /// <returns>Task.</returns>
- private void FetchActors(Series series, XmlDocument actorsDoc, XmlDocument seriesDoc)
+ /// <param name="actorsXmlPath">The actors XML path.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void FetchActors(Series series, string actorsXmlPath, CancellationToken cancellationToken)
{
- var xmlNodeList = actorsDoc.SelectNodes("Actors/Actor");
-
- if (xmlNodeList != null)
+ var settings = new XmlReaderSettings
{
- series.People.Clear();
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
- foreach (XmlNode p in xmlNodeList)
+ using (var streamReader = new StreamReader(actorsXmlPath, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
{
- string actorName = p.SafeGetString("Name");
- string actorRole = p.SafeGetString("Role");
+ reader.MoveToContent();
- if (!string.IsNullOrWhiteSpace(actorName))
+ // Loop through each element
+ while (reader.Read())
{
- // Sometimes tvdb actors have leading spaces
- series.AddPerson(new PersonInfo { Type = PersonType.Actor, Name = actorName.Trim(), Role = actorRole });
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Actor":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ FetchDataFromActorNode(series, subtree);
+ }
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
+ }
+ }
}
}
}
}
/// <summary>
+ /// Fetches the data from actor node.
+ /// </summary>
+ /// <param name="series">The series.</param>
+ /// <param name="reader">The reader.</param>
+ private void FetchDataFromActorNode(Series series, XmlReader reader)
+ {
+ reader.MoveToContent();
+
+ var personInfo = new PersonInfo();
+
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Name":
+ {
+ personInfo.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+
+ case "Role":
+ {
+ personInfo.Role = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(personInfo.Name))
+ {
+ series.AddPerson(personInfo);
+ }
+ }
+
+ /// <summary>
/// The us culture
/// </summary>
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
diff --git a/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
index bcdd03fc1..2f1ae8e98 100644
--- a/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
@@ -5,10 +5,10 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
-using MediaBrowser.Providers.Extensions;
using System;
using System.IO;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
@@ -87,38 +87,114 @@ namespace MediaBrowser.Providers.TV
var actorXmlPath = Path.Combine(tvdbPath, "actors.xml");
- var xmlDoc = new XmlDocument();
+ var url = FetchImageUrl(item, actorXmlPath, cancellationToken);
- xmlDoc.Load(actorXmlPath);
-
- var actorNodes = xmlDoc.SelectNodes("//Actor");
-
- if (actorNodes == null)
+ if (!string.IsNullOrEmpty(url))
{
- return;
- }
+ url = TVUtils.BannerUrl + url;
- foreach (var actorNode in actorNodes.OfType<XmlNode>())
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool,
+ ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ private string FetchImageUrl(BaseItem item, string actorsXmlPath, CancellationToken cancellationToken)
+ {
+ var settings = new XmlReaderSettings
{
- var name = actorNode.SafeGetString("Name");
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
- if (string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase))
+ using (var streamReader = new StreamReader(actorsXmlPath, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
{
- var image = actorNode.SafeGetString("Image");
+ reader.MoveToContent();
- if (!string.IsNullOrEmpty(image))
+ // Loop through each element
+ while (reader.Read())
{
- var url = TVUtils.BannerUrl + image;
-
- await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool,
- ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Actor":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ var url = FetchImageUrlFromActorNode(item, subtree);
+
+ if (!string.IsNullOrEmpty(url))
+ {
+ return url;
+ }
+ }
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
+ }
+ }
}
+ }
+ }
- break;
+ return null;
+ }
+
+ /// <summary>
+ /// Fetches the data from actor node.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="reader">The reader.</param>
+ private string FetchImageUrlFromActorNode(BaseItem item, XmlReader reader)
+ {
+ reader.MoveToContent();
+
+ string name = null;
+ string image = null;
+
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Name":
+ {
+ name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+
+ case "Image":
+ {
+ image = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
}
}
+
+ if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(image) &&
+ string.Equals(name, item.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return image;
+ }
+
+ return null;
}
+
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.Third; }
diff --git a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
index 2e6038b76..e6aa07be0 100644
--- a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
@@ -11,7 +11,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using MediaBrowser.Providers.Extensions;
namespace MediaBrowser.Providers.TV
{
@@ -102,11 +101,7 @@ namespace MediaBrowser.Providers.TV
}).ConfigureAwait(false))
{
- var doc = new XmlDocument();
-
- doc.Load(stream);
-
- newUpdateTime = doc.SafeGetString("//Time");
+ newUpdateTime = GetUpdateTime(stream);
}
await UpdateSeries(existingDirectories, path, progress, cancellationToken).ConfigureAwait(false);
@@ -125,6 +120,51 @@ namespace MediaBrowser.Providers.TV
}
/// <summary>
+ /// Gets the update time.
+ /// </summary>
+ /// <param name="response">The response.</param>
+ /// <returns>System.String.</returns>
+ private string GetUpdateTime(Stream response)
+ {
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
+
+ using (var streamReader = new StreamReader(response, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
+ {
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Time":
+ {
+ return (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ }
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
/// Gets the series ids to update.
/// </summary>
/// <param name="existingSeriesIds">The existing series ids.</param>
@@ -143,23 +183,65 @@ namespace MediaBrowser.Providers.TV
}).ConfigureAwait(false))
{
- var doc = new XmlDocument();
+ var data = GetUpdatedSeriesIdList(stream);
- doc.Load(stream);
+ var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
- var newUpdateTime = doc.SafeGetString("//Time");
+ var seriesList = data.Item1
+ .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
- var seriesNodes = doc.SelectNodes("//Series");
+ return new Tuple<IEnumerable<string>, string>(seriesList, data.Item2);
+ }
+ }
- var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+ private Tuple<List<string>, string> GetUpdatedSeriesIdList(Stream stream)
+ {
+ string updateTime = null;
+ var idList = new List<string>();
- var seriesList = seriesNodes == null ? new string[] { } :
- seriesNodes.Cast<XmlNode>()
- .Select(i => i.InnerText)
- .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
- return new Tuple<IEnumerable<string>, string>(seriesList, newUpdateTime);
+ using (var streamReader = new StreamReader(stream, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
+ {
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Time":
+ {
+ updateTime = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+ case "Series":
+ {
+ var id = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ idList.Add(id);
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ }
}
+
+ return new Tuple<List<string>, string>(idList, updateTime);
}
/// <summary>
@@ -217,7 +299,7 @@ namespace MediaBrowser.Providers.TV
{
Directory.CreateDirectory(seriesDataPath);
}
-
+
return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken);
}
}
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
index 2ac201a56..9e3799ab8 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
@@ -7,8 +7,10 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
@@ -86,7 +88,7 @@ namespace MediaBrowser.Providers.TV
return ItemUpdateType.ImageUpdate;
}
}
-
+
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
@@ -127,7 +129,7 @@ namespace MediaBrowser.Providers.TV
return imagesFileInfo.LastWriteTimeUtc;
}
}
-
+
return base.CompareDate(item);
}
@@ -150,16 +152,18 @@ namespace MediaBrowser.Providers.TV
// Process images
var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
- var imagesFileInfo = new FileInfo(imagesXmlPath);
-
- if (imagesFileInfo.Exists)
+ if (!series.HasImage(ImageType.Primary) || !series.HasImage(ImageType.Banner) || series.BackdropImagePaths.Count == 0)
{
- if (!series.HasImage(ImageType.Primary) || !series.HasImage(ImageType.Banner) || series.BackdropImagePaths.Count == 0)
- {
- var xmlDoc = new XmlDocument();
- xmlDoc.Load(imagesXmlPath);
+ var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
- await FetchImages(series, xmlDoc, cancellationToken).ConfigureAwait(false);
+ try
+ {
+ var fanartData = FetchFanartXmlData(imagesXmlPath, backdropLimit, cancellationToken);
+ await DownloadImages(item, fanartData, backdropLimit, cancellationToken).ConfigureAwait(false);
+ }
+ catch (FileNotFoundException)
+ {
+ // No biggie. Not all series have images
}
}
@@ -179,72 +183,183 @@ namespace MediaBrowser.Providers.TV
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
- /// <summary>
- /// Fetches the images.
- /// </summary>
- /// <param name="series">The series.</param>
- /// <param name="images">The images.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task FetchImages(Series series, XmlDocument images, CancellationToken cancellationToken)
+ private async Task DownloadImages(BaseItem item, FanartXmlData data, int backdropLimit, CancellationToken cancellationToken)
{
- if (!series.HasImage(ImageType.Primary))
+ if (!item.HasImage(ImageType.Primary))
{
- var n = images.SelectSingleNode("//Banner[BannerType='poster']");
- if (n != null)
+ var url = data.LanguagePoster ?? data.Poster;
+ if (!string.IsNullOrEmpty(url))
{
- n = n.SelectSingleNode("./BannerPath");
- if (n != null)
- {
- var url = TVUtils.BannerUrl + n.InnerText;
+ url = TVUtils.BannerUrl + url;
- await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
- .ConfigureAwait(false);
- }
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+ .ConfigureAwait(false);
}
}
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !series.HasImage(ImageType.Banner))
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
{
- var n = images.SelectSingleNode("//Banner[BannerType='series']");
- if (n != null)
+ var url = data.LanguageBanner ?? data.Banner;
+ if (!string.IsNullOrEmpty(url))
{
- n = n.SelectSingleNode("./BannerPath");
- if (n != null)
- {
- var url = TVUtils.BannerUrl + n.InnerText;
+ url = TVUtils.BannerUrl + url;
- await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
- .ConfigureAwait(false);
- }
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
+
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count == 0)
+ {
+ var bdNo = item.BackdropImagePaths.Count;
+
+ foreach (var backdrop in data.Backdrops)
+ {
+ var url = TVUtils.BannerUrl + backdrop;
+
+ await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
+ .ConfigureAwait(false);
+
+ bdNo++;
+
+ if (item.BackdropImagePaths.Count >= backdropLimit) break;
}
}
+ }
- if (series.BackdropImagePaths.Count == 0)
+ private FanartXmlData FetchFanartXmlData(string bannersXmlPath, int backdropLimit, CancellationToken cancellationToken)
+ {
+ var settings = new XmlReaderSettings
{
- var bdNo = series.BackdropImagePaths.Count;
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
+
+ var data = new FanartXmlData();
- var xmlNodeList = images.SelectNodes("//Banner[BannerType='fanart']");
- if (xmlNodeList != null)
+ using (var streamReader = new StreamReader(bannersXmlPath, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
{
- foreach (XmlNode b in xmlNodeList)
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
{
- var p = b.SelectSingleNode("./BannerPath");
+ cancellationToken.ThrowIfCancellationRequested();
- if (p != null)
+ if (reader.NodeType == XmlNodeType.Element)
{
- var url = TVUtils.BannerUrl + p.InnerText;
-
- await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
- .ConfigureAwait(false);
-
- bdNo++;
+ switch (reader.Name)
+ {
+ case "Banner":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ FetchInfoFromBannerNode(data, subtree, backdropLimit);
+ }
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
+ }
}
+ }
+ }
+ }
+
+ return data;
+ }
+
+ private void FetchInfoFromBannerNode(FanartXmlData data, XmlReader reader, int backdropLimit)
+ {
+ reader.MoveToContent();
+
+ string type = null;
+ string url = null;
+
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "BannerType":
+ {
+ type = reader.ReadElementContentAsString() ?? string.Empty;
+
+ if (string.Equals(type, "poster", StringComparison.OrdinalIgnoreCase))
+ {
+ // Already got it
+ if (!string.IsNullOrEmpty(data.Poster))
+ {
+ return;
+ }
+ }
+ else if (string.Equals(type, "series", StringComparison.OrdinalIgnoreCase))
+ {
+ // Already got it
+ if (!string.IsNullOrEmpty(data.Banner))
+ {
+ return;
+ }
+ }
+ else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase))
+ {
+ if (data.Backdrops.Count >= backdropLimit)
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
- if (series.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) break;
+ break;
+ }
+
+ case "BannerPath":
+ {
+ url = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
}
}
}
+
+ if (!string.IsNullOrEmpty(url))
+ {
+ if (string.Equals(type, "poster", StringComparison.OrdinalIgnoreCase))
+ {
+ data.Poster = url;
+ }
+ else if (string.Equals(type, "series", StringComparison.OrdinalIgnoreCase))
+ {
+ data.Banner = url;
+ }
+ else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase))
+ {
+ data.Backdrops.Add(url);
+ }
+ }
}
}
+
+ internal class FanartXmlData
+ {
+ public string LanguagePoster { get; set; }
+ public string LanguageBanner { get; set; }
+ public string Poster { get; set; }
+ public string Banner { get; set; }
+ public List<string> Backdrops = new List<string>();
+ }
}