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/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 453039ba8a..6b4ca61468 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; using MediaBrowser.Controller.Dto; @@ -80,11 +80,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) @@ -93,14 +88,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 fe5d22f584..0ef6d13ccc 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; @@ -220,11 +220,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) @@ -232,19 +227,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 /// diff --git a/MediaBrowser.Api/RemoteImageService.cs b/MediaBrowser.Api/RemoteImageService.cs new file mode 100644 index 0000000000..c5f1005c75 --- /dev/null +++ b/MediaBrowser.Api/RemoteImageService.cs @@ -0,0 +1,132 @@ +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; +using System.Threading.Tasks; + +namespace MediaBrowser.Api +{ + [Route("/Items/{Id}/RemoteImages", "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 = "query", 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; } + + [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 + { + 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, CancellationToken.None, request.ProviderName, request.Type).Result; + + var imagesList = images.ToList(); + + var result = new RemoteImageResult + { + TotalRecordCount = imagesList.Count, + Providers = _providerManager.GetImageProviders(item).Select(i => i.Name).ToList() + }; + + 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); + } + + 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.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 => { 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.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index e254f9007c..542620d47d 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -299,6 +299,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 b8625c7c67..31f781faef 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -286,6 +286,9 @@ Providers\RemoteImageInfo.cs + + Providers\RemoteImageResult.cs + Querying\ArtistsQuery.cs 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.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/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 30ce53121c..3ca4ff571e 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -85,6 +85,7 @@ + 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 new file mode 100644 index 0000000000..1c60db6ae1 --- /dev/null +++ b/MediaBrowser.Model/Providers/RemoteImageResult.cs @@ -0,0 +1,28 @@ +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; } + + /// + /// 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..9906ad14cb 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -61,6 +61,7 @@ + @@ -109,6 +110,9 @@ + + + diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 3458622d34..24f17556b4 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -4,18 +4,17 @@ 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; 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 { @@ -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); @@ -234,41 +193,44 @@ 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); } SetLastRefreshed(item, DateTime.UtcNow); return true; } + public string GetFanartXmlPath(string tmdbId) + { + var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId); + return Path.Combine(movieDataPath, "fanart.xml"); + } + /// /// 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 { @@ -285,82 +247,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); } } @@ -368,13 +301,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); } } @@ -382,40 +313,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 new file mode 100644 index 0000000000..d714128ea6 --- /dev/null +++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs @@ -0,0 +1,301 @@ +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 ProviderName; } + } + + public static string ProviderName + { + 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); + + // 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 "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: + { + using (reader.ReadSubtree()) + { + } + 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..7c6ede0c23 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,20 @@ namespace MediaBrowser.Providers.Movies public string Name { - get { return "TheMovieDB"; } + get { return ProviderName; } } - public bool Supports(BaseItem item, ImageType imageType) + public static string ProviderName { - if (MovieDbImagesProvider.SupportsItem(item)) - { - return imageType == ImageType.Primary || imageType == ImageType.Backdrop; - } - - return false; + get { return "TheMovieDb"; } } - public async Task> GetAvailableImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) + public bool Supports(BaseItem item) + { + return MovieDbImagesProvider.SupportsItem(item); + } + + public async Task> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) { var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); @@ -70,7 +71,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 +83,8 @@ namespace MediaBrowser.Providers.Movies Width = i.width, Height = i.height, ProviderName = Name, - Type = ImageType.Backdrop + Type = ImageType.Backdrop, + RatingType = RatingType.Score })); return list; @@ -101,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 => @@ -124,6 +127,7 @@ namespace MediaBrowser.Providers.Movies return 0; }) .ThenByDescending(i => i.vote_average) + .ThenByDescending(i => i.vote_count) .ToList(); } @@ -136,10 +140,11 @@ 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); + return eligibleBackdrops.OrderByDescending(i => i.vote_average) + .ThenByDescending(i => i.vote_count); } /// @@ -150,7 +155,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)) { @@ -164,5 +169,10 @@ namespace MediaBrowser.Providers.Movies return null; } + + public int Priority + { + get { return 2; } + } } } 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 d7b7faeea7..eb9c920d76 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; } @@ -209,20 +208,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 imagesFilePath = GetImagesDataFilePath(item); + var fileInfo = new FileInfo(path); + var imagesFileInfo = new FileInfo(imagesFilePath); - if (fileInfo.Exists) - { - return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; - } - - return true; + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed || + !imagesFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed; } return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); @@ -508,9 +504,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)) + if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(GetImagesDataFilePath(item))) { var isBoxSet = item is BoxSet; @@ -538,7 +534,7 @@ namespace MediaBrowser.Providers.Movies if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item)) { - dataFilePath = GetDataFilePath(item, language); + dataFilePath = GetDataFilePath(item); var mainResult = JsonSerializer.DeserializeFromFile(dataFilePath); @@ -580,10 +576,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)) @@ -598,6 +595,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/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/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/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index fe316e85b7..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 { @@ -73,7 +75,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); @@ -99,69 +101,31 @@ 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 = Path.Combine(FanArtTvProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "fanart.xml"); + await FetchImages(season, images.ToList(), cancellationToken).ConfigureAwait(false); - 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); - } - } - - 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; } /// /// 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/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index af89bc205e..31f8f9cb4a 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -4,18 +4,18 @@ 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; 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 { @@ -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,19 +167,17 @@ 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)) - { - 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); @@ -219,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); } } @@ -247,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); } } @@ -263,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); } } @@ -279,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; } } @@ -334,19 +278,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..b66d9faf52 --- /dev/null +++ b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs @@ -0,0 +1,251 @@ +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) + { + 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; + } + case "showbackgrounds": + { + using (var subReader = reader.ReadSubtree()) + { + PopulateImageCategory(list, subReader, cancellationToken, ImageType.Backdrop, 1920, 1080, seasonNumber); + } + break; + } + default: + { + using (reader.ReadSubtree()) + { + } + 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": + case "showbackground": + { + var url = reader.GetAttribute("url"); + var season = reader.GetAttribute("season"); + + 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; + + 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/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; } + } + } +} 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(); 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/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index a66f9c56ba..a29a3d069f 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.Controller.IO; @@ -602,6 +602,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"; } 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 cbca2ba763..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; @@ -69,11 +70,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) @@ -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/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.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; } diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 3606396652..25cbc98779 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -305,17 +305,52 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi url: url }); }; + + function getRemoteImagePrefix(options) { + + var urlPrefix; - self.getAvailableRemoteImages = function (itemId, imageType) { - - if (!itemId) { - throw new Error("null itemId"); + if (options.artist) { + urlPrefix = "Artists/" + encodeName(options.artist); + delete options.artist; } - if (!imageType) { - throw new Error("null imageType"); + 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; } - var url = self.getUrl("Items/" + itemId + "/RemoteImages/" + imageType); + return urlPrefix; + } + + self.getAvailableRemoteImages = function (options) { + + if (!options) { + throw new Error("null options"); + } + + var urlPrefix = getRemoteImagePrefix(options); + + var url = self.getUrl(urlPrefix + "/RemoteImages", options); return self.ajax({ type: "GET", @@ -324,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 22560b1331..fb2cf4df5b 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file