From a2c6f687aea4164091b78b554372d523edeeb7fd Mon Sep 17 00:00:00 2001 From: tikuf Date: Mon, 28 Oct 2013 15:01:35 +1100 Subject: [PATCH 01/12] Move depreciated audio re-sample into audio filters --- .../Playback/Hls/VideoHlsService.cs | 15 +++++---- .../Playback/Progressive/VideoService.cs | 33 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index ecc53ce349..f6bad78bf8 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.MediaInfo; +using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -79,11 +79,6 @@ namespace MediaBrowser.Api.Playback.Hls args += " -ac " + channels.Value; } - if (state.Request.AudioSampleRate.HasValue) - { - args += " -ar " + state.Request.AudioSampleRate.Value; - } - var bitrate = GetAudioBitrateParam(state); if (bitrate.HasValue) @@ -92,14 +87,20 @@ namespace MediaBrowser.Api.Playback.Hls } var volParam = string.Empty; + var AudioSampleRate = string.Empty; // Boost volume to 200% when downsampling from 6ch to 2ch if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) { volParam = ",volume=2.000000"; } + + if (state.Request.AudioSampleRate.HasValue) + { + AudioSampleRate= state.Request.AudioSampleRate.Value + ":"; + } - args += string.Format(" -af \"adelay=1,aresample=async=1000{0}\"", volParam); + args += string.Format(" -af \"adelay=1,aresample={0}async=1000{1}\"",AudioSampleRate, volParam); return args; } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index c1dd7fa017..8e106b2d40 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.MediaInfo; +using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -219,11 +219,6 @@ namespace MediaBrowser.Api.Playback.Progressive args += " -ac " + channels.Value; } - if (request.AudioSampleRate.HasValue) - { - args += " -ar " + request.AudioSampleRate.Value; - } - var bitrate = GetAudioBitrateParam(state); if (bitrate.HasValue) @@ -231,19 +226,25 @@ namespace MediaBrowser.Api.Playback.Progressive args += " -ab " + bitrate.Value.ToString(UsCulture); } - var volParam = string.Empty; + var volParam = string.Empty; + var AudioSampleRate = string.Empty; - // Boost volume to 200% when downsampling from 6ch to 2ch - if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) - { - volParam = ",volume=2.000000"; + // Boost volume to 200% when downsampling from 6ch to 2ch + if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) + { + volParam = ",volume=2.000000"; + } + + if (state.Request.AudioSampleRate.HasValue) + { + AudioSampleRate= state.Request.AudioSampleRate.Value + ":"; + } + + args += string.Format(" -af \"aresample={0}async=1000{1}\"",AudioSampleRate, volParam); + + return args; } - args += string.Format(" -af \"aresample=async=1000{0}\"", volParam); - - return args; - } - /// /// Gets the video bitrate to specify on the command line /// From 407b64d0f90a0b6d145b264f198dcb7f33fee95f Mon Sep 17 00:00:00 2001 From: tikuf Date: Wed, 30 Oct 2013 18:00:54 +1100 Subject: [PATCH 02/12] Support Korean Subtitles --- .../MediaEncoder/MediaEncoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index 785bbca7c1..6f4d01e3e2 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Model.Entities; @@ -600,6 +600,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder return "-sub_charenc windows-1251"; case "vie": return "-sub_charenc windows-1258"; + case "kor": + return "-sub_charenc cp949"; default: return "-sub_charenc windows-1252"; } From 9adcdd007afb453cf9b860bfbb49d3b0bb958fa2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 31 Oct 2013 17:03:24 -0400 Subject: [PATCH 03/12] #551 - Add manual image selection for movies --- MediaBrowser.Api/LibraryService.cs | 30 +------ MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Api/RemoteImageService.cs | 82 +++++++++++++++++++ .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../Providers/RemoteImageResult.cs | 22 +++++ .../Providers/ImageSaver.cs | 6 +- MediaBrowser.WebDashboard/ApiClient.js | 18 ++-- MediaBrowser.WebDashboard/packages.config | 2 +- 10 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 MediaBrowser.Api/RemoteImageService.cs create mode 100644 MediaBrowser.Model/Providers/RemoteImageResult.cs diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index 5b133fbd3e..202d372e83 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -5,10 +5,8 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; using System; @@ -34,21 +32,6 @@ namespace MediaBrowser.Api public string Id { get; set; } } - [Route("/Items/{Id}/RemoteImages/{Type}", "GET")] - [Api(Description = "Gets available remote images for an item")] - public class GetRemoteImages : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public ImageType Type { get; set; } - } - /// /// Class GetCriticReviews /// @@ -225,7 +208,6 @@ namespace MediaBrowser.Api private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; - private readonly IProviderManager _providerManager; private readonly IDtoService _dtoService; @@ -233,14 +215,13 @@ namespace MediaBrowser.Api /// Initializes a new instance of the class. /// public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, - IDtoService dtoService, IUserDataManager userDataManager, IProviderManager providerManager) + IDtoService dtoService, IUserDataManager userDataManager) { _itemRepo = itemRepo; _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; _userDataManager = userDataManager; - _providerManager = providerManager; } public object Get(GetFile request) @@ -259,15 +240,6 @@ namespace MediaBrowser.Api return ToStaticFileResult(item.Path); } - public object Get(GetRemoteImages request) - { - var item = _dtoService.GetItemByDtoId(request.Id); - - var result = _providerManager.GetAvailableRemoteImages(item, request.Type, CancellationToken.None).Result; - - return ToOptimizedResult(result); - } - /// /// Gets the specified request. /// diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 21e73361c2..248dc7d6d8 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -100,6 +100,7 @@ + diff --git a/MediaBrowser.Api/RemoteImageService.cs b/MediaBrowser.Api/RemoteImageService.cs new file mode 100644 index 0000000000..b50bf4ffe3 --- /dev/null +++ b/MediaBrowser.Api/RemoteImageService.cs @@ -0,0 +1,82 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using ServiceStack.ServiceHost; +using System.Linq; +using System.Threading; + +namespace MediaBrowser.Api +{ + [Route("/Items/{Id}/RemoteImages/{Type}", "GET")] + [Api(Description = "Gets available remote images for an item")] + public class GetRemoteImages : IReturn + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public ImageType Type { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + public class RemoteImageService : BaseApiService + { + private readonly IProviderManager _providerManager; + + private readonly IDtoService _dtoService; + + public RemoteImageService(IProviderManager providerManager, IDtoService dtoService) + { + _providerManager = providerManager; + _dtoService = dtoService; + } + + public object Get(GetRemoteImages request) + { + var item = _dtoService.GetItemByDtoId(request.Id); + + var images = _providerManager.GetAvailableRemoteImages(item, request.Type, CancellationToken.None).Result; + + var imagesList = images.ToList(); + + var result = new RemoteImageResult + { + TotalRecordCount = imagesList.Count + }; + + if (request.StartIndex.HasValue) + { + imagesList = imagesList.Skip(request.StartIndex.Value) + .ToList(); + } + + if (request.Limit.HasValue) + { + imagesList = imagesList.Take(request.Limit.Value) + .ToList(); + } + + result.Images = imagesList; + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 0f54073ef5..284ac8c90a 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -290,6 +290,9 @@ Providers\RemoteImageInfo.cs + + Providers\RemoteImageResult.cs + Querying\ArtistsQuery.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 3d4b8d2c7d..0f3cfa9704 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -277,6 +277,9 @@ Providers\RemoteImageInfo.cs + + Providers\RemoteImageResult.cs + Querying\ArtistsQuery.cs diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b048dc1c3e..ee78980774 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -82,6 +82,7 @@ + diff --git a/MediaBrowser.Model/Providers/RemoteImageResult.cs b/MediaBrowser.Model/Providers/RemoteImageResult.cs new file mode 100644 index 0000000000..9c3fd94070 --- /dev/null +++ b/MediaBrowser.Model/Providers/RemoteImageResult.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Model.Providers +{ + /// + /// Class RemoteImageResult. + /// + public class RemoteImageResult + { + /// + /// Gets or sets the images. + /// + /// The images. + public List Images { get; set; } + + /// + /// Gets or sets the total record count. + /// + /// The total record count. + public int TotalRecordCount { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index cbca2ba763..7b5c0df34d 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -69,11 +69,11 @@ namespace MediaBrowser.Server.Implementations.Providers throw new ArgumentNullException("mimeType"); } - var saveLocally = _config.Configuration.SaveLocalMeta || item is IItemByName || item is User; + var saveLocally = _config.Configuration.SaveLocalMeta && item.Parent != null && !(item is Audio); - if (item is Audio || item.Parent == null) + if (item is IItemByName || item is User) { - saveLocally = false; + saveLocally = true; } if (type != ImageType.Primary && item is Episode) diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 3606396652..9a75c12be0 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -306,16 +306,20 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; - self.getAvailableRemoteImages = function (itemId, imageType) { + self.getAvailableRemoteImages = function (options) { - if (!itemId) { - throw new Error("null itemId"); - } - if (!imageType) { - throw new Error("null imageType"); + if (!options) { + throw new Error("null options"); } - var url = self.getUrl("Items/" + itemId + "/RemoteImages/" + imageType); + var urlPrefix = "Items/" + options.itemId; + + var imageType = options.imageType; + + delete options.itemId; + delete options.imageType; + + var url = self.getUrl(urlPrefix + "/RemoteImages/" + imageType, options); return self.ajax({ type: "GET", diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 22560b1331..ba7498605a 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file From 926a6100751e38d687111e961209b565bd8a3eb4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 31 Oct 2013 21:48:14 -0400 Subject: [PATCH 04/12] fixes #551 - Add manual image selection for movies --- MediaBrowser.Api/RemoteImageService.cs | 60 +++- .../Providers/IImageProvider.cs | 21 +- .../Providers/IProviderManager.cs | 12 +- MediaBrowser.Model/Dto/IItemDto.cs | 6 + .../Providers/RemoteImageInfo.cs | 9 +- .../Providers/RemoteImageResult.cs | 6 + .../MediaBrowser.Providers.csproj | 1 + .../Movies/FanArtMovieProvider.cs | 53 +--- .../Movies/ManualFanartMovieImageProvider.cs | 291 ++++++++++++++++++ .../Movies/ManualMovieDbImageProvider.cs | 28 +- .../Movies/MovieDbProvider.cs | 13 +- .../Providers/ProviderManager.cs | 53 +++- MediaBrowser.WebDashboard/ApiClient.js | 61 +++- MediaBrowser.WebDashboard/packages.config | 2 +- 14 files changed, 519 insertions(+), 97 deletions(-) create mode 100644 MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs diff --git a/MediaBrowser.Api/RemoteImageService.cs b/MediaBrowser.Api/RemoteImageService.cs index b50bf4ffe3..c5f1005c75 100644 --- a/MediaBrowser.Api/RemoteImageService.cs +++ b/MediaBrowser.Api/RemoteImageService.cs @@ -5,10 +5,11 @@ using MediaBrowser.Model.Providers; using ServiceStack.ServiceHost; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Api { - [Route("/Items/{Id}/RemoteImages/{Type}", "GET")] + [Route("/Items/{Id}/RemoteImages", "GET")] [Api(Description = "Gets available remote images for an item")] public class GetRemoteImages : IReturn { @@ -19,8 +20,8 @@ namespace MediaBrowser.Api [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public ImageType Type { get; set; } + [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public ImageType? Type { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -35,6 +36,30 @@ namespace MediaBrowser.Api /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } + + [ApiMember(Name = "ProviderName", Description = "Optional. The image provider to use", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ProviderName { get; set; } + } + + [Route("/Items/{Id}/RemoteImages/Download", "POST")] + [Api(Description = "Downloads a remote image for an item")] + public class DownloadRemoteImage : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public ImageType Type { get; set; } + + [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ProviderName { get; set; } + + [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ImageUrl { get; set; } } public class RemoteImageService : BaseApiService @@ -53,13 +78,14 @@ namespace MediaBrowser.Api { var item = _dtoService.GetItemByDtoId(request.Id); - var images = _providerManager.GetAvailableRemoteImages(item, request.Type, CancellationToken.None).Result; + var images = _providerManager.GetAvailableRemoteImages(item, CancellationToken.None, request.ProviderName, request.Type).Result; var imagesList = images.ToList(); var result = new RemoteImageResult { - TotalRecordCount = imagesList.Count + TotalRecordCount = imagesList.Count, + Providers = _providerManager.GetImageProviders(item).Select(i => i.Name).ToList() }; if (request.StartIndex.HasValue) @@ -78,5 +104,29 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + + public void Post(DownloadRemoteImage request) + { + var task = DownloadRemoteImage(request); + + Task.WaitAll(task); + } + + private async Task DownloadRemoteImage(DownloadRemoteImage request) + { + var item = _dtoService.GetItemByDtoId(request.Id); + + int? index = null; + + if (request.Type == ImageType.Backdrop) + { + index = item.BackdropImagePaths.Count; + } + + await _providerManager.SaveImage(item, request.ImageUrl, null, request.Type, index, CancellationToken.None).ConfigureAwait(false); + + await item.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false) + .ConfigureAwait(false); + } } } diff --git a/MediaBrowser.Controller/Providers/IImageProvider.cs b/MediaBrowser.Controller/Providers/IImageProvider.cs index 0764794388..d70532b592 100644 --- a/MediaBrowser.Controller/Providers/IImageProvider.cs +++ b/MediaBrowser.Controller/Providers/IImageProvider.cs @@ -22,17 +22,30 @@ namespace MediaBrowser.Controller.Providers /// Supportses the specified item. /// /// The item. - /// Type of the image. /// true if XXXX, false otherwise - bool Supports(BaseItem item, ImageType imageType); + bool Supports(BaseItem item); /// - /// Gets the available images. + /// Gets the images. /// /// The item. /// Type of the image. /// The cancellation token. /// Task{IEnumerable{RemoteImageInfo}}. - Task> GetAvailableImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken); + Task> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken); + + /// + /// Gets the images. + /// + /// The item. + /// The cancellation token. + /// Task{IEnumerable{RemoteImageInfo}}. + Task> GetAllImages(BaseItem item, CancellationToken cancellationToken); + + /// + /// Gets the priority. + /// + /// The priority. + int Priority { get; } } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 2eb2be6db1..728030ecc9 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -60,9 +60,17 @@ namespace MediaBrowser.Controller.Providers /// Gets the available remote images. /// /// The item. - /// The type. /// The cancellation token. + /// Name of the provider. + /// The type. /// Task{IEnumerable{RemoteImageInfo}}. - Task> GetAvailableRemoteImages(BaseItem item, ImageType type, CancellationToken cancellationToken); + Task> GetAvailableRemoteImages(BaseItem item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null); + + /// + /// Gets the image providers. + /// + /// The item. + /// IEnumerable{IImageProvider}. + IEnumerable GetImageProviders(BaseItem item); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dto/IItemDto.cs b/MediaBrowser.Model/Dto/IItemDto.cs index af46d29b9f..3ec6419184 100644 --- a/MediaBrowser.Model/Dto/IItemDto.cs +++ b/MediaBrowser.Model/Dto/IItemDto.cs @@ -18,4 +18,10 @@ namespace MediaBrowser.Model.Dto /// The original primary image aspect ratio. double? OriginalPrimaryImageAspectRatio { get; set; } } + + public enum RatingType + { + Score, + Likes + } } diff --git a/MediaBrowser.Model/Providers/RemoteImageInfo.cs b/MediaBrowser.Model/Providers/RemoteImageInfo.cs index 1a281f07d2..9a16d89d35 100644 --- a/MediaBrowser.Model/Providers/RemoteImageInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteImageInfo.cs @@ -1,4 +1,4 @@ - +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Providers @@ -55,5 +55,12 @@ namespace MediaBrowser.Model.Providers /// /// The type. public ImageType Type { get; set; } + + /// + /// Gets or sets the type of the rating. + /// + /// The type of the rating. + public RatingType RatingType { get; set; } } + } diff --git a/MediaBrowser.Model/Providers/RemoteImageResult.cs b/MediaBrowser.Model/Providers/RemoteImageResult.cs index 9c3fd94070..1c60db6ae1 100644 --- a/MediaBrowser.Model/Providers/RemoteImageResult.cs +++ b/MediaBrowser.Model/Providers/RemoteImageResult.cs @@ -18,5 +18,11 @@ namespace MediaBrowser.Model.Providers /// /// The total record count. public int TotalRecordCount { get; set; } + + /// + /// Gets or sets the providers. + /// + /// The providers. + public List Providers { get; set; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 7a8975937d..e944787c6d 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -61,6 +61,7 @@ + diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 3458622d34..355d3df0d1 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -35,11 +34,6 @@ namespace MediaBrowser.Providers.Movies /// private readonly IProviderManager _providerManager; - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - internal static FanArtMovieProvider Current { get; private set; } private readonly IFileSystem _fileSystem; @@ -139,28 +133,6 @@ namespace MediaBrowser.Providers.Movies return false; } - if (!ConfigurationManager.Configuration.DownloadMovieImages.Art && - !ConfigurationManager.Configuration.DownloadMovieImages.Logo && - !ConfigurationManager.Configuration.DownloadMovieImages.Disc && - !ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && - !ConfigurationManager.Configuration.DownloadMovieImages.Banner && - !ConfigurationManager.Configuration.DownloadMovieImages.Thumb && - !ConfigurationManager.Configuration.DownloadMovieImages.Primary) - { - return false; - } - - if (item.HasImage(ImageType.Primary) && - item.HasImage(ImageType.Art) && - item.HasImage(ImageType.Logo) && - item.HasImage(ImageType.Disc) && - item.HasImage(ImageType.Banner) && - item.HasImage(ImageType.Thumb) && - item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) - { - return false; - } - return base.NeedsRefreshInternal(item, providerInfo); } @@ -171,24 +143,11 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(id)) { // Process images - var path = GetMovieDataPath(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) - { - return true; - } + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; } return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); @@ -253,6 +212,12 @@ namespace MediaBrowser.Providers.Movies return true; } + public string GetFanartXmlPath(string tmdbId) + { + var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId); + return Path.Combine(movieDataPath, "fanart.xml"); + } + /// /// Downloads the movie XML. /// diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs new file mode 100644 index 0000000000..85dd13936a --- /dev/null +++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs @@ -0,0 +1,291 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +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.Movies +{ + public class ManualFanartMovieImageProvider : IImageProvider + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IServerConfigurationManager _config; + + public ManualFanartMovieImageProvider(IServerConfigurationManager config) + { + _config = config; + } + + public string Name + { + get { return "FanArt"; } + } + + public bool Supports(BaseItem item) + { + return FanArtMovieProvider.Current.Supports(item); + } + + 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 movieId = item.GetProviderId(MetadataProviders.Tmdb); + + if (!string.IsNullOrEmpty(movieId)) + { + var xmlPath = FanArtMovieProvider.Current.GetFanartXmlPath(movieId); + + try + { + AddImages(list, xmlPath, cancellationToken); + } + catch (FileNotFoundException) + { + // No biggie. Don't blow up + } + } + + var language = _config.Configuration.PreferredMetadataLanguage; + + var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); + + 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, 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 "movie": + { + using (var subReader = reader.ReadSubtree()) + { + AddImages(list, subReader, cancellationToken); + } + break; + } + + default: + reader.Skip(); + break; + } + } + } + } + } + } + + private void AddImages(List list, XmlReader reader, CancellationToken cancellationToken) + { + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "hdmoviecleararts": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Art, 1000, 562); + } + break; + } + case "hdmovielogos": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Logo, 800, 310); + } + break; + } + case "moviediscs": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Disc, 1000, 1000); + } + break; + } + case "movieposters": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Primary, 1000, 1426); + } + break; + } + case "movielogos": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Logo, 400, 155); + } + break; + } + case "moviearts": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Art, 500, 281); + } + break; + } + case "moviethumbs": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Thumb, 1000, 562); + } + break; + } + case "moviebanners": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Banner, 1000, 185); + } + break; + } + case "moviebackgrounds": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Backdrop, 1920, 1080); + } + break; + } + default: + reader.Skip(); + break; + } + } + } + } + + private void PopulateImageCategory(List list, XmlReader reader, CancellationToken cancellationToken, ImageType type, int width, int height) + { + reader.MoveToContent(); + + while (reader.Read()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "hdmovielogo": + case "moviedisc": + case "hdmovieclearart": + case "movieposter": + case "movielogo": + case "movieart": + case "moviethumb": + case "moviebanner": + case "moviebackground": + { + var url = reader.GetAttribute("url"); + + if (!string.IsNullOrEmpty(url)) + { + 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; } + } + } +} diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs index f84845af7d..c02c60f754 100644 --- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; @@ -26,20 +27,15 @@ namespace MediaBrowser.Providers.Movies public string Name { - get { return "TheMovieDB"; } + get { return "TheMovieDb"; } } - public bool Supports(BaseItem item, ImageType imageType) + public bool Supports(BaseItem item) { - if (MovieDbImagesProvider.SupportsItem(item)) - { - return imageType == ImageType.Primary || imageType == ImageType.Backdrop; - } - - return false; + return MovieDbImagesProvider.SupportsItem(item); } - public async Task> GetAvailableImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) + public async Task> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) { var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); @@ -70,7 +66,8 @@ namespace MediaBrowser.Providers.Movies Height = i.height, Language = i.iso_639_1, ProviderName = Name, - Type = ImageType.Primary + Type = ImageType.Primary, + RatingType = RatingType.Score })); list.AddRange(GetBackdrops(results, item).Select(i => new RemoteImageInfo @@ -81,7 +78,8 @@ namespace MediaBrowser.Providers.Movies Width = i.width, Height = i.height, ProviderName = Name, - Type = ImageType.Backdrop + Type = ImageType.Backdrop, + RatingType = RatingType.Score })); return list; @@ -124,6 +122,7 @@ namespace MediaBrowser.Providers.Movies return 0; }) .ThenByDescending(i => i.vote_average) + .ThenByDescending(i => i.vote_count) .ToList(); } @@ -139,7 +138,7 @@ namespace MediaBrowser.Providers.Movies images.backdrops.Where(i => i.width >= _config.Configuration.MinMovieBackdropWidth) .ToList(); - return eligibleBackdrops.OrderByDescending(i => i.vote_average); + return eligibleBackdrops.OrderByDescending(i => i.vote_average).ThenByDescending(i => i.vote_count); } /// @@ -164,5 +163,10 @@ namespace MediaBrowser.Providers.Movies return null; } + + public int Priority + { + get { return 2; } + } } } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index d7b7faeea7..50adda6bbe 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -216,16 +216,13 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(path)) { var fileInfo = new FileInfo(path); + var defaultFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(path), "default.json")); - if (fileInfo.Exists) - { - return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; - } - - return true; + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed || + !defaultFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(defaultFileInfo) > providerInfo.LastRefreshed; } - return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); + return true; } /// @@ -510,7 +507,7 @@ namespace MediaBrowser.Providers.Movies var dataFilePath = GetDataFilePath(item, language); - if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath)) + if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(Path.Combine(Path.GetDirectoryName(dataFilePath), "default.json"))) { var isBoxSet = item is BoxSet; diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index af89122db1..0252373f00 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -7,13 +7,13 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Providers; namespace MediaBrowser.Server.Implementations.Providers { @@ -77,7 +77,7 @@ namespace MediaBrowser.Server.Implementations.Providers { MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); - ImageProviders = imageProviders.ToArray(); + ImageProviders = imageProviders.OrderByDescending(i => i.Priority).ToArray(); } /// @@ -356,52 +356,79 @@ namespace MediaBrowser.Server.Implementations.Providers /// Gets the available remote images. /// /// The item. - /// The type. /// The cancellation token. + /// Name of the provider. + /// The type. /// Task{IEnumerable{RemoteImageInfo}}. - public async Task> GetAvailableRemoteImages(BaseItem item, ImageType type, CancellationToken cancellationToken) + public async Task> GetAvailableRemoteImages(BaseItem item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null) { - var providers = GetSupportedImageProviders(item, type); + var providers = GetImageProviders(item); + + if (!string.IsNullOrEmpty(providerName)) + { + providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)); + } + + var preferredLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage; var tasks = providers.Select(i => Task.Run(async () => { try { - var result = await i.GetAvailableImages(item, type, cancellationToken).ConfigureAwait(false); - return result.ToList(); + if (type.HasValue) + { + var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false); + + return FilterImages(result, preferredLanguage); + } + else + { + var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false); + return FilterImages(result, preferredLanguage); + } } catch (Exception ex) { - _logger.ErrorException("{0} failed in GetAvailableImages for type {1}", ex, i.GetType().Name, item.GetType().Name); + _logger.ErrorException("{0} failed in GetImages for type {1}", ex, i.GetType().Name, item.GetType().Name); return new List(); } - })); + + }, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); return results.SelectMany(i => i); } + private IEnumerable FilterImages(IEnumerable images, string preferredLanguage) + { + if (string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase)) + { + images = images.Where(i => string.IsNullOrEmpty(i.Language) || + string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase)); + } + + return images; + } + /// /// Gets the supported image providers. /// /// The item. - /// The type. /// IEnumerable{IImageProvider}. - private IEnumerable GetSupportedImageProviders(BaseItem item, ImageType type) + public IEnumerable GetImageProviders(BaseItem item) { return ImageProviders.Where(i => { try { - return i.Supports(item, type); + return i.Supports(item); } catch (Exception ex) { _logger.ErrorException("{0} failed in Supports for type {1}", ex, i.GetType().Name, item.GetType().Name); return false; } - }); } } diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 9a75c12be0..25cbc98779 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -305,6 +305,42 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi url: url }); }; + + function getRemoteImagePrefix(options) { + + var urlPrefix; + + if (options.artist) { + urlPrefix = "Artists/" + encodeName(options.artist); + delete options.artist; + } + else if (options.person) { + urlPrefix = "Persons/" + encodeName(options.person); + delete options.person; + } + else if (options.genre) { + urlPrefix = "Genres/" + encodeName(options.genre); + delete options.genre; + } + else if (options.musicGenre) { + urlPrefix = "MusicGenres/" + encodeName(options.musicGenre); + delete options.musicGenre; + } + else if (options.gameGenre) { + urlPrefix = "GameGenres/" + encodeName(options.gameGenre); + delete options.gameGenre; + } + else if (options.studio) { + urlPrefix = "Studios/" + encodeName(options.studio); + delete options.studio; + } + else { + urlPrefix = "Items/" + options.itemId; + delete options.itemId; + } + + return urlPrefix; + } self.getAvailableRemoteImages = function (options) { @@ -312,14 +348,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi throw new Error("null options"); } - var urlPrefix = "Items/" + options.itemId; + var urlPrefix = getRemoteImagePrefix(options); - var imageType = options.imageType; - - delete options.itemId; - delete options.imageType; - - var url = self.getUrl(urlPrefix + "/RemoteImages/" + imageType, options); + var url = self.getUrl(urlPrefix + "/RemoteImages", options); return self.ajax({ type: "GET", @@ -328,6 +359,22 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + self.downloadRemoteImage = function (options) { + + if (!options) { + throw new Error("null options"); + } + + var urlPrefix = getRemoteImagePrefix(options); + + var url = self.getUrl(urlPrefix + "/RemoteImages/Download", options); + + return self.ajax({ + type: "POST", + url: url + }); + }; + /** * Gets the current server status */ diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index ba7498605a..fb2cf4df5b 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file From 142d0e5f48fcbd6d93896ba59b4363fd6b112b4c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 11:55:25 -0400 Subject: [PATCH 05/12] fixes #607 - Add manual image selection for episodes --- .../MediaBrowser.Providers.csproj | 1 + .../Movies/FanArtMovieProvider.cs | 139 ++++++--------- .../Movies/FanArtMovieUpdatesPrescanTask.cs | 19 +- .../Movies/ManualFanartMovieImageProvider.cs | 6 + .../Movies/ManualMovieDbImageProvider.cs | 11 +- .../Movies/MovieDbImagesProvider.cs | 23 +-- .../Movies/MovieDbProvider.cs | 37 ++-- .../TV/ManualTvdbEpisodeImageProvider.cs | 166 ++++++++++++++++++ .../TV/RemoteEpisodeProvider.cs | 5 +- 9 files changed, 269 insertions(+), 138 deletions(-) create mode 100644 MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index e944787c6d..6d0ab05f8e 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 355d3df0d1..30fb8c659b 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -8,13 +8,13 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; using System; -using System.Globalization; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml; namespace MediaBrowser.Providers.Movies { @@ -193,18 +193,19 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(movieId)) { - var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, movieId); - var xmlPath = Path.Combine(movieDataPath, "fanart.xml"); + var xmlPath = GetFanartXmlPath(movieId); // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates if (!File.Exists(xmlPath)) { - await DownloadMovieXml(movieDataPath, movieId, cancellationToken).ConfigureAwait(false); + await DownloadMovieXml(movieId, cancellationToken).ConfigureAwait(false); } if (File.Exists(xmlPath)) { - await FetchFromXml(item, xmlPath, cancellationToken).ConfigureAwait(false); + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false); + + await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); } } @@ -221,19 +222,18 @@ namespace MediaBrowser.Providers.Movies /// /// Downloads the movie XML. /// - /// The movie data path. /// The TMDB id. /// The cancellation token. /// Task. - internal async Task DownloadMovieXml(string movieDataPath, string tmdbId, CancellationToken cancellationToken) + internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - string url = string.Format(FanArtBaseUrl, ApiKey, tmdbId); + var url = string.Format(FanArtBaseUrl, ApiKey, tmdbId); - var xmlPath = Path.Combine(movieDataPath, "fanart.xml"); + var xmlPath = GetFanartXmlPath(tmdbId); - Directory.CreateDirectory(movieDataPath); + Directory.CreateDirectory(Path.GetDirectoryName(xmlPath)); using (var response = await HttpClient.Get(new HttpRequestOptions { @@ -250,82 +250,53 @@ namespace MediaBrowser.Providers.Movies } } - /// - /// Fetches from XML. - /// - /// The item. - /// The XML file path. - /// The cancellation token. - /// Task. - private async Task FetchFromXml(BaseItem item, string xmlFilePath, CancellationToken cancellationToken) + private async Task FetchImages(BaseItem item, List images, CancellationToken cancellationToken) { - var doc = new XmlDocument(); - doc.Load(xmlFilePath); - - var language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower(); - cancellationToken.ThrowIfCancellationRequested(); - string path; - if (ConfigurationManager.Configuration.DownloadMovieImages.Primary && !item.HasImage(ImageType.Primary)) { - var node = doc.SelectSingleNode("//fanart/movie/movieposters/movieposter[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/movieposters/movieposter/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Primary, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); } } cancellationToken.ThrowIfCancellationRequested(); - + if (ConfigurationManager.Configuration.DownloadMovieImages.Logo && !item.HasImage(ImageType.Logo)) { - var node = - doc.SelectSingleNode("//fanart/movie/hdmovielogos/hdmovielogo[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/movielogos/movielogo[@lang = \"" + language + "\"]/@url"); - if (node == null && language != "en") + var image = images.FirstOrDefault(i => i.Type == ImageType.Logo); + + if (image != null) { - //maybe just couldn't find language - try just first one - node = doc.SelectSingleNode("//fanart/movie/hdmovielogos/hdmovielogo/@url") ?? - doc.SelectSingleNode("//fanart/movie/movielogos/movielogo/@url"); - } - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); } } + cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Art && !item.HasImage(ImageType.Art)) { - var node = - doc.SelectSingleNode("//fanart/movie/hdmoviecleararts/hdmovieclearart[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/hdmoviecleararts/hdmovieclearart/@url") ?? - doc.SelectSingleNode("//fanart/movie/moviearts/movieart[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/moviearts/movieart/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Art); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Art, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Art, null, cancellationToken).ConfigureAwait(false); } } + cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Disc && !item.HasImage(ImageType.Disc)) { - var node = doc.SelectSingleNode("//fanart/movie/moviediscs/moviedisc[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/moviediscs/moviedisc/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Disc); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Disc, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Disc, null, cancellationToken).ConfigureAwait(false); } } @@ -333,13 +304,11 @@ namespace MediaBrowser.Providers.Movies if (ConfigurationManager.Configuration.DownloadMovieImages.Banner && !item.HasImage(ImageType.Banner)) { - var node = doc.SelectSingleNode("//fanart/movie/moviebanners/moviebanner[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/moviebanners/moviebanner/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Banner); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Banner, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Banner, null, cancellationToken).ConfigureAwait(false); } } @@ -347,40 +316,30 @@ namespace MediaBrowser.Providers.Movies if (ConfigurationManager.Configuration.DownloadMovieImages.Thumb && !item.HasImage(ImageType.Thumb)) { - var node = doc.SelectSingleNode("//fanart/movie/moviethumbs/moviethumb[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/movie/moviethumbs/moviethumb/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Thumb); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Thumb, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Thumb, null, cancellationToken).ConfigureAwait(false); } } + cancellationToken.ThrowIfCancellationRequested(); + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; - if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) + if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && + item.BackdropImagePaths.Count < backdropLimit) { - var nodes = doc.SelectNodes("//fanart/movie/moviebackgrounds//@url"); + var numBackdrops = item.BackdropImagePaths.Count; - if (nodes != null) + foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) { - var numBackdrops = item.BackdropImagePaths.Count; + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) + .ConfigureAwait(false); - foreach (XmlNode node in nodes) - { - path = node.Value; - - if (!string.IsNullOrEmpty(path) && !item.ContainsImageWithSourceUrl(path)) - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) - .ConfigureAwait(false); - - numBackdrops++; - - if (item.BackdropImagePaths.Count >= backdropLimit) break; - } - } + numBackdrops++; + if (item.BackdropImagePaths.Count >= backdropLimit) break; } } } diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs index 706dffa7e0..cc22939689 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs @@ -86,7 +86,7 @@ namespace MediaBrowser.Providers.Movies progress.Report(5); - await UpdateMovies(moviesToUpdate, path, progress, cancellationToken).ConfigureAwait(false); + await UpdateMovies(moviesToUpdate, progress, cancellationToken).ConfigureAwait(false); } var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture); @@ -127,14 +127,16 @@ namespace MediaBrowser.Providers.Movies } } - private async Task UpdateMovies(IEnumerable idList, string moviesDataPath, IProgress progress, CancellationToken cancellationToken) + private async Task UpdateMovies(IEnumerable idList, IProgress progress, CancellationToken cancellationToken) { var list = idList.ToList(); var numComplete = 0; foreach (var id in list) { - await UpdateMovie(id, moviesDataPath, cancellationToken).ConfigureAwait(false); + _logger.Info("Updating movie " + id); + + await FanArtMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false); numComplete++; double percent = numComplete; @@ -145,17 +147,6 @@ namespace MediaBrowser.Providers.Movies } } - private Task UpdateMovie(string tmdbId, string movieDataPath, CancellationToken cancellationToken) - { - _logger.Info("Updating movie " + tmdbId); - - movieDataPath = Path.Combine(movieDataPath, tmdbId); - - Directory.CreateDirectory(movieDataPath); - - return FanArtMovieProvider.Current.DownloadMovieXml(movieDataPath, tmdbId, cancellationToken); - } - /// /// Dates the time to unix timestamp. /// diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs index 85dd13936a..64843de993 100644 --- a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs @@ -27,6 +27,11 @@ namespace MediaBrowser.Providers.Movies } public string Name + { + get { return ProviderName; } + } + + public static string ProviderName { get { return "FanArt"; } } @@ -67,6 +72,7 @@ namespace MediaBrowser.Providers.Movies 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 => { diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs index c02c60f754..4ae15e91f4 100644 --- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs @@ -26,6 +26,11 @@ namespace MediaBrowser.Providers.Movies } public string Name + { + get { return ProviderName; } + } + + public static string ProviderName { get { return "TheMovieDb"; } } @@ -99,7 +104,7 @@ namespace MediaBrowser.Providers.Movies var eligiblePosters = images.posters == null ? new List() : - images.posters.Where(i => i.width >= _config.Configuration.MinMoviePosterWidth) + images.posters .ToList(); return eligiblePosters.OrderByDescending(i => @@ -135,7 +140,7 @@ namespace MediaBrowser.Providers.Movies private IEnumerable GetBackdrops(MovieDbProvider.Images images, BaseItem item) { var eligibleBackdrops = images.backdrops == null ? new List() : - images.backdrops.Where(i => i.width >= _config.Configuration.MinMovieBackdropWidth) + images.backdrops .ToList(); return eligibleBackdrops.OrderByDescending(i => i.vote_average).ThenByDescending(i => i.vote_count); @@ -149,7 +154,7 @@ namespace MediaBrowser.Providers.Movies /// Task{MovieImages}. private MovieDbProvider.Images FetchImages(BaseItem item, IJsonSerializer jsonSerializer) { - var path = MovieDbProvider.Current.GetDataFilePath(item, "default"); + var path = MovieDbProvider.Current.GetImagesDataFilePath(item); if (!string.IsNullOrEmpty(path)) { diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs index e34cbc54f0..6c503ad3ac 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.IO; @@ -28,10 +27,6 @@ namespace MediaBrowser.Providers.Movies /// private readonly IProviderManager _providerManager; - /// - /// The _json serializer - /// - private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; /// @@ -40,12 +35,10 @@ namespace MediaBrowser.Providers.Movies /// The log manager. /// The configuration manager. /// The provider manager. - /// The json serializer. - public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem) + public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem) : base(logManager, configurationManager) { _providerManager = providerManager; - _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; } @@ -149,12 +142,7 @@ namespace MediaBrowser.Providers.Movies protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) { - if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) - { - return false; - } - - var path = MovieDbProvider.Current.GetDataFilePath(item, "default"); + var path = MovieDbProvider.Current.GetImagesDataFilePath(item); if (!string.IsNullOrEmpty(path)) { @@ -182,8 +170,7 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(id)) { - var images = await new ManualMovieDbImageProvider(_jsonSerializer, ConfigurationManager).GetAllImages(item, - cancellationToken).ConfigureAwait(false); + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false); await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); } @@ -204,7 +191,7 @@ namespace MediaBrowser.Providers.Movies cancellationToken.ThrowIfCancellationRequested(); var eligiblePosters = images - .Where(i => i.Type == ImageType.Primary) + .Where(i => i.Type == ImageType.Primary && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MinMoviePosterWidth) .ToList(); // poster @@ -228,7 +215,7 @@ namespace MediaBrowser.Providers.Movies cancellationToken.ThrowIfCancellationRequested(); var eligibleBackdrops = images - .Where(i => i.Type == ImageType.Backdrop) + .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MinMovieBackdropWidth) .ToList(); var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 50adda6bbe..67cec7498e 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -209,17 +209,17 @@ namespace MediaBrowser.Providers.Movies protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) { - var language = ConfigurationManager.Configuration.PreferredMetadataLanguage; - - var path = GetDataFilePath(item, language); + var path = GetDataFilePath(item); if (!string.IsNullOrEmpty(path)) { - var fileInfo = new FileInfo(path); - var defaultFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(path), "default.json")); + var imagesFilePath = GetImagesDataFilePath(item); - return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed || - !defaultFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(defaultFileInfo) > providerInfo.LastRefreshed; + var fileInfo = new FileInfo(path); + var imagesFileInfo = new FileInfo(imagesFilePath); + + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed || + !imagesFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed; } return true; @@ -505,9 +505,9 @@ namespace MediaBrowser.Providers.Movies var language = ConfigurationManager.Configuration.PreferredMetadataLanguage; - var dataFilePath = GetDataFilePath(item, language); + var dataFilePath = GetDataFilePath(item); - if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(Path.Combine(Path.GetDirectoryName(dataFilePath), "default.json"))) + if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(GetImagesDataFilePath(item))) { var isBoxSet = item is BoxSet; @@ -535,7 +535,7 @@ namespace MediaBrowser.Providers.Movies if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item)) { - dataFilePath = GetDataFilePath(item, language); + dataFilePath = GetDataFilePath(item); var mainResult = JsonSerializer.DeserializeFromFile(dataFilePath); @@ -577,10 +577,11 @@ namespace MediaBrowser.Providers.Movies /// Gets the data file path. /// /// The item. - /// The language. /// System.String. - internal string GetDataFilePath(BaseItem item, string language) + internal string GetDataFilePath(BaseItem item) { + var language = ConfigurationManager.Configuration.PreferredMetadataLanguage; + var id = item.GetProviderId(MetadataProviders.Tmdb); if (string.IsNullOrEmpty(id)) @@ -595,6 +596,18 @@ namespace MediaBrowser.Providers.Movies return path; } + internal string GetImagesDataFilePath(BaseItem item) + { + var path = GetDataFilePath(item); + + if (!string.IsNullOrEmpty(path)) + { + path = Path.Combine(Path.GetDirectoryName(path), "default.json"); + } + + return path; + } + /// /// Fetches the main result. /// diff --git a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs new file mode 100644 index 0000000000..3d56b3c716 --- /dev/null +++ b/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs @@ -0,0 +1,166 @@ +using System.Globalization; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System.Collections.Generic; +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 ManualTvdbEpisodeImageProvider : IImageProvider + { + private readonly IServerConfigurationManager _config; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public ManualTvdbEpisodeImageProvider(IServerConfigurationManager config) + { + _config = config; + } + + public string Name + { + get { return "TvDb"; } + } + + public bool Supports(BaseItem item) + { + return item is Episode; + } + + public Task> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) + { + return GetAllImages(item, cancellationToken); + } + + public Task> GetAllImages(BaseItem item, CancellationToken cancellationToken) + { + var episode = (Episode)item; + + var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null; + + if (!string.IsNullOrEmpty(seriesId)) + { + // Process images + var seriesDataPath = RemoteSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId); + + var files = RemoteEpisodeProvider.Current.GetEpisodeXmlFiles(episode, seriesDataPath); + + var result = files.Select(i => GetImageInfo(i, cancellationToken)).Where(i => i != null); + + return Task.FromResult(result); + } + + return Task.FromResult>(new RemoteImageInfo[] { }); + } + + private RemoteImageInfo GetImageInfo(FileInfo xmlFile, CancellationToken cancellationToken) + { + var height = 225; + var width = 400; + var url = string.Empty; + + using (var streamReader = new StreamReader(xmlFile.FullName, 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 "thumb_width": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + { + width = rval; + } + } + break; + } + + case "thumb_height": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + { + height = rval; + } + } + break; + } + + case "filename": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + url = TVUtils.BannerUrl + val; + } + break; + } + + default: + reader.Skip(); + break; + } + } + } + } + } + + if (string.IsNullOrEmpty(url)) + { + return null; + } + + return new RemoteImageInfo + { + Width = width, + Height = height, + ProviderName = Name, + Url = url, + Type = ImageType.Primary + }; + } + + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs index cc6bca0b3f..63b755bf90 100644 --- a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs @@ -39,6 +39,8 @@ namespace MediaBrowser.Providers.TV protected IHttpClient HttpClient { get; private set; } private readonly IFileSystem _fileSystem; + internal static RemoteEpisodeProvider Current; + /// /// Initializes a new instance of the class. /// @@ -52,6 +54,7 @@ namespace MediaBrowser.Providers.TV HttpClient = httpClient; _providerManager = providerManager; _fileSystem = fileSystem; + Current = this; } /// @@ -165,7 +168,7 @@ namespace MediaBrowser.Providers.TV /// The episode. /// The series data path. /// List{FileInfo}. - private List GetEpisodeXmlFiles(Episode episode, string seriesDataPath) + internal List GetEpisodeXmlFiles(Episode episode, string seriesDataPath) { var files = new List(); From 4301440bfdedc893ff29fd205e2d5abb39652cda Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 13:37:27 -0400 Subject: [PATCH 06/12] fix missing seasons being returned when they shouldn't --- MediaBrowser.Api/UserLibrary/ItemsService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 9c3ea7bf04..b6963f8b2b 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -492,12 +492,12 @@ namespace MediaBrowser.Api.UserLibrary private IEnumerable FilterVirtualSeasons(GetItems request, IEnumerable items, User user) { - if (request.IsMissing.HasValue && request.IsUnaired.HasValue) + if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue) { var isMissing = request.IsMissing.Value; - var isUnaired = request.IsUnaired.Value; + var isVirtualUnaired = request.IsVirtualUnaired.Value; - if (!isMissing && !isUnaired) + if (!isMissing && !isVirtualUnaired) { return items.Where(i => { From 0c9344738040a08f1486599d7e67c7350ec71f71 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 15:54:25 -0400 Subject: [PATCH 07/12] #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 6d0ab05f8e..ef1aa67778 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 30fb8c659b..24f17556b4 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 4ae15e91f4..7c6ede0c23 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 67cec7498e..baec90c66a 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 4c1838cfc5..50e04a9afd 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 fe316e85b7..3d1d3f8b92 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 af89bc205e..e349b82ce1 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 5c1c7a69d9..4bc7c3c4f5 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 0000000000..71b96f0372 --- /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; } + } + } +} From d187b3b7dd5e683fc0c96ce212010bf4df6f5f4e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 16:14:21 -0400 Subject: [PATCH 08/12] #606 - Add manual image selection for Seasons --- .../TV/FanArtSeasonProvider.cs | 51 +++++-------------- .../TV/ManualFanartSeasonProvider.cs | 45 ++++++++++------ 2 files changed, 41 insertions(+), 55 deletions(-) diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index 3d1d3f8b92..2a5b16bef4 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -6,11 +6,13 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml; namespace MediaBrowser.Providers.TV { @@ -99,26 +101,10 @@ namespace MediaBrowser.Providers.TV var season = (Season)item; - var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null; + // Process images + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeasonImageProvider.ProviderName).ConfigureAwait(false); - if (!string.IsNullOrEmpty(seriesId)) - { - // Process images - var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(seriesId); - - var imagesFileInfo = new FileInfo(imagesXmlPath); - - if (imagesFileInfo.Exists) - { - if (!season.HasImage(ImageType.Thumb)) - { - var xmlDoc = new XmlDocument(); - xmlDoc.Load(imagesXmlPath); - - await FetchImages(season, xmlDoc, cancellationToken).ConfigureAwait(false); - } - } - } + await FetchImages(season, images.ToList(), cancellationToken).ConfigureAwait(false); SetLastRefreshed(item, DateTime.UtcNow); return true; @@ -128,31 +114,18 @@ namespace MediaBrowser.Providers.TV /// Fetches the images. /// /// The season. - /// The doc. + /// The images. /// The cancellation token. /// Task. - private async Task FetchImages(Season season, XmlDocument doc, CancellationToken cancellationToken) + private async Task FetchImages(Season season, List images, CancellationToken cancellationToken) { - var seasonNumber = season.IndexNumber ?? -1; - - if (seasonNumber == -1) - { - return; - } - - var language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower(); - if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb)) { - var node = doc.SelectSingleNode("//fanart/series/seasonthumbs/seasonthumb[@lang = \"" + language + "\"][@season = \"" + seasonNumber + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/seasonthumbs/seasonthumb[@season = \"" + seasonNumber + "\"]/@url"); - - var path = node != null ? node.Value : null; - - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Thumb); + + if (image != null) { - await _providerManager.SaveImage(season, path, FanArtResourcePool, ImageType.Thumb, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(season, image.Url, FanArtResourcePool, ImageType.Thumb, null, cancellationToken).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs index 71b96f0372..d1d172699d 100644 --- a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs @@ -55,24 +55,22 @@ namespace MediaBrowser.Providers.TV var series = ((Season) item).Series; - if (series == null) + if (series != null) { - return Task.FromResult>(list); - } + var id = series.GetProviderId(MetadataProviders.Tvdb); - var id = series.GetProviderId(MetadataProviders.Tvdb); - - if (!string.IsNullOrEmpty(id) && item.IndexNumber.HasValue) - { - var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id); - - try + if (!string.IsNullOrEmpty(id) && item.IndexNumber.HasValue) { - AddImages(list, item.IndexNumber.Value, xmlPath, cancellationToken); - } - catch (FileNotFoundException) - { - // No biggie. Don't blow up + var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id); + + try + { + AddImages(list, item.IndexNumber.Value, xmlPath, cancellationToken); + } + catch (FileNotFoundException) + { + // No biggie. Don't blow up + } } } @@ -168,6 +166,14 @@ namespace MediaBrowser.Providers.TV } break; } + case "showbackgrounds": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Backdrop, 1920, 1080, seasonNumber); + } + break; + } default: reader.Skip(); break; @@ -189,11 +195,17 @@ namespace MediaBrowser.Providers.TV switch (reader.Name) { case "seasonthumb": + case "showbackground": { var url = reader.GetAttribute("url"); var season = reader.GetAttribute("season"); - if (!string.IsNullOrEmpty(url) && string.Equals(season, seasonNumber.ToString(_usCulture))) + int imageSeasonNumber; + + if (!string.IsNullOrEmpty(url) && + !string.IsNullOrEmpty(season) && + int.TryParse(season, NumberStyles.Any, _usCulture, out imageSeasonNumber) && + seasonNumber == imageSeasonNumber) { var likesString = reader.GetAttribute("likes"); int likes; @@ -216,6 +228,7 @@ namespace MediaBrowser.Providers.TV list.Add(info); } + break; } default: From df65ee2e49a8c2dd51ff0f555db16d82c50ab3fe Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 16:34:26 -0400 Subject: [PATCH 09/12] #606 - Add manual image selection for Seasons --- .../Movies/ManualFanartMovieImageProvider.cs | 8 ++++++-- .../TV/ManualFanartSeasonProvider.cs | 14 +++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs index 64843de993..d714128ea6 100644 --- a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs @@ -225,8 +225,12 @@ namespace MediaBrowser.Providers.Movies break; } default: - reader.Skip(); - break; + { + using (reader.ReadSubtree()) + { + } + break; + } } } } diff --git a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs index d1d172699d..b66d9faf52 100644 --- a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.TV { var list = new List(); - var series = ((Season) item).Series; + var series = ((Season)item).Series; if (series != null) { @@ -175,8 +175,12 @@ namespace MediaBrowser.Providers.TV break; } default: - reader.Skip(); - break; + { + using (reader.ReadSubtree()) + { + } + break; + } } } } @@ -202,8 +206,8 @@ namespace MediaBrowser.Providers.TV int imageSeasonNumber; - if (!string.IsNullOrEmpty(url) && - !string.IsNullOrEmpty(season) && + if (!string.IsNullOrEmpty(url) && + !string.IsNullOrEmpty(season) && int.TryParse(season, NumberStyles.Any, _usCulture, out imageSeasonNumber) && seasonNumber == imageSeasonNumber) { From 5fa577a9ee143ac570dbd237270d33374e8a15a2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Nov 2013 17:26:07 -0400 Subject: [PATCH 10/12] #605 - Add manual image selection for Series --- .../MediaBrowser.Providers.csproj | 1 + MediaBrowser.Providers/TV/FanArtTVProvider.cs | 2 - .../TV/ManualFanartSeriesProvider.cs | 309 ++++++++++++++++++ 3 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index ef1aa67778..9906ad14cb 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -111,6 +111,7 @@ + diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index e349b82ce1..0578887adf 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -12,7 +11,6 @@ using MediaBrowser.Model.Logging; using System; using System.Globalization; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Xml; diff --git a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs new file mode 100644 index 0000000000..21b8e278c7 --- /dev/null +++ b/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs @@ -0,0 +1,309 @@ +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 ManualFanartSeriesImageProvider : IImageProvider + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IServerConfigurationManager _config; + + public ManualFanartSeriesImageProvider(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 Series; + } + + 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 = (Series)item; + + var id = series.GetProviderId(MetadataProviders.Tvdb); + + if (!string.IsNullOrEmpty(id)) + { + var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id); + + try + { + AddImages(list, 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, 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, cancellationToken); + } + break; + } + + default: + reader.Skip(); + break; + } + } + } + } + } + } + + private void AddImages(List list, XmlReader reader, CancellationToken cancellationToken) + { + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "hdtvlogos": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Logo, 800, 310); + } + break; + } + case "hdcleararts": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Art, 1000, 562); + } + break; + } + case "clearlogos": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Logo, 400, 155); + } + break; + } + case "cleararts": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Art, 500, 281); + } + break; + } + case "showbackgrounds": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Backdrop, 1920, 1080, true); + } + break; + } + case "seasonthumbs": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Thumb, 500, 281); + } + break; + } + case "tvthumbs": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Thumb, 500, 281); + } + break; + } + case "tvbanners": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Banner, 1000, 185); + } + break; + } + case "tvposters": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Primary, 1000, 1426); + } + break; + } + default: + { + using (reader.ReadSubtree()) + { + } + break; + } + } + } + } + } + + private void PopulateImageCategory(List list, XmlReader reader, CancellationToken cancellationToken, ImageType type, int width, int height, bool allowSeasonAll = false) + { + reader.MoveToContent(); + + while (reader.Read()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "hdtvlogo": + case "hdclearart": + case "clearlogo": + case "clearart": + case "showbackground": + case "seasonthumb": + case "tvthumb": + case "tvbanner": + case "tvposter": + { + var url = reader.GetAttribute("url"); + var season = reader.GetAttribute("season"); + + var isSeasonValid = string.IsNullOrEmpty(season) || + (allowSeasonAll && string.Equals(season, "all", StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(url) && isSeasonValid) + { + 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; } + } + } +} From ab490d7467967c40435828bb4fc6c0066955930c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Nov 2013 10:19:24 -0400 Subject: [PATCH 11/12] #605 - Add manual image selection for Series --- MediaBrowser.Providers/TV/FanArtTVProvider.cs | 105 +++++++----------- 1 file changed, 40 insertions(+), 65 deletions(-) diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index 0578887adf..31f8f9cb4a 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -8,12 +8,14 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml; namespace MediaBrowser.Providers.TV { @@ -173,10 +175,9 @@ namespace MediaBrowser.Providers.TV await DownloadSeriesXml(seriesId, cancellationToken).ConfigureAwait(false); } - if (File.Exists(xmlPath)) - { - await FetchFromXml(item, xmlPath, cancellationToken).ConfigureAwait(false); - } + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeriesImageProvider.ProviderName).ConfigureAwait(false); + + await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false); } SetLastRefreshed(item, DateTime.UtcNow); @@ -188,27 +189,20 @@ namespace MediaBrowser.Providers.TV /// Fetches from XML. /// /// The item. - /// The XML file path. + /// The images. /// The cancellation token. /// Task. - private async Task FetchFromXml(BaseItem item, string xmlFilePath, CancellationToken cancellationToken) + private async Task FetchFromXml(BaseItem item, List images, CancellationToken cancellationToken) { - var doc = new XmlDocument(); - doc.Load(xmlFilePath); - cancellationToken.ThrowIfCancellationRequested(); - var language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower(); - if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary)) { - var node = doc.SelectSingleNode("//fanart/series/tvposters/tvposter[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/tvposters/tvposter/@url"); - var path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Primary, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); } } @@ -216,15 +210,11 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo)) { - var node = doc.SelectSingleNode("//fanart/series/hdtvlogos/hdtvlogo[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/clearlogos/clearlogo[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/hdtvlogos/hdtvlogo/@url") ?? - doc.SelectSingleNode("//fanart/series/clearlogos/clearlogo/@url"); - var path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Logo); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Logo, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); } } @@ -232,15 +222,11 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art)) { - var node = doc.SelectSingleNode("//fanart/series/hdcleararts/hdclearart[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/cleararts/clearart[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/hdcleararts/hdclearart/@url") ?? - doc.SelectSingleNode("//fanart/series/cleararts/clearart/@url"); - var path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Art); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Art, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Art, null, cancellationToken).ConfigureAwait(false); } } @@ -248,53 +234,42 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb)) { - var node = doc.SelectSingleNode("//fanart/series/tvthumbs/tvthumb[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/tvthumbs/tvthumb/@url"); - var path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Thumb); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Thumb, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Thumb, null, cancellationToken).ConfigureAwait(false); } } + cancellationToken.ThrowIfCancellationRequested(); + if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner)) { - var node = doc.SelectSingleNode("//fanart/series/tbbanners/tvbanner[@lang = \"" + language + "\"]/@url") ?? - doc.SelectSingleNode("//fanart/series/tbbanners/tvbanner/@url"); - var path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Banner); + + if (image != null) { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Banner, null, cancellationToken) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Banner, null, cancellationToken).ConfigureAwait(false); } } + cancellationToken.ThrowIfCancellationRequested(); + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; - - if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) + if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && + item.BackdropImagePaths.Count < backdropLimit) { - var nodes = doc.SelectNodes("//fanart/series/showbackgrounds//@url"); + var numBackdrops = item.BackdropImagePaths.Count; - if (nodes != null) + foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) { - var numBackdrops = item.BackdropImagePaths.Count; + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) + .ConfigureAwait(false); - foreach (XmlNode node in nodes) - { - var path = node.Value; - - if (!string.IsNullOrEmpty(path) && !item.ContainsImageWithSourceUrl(path)) - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) - .ConfigureAwait(false); - - numBackdrops++; - - if (item.BackdropImagePaths.Count >= backdropLimit) break; - } - } + numBackdrops++; + if (item.BackdropImagePaths.Count >= backdropLimit) break; } } From ed34b67f512b07ea2e97f8708811cc4150906f12 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Nov 2013 15:30:29 -0400 Subject: [PATCH 12/12] fix library scan stopping and restarting itself --- .../Configuration/ServerConfiguration.cs | 5 +++- .../Movies/MovieDbProvider.cs | 2 +- .../Savers/XmlSaverHelpers.cs | 4 +-- .../TV/RemoteSeasonProvider.cs | 2 +- .../IO/DirectoryWatchers.cs | 28 +++++++++++++++++- .../SqliteDisplayPreferencesRepository.cs | 2 +- .../Persistence/SqliteExtensions.cs | 5 +++- .../Persistence/SqliteItemRepository.cs | 4 +-- .../SqliteNotificationsRepository.cs | 2 +- .../Persistence/SqliteUserDataRepository.cs | 2 +- .../Persistence/SqliteUserRepository.cs | 2 +- .../Providers/ImageSaver.cs | 29 +++++++++++++++---- .../Sorting/PremiereDateComparer.cs | 9 +++++- 13 files changed, 77 insertions(+), 19 deletions(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 54ab7a5407..0ce3d770ee 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -275,7 +275,10 @@ namespace MediaBrowser.Model.Configuration MetadataCountryCode = "US"; DownloadMovieImages = new ImageDownloadOptions(); DownloadSeriesImages = new ImageDownloadOptions(); - DownloadSeasonImages = new ImageDownloadOptions(); + DownloadSeasonImages = new ImageDownloadOptions + { + Backdrops = false + }; DownloadMusicArtistImages = new ImageDownloadOptions(); DownloadMusicAlbumImages = new ImageDownloadOptions(); MaxBackdrops = 3; diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index baec90c66a..eb9c920d76 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -221,7 +221,7 @@ namespace MediaBrowser.Providers.Movies !imagesFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed; } - return true; + return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); } /// diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index e9389d2db1..d9e0fb6e2f 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -562,8 +562,8 @@ namespace MediaBrowser.Providers.Savers { var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value); - builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); - builder.Append("" + Convert.ToInt32(timespan.TotalSeconds).ToString(UsCulture) + ""); + builder.Append("" + Convert.ToInt64(timespan.TotalMinutes).ToString(UsCulture) + ""); + builder.Append("" + Convert.ToInt64(timespan.TotalSeconds).ToString(UsCulture) + ""); } if (video != null && video.Video3DFormat.HasValue) diff --git a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs index 1f702a2d21..ca6f699c39 100644 --- a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs @@ -158,7 +158,7 @@ namespace MediaBrowser.Providers.TV try { var fanartData = FetchFanartXmlData(imagesXmlPath, seasonNumber.Value, cancellationToken); - await DownloadImages(item, fanartData, 1, cancellationToken).ConfigureAwait(false); + await DownloadImages(item, fanartData, ConfigurationManager.Configuration.MaxBackdrops, cancellationToken).ConfigureAwait(false); } catch (FileNotFoundException) { diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs index 330469877a..748ba0df47 100644 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs @@ -361,7 +361,33 @@ namespace MediaBrowser.Server.Implementations.IO if (e.ChangeType == WatcherChangeTypes.Changed) { // If the parent of an ignored path has a change event, ignore that too - if (tempIgnorePaths.Any(i => string.Equals(Path.GetDirectoryName(i), e.FullPath, StringComparison.OrdinalIgnoreCase) || string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase))) + if (tempIgnorePaths.Any(i => + { + if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Go up a level + var parent = Path.GetDirectoryName(i); + if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Go up another level + if (!string.IsNullOrEmpty(parent)) + { + parent = Path.GetDirectoryName(i); + if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + + })) { return; } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs index bfbb0e206c..09f438aef9 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db"); - _connection = await SqliteExtensions.ConnectToDb(dbFile).ConfigureAwait(false); + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs index b5672c39bf..9836de7350 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs @@ -128,15 +128,18 @@ namespace MediaBrowser.Server.Implementations.Persistence /// Connects to db. /// /// The db path. + /// The logger. /// Task{IDbConnection}. /// dbPath - public static async Task ConnectToDb(string dbPath) + public static async Task ConnectToDb(string dbPath, ILogger logger) { if (string.IsNullOrEmpty(dbPath)) { throw new ArgumentNullException("dbPath"); } + logger.Info("Opening {0}", dbPath); + #if __MonoCS__ var connectionstr = new SqliteConnectionStringBuilder { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index d61178d282..fc2a6de248 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Server.Implementations.Persistence var chapterDbFile = Path.Combine(_appPaths.DataPath, "chapters.db"); - var chapterConnection = SqliteExtensions.ConnectToDb(chapterDbFile).Result; + var chapterConnection = SqliteExtensions.ConnectToDb(chapterDbFile, _logger).Result; _chapterRepository = new SqliteChapterRepository(chapterConnection, logManager); } @@ -104,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var dbFile = Path.Combine(_appPaths.DataPath, "library.db"); - _connection = await SqliteExtensions.ConnectToDb(dbFile).ConfigureAwait(false); + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteNotificationsRepository.cs index d85b1d8746..c5f391765c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteNotificationsRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteNotificationsRepository.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var dbFile = Path.Combine(_appPaths.DataPath, "notifications.db"); - _connection = await SqliteExtensions.ConnectToDb(dbFile).ConfigureAwait(false); + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs index 7fabe6a903..a9d5d8746e 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var dbFile = Path.Combine(_appPaths.DataPath, "userdata_v2.db"); - _connection = await SqliteExtensions.ConnectToDb(dbFile).ConfigureAwait(false); + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs index 8749c929ca..222cc94229 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var dbFile = Path.Combine(_appPaths.DataPath, "users.db"); - _connection = await SqliteExtensions.ConnectToDb(dbFile).ConfigureAwait(false); + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index 7b5c0df34d..0653bcebd7 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.Collections.Generic; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -268,7 +269,7 @@ namespace MediaBrowser.Server.Implementations.Providers { item.ScreenshotImagePaths[imageIndex.Value] = path; } - else + else if (!item.ScreenshotImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase)) { item.ScreenshotImagePaths.Add(path); } @@ -282,7 +283,7 @@ namespace MediaBrowser.Server.Implementations.Providers { item.BackdropImagePaths[imageIndex.Value] = path; } - else + else if (!item.BackdropImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase)) { item.BackdropImagePaths.Add(path); } @@ -333,14 +334,14 @@ namespace MediaBrowser.Server.Implementations.Providers { throw new ArgumentNullException("imageIndex"); } - filename = imageIndex.Value == 0 ? "backdrop" : "backdrop" + imageIndex.Value.ToString(UsCulture); + filename = GetBackdropSaveFilename(item.BackdropImagePaths, "backdrop", "backdrop", imageIndex.Value); break; case ImageType.Screenshot: if (!imageIndex.HasValue) { throw new ArgumentNullException("imageIndex"); } - filename = imageIndex.Value == 0 ? "screenshot" : "screenshot" + imageIndex.Value.ToString(UsCulture); + filename = GetBackdropSaveFilename(item.ScreenshotImagePaths, "screenshot", "screenshot", imageIndex.Value); break; default: filename = type.ToString().ToLower(); @@ -380,6 +381,24 @@ namespace MediaBrowser.Server.Implementations.Providers return path; } + private string GetBackdropSaveFilename(List images, string zeroIndexFilename, string numberedIndexPrefix, int index) + { + var filesnames = images.Select(Path.GetFileNameWithoutExtension).ToList(); + + if (index == 0) + { + return zeroIndexFilename; + } + + var current = index; + while (filesnames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) + { + current++; + } + + return numberedIndexPrefix + current.ToString(UsCulture); + } + /// /// Gets the compatible save paths. /// diff --git a/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs index ef589e1fa6..ffe1fc24a1 100644 --- a/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -35,7 +35,14 @@ namespace MediaBrowser.Server.Implementations.Sorting if (x.ProductionYear.HasValue) { - return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc); + try + { + return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc); + } + catch (ArgumentOutOfRangeException) + { + // Don't blow up if the item has a bad ProductionYear, just return MinValue + } } return DateTime.MinValue; }