From 0c9344738040a08f1486599d7e67c7350ec71f71 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 15:54:25 -0400 Subject: [PATCH] #606 - Add manual image selection for Seasons --- .../MediaBrowser.Providers.csproj | 1 + .../Movies/FanArtMovieProvider.cs | 7 +- .../Movies/ManualMovieDbImageProvider.cs | 3 +- .../Movies/MovieDbProvider.cs | 3 +- .../Movies/MovieUpdatesPrescanTask.cs | 4 +- .../TV/FanArtSeasonProvider.cs | 17 +- MediaBrowser.Providers/TV/FanArtTVProvider.cs | 64 ++--- .../TV/FanArtTvUpdatesPrescanTask.cs | 20 +- .../TV/ManualFanartSeasonProvider.cs | 234 ++++++++++++++++++ 9 files changed, 267 insertions(+), 86 deletions(-) create mode 100644 MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 6d0ab05f8..ef1aa6777 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -110,6 +110,7 @@ + diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 30fb8c659..24f17556b 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -201,12 +201,9 @@ namespace MediaBrowser.Providers.Movies await DownloadMovieXml(movieId, cancellationToken).ConfigureAwait(false); } - if (File.Exists(xmlPath)) - { - var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false); + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false); - await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); - } + await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); } SetLastRefreshed(item, DateTime.UtcNow); diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs index 4ae15e91f..7c6ede0c2 100644 --- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs @@ -143,7 +143,8 @@ namespace MediaBrowser.Providers.Movies images.backdrops .ToList(); - return eligibleBackdrops.OrderByDescending(i => i.vote_average).ThenByDescending(i => i.vote_count); + return eligibleBackdrops.OrderByDescending(i => i.vote_average) + .ThenByDescending(i => i.vote_count); } /// diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 67cec7498..baec90c66 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -198,8 +198,7 @@ namespace MediaBrowser.Providers.Movies protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { - // Boxsets require two passes because we need the children to be refreshed - if (item is BoxSet && string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) + if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) { return true; } diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs index 4c1838cfc..50e04a9af 100644 --- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs @@ -100,10 +100,8 @@ namespace MediaBrowser.Providers.Movies var timestampFileInfo = new FileInfo(timestampFile); - var refreshDays = _config.Configuration.EnableTmdbUpdates ? 1 : 7; - // Don't check for tvdb updates anymore frequently than 24 hours - if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < refreshDays) + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) { return; } diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index fe316e85b..3d1d3f8b9 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrEmpty(seriesId)) { // Process images - var imagesXmlPath = Path.Combine(FanArtTvProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "fanart.xml"); + var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(seriesId); var imagesFileInfo = new FileInfo(imagesXmlPath); @@ -104,7 +104,7 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrEmpty(seriesId)) { // Process images - var imagesXmlPath = Path.Combine(FanArtTvProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "fanart.xml"); + var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(seriesId); var imagesFileInfo = new FileInfo(imagesXmlPath); @@ -118,19 +118,10 @@ namespace MediaBrowser.Providers.TV await FetchImages(season, xmlDoc, cancellationToken).ConfigureAwait(false); } } - - BaseProviderInfo data; - if (!item.ProviderData.TryGetValue(Id, out data)) - { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } - - SetLastRefreshed(item, DateTime.UtcNow); - return true; } - return false; + SetLastRefreshed(item, DateTime.UtcNow); + return true; } /// diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index af89bc205..e349b82ce 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -82,26 +82,6 @@ namespace MediaBrowser.Providers.TV return false; } - if (!ConfigurationManager.Configuration.DownloadSeriesImages.Art && - !ConfigurationManager.Configuration.DownloadSeriesImages.Logo && - !ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && - !ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && - !ConfigurationManager.Configuration.DownloadSeriesImages.Banner && - !ConfigurationManager.Configuration.DownloadSeriesImages.Primary) - { - return false; - } - - if (item.HasImage(ImageType.Primary) && - item.HasImage(ImageType.Art) && - item.HasImage(ImageType.Logo) && - item.HasImage(ImageType.Banner) && - item.HasImage(ImageType.Thumb) && - item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) - { - return false; - } - return base.NeedsRefreshInternal(item, providerInfo); } @@ -112,28 +92,14 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrEmpty(id)) { // Process images - var path = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, id); + var xmlPath = GetFanartXmlPath(id); - try - { - var files = new DirectoryInfo(path) - .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly) - .Select(i => _fileSystem.GetLastWriteTimeUtc(i)) - .ToList(); + var fileInfo = new FileInfo(xmlPath); - if (files.Count > 0) - { - return files.Max() > providerInfo.LastRefreshed; - } - } - catch (DirectoryNotFoundException) - { - // Don't blow up - return true; - } + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; } - - return false; + + return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); } /// @@ -184,6 +150,12 @@ namespace MediaBrowser.Providers.TV return dataPath; } + + public string GetFanartXmlPath(string tvdbId) + { + var dataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tvdbId); + return Path.Combine(dataPath, "fanart.xml"); + } protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -195,13 +167,12 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrEmpty(seriesId)) { - var seriesDataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId); - var xmlPath = Path.Combine(seriesDataPath, "fanart.xml"); + var xmlPath = GetFanartXmlPath(seriesId); // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates if (!File.Exists(xmlPath)) { - await DownloadSeriesXml(seriesDataPath, seriesId, cancellationToken).ConfigureAwait(false); + await DownloadSeriesXml(seriesId, cancellationToken).ConfigureAwait(false); } if (File.Exists(xmlPath)) @@ -334,19 +305,18 @@ namespace MediaBrowser.Providers.TV /// /// Downloads the series XML. /// - /// The series data path. /// The TVDB id. /// The cancellation token. /// Task. - internal async Task DownloadSeriesXml(string seriesDataPath, string tvdbId, CancellationToken cancellationToken) + internal async Task DownloadSeriesXml(string tvdbId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - string url = string.Format(FanArtBaseUrl, ApiKey, tvdbId); + var url = string.Format(FanArtBaseUrl, ApiKey, tvdbId); - var xmlPath = Path.Combine(seriesDataPath, "fanart.xml"); + var xmlPath = GetFanartXmlPath(tvdbId); - Directory.CreateDirectory(seriesDataPath); + Directory.CreateDirectory(Path.GetDirectoryName(xmlPath)); using (var response = await HttpClient.Get(new HttpRequestOptions { diff --git a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs index 5c1c7a69d..4bc7c3c4f 100644 --- a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs @@ -86,7 +86,7 @@ namespace MediaBrowser.Providers.TV progress.Report(5); - await UpdateSeries(seriesToUpdate, path, progress, cancellationToken).ConfigureAwait(false); + await UpdateSeries(seriesToUpdate, progress, cancellationToken).ConfigureAwait(false); } var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture); @@ -138,18 +138,19 @@ namespace MediaBrowser.Providers.TV /// Updates the series. /// /// The id list. - /// The artists data path. /// The progress. /// The cancellation token. /// Task. - private async Task UpdateSeries(IEnumerable idList, string seriesDataPath, IProgress progress, CancellationToken cancellationToken) + private async Task UpdateSeries(IEnumerable idList, IProgress progress, CancellationToken cancellationToken) { var list = idList.ToList(); var numComplete = 0; foreach (var id in list) { - await UpdateSeries(id, seriesDataPath, cancellationToken).ConfigureAwait(false); + _logger.Info("Updating series " + id); + + await FanArtTvProvider.Current.DownloadSeriesXml(id, cancellationToken).ConfigureAwait(false); numComplete++; double percent = numComplete; @@ -160,17 +161,6 @@ namespace MediaBrowser.Providers.TV } } - private Task UpdateSeries(string tvdbId, string seriesDataPath, CancellationToken cancellationToken) - { - _logger.Info("Updating series " + tvdbId); - - seriesDataPath = Path.Combine(seriesDataPath, tvdbId); - - Directory.CreateDirectory(seriesDataPath); - - return FanArtTvProvider.Current.DownloadSeriesXml(seriesDataPath, tvdbId, cancellationToken); - } - /// /// Dates the time to unix timestamp. /// diff --git a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs new file mode 100644 index 000000000..71b96f037 --- /dev/null +++ b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs @@ -0,0 +1,234 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace MediaBrowser.Providers.TV +{ + public class ManualFanartSeasonImageProvider : IImageProvider + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IServerConfigurationManager _config; + + public ManualFanartSeasonImageProvider(IServerConfigurationManager config) + { + _config = config; + } + + public string Name + { + get { return ProviderName; } + } + + public static string ProviderName + { + get { return "FanArt"; } + } + + public bool Supports(BaseItem item) + { + return item is Season; + } + + public async Task> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) + { + var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); + + return images.Where(i => i.Type == imageType); + } + + public Task> GetAllImages(BaseItem item, CancellationToken cancellationToken) + { + var list = new List(); + + var series = ((Season) item).Series; + + if (series == null) + { + return Task.FromResult>(list); + } + + var id = series.GetProviderId(MetadataProviders.Tvdb); + + if (!string.IsNullOrEmpty(id) && item.IndexNumber.HasValue) + { + var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id); + + try + { + AddImages(list, item.IndexNumber.Value, xmlPath, cancellationToken); + } + catch (FileNotFoundException) + { + // No biggie. Don't blow up + } + } + + var language = _config.Configuration.PreferredMetadataLanguage; + + var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); + + // Sort first by width to prioritize HD versions + list = list.OrderByDescending(i => i.Width ?? 0) + .ThenByDescending(i => + { + if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (!isLanguageEn) + { + if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + } + if (string.IsNullOrEmpty(i.Language)) + { + return isLanguageEn ? 3 : 2; + } + return 0; + }) + .ThenByDescending(i => i.CommunityRating ?? 0) + .ToList(); + + return Task.FromResult>(list); + } + + private void AddImages(List list, int seasonNumber, string xmlPath, CancellationToken cancellationToken) + { + using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8)) + { + // Use XmlReader for best performance + using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings + { + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true, + ValidationType = ValidationType.None + })) + { + reader.MoveToContent(); + + // Loop through each element + while (reader.Read()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "series": + { + using (var subReader = reader.ReadSubtree()) + { + AddImages(list, subReader, seasonNumber, cancellationToken); + } + break; + } + + default: + reader.Skip(); + break; + } + } + } + } + } + } + + private void AddImages(List list, XmlReader reader, int seasonNumber, CancellationToken cancellationToken) + { + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "seasonthumbs": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Thumb, 500, 281, seasonNumber); + } + break; + } + default: + reader.Skip(); + break; + } + } + } + } + + private void PopulateImageCategory(List list, XmlReader reader, CancellationToken cancellationToken, ImageType type, int width, int height, int seasonNumber) + { + reader.MoveToContent(); + + while (reader.Read()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "seasonthumb": + { + var url = reader.GetAttribute("url"); + var season = reader.GetAttribute("season"); + + if (!string.IsNullOrEmpty(url) && string.Equals(season, seasonNumber.ToString(_usCulture))) + { + var likesString = reader.GetAttribute("likes"); + int likes; + + var info = new RemoteImageInfo + { + RatingType = RatingType.Likes, + Type = type, + Width = width, + Height = height, + ProviderName = Name, + Url = url, + Language = reader.GetAttribute("lang") + }; + + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) + { + info.CommunityRating = likes; + } + + list.Add(info); + } + break; + } + default: + reader.Skip(); + break; + } + } + } + } + + public int Priority + { + get { return 1; } + } + } +}