From a07ee6536567a048b85d4bdc7d17e1c46f292572 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 23 Feb 2020 10:53:51 +0100 Subject: Minor improvements --- Emby.Server.Implementations/ApplicationHost.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index fd01122669..a8f457390d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -852,10 +852,15 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(new DeviceDiscovery(ServerConfigurationManager)); - ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); + ChapterManager = new ChapterManager(ItemRepository); serviceCollection.AddSingleton(ChapterManager); - EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); + EncodingManager = new MediaEncoder.EncodingManager( + LoggerFactory.CreateLogger(), + FileSystemManager, + MediaEncoder, + ChapterManager, + LibraryManager); serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); -- cgit v1.2.3 From d7c34b461134d591062416da7d5c8639efb98b9a Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 9 Mar 2020 23:53:07 +0900 Subject: start tvdb migration for plugin interface --- Emby.Server.Implementations/ApplicationHost.cs | 4 +- .../People/TvdbPersonImageProvider.cs | 116 ------ .../Plugins/TheTvdb/TvdbClientManager.cs | 284 +++++++++++++ .../Plugins/TheTvdb/TvdbEpisodeImageProvider.cs | 123 ++++++ .../Plugins/TheTvdb/TvdbEpisodeProvider.cs | 256 ++++++++++++ .../Plugins/TheTvdb/TvdbPersonImageProvider.cs | 115 ++++++ .../Plugins/TheTvdb/TvdbSeasonImageProvider.cs | 155 ++++++++ .../Plugins/TheTvdb/TvdbSeriesImageProvider.cs | 152 +++++++ .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 437 +++++++++++++++++++++ .../Plugins/TheTvdb/TvdbUtils.cs | 36 ++ .../TV/MissingEpisodeProvider.cs | 10 +- MediaBrowser.Providers/TV/SeriesMetadataService.cs | 10 +- .../TV/TheTVDB/TvDbClientManager.cs | 284 ------------- .../TV/TheTVDB/TvdbEpisodeImageProvider.cs | 123 ------ .../TV/TheTVDB/TvdbEpisodeProvider.cs | 256 ------------ .../TV/TheTVDB/TvdbSeasonImageProvider.cs | 155 -------- .../TV/TheTVDB/TvdbSeriesImageProvider.cs | 152 ------- .../TV/TheTVDB/TvdbSeriesProvider.cs | 437 --------------------- MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs | 35 -- MediaBrowser.Providers/TV/TvExternalIds.cs | 2 +- 20 files changed, 1571 insertions(+), 1571 deletions(-) delete mode 100644 MediaBrowser.Providers/People/TvdbPersonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs delete mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 679ef4851e..e66c1b3d28 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -100,8 +100,8 @@ using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; +using MediaBrowser.Providers.Plugins.TheTvdb; using MediaBrowser.Providers.Subtitles; -using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; @@ -676,7 +676,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(Logger); serviceCollection.AddSingleton(FileSystemManager); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); HttpClient = new HttpClientManager.HttpClientManager( ApplicationPaths, diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs deleted file mode 100644 index 50476044b3..0000000000 --- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Dto; -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 MediaBrowser.Providers.TV.TheTVDB; -using Microsoft.Extensions.Logging; -using TvDbSharper; - -namespace MediaBrowser.Providers.People -{ - public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder - { - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly TvDbClientManager _tvDbClientManager; - - public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger logger, TvDbClientManager tvDbClientManager) - { - _libraryManager = libraryManager; - _httpClient = httpClient; - _logger = logger; - _tvDbClientManager = tvDbClientManager; - } - - /// - public string Name => "TheTVDB"; - - /// - public int Order => 1; - - /// - public bool Supports(BaseItem item) => item is Person; - - /// - public IEnumerable GetSupportedImages(BaseItem item) - { - yield return ImageType.Primary; - } - - /// - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - var seriesWithPerson = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Series).Name }, - PersonIds = new[] { item.Id }, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - - }).Cast() - .Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds)) - .ToList(); - - var infos = (await Task.WhenAll(seriesWithPerson.Select(async i => - await GetImageFromSeriesData(i, item.Name, cancellationToken).ConfigureAwait(false))) - .ConfigureAwait(false)) - .Where(i => i != null) - .Take(1); - - return infos; - } - - private async Task GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken) - { - var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb)); - - try - { - var actorsResult = await _tvDbClientManager - .GetActorsAsync(tvdbId, series.GetPreferredMetadataLanguage(), cancellationToken) - .ConfigureAwait(false); - var actor = actorsResult.Data.FirstOrDefault(a => - string.Equals(a.Name, personName, StringComparison.OrdinalIgnoreCase) && - !string.IsNullOrEmpty(a.Image)); - if (actor == null) - { - return null; - } - - return new RemoteImageInfo - { - Url = TvdbUtils.BannerUrl + actor.Image, - Type = ImageType.Primary, - ProviderName = Name - }; - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to retrieve actor {ActorName} from series {SeriesTvdbId}", personName, tvdbId); - return null; - } - } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url - }); - } - } -} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs new file mode 100644 index 0000000000..a12b4d3ada --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Caching.Memory; +using TvDbSharper; +using TvDbSharper.Dto; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class TvdbClientManager + { + private const string DefaultLanguage = "en"; + + private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1); + private readonly IMemoryCache _cache; + private readonly TvDbClient _tvDbClient; + private DateTime _tokenCreatedAt; + + public TvdbClientManager(IMemoryCache memoryCache) + { + _cache = memoryCache; + _tvDbClient = new TvDbClient(); + } + + private TvDbClient TvDbClient + { + get + { + if (string.IsNullOrEmpty(_tvDbClient.Authentication.Token)) + { + _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult(); + _tokenCreatedAt = DateTime.Now; + } + + // Refresh if necessary + if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20))) + { + try + { + _tvDbClient.Authentication.RefreshTokenAsync().GetAwaiter().GetResult(); + } + catch + { + _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult(); + } + + _tokenCreatedAt = DateTime.Now; + } + + return _tvDbClient; + } + } + + public Task> GetSeriesByNameAsync(string name, string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("series", name, language); + return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken)); + } + + public Task> GetSeriesByIdAsync(int tvdbId, string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("series", tvdbId, language); + return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken)); + } + + public Task> GetEpisodesAsync(int episodeTvdbId, string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("episode", episodeTvdbId, language); + return TryGetValue(cacheKey, language,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken)); + } + + public async Task> GetAllEpisodesAsync(int tvdbId, string language, + CancellationToken cancellationToken) + { + // Traverse all episode pages and join them together + var episodes = new List(); + var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken) + .ConfigureAwait(false); + episodes.AddRange(episodePage.Data); + if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue) + { + return episodes; + } + + int next = episodePage.Links.Next.Value; + int last = episodePage.Links.Last.Value; + + for (var page = next; page <= last; ++page) + { + episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken) + .ConfigureAwait(false); + episodes.AddRange(episodePage.Data); + } + + return episodes; + } + + public Task> GetSeriesByImdbIdAsync( + string imdbId, + string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("series", imdbId, language); + return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken)); + } + + public Task> GetSeriesByZap2ItIdAsync( + string zap2ItId, + string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("series", zap2ItId, language); + return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken)); + } + public Task> GetActorsAsync( + int tvdbId, + string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("actors", tvdbId, language); + return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken)); + } + + public Task> GetImagesAsync( + int tvdbId, + ImagesQuery imageQuery, + string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("images", tvdbId, language, imageQuery); + return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken)); + } + + public Task> GetLanguagesAsync(CancellationToken cancellationToken) + { + return TryGetValue("languages", null, () => TvDbClient.Languages.GetAllAsync(cancellationToken)); + } + + public Task> GetSeriesEpisodeSummaryAsync( + int tvdbId, + string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey("seriesepisodesummary", tvdbId, language); + return TryGetValue(cacheKey, language, + () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken)); + } + + public Task> GetEpisodesPageAsync( + int tvdbId, + int page, + EpisodeQuery episodeQuery, + string language, + CancellationToken cancellationToken) + { + var cacheKey = GenerateKey(language, tvdbId, episodeQuery); + + return TryGetValue(cacheKey, language, + () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken)); + } + + public Task GetEpisodeTvdbId( + EpisodeInfo searchInfo, + string language, + CancellationToken cancellationToken) + { + searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), + out var seriesTvdbId); + + var episodeQuery = new EpisodeQuery(); + + // Prefer SxE over premiere date as it is more robust + if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue) + { + switch (searchInfo.SeriesDisplayOrder) + { + case "dvd": + episodeQuery.DvdEpisode = searchInfo.IndexNumber.Value; + episodeQuery.DvdSeason = searchInfo.ParentIndexNumber.Value; + break; + case "absolute": + episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value; + break; + default: + //aired order + episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value; + episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value; + break; + } + } + else if (searchInfo.PremiereDate.HasValue) + { + // tvdb expects yyyy-mm-dd format + episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd"); + } + + return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken); + } + + public async Task GetEpisodeTvdbId( + int seriesTvdbId, + EpisodeQuery episodeQuery, + string language, + CancellationToken cancellationToken) + { + var episodePage = + await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken) + .ConfigureAwait(false); + return episodePage.Data.FirstOrDefault()?.Id.ToString(); + } + + public Task> GetEpisodesPageAsync( + int tvdbId, + EpisodeQuery episodeQuery, + string language, + CancellationToken cancellationToken) + { + return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken); + } + + private async Task TryGetValue(string key, string language, Func> resultFactory) + { + if (_cache.TryGetValue(key, out T cachedValue)) + { + return cachedValue; + } + + await _cacheWriteLock.WaitAsync().ConfigureAwait(false); + try + { + if (_cache.TryGetValue(key, out cachedValue)) + { + return cachedValue; + } + + _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage; + var result = await resultFactory.Invoke().ConfigureAwait(false); + _cache.Set(key, result, TimeSpan.FromHours(1)); + return result; + } + finally + { + _cacheWriteLock.Release(); + } + } + + private static string GenerateKey(params object[] objects) + { + var key = string.Empty; + + foreach (var obj in objects) + { + var objType = obj.GetType(); + if (objType.IsPrimitive || objType == typeof(string)) + { + key += obj + ";"; + } + else + { + foreach (PropertyInfo propertyInfo in objType.GetProperties()) + { + var currentValue = propertyInfo.GetValue(obj, null); + if (currentValue == null) + { + continue; + } + + key += propertyInfo.Name + "=" + currentValue + ";"; + } + } + } + + return key; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs new file mode 100644 index 0000000000..6118a9c53e --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using TvDbSharper; +using TvDbSharper.Dto; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class TvdbEpisodeImageProvider : IRemoteImageProvider + { + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + + public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) + { + _httpClient = httpClient; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + public string Name => "TheTVDB"; + + public bool Supports(BaseItem item) + { + return item is Episode; + } + + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary + }; + } + + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var episode = (Episode)item; + var series = episode.Series; + var imageResult = new List(); + var language = item.GetPreferredMetadataLanguage(); + if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) + { + // Process images + try + { + var episodeInfo = new EpisodeInfo + { + IndexNumber = episode.IndexNumber.Value, + ParentIndexNumber = episode.ParentIndexNumber.Value, + SeriesProviderIds = series.ProviderIds, + SeriesDisplayOrder = series.DisplayOrder + }; + string episodeTvdbId = await _tvdbClientManager + .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false); + if (string.IsNullOrEmpty(episodeTvdbId)) + { + _logger.LogError( + "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}", + episodeInfo.ParentIndexNumber, + episodeInfo.IndexNumber, + series.GetProviderId(MetadataProviders.Tvdb)); + return imageResult; + } + + var episodeResult = + await _tvdbClientManager + .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId), language, cancellationToken) + .ConfigureAwait(false); + + var image = GetImageInfo(episodeResult.Data); + if (image != null) + { + imageResult.Add(image); + } + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProviders.Tvdb)); + } + } + + return imageResult; + } + + private RemoteImageInfo GetImageInfo(EpisodeRecord episode) + { + if (string.IsNullOrEmpty(episode.Filename)) + { + return null; + } + + return new RemoteImageInfo + { + Width = Convert.ToInt32(episode.ThumbWidth), + Height = Convert.ToInt32(episode.ThumbHeight), + ProviderName = Name, + Url = TvdbUtils.BannerUrl + episode.Filename, + Type = ImageType.Primary + }; + } + + public int Order => 0; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs new file mode 100644 index 0000000000..f58c58a2ea --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using TvDbSharper; +using TvDbSharper.Dto; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + + /// + /// Class RemoteEpisodeProvider + /// + public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder + { + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + + public TvdbEpisodeProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) + { + _httpClient = httpClient; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) + { + var list = new List(); + + // Either an episode number or date must be provided; and the dictionary of provider ids must be valid + if ((searchInfo.IndexNumber == null && searchInfo.PremiereDate == null) + || !TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds)) + { + return list; + } + + var metadataResult = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false); + + if (!metadataResult.HasMetadata) + { + return list; + } + + var item = metadataResult.Item; + + list.Add(new RemoteSearchResult + { + IndexNumber = item.IndexNumber, + Name = item.Name, + ParentIndexNumber = item.ParentIndexNumber, + PremiereDate = item.PremiereDate, + ProductionYear = item.ProductionYear, + ProviderIds = item.ProviderIds, + SearchProviderName = Name, + IndexNumberEnd = item.IndexNumberEnd + }); + + return list; + } + + public string Name => "TheTVDB"; + + public async Task> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + QueriedById = true + }; + + if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && + (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue)) + { + result = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false); + } + else + { + _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name); + } + + return result; + } + + private async Task> GetEpisode(EpisodeInfo searchInfo, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + QueriedById = true + }; + + string seriesTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb); + string episodeTvdbId = null; + try + { + episodeTvdbId = await _tvdbClientManager + .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken) + .ConfigureAwait(false); + if (string.IsNullOrEmpty(episodeTvdbId)) + { + _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}", + searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId); + return result; + } + + var episodeResult = await _tvdbClientManager.GetEpisodesAsync( + Convert.ToInt32(episodeTvdbId), searchInfo.MetadataLanguage, + cancellationToken).ConfigureAwait(false); + + result = MapEpisodeToResult(searchInfo, episodeResult.Data); + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to retrieve episode with id {EpisodeTvDbId}, series id {SeriesTvdbId}", episodeTvdbId, seriesTvdbId); + } + + return result; + } + + private static MetadataResult MapEpisodeToResult(EpisodeInfo id, EpisodeRecord episode) + { + var result = new MetadataResult + { + HasMetadata = true, + Item = new Episode + { + IndexNumber = id.IndexNumber, + ParentIndexNumber = id.ParentIndexNumber, + IndexNumberEnd = id.IndexNumberEnd, + AirsBeforeEpisodeNumber = episode.AirsBeforeEpisode, + AirsAfterSeasonNumber = episode.AirsAfterSeason, + AirsBeforeSeasonNumber = episode.AirsBeforeSeason, + Name = episode.EpisodeName, + Overview = episode.Overview, + CommunityRating = (float?)episode.SiteRating, + + } + }; + result.ResetPeople(); + + var item = result.Item; + item.SetProviderId(MetadataProviders.Tvdb, episode.Id.ToString()); + item.SetProviderId(MetadataProviders.Imdb, episode.ImdbId); + + if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase)) + { + item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber); + item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason; + } + else if (episode.AiredEpisodeNumber.HasValue) + { + item.IndexNumber = episode.AiredEpisodeNumber; + } + else if (episode.AiredSeason.HasValue) + { + item.ParentIndexNumber = episode.AiredSeason; + } + + if (DateTime.TryParse(episode.FirstAired, out var date)) + { + // dates from tvdb are UTC but without offset or Z + item.PremiereDate = date; + item.ProductionYear = date.Year; + } + + foreach (var director in episode.Directors) + { + result.AddPerson(new PersonInfo + { + Name = director, + Type = PersonType.Director + }); + } + + // GuestStars is a weird list of names and roles + // Example: + // 1: Some Actor (Role1 + // 2: Role2 + // 3: Role3) + // 4: Another Actor (Role1 + // ... + for (var i = 0; i < episode.GuestStars.Length; ++i) + { + var currentActor = episode.GuestStars[i]; + var roleStartIndex = currentActor.IndexOf('('); + + if (roleStartIndex == -1) + { + result.AddPerson(new PersonInfo + { + Type = PersonType.GuestStar, + Name = currentActor, + Role = string.Empty + }); + continue; + } + + var roles = new List {currentActor.Substring(roleStartIndex + 1)}; + + // Fetch all roles + for (var j = i + 1; j < episode.GuestStars.Length; ++j) + { + var currentRole = episode.GuestStars[j]; + var roleEndIndex = currentRole.IndexOf(')'); + + if (roleEndIndex == -1) + { + roles.Add(currentRole); + continue; + } + + roles.Add(currentRole.TrimEnd(')')); + // Update the outer index (keep in mind it adds 1 after the iteration) + i = j; + break; + } + + result.AddPerson(new PersonInfo + { + Type = PersonType.GuestStar, + Name = currentActor.Substring(0, roleStartIndex).Trim(), + Role = string.Join(", ", roles) + }); + } + + foreach (var writer in episode.Writers) + { + result.AddPerson(new PersonInfo + { + Name = writer, + Type = PersonType.Writer + }); + } + + result.ResultLanguage = episode.Language.EpisodeName; + return result; + } + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + + public int Order => 0; + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs new file mode 100644 index 0000000000..c1cdc90e90 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Dto; +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 Microsoft.Extensions.Logging; +using TvDbSharper; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly TvdbClientManager _tvdbClientManager; + + public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) + { + _libraryManager = libraryManager; + _httpClient = httpClient; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + /// + public string Name => "TheTVDB"; + + /// + public int Order => 1; + + /// + public bool Supports(BaseItem item) => item is Person; + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + yield return ImageType.Primary; + } + + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var seriesWithPerson = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(Series).Name }, + PersonIds = new[] { item.Id }, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + + }).Cast() + .Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds)) + .ToList(); + + var infos = (await Task.WhenAll(seriesWithPerson.Select(async i => + await GetImageFromSeriesData(i, item.Name, cancellationToken).ConfigureAwait(false))) + .ConfigureAwait(false)) + .Where(i => i != null) + .Take(1); + + return infos; + } + + private async Task GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken) + { + var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb)); + + try + { + var actorsResult = await _tvdbClientManager + .GetActorsAsync(tvdbId, series.GetPreferredMetadataLanguage(), cancellationToken) + .ConfigureAwait(false); + var actor = actorsResult.Data.FirstOrDefault(a => + string.Equals(a.Name, personName, StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrEmpty(a.Image)); + if (actor == null) + { + return null; + } + + return new RemoteImageInfo + { + Url = TvdbUtils.BannerUrl + actor.Image, + Type = ImageType.Primary, + ProviderName = Name + }; + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to retrieve actor {ActorName} from series {SeriesTvdbId}", personName, tvdbId); + return null; + } + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs new file mode 100644 index 0000000000..a5d183df77 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using TvDbSharper; +using TvDbSharper.Dto; +using RatingType = MediaBrowser.Model.Dto.RatingType; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + + public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) + { + _httpClient = httpClient; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + public string Name => ProviderName; + + public static string ProviderName => "TheTVDB"; + + public bool Supports(BaseItem item) + { + return item is Season; + } + + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary, + ImageType.Banner, + ImageType.Backdrop + }; + } + + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var season = (Season)item; + var series = season.Series; + + if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) + { + return new RemoteImageInfo[] { }; + } + + var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb)); + var seasonNumber = season.IndexNumber.Value; + var language = item.GetPreferredMetadataLanguage(); + var remoteImages = new List(); + + var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart }; + foreach (var keyType in keyTypes) + { + var imageQuery = new ImagesQuery + { + KeyType = keyType, + SubKey = seasonNumber.ToString() + }; + try + { + var imageResults = await _tvdbClientManager + .GetImagesAsync(tvdbId, imageQuery, language, cancellationToken).ConfigureAwait(false); + remoteImages.AddRange(GetImages(imageResults.Data, language)); + } + catch (TvDbServerException) + { + _logger.LogDebug("No images of type {KeyType} found for series {TvdbId}", keyType, tvdbId); + } + } + + return remoteImages; + } + + private IEnumerable GetImages(Image[] images, string preferredLanguage) + { + var list = new List(); + var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data; + foreach (Image image in images) + { + var imageInfo = new RemoteImageInfo + { + RatingType = RatingType.Score, + CommunityRating = (double?)image.RatingsInfo.Average, + VoteCount = image.RatingsInfo.Count, + Url = TvdbUtils.BannerUrl + image.FileName, + ProviderName = ProviderName, + Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation, + ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail + }; + + var resolution = image.Resolution.Split('x'); + if (resolution.Length == 2) + { + imageInfo.Width = Convert.ToInt32(resolution[0]); + imageInfo.Height = Convert.ToInt32(resolution[1]); + } + + imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType); + list.Add(imageInfo); + } + var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); + + return list.OrderByDescending(i => + { + if (string.Equals(preferredLanguage, 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) + .ThenByDescending(i => i.VoteCount ?? 0); + } + + public int Order => 0; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs new file mode 100644 index 0000000000..1bad607565 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using TvDbSharper; +using TvDbSharper.Dto; +using RatingType = MediaBrowser.Model.Dto.RatingType; +using Series = MediaBrowser.Controller.Entities.TV.Series; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + + public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) + { + _httpClient = httpClient; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + public string Name => ProviderName; + + public static string ProviderName => "TheTVDB"; + + public bool Supports(BaseItem item) + { + return item is Series; + } + + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary, + ImageType.Banner, + ImageType.Backdrop + }; + } + + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) + { + return Array.Empty(); + } + + var language = item.GetPreferredMetadataLanguage(); + var remoteImages = new List(); + var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart }; + var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProviders.Tvdb)); + foreach (KeyType keyType in keyTypes) + { + var imageQuery = new ImagesQuery + { + KeyType = keyType + }; + try + { + var imageResults = + await _tvdbClientManager.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken) + .ConfigureAwait(false); + + remoteImages.AddRange(GetImages(imageResults.Data, language)); + } + catch (TvDbServerException) + { + _logger.LogDebug("No images of type {KeyType} exist for series {TvDbId}", keyType, + tvdbId); + } + } + return remoteImages; + } + + private IEnumerable GetImages(Image[] images, string preferredLanguage) + { + var list = new List(); + var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data; + + foreach (Image image in images) + { + var imageInfo = new RemoteImageInfo + { + RatingType = RatingType.Score, + CommunityRating = (double?)image.RatingsInfo.Average, + VoteCount = image.RatingsInfo.Count, + Url = TvdbUtils.BannerUrl + image.FileName, + ProviderName = Name, + Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation, + ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail + }; + + var resolution = image.Resolution.Split('x'); + if (resolution.Length == 2) + { + imageInfo.Width = Convert.ToInt32(resolution[0]); + imageInfo.Height = Convert.ToInt32(resolution[1]); + } + + imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType); + list.Add(imageInfo); + } + var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); + + return list.OrderByDescending(i => + { + if (string.Equals(preferredLanguage, 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) + .ThenByDescending(i => i.VoteCount ?? 0); + } + + public int Order => 0; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs new file mode 100644 index 0000000000..97a5b3478e --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using TvDbSharper; +using TvDbSharper.Dto; +using Series = MediaBrowser.Controller.Entities.TV.Series; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder + { + internal static TvdbSeriesProvider Current { get; private set; } + private readonly IHttpClient _httpClient; + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localizationManager; + private readonly TvdbClientManager _tvdbClientManager; + + public TvdbSeriesProvider(IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager) + { + _httpClient = httpClient; + _logger = logger; + _libraryManager = libraryManager; + _localizationManager = localizationManager; + Current = this; + _tvdbClientManager = tvdbClientManager; + } + + public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) + { + if (IsValidSeries(searchInfo.ProviderIds)) + { + var metadata = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false); + + if (metadata.HasMetadata) + { + return new List + { + new RemoteSearchResult + { + Name = metadata.Item.Name, + PremiereDate = metadata.Item.PremiereDate, + ProductionYear = metadata.Item.ProductionYear, + ProviderIds = metadata.Item.ProviderIds, + SearchProviderName = Name + } + }; + } + } + + return await FindSeries(searchInfo.Name, searchInfo.Year, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); + } + + public async Task> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + QueriedById = true + }; + + if (!IsValidSeries(itemId.ProviderIds)) + { + result.QueriedById = false; + await Identify(itemId).ConfigureAwait(false); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (IsValidSeries(itemId.ProviderIds)) + { + result.Item = new Series(); + result.HasMetadata = true; + + await FetchSeriesData(result, itemId.MetadataLanguage, itemId.ProviderIds, cancellationToken) + .ConfigureAwait(false); + } + + return result; + } + + private async Task FetchSeriesData(MetadataResult result, string metadataLanguage, Dictionary seriesProviderIds, CancellationToken cancellationToken) + { + var series = result.Item; + + if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId)) + { + series.SetProviderId(MetadataProviders.Tvdb, tvdbId); + } + + if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId)) + { + series.SetProviderId(MetadataProviders.Imdb, imdbId); + tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProviders.Imdb.ToString(), metadataLanguage, + cancellationToken).ConfigureAwait(false); + } + + if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It)) + { + series.SetProviderId(MetadataProviders.Zap2It, zap2It); + tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProviders.Zap2It.ToString(), metadataLanguage, + cancellationToken).ConfigureAwait(false); + } + + try + { + var seriesResult = + await _tvdbClientManager + .GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken) + .ConfigureAwait(false); + MapSeriesToResult(result, seriesResult.Data, metadataLanguage); + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to retrieve series with id {TvdbId}", tvdbId); + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + result.ResetPeople(); + + try + { + var actorsResult = await _tvdbClientManager + .GetActorsAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken).ConfigureAwait(false); + MapActorsToResult(result, actorsResult.Data); + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to retrieve actors for series {TvdbId}", tvdbId); + } + } + + private async Task GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken) + { + + TvDbResponse result = null; + + try + { + if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase)) + { + result = await _tvdbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken) + .ConfigureAwait(false); + } + else + { + result = await _tvdbClientManager.GetSeriesByImdbIdAsync(id, language, cancellationToken) + .ConfigureAwait(false); + } + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to retrieve series with remote id {RemoteId}", id); + } + + return result?.Data.First().Id.ToString(); + } + + /// + /// Check whether a dictionary of provider IDs includes an entry for a valid TV metadata provider. + /// + /// The dictionary to check. + /// True, if the dictionary contains a valid TV provider ID, otherwise false. + internal static bool IsValidSeries(Dictionary seriesProviderIds) + { + return seriesProviderIds.ContainsKey(MetadataProviders.Tvdb.ToString()) || + seriesProviderIds.ContainsKey(MetadataProviders.Imdb.ToString()) || + seriesProviderIds.ContainsKey(MetadataProviders.Zap2It.ToString()); + } + + /// + /// Finds the series. + /// + /// The name. + /// The year. + /// The language. + /// The cancellation token. + /// Task{System.String}. + private async Task> FindSeries(string name, int? year, string language, CancellationToken cancellationToken) + { + var results = await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false); + + if (results.Count == 0) + { + var parsedName = _libraryManager.ParseName(name); + var nameWithoutYear = parsedName.Name; + + if (!string.IsNullOrWhiteSpace(nameWithoutYear) && !string.Equals(nameWithoutYear, name, StringComparison.OrdinalIgnoreCase)) + { + results = await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false); + } + } + + return results.Where(i => + { + if (year.HasValue && i.ProductionYear.HasValue) + { + // Allow one year tolerance + return Math.Abs(year.Value - i.ProductionYear.Value) <= 1; + } + + return true; + }); + } + + private async Task> FindSeriesInternal(string name, string language, CancellationToken cancellationToken) + { + var comparableName = GetComparableName(name); + var list = new List, RemoteSearchResult>>(); + TvDbResponse result; + try + { + result = await _tvdbClientManager.GetSeriesByNameAsync(comparableName, language, cancellationToken) + .ConfigureAwait(false); + } + catch (TvDbServerException e) + { + _logger.LogError(e, "No series results found for {Name}", comparableName); + return new List(); + } + + foreach (var seriesSearchResult in result.Data) + { + var tvdbTitles = new List + { + GetComparableName(seriesSearchResult.SeriesName) + }; + tvdbTitles.AddRange(seriesSearchResult.Aliases.Select(GetComparableName)); + + DateTime.TryParse(seriesSearchResult.FirstAired, out var firstAired); + var remoteSearchResult = new RemoteSearchResult + { + Name = tvdbTitles.FirstOrDefault(), + ProductionYear = firstAired.Year, + SearchProviderName = Name, + ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner + + }; + try + { + var seriesSesult = + await _tvdbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken) + .ConfigureAwait(false); + remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSesult.Data.ImdbId); + remoteSearchResult.SetProviderId(MetadataProviders.Zap2It, seriesSesult.Data.Zap2itId); + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Unable to retrieve series with id {TvdbId}", seriesSearchResult.Id); + } + + remoteSearchResult.SetProviderId(MetadataProviders.Tvdb, seriesSearchResult.Id.ToString()); + list.Add(new Tuple, RemoteSearchResult>(tvdbTitles, remoteSearchResult)); + } + + return list + .OrderBy(i => i.Item1.Contains(comparableName, StringComparer.OrdinalIgnoreCase) ? 0 : 1) + .ThenBy(i => list.IndexOf(i)) + .Select(i => i.Item2) + .ToList(); + } + + /// + /// The remove + /// + const string remove = "\"'!`?"; + /// + /// The spacers + /// + const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are two types of dashes, short and long) + + /// + /// Gets the name of the comparable. + /// + /// The name. + /// System.String. + private string GetComparableName(string name) + { + name = name.ToLowerInvariant(); + name = name.Normalize(NormalizationForm.FormKD); + var sb = new StringBuilder(); + foreach (var c in name) + { + if (c >= 0x2B0 && c <= 0x0333) + { + // skip char modifier and diacritics + } + else if (remove.IndexOf(c) > -1) + { + // skip chars we are removing + } + else if (spacers.IndexOf(c) > -1) + { + sb.Append(" "); + } + else if (c == '&') + { + sb.Append(" and "); + } + else + { + sb.Append(c); + } + } + sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " "); + + return Regex.Replace(sb.ToString().Trim(), @"\s+", " "); + } + + private void MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) + { + Series series = result.Item; + series.SetProviderId(MetadataProviders.Tvdb, tvdbSeries.Id.ToString()); + series.Name = tvdbSeries.SeriesName; + series.Overview = (tvdbSeries.Overview ?? string.Empty).Trim(); + result.ResultLanguage = metadataLanguage; + series.AirDays = TVUtils.GetAirDays(tvdbSeries.AirsDayOfWeek); + series.AirTime = tvdbSeries.AirsTime; + series.CommunityRating = (float?)tvdbSeries.SiteRating; + series.SetProviderId(MetadataProviders.Imdb, tvdbSeries.ImdbId); + series.SetProviderId(MetadataProviders.Zap2It, tvdbSeries.Zap2itId); + if (Enum.TryParse(tvdbSeries.Status, true, out SeriesStatus seriesStatus)) + { + series.Status = seriesStatus; + } + + if (DateTime.TryParse(tvdbSeries.FirstAired, out var date)) + { + // dates from tvdb are UTC but without offset or Z + series.PremiereDate = date; + series.ProductionYear = date.Year; + } + + series.RunTimeTicks = TimeSpan.FromMinutes(Convert.ToDouble(tvdbSeries.Runtime)).Ticks; + foreach (var genre in tvdbSeries.Genre) + { + series.AddGenre(genre); + } + + if (!string.IsNullOrEmpty(tvdbSeries.Network)) + { + series.AddStudio(tvdbSeries.Network); + } + + if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended) + { + try + { + var episodeSummary = _tvdbClientManager + .GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data; + var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); + var episodeQuery = new EpisodeQuery + { + AiredSeason = maxSeasonNumber + }; + var episodesPage = + _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data; + result.Item.EndDate = episodesPage.Select(e => + { + DateTime.TryParse(e.FirstAired, out var firstAired); + return firstAired; + }).Max(); + } + catch (TvDbServerException e) + { + _logger.LogError(e, "Failed to find series end date for series {TvdbId}", tvdbSeries.Id); + } + } + } + + private static void MapActorsToResult(MetadataResult result, IEnumerable actors) + { + foreach (Actor actor in actors) + { + var personInfo = new PersonInfo + { + Type = PersonType.Actor, + Name = (actor.Name ?? string.Empty).Trim(), + Role = actor.Role, + ImageUrl = TvdbUtils.BannerUrl + actor.Image, + SortOrder = actor.SortOrder + }; + + if (!string.IsNullOrWhiteSpace(personInfo.Name)) + { + result.AddPerson(personInfo); + } + } + } + + public string Name => "TheTVDB"; + + public async Task Identify(SeriesInfo info) + { + if (!string.IsNullOrWhiteSpace(info.GetProviderId(MetadataProviders.Tvdb))) + { + return; + } + + var srch = await FindSeries(info.Name, info.Year, info.MetadataLanguage, CancellationToken.None) + .ConfigureAwait(false); + + var entry = srch.FirstOrDefault(); + + if (entry != null) + { + var id = entry.GetProviderId(MetadataProviders.Tvdb); + info.SetProviderId(MetadataProviders.Tvdb, id); + } + } + + public int Order => 0; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url, + BufferContent = false + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs new file mode 100644 index 0000000000..79d879aa1b --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs @@ -0,0 +1,36 @@ +using System; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public static class TvdbUtils + { + public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K"; + public const string TvdbBaseUrl = "https://www.thetvdb.com/"; + public const string BannerUrl = TvdbBaseUrl + "banners/"; + + public static ImageType GetImageTypeFromKeyType(string keyType) + { + switch (keyType.ToLowerInvariant()) + { + case "poster": + case "season": return ImageType.Primary; + case "series": + case "seasonwide": return ImageType.Banner; + case "fanart": return ImageType.Backdrop; + default: throw new ArgumentException($"Invalid or unknown keytype: {keyType}", nameof(keyType)); + } + } + + public static string NormalizeLanguage(string language) + { + if (string.IsNullOrWhiteSpace(language)) + { + return null; + } + + // pt-br is just pt to tvdb + return language.Split('-')[0].ToLowerInvariant(); + } + } +} diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index e72df50de0..0721c4bb40 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -12,7 +12,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using MediaBrowser.Providers.TV.TheTVDB; +using MediaBrowser.Providers.Plugins.TheTvdb; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.TV @@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.TV private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; private readonly IFileSystem _fileSystem; - private readonly TvDbClientManager _tvDbClientManager; + private readonly TvdbClientManager _tvdbClientManager; public MissingEpisodeProvider( ILogger logger, @@ -34,14 +34,14 @@ namespace MediaBrowser.Providers.TV ILibraryManager libraryManager, ILocalizationManager localization, IFileSystem fileSystem, - TvDbClientManager tvDbClientManager) + TvdbClientManager tvdbClientManager) { _logger = logger; _config = config; _libraryManager = libraryManager; _localization = localization; _fileSystem = fileSystem; - _tvDbClientManager = tvDbClientManager; + _tvdbClientManager = tvdbClientManager; } public async Task Run(Series series, bool addNewItems, CancellationToken cancellationToken) @@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.TV return false; } - var episodes = await _tvDbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken); + var episodes = await _tvdbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken); var episodeLookup = episodes .Select(i => diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index e9e633ce77..7d1c8ba6bc 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; -using MediaBrowser.Providers.TV.TheTVDB; +using MediaBrowser.Providers.Plugins.TheTvdb; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.TV @@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.TV public class SeriesMetadataService : MetadataService { private readonly ILocalizationManager _localization; - private readonly TvDbClientManager _tvDbClientManager; + private readonly TvdbClientManager _tvdbClientManager; public SeriesMetadataService( IServerConfigurationManager serverConfigurationManager, @@ -26,11 +26,11 @@ namespace MediaBrowser.Providers.TV IFileSystem fileSystem, ILibraryManager libraryManager, ILocalizationManager localization, - TvDbClientManager tvDbClientManager) + TvdbClientManager tvdbClientManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) { _localization = localization; - _tvDbClientManager = tvDbClientManager; + _tvdbClientManager = tvdbClientManager; } /// @@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.TV LibraryManager, _localization, FileSystem, - _tvDbClientManager); + _tvdbClientManager); try { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs deleted file mode 100644 index 4abe6a943f..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using Microsoft.Extensions.Caching.Memory; -using TvDbSharper; -using TvDbSharper.Dto; - -namespace MediaBrowser.Providers.TV.TheTVDB -{ - public class TvDbClientManager - { - private const string DefaultLanguage = "en"; - - private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1); - private readonly IMemoryCache _cache; - private readonly TvDbClient _tvDbClient; - private DateTime _tokenCreatedAt; - - public TvDbClientManager(IMemoryCache memoryCache) - { - _cache = memoryCache; - _tvDbClient = new TvDbClient(); - } - - private TvDbClient TvDbClient - { - get - { - if (string.IsNullOrEmpty(_tvDbClient.Authentication.Token)) - { - _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult(); - _tokenCreatedAt = DateTime.Now; - } - - // Refresh if necessary - if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20))) - { - try - { - _tvDbClient.Authentication.RefreshTokenAsync().GetAwaiter().GetResult(); - } - catch - { - _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult(); - } - - _tokenCreatedAt = DateTime.Now; - } - - return _tvDbClient; - } - } - - public Task> GetSeriesByNameAsync(string name, string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("series", name, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken)); - } - - public Task> GetSeriesByIdAsync(int tvdbId, string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("series", tvdbId, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken)); - } - - public Task> GetEpisodesAsync(int episodeTvdbId, string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("episode", episodeTvdbId, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken)); - } - - public async Task> GetAllEpisodesAsync(int tvdbId, string language, - CancellationToken cancellationToken) - { - // Traverse all episode pages and join them together - var episodes = new List(); - var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken) - .ConfigureAwait(false); - episodes.AddRange(episodePage.Data); - if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue) - { - return episodes; - } - - int next = episodePage.Links.Next.Value; - int last = episodePage.Links.Last.Value; - - for (var page = next; page <= last; ++page) - { - episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken) - .ConfigureAwait(false); - episodes.AddRange(episodePage.Data); - } - - return episodes; - } - - public Task> GetSeriesByImdbIdAsync( - string imdbId, - string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("series", imdbId, language); - return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken)); - } - - public Task> GetSeriesByZap2ItIdAsync( - string zap2ItId, - string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("series", zap2ItId, language); - return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken)); - } - public Task> GetActorsAsync( - int tvdbId, - string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("actors", tvdbId, language); - return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken)); - } - - public Task> GetImagesAsync( - int tvdbId, - ImagesQuery imageQuery, - string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("images", tvdbId, language, imageQuery); - return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken)); - } - - public Task> GetLanguagesAsync(CancellationToken cancellationToken) - { - return TryGetValue("languages", null, () => TvDbClient.Languages.GetAllAsync(cancellationToken)); - } - - public Task> GetSeriesEpisodeSummaryAsync( - int tvdbId, - string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey("seriesepisodesummary", tvdbId, language); - return TryGetValue(cacheKey, language, - () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken)); - } - - public Task> GetEpisodesPageAsync( - int tvdbId, - int page, - EpisodeQuery episodeQuery, - string language, - CancellationToken cancellationToken) - { - var cacheKey = GenerateKey(language, tvdbId, episodeQuery); - - return TryGetValue(cacheKey, language, - () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken)); - } - - public Task GetEpisodeTvdbId( - EpisodeInfo searchInfo, - string language, - CancellationToken cancellationToken) - { - searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), - out var seriesTvdbId); - - var episodeQuery = new EpisodeQuery(); - - // Prefer SxE over premiere date as it is more robust - if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue) - { - switch (searchInfo.SeriesDisplayOrder) - { - case "dvd": - episodeQuery.DvdEpisode = searchInfo.IndexNumber.Value; - episodeQuery.DvdSeason = searchInfo.ParentIndexNumber.Value; - break; - case "absolute": - episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value; - break; - default: - //aired order - episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value; - episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value; - break; - } - } - else if (searchInfo.PremiereDate.HasValue) - { - // tvdb expects yyyy-mm-dd format - episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd"); - } - - return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken); - } - - public async Task GetEpisodeTvdbId( - int seriesTvdbId, - EpisodeQuery episodeQuery, - string language, - CancellationToken cancellationToken) - { - var episodePage = - await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken) - .ConfigureAwait(false); - return episodePage.Data.FirstOrDefault()?.Id.ToString(); - } - - public Task> GetEpisodesPageAsync( - int tvdbId, - EpisodeQuery episodeQuery, - string language, - CancellationToken cancellationToken) - { - return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken); - } - - private async Task TryGetValue(string key, string language, Func> resultFactory) - { - if (_cache.TryGetValue(key, out T cachedValue)) - { - return cachedValue; - } - - await _cacheWriteLock.WaitAsync().ConfigureAwait(false); - try - { - if (_cache.TryGetValue(key, out cachedValue)) - { - return cachedValue; - } - - _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage; - var result = await resultFactory.Invoke().ConfigureAwait(false); - _cache.Set(key, result, TimeSpan.FromHours(1)); - return result; - } - finally - { - _cacheWriteLock.Release(); - } - } - - private static string GenerateKey(params object[] objects) - { - var key = string.Empty; - - foreach (var obj in objects) - { - var objType = obj.GetType(); - if (objType.IsPrimitive || objType == typeof(string)) - { - key += obj + ";"; - } - else - { - foreach (PropertyInfo propertyInfo in objType.GetProperties()) - { - var currentValue = propertyInfo.GetValue(obj, null); - if (currentValue == null) - { - continue; - } - - key += propertyInfo.Name + "=" + currentValue + ";"; - } - } - } - - return key; - } - } -} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs deleted file mode 100644 index fc7f12b1a8..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Logging; -using TvDbSharper; -using TvDbSharper.Dto; - -namespace MediaBrowser.Providers.TV.TheTVDB -{ - public class TvdbEpisodeImageProvider : IRemoteImageProvider - { - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly TvDbClientManager _tvDbClientManager; - - public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger logger, TvDbClientManager tvDbClientManager) - { - _httpClient = httpClient; - _logger = logger; - _tvDbClientManager = tvDbClientManager; - } - - public string Name => "TheTVDB"; - - public bool Supports(BaseItem item) - { - return item is Episode; - } - - public IEnumerable GetSupportedImages(BaseItem item) - { - return new List - { - ImageType.Primary - }; - } - - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - var episode = (Episode)item; - var series = episode.Series; - var imageResult = new List(); - var language = item.GetPreferredMetadataLanguage(); - if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) - { - // Process images - try - { - var episodeInfo = new EpisodeInfo - { - IndexNumber = episode.IndexNumber.Value, - ParentIndexNumber = episode.ParentIndexNumber.Value, - SeriesProviderIds = series.ProviderIds, - SeriesDisplayOrder = series.DisplayOrder - }; - string episodeTvdbId = await _tvDbClientManager - .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false); - if (string.IsNullOrEmpty(episodeTvdbId)) - { - _logger.LogError( - "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}", - episodeInfo.ParentIndexNumber, - episodeInfo.IndexNumber, - series.GetProviderId(MetadataProviders.Tvdb)); - return imageResult; - } - - var episodeResult = - await _tvDbClientManager - .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId), language, cancellationToken) - .ConfigureAwait(false); - - var image = GetImageInfo(episodeResult.Data); - if (image != null) - { - imageResult.Add(image); - } - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProviders.Tvdb)); - } - } - - return imageResult; - } - - private RemoteImageInfo GetImageInfo(EpisodeRecord episode) - { - if (string.IsNullOrEmpty(episode.Filename)) - { - return null; - } - - return new RemoteImageInfo - { - Width = Convert.ToInt32(episode.ThumbWidth), - Height = Convert.ToInt32(episode.ThumbHeight), - ProviderName = Name, - Url = TvdbUtils.BannerUrl + episode.Filename, - Type = ImageType.Primary - }; - } - - public int Order => 0; - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url - }); - } - } -} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs deleted file mode 100644 index 4269d34201..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Logging; -using TvDbSharper; -using TvDbSharper.Dto; - -namespace MediaBrowser.Providers.TV.TheTVDB -{ - - /// - /// Class RemoteEpisodeProvider - /// - public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder - { - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly TvDbClientManager _tvDbClientManager; - - public TvdbEpisodeProvider(IHttpClient httpClient, ILogger logger, TvDbClientManager tvDbClientManager) - { - _httpClient = httpClient; - _logger = logger; - _tvDbClientManager = tvDbClientManager; - } - - public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) - { - var list = new List(); - - // Either an episode number or date must be provided; and the dictionary of provider ids must be valid - if ((searchInfo.IndexNumber == null && searchInfo.PremiereDate == null) - || !TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds)) - { - return list; - } - - var metadataResult = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false); - - if (!metadataResult.HasMetadata) - { - return list; - } - - var item = metadataResult.Item; - - list.Add(new RemoteSearchResult - { - IndexNumber = item.IndexNumber, - Name = item.Name, - ParentIndexNumber = item.ParentIndexNumber, - PremiereDate = item.PremiereDate, - ProductionYear = item.ProductionYear, - ProviderIds = item.ProviderIds, - SearchProviderName = Name, - IndexNumberEnd = item.IndexNumberEnd - }); - - return list; - } - - public string Name => "TheTVDB"; - - public async Task> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken) - { - var result = new MetadataResult - { - QueriedById = true - }; - - if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && - (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue)) - { - result = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false); - } - else - { - _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name); - } - - return result; - } - - private async Task> GetEpisode(EpisodeInfo searchInfo, CancellationToken cancellationToken) - { - var result = new MetadataResult - { - QueriedById = true - }; - - string seriesTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb); - string episodeTvdbId = null; - try - { - episodeTvdbId = await _tvDbClientManager - .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken) - .ConfigureAwait(false); - if (string.IsNullOrEmpty(episodeTvdbId)) - { - _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}", - searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId); - return result; - } - - var episodeResult = await _tvDbClientManager.GetEpisodesAsync( - Convert.ToInt32(episodeTvdbId), searchInfo.MetadataLanguage, - cancellationToken).ConfigureAwait(false); - - result = MapEpisodeToResult(searchInfo, episodeResult.Data); - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to retrieve episode with id {EpisodeTvDbId}, series id {SeriesTvdbId}", episodeTvdbId, seriesTvdbId); - } - - return result; - } - - private static MetadataResult MapEpisodeToResult(EpisodeInfo id, EpisodeRecord episode) - { - var result = new MetadataResult - { - HasMetadata = true, - Item = new Episode - { - IndexNumber = id.IndexNumber, - ParentIndexNumber = id.ParentIndexNumber, - IndexNumberEnd = id.IndexNumberEnd, - AirsBeforeEpisodeNumber = episode.AirsBeforeEpisode, - AirsAfterSeasonNumber = episode.AirsAfterSeason, - AirsBeforeSeasonNumber = episode.AirsBeforeSeason, - Name = episode.EpisodeName, - Overview = episode.Overview, - CommunityRating = (float?)episode.SiteRating, - - } - }; - result.ResetPeople(); - - var item = result.Item; - item.SetProviderId(MetadataProviders.Tvdb, episode.Id.ToString()); - item.SetProviderId(MetadataProviders.Imdb, episode.ImdbId); - - if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase)) - { - item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber); - item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason; - } - else if (episode.AiredEpisodeNumber.HasValue) - { - item.IndexNumber = episode.AiredEpisodeNumber; - } - else if (episode.AiredSeason.HasValue) - { - item.ParentIndexNumber = episode.AiredSeason; - } - - if (DateTime.TryParse(episode.FirstAired, out var date)) - { - // dates from tvdb are UTC but without offset or Z - item.PremiereDate = date; - item.ProductionYear = date.Year; - } - - foreach (var director in episode.Directors) - { - result.AddPerson(new PersonInfo - { - Name = director, - Type = PersonType.Director - }); - } - - // GuestStars is a weird list of names and roles - // Example: - // 1: Some Actor (Role1 - // 2: Role2 - // 3: Role3) - // 4: Another Actor (Role1 - // ... - for (var i = 0; i < episode.GuestStars.Length; ++i) - { - var currentActor = episode.GuestStars[i]; - var roleStartIndex = currentActor.IndexOf('('); - - if (roleStartIndex == -1) - { - result.AddPerson(new PersonInfo - { - Type = PersonType.GuestStar, - Name = currentActor, - Role = string.Empty - }); - continue; - } - - var roles = new List {currentActor.Substring(roleStartIndex + 1)}; - - // Fetch all roles - for (var j = i + 1; j < episode.GuestStars.Length; ++j) - { - var currentRole = episode.GuestStars[j]; - var roleEndIndex = currentRole.IndexOf(')'); - - if (roleEndIndex == -1) - { - roles.Add(currentRole); - continue; - } - - roles.Add(currentRole.TrimEnd(')')); - // Update the outer index (keep in mind it adds 1 after the iteration) - i = j; - break; - } - - result.AddPerson(new PersonInfo - { - Type = PersonType.GuestStar, - Name = currentActor.Substring(0, roleStartIndex).Trim(), - Role = string.Join(", ", roles) - }); - } - - foreach (var writer in episode.Writers) - { - result.AddPerson(new PersonInfo - { - Name = writer, - Type = PersonType.Writer - }); - } - - result.ResultLanguage = episode.Language.EpisodeName; - return result; - } - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url - }); - } - - public int Order => 0; - } -} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs deleted file mode 100644 index 94ca603f20..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Logging; -using TvDbSharper; -using TvDbSharper.Dto; -using RatingType = MediaBrowser.Model.Dto.RatingType; - -namespace MediaBrowser.Providers.TV.TheTVDB -{ - public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder - { - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly TvDbClientManager _tvDbClientManager; - - public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger logger, TvDbClientManager tvDbClientManager) - { - _httpClient = httpClient; - _logger = logger; - _tvDbClientManager = tvDbClientManager; - } - - public string Name => ProviderName; - - public static string ProviderName => "TheTVDB"; - - public bool Supports(BaseItem item) - { - return item is Season; - } - - public IEnumerable GetSupportedImages(BaseItem item) - { - return new List - { - ImageType.Primary, - ImageType.Banner, - ImageType.Backdrop - }; - } - - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - var season = (Season)item; - var series = season.Series; - - if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) - { - return new RemoteImageInfo[] { }; - } - - var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb)); - var seasonNumber = season.IndexNumber.Value; - var language = item.GetPreferredMetadataLanguage(); - var remoteImages = new List(); - - var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart }; - foreach (var keyType in keyTypes) - { - var imageQuery = new ImagesQuery - { - KeyType = keyType, - SubKey = seasonNumber.ToString() - }; - try - { - var imageResults = await _tvDbClientManager - .GetImagesAsync(tvdbId, imageQuery, language, cancellationToken).ConfigureAwait(false); - remoteImages.AddRange(GetImages(imageResults.Data, language)); - } - catch (TvDbServerException) - { - _logger.LogDebug("No images of type {KeyType} found for series {TvdbId}", keyType, tvdbId); - } - } - - return remoteImages; - } - - private IEnumerable GetImages(Image[] images, string preferredLanguage) - { - var list = new List(); - var languages = _tvDbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data; - foreach (Image image in images) - { - var imageInfo = new RemoteImageInfo - { - RatingType = RatingType.Score, - CommunityRating = (double?)image.RatingsInfo.Average, - VoteCount = image.RatingsInfo.Count, - Url = TvdbUtils.BannerUrl + image.FileName, - ProviderName = ProviderName, - Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation, - ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail - }; - - var resolution = image.Resolution.Split('x'); - if (resolution.Length == 2) - { - imageInfo.Width = Convert.ToInt32(resolution[0]); - imageInfo.Height = Convert.ToInt32(resolution[1]); - } - - imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType); - list.Add(imageInfo); - } - var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); - - return list.OrderByDescending(i => - { - if (string.Equals(preferredLanguage, 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) - .ThenByDescending(i => i.VoteCount ?? 0); - } - - public int Order => 0; - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url - }); - } - } -} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs deleted file mode 100644 index 365f49fb75..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Logging; -using TvDbSharper; -using TvDbSharper.Dto; -using RatingType = MediaBrowser.Model.Dto.RatingType; -using Series = MediaBrowser.Controller.Entities.TV.Series; - -namespace MediaBrowser.Providers.TV.TheTVDB -{ - public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder - { - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly TvDbClientManager _tvDbClientManager; - - public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger logger, TvDbClientManager tvDbClientManager) - { - _httpClient = httpClient; - _logger = logger; - _tvDbClientManager = tvDbClientManager; - } - - public string Name => ProviderName; - - public static string ProviderName => "TheTVDB"; - - public bool Supports(BaseItem item) - { - return item is Series; - } - - public IEnumerable GetSupportedImages(BaseItem item) - { - return new List - { - ImageType.Primary, - ImageType.Banner, - ImageType.Backdrop - }; - } - - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) - { - return Array.Empty(); - } - - var language = item.GetPreferredMetadataLanguage(); - var remoteImages = new List(); - var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart }; - var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProviders.Tvdb)); - foreach (KeyType keyType in keyTypes) - { - var imageQuery = new ImagesQuery - { - KeyType = keyType - }; - try - { - var imageResults = - await _tvDbClientManager.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken) - .ConfigureAwait(false); - - remoteImages.AddRange(GetImages(imageResults.Data, language)); - } - catch (TvDbServerException) - { - _logger.LogDebug("No images of type {KeyType} exist for series {TvDbId}", keyType, - tvdbId); - } - } - return remoteImages; - } - - private IEnumerable GetImages(Image[] images, string preferredLanguage) - { - var list = new List(); - var languages = _tvDbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data; - - foreach (Image image in images) - { - var imageInfo = new RemoteImageInfo - { - RatingType = RatingType.Score, - CommunityRating = (double?)image.RatingsInfo.Average, - VoteCount = image.RatingsInfo.Count, - Url = TvdbUtils.BannerUrl + image.FileName, - ProviderName = Name, - Language = languages.FirstOrDefault(lang => lang.Id == image.LanguageId)?.Abbreviation, - ThumbnailUrl = TvdbUtils.BannerUrl + image.Thumbnail - }; - - var resolution = image.Resolution.Split('x'); - if (resolution.Length == 2) - { - imageInfo.Width = Convert.ToInt32(resolution[0]); - imageInfo.Height = Convert.ToInt32(resolution[1]); - } - - imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType); - list.Add(imageInfo); - } - var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); - - return list.OrderByDescending(i => - { - if (string.Equals(preferredLanguage, 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) - .ThenByDescending(i => i.VoteCount ?? 0); - } - - public int Order => 0; - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url - }); - } - } -} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs deleted file mode 100644 index 9e791bd9d5..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ /dev/null @@ -1,437 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Providers; -using Microsoft.Extensions.Logging; -using TvDbSharper; -using TvDbSharper.Dto; -using Series = MediaBrowser.Controller.Entities.TV.Series; - -namespace MediaBrowser.Providers.TV.TheTVDB -{ - public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder - { - internal static TvdbSeriesProvider Current { get; private set; } - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly ILocalizationManager _localizationManager; - private readonly TvDbClientManager _tvDbClientManager; - - public TvdbSeriesProvider(IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvDbClientManager tvDbClientManager) - { - _httpClient = httpClient; - _logger = logger; - _libraryManager = libraryManager; - _localizationManager = localizationManager; - Current = this; - _tvDbClientManager = tvDbClientManager; - } - - public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) - { - if (IsValidSeries(searchInfo.ProviderIds)) - { - var metadata = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false); - - if (metadata.HasMetadata) - { - return new List - { - new RemoteSearchResult - { - Name = metadata.Item.Name, - PremiereDate = metadata.Item.PremiereDate, - ProductionYear = metadata.Item.ProductionYear, - ProviderIds = metadata.Item.ProviderIds, - SearchProviderName = Name - } - }; - } - } - - return await FindSeries(searchInfo.Name, searchInfo.Year, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); - } - - public async Task> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken) - { - var result = new MetadataResult - { - QueriedById = true - }; - - if (!IsValidSeries(itemId.ProviderIds)) - { - result.QueriedById = false; - await Identify(itemId).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (IsValidSeries(itemId.ProviderIds)) - { - result.Item = new Series(); - result.HasMetadata = true; - - await FetchSeriesData(result, itemId.MetadataLanguage, itemId.ProviderIds, cancellationToken) - .ConfigureAwait(false); - } - - return result; - } - - private async Task FetchSeriesData(MetadataResult result, string metadataLanguage, Dictionary seriesProviderIds, CancellationToken cancellationToken) - { - var series = result.Item; - - if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId)) - { - series.SetProviderId(MetadataProviders.Tvdb, tvdbId); - } - - if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId)) - { - series.SetProviderId(MetadataProviders.Imdb, imdbId); - tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProviders.Imdb.ToString(), metadataLanguage, - cancellationToken).ConfigureAwait(false); - } - - if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It)) - { - series.SetProviderId(MetadataProviders.Zap2It, zap2It); - tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProviders.Zap2It.ToString(), metadataLanguage, - cancellationToken).ConfigureAwait(false); - } - - try - { - var seriesResult = - await _tvDbClientManager - .GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken) - .ConfigureAwait(false); - MapSeriesToResult(result, seriesResult.Data, metadataLanguage); - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to retrieve series with id {TvdbId}", tvdbId); - return; - } - - cancellationToken.ThrowIfCancellationRequested(); - - result.ResetPeople(); - - try - { - var actorsResult = await _tvDbClientManager - .GetActorsAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken).ConfigureAwait(false); - MapActorsToResult(result, actorsResult.Data); - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to retrieve actors for series {TvdbId}", tvdbId); - } - } - - private async Task GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken) - { - - TvDbResponse result = null; - - try - { - if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase)) - { - result = await _tvDbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken) - .ConfigureAwait(false); - } - else - { - result = await _tvDbClientManager.GetSeriesByImdbIdAsync(id, language, cancellationToken) - .ConfigureAwait(false); - } - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to retrieve series with remote id {RemoteId}", id); - } - - return result?.Data.First().Id.ToString(); - } - - /// - /// Check whether a dictionary of provider IDs includes an entry for a valid TV metadata provider. - /// - /// The dictionary to check. - /// True, if the dictionary contains a valid TV provider ID, otherwise false. - internal static bool IsValidSeries(Dictionary seriesProviderIds) - { - return seriesProviderIds.ContainsKey(MetadataProviders.Tvdb.ToString()) || - seriesProviderIds.ContainsKey(MetadataProviders.Imdb.ToString()) || - seriesProviderIds.ContainsKey(MetadataProviders.Zap2It.ToString()); - } - - /// - /// Finds the series. - /// - /// The name. - /// The year. - /// The language. - /// The cancellation token. - /// Task{System.String}. - private async Task> FindSeries(string name, int? year, string language, CancellationToken cancellationToken) - { - var results = await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false); - - if (results.Count == 0) - { - var parsedName = _libraryManager.ParseName(name); - var nameWithoutYear = parsedName.Name; - - if (!string.IsNullOrWhiteSpace(nameWithoutYear) && !string.Equals(nameWithoutYear, name, StringComparison.OrdinalIgnoreCase)) - { - results = await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false); - } - } - - return results.Where(i => - { - if (year.HasValue && i.ProductionYear.HasValue) - { - // Allow one year tolerance - return Math.Abs(year.Value - i.ProductionYear.Value) <= 1; - } - - return true; - }); - } - - private async Task> FindSeriesInternal(string name, string language, CancellationToken cancellationToken) - { - var comparableName = GetComparableName(name); - var list = new List, RemoteSearchResult>>(); - TvDbResponse result; - try - { - result = await _tvDbClientManager.GetSeriesByNameAsync(comparableName, language, cancellationToken) - .ConfigureAwait(false); - } - catch (TvDbServerException e) - { - _logger.LogError(e, "No series results found for {Name}", comparableName); - return new List(); - } - - foreach (var seriesSearchResult in result.Data) - { - var tvdbTitles = new List - { - GetComparableName(seriesSearchResult.SeriesName) - }; - tvdbTitles.AddRange(seriesSearchResult.Aliases.Select(GetComparableName)); - - DateTime.TryParse(seriesSearchResult.FirstAired, out var firstAired); - var remoteSearchResult = new RemoteSearchResult - { - Name = tvdbTitles.FirstOrDefault(), - ProductionYear = firstAired.Year, - SearchProviderName = Name, - ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner - - }; - try - { - var seriesSesult = - await _tvDbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken) - .ConfigureAwait(false); - remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSesult.Data.ImdbId); - remoteSearchResult.SetProviderId(MetadataProviders.Zap2It, seriesSesult.Data.Zap2itId); - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Unable to retrieve series with id {TvdbId}", seriesSearchResult.Id); - } - - remoteSearchResult.SetProviderId(MetadataProviders.Tvdb, seriesSearchResult.Id.ToString()); - list.Add(new Tuple, RemoteSearchResult>(tvdbTitles, remoteSearchResult)); - } - - return list - .OrderBy(i => i.Item1.Contains(comparableName, StringComparer.OrdinalIgnoreCase) ? 0 : 1) - .ThenBy(i => list.IndexOf(i)) - .Select(i => i.Item2) - .ToList(); - } - - /// - /// The remove - /// - const string remove = "\"'!`?"; - /// - /// The spacers - /// - const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are two types of dashes, short and long) - - /// - /// Gets the name of the comparable. - /// - /// The name. - /// System.String. - private string GetComparableName(string name) - { - name = name.ToLowerInvariant(); - name = name.Normalize(NormalizationForm.FormKD); - var sb = new StringBuilder(); - foreach (var c in name) - { - if (c >= 0x2B0 && c <= 0x0333) - { - // skip char modifier and diacritics - } - else if (remove.IndexOf(c) > -1) - { - // skip chars we are removing - } - else if (spacers.IndexOf(c) > -1) - { - sb.Append(" "); - } - else if (c == '&') - { - sb.Append(" and "); - } - else - { - sb.Append(c); - } - } - sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " "); - - return Regex.Replace(sb.ToString().Trim(), @"\s+", " "); - } - - private void MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) - { - Series series = result.Item; - series.SetProviderId(MetadataProviders.Tvdb, tvdbSeries.Id.ToString()); - series.Name = tvdbSeries.SeriesName; - series.Overview = (tvdbSeries.Overview ?? string.Empty).Trim(); - result.ResultLanguage = metadataLanguage; - series.AirDays = TVUtils.GetAirDays(tvdbSeries.AirsDayOfWeek); - series.AirTime = tvdbSeries.AirsTime; - series.CommunityRating = (float?)tvdbSeries.SiteRating; - series.SetProviderId(MetadataProviders.Imdb, tvdbSeries.ImdbId); - series.SetProviderId(MetadataProviders.Zap2It, tvdbSeries.Zap2itId); - if (Enum.TryParse(tvdbSeries.Status, true, out SeriesStatus seriesStatus)) - { - series.Status = seriesStatus; - } - - if (DateTime.TryParse(tvdbSeries.FirstAired, out var date)) - { - // dates from tvdb are UTC but without offset or Z - series.PremiereDate = date; - series.ProductionYear = date.Year; - } - - series.RunTimeTicks = TimeSpan.FromMinutes(Convert.ToDouble(tvdbSeries.Runtime)).Ticks; - foreach (var genre in tvdbSeries.Genre) - { - series.AddGenre(genre); - } - - if (!string.IsNullOrEmpty(tvdbSeries.Network)) - { - series.AddStudio(tvdbSeries.Network); - } - - if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended) - { - try - { - var episodeSummary = _tvDbClientManager - .GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data; - var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); - var episodeQuery = new EpisodeQuery - { - AiredSeason = maxSeasonNumber - }; - var episodesPage = - _tvDbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data; - result.Item.EndDate = episodesPage.Select(e => - { - DateTime.TryParse(e.FirstAired, out var firstAired); - return firstAired; - }).Max(); - } - catch (TvDbServerException e) - { - _logger.LogError(e, "Failed to find series end date for series {TvdbId}", tvdbSeries.Id); - } - } - } - - private static void MapActorsToResult(MetadataResult result, IEnumerable actors) - { - foreach (Actor actor in actors) - { - var personInfo = new PersonInfo - { - Type = PersonType.Actor, - Name = (actor.Name ?? string.Empty).Trim(), - Role = actor.Role, - ImageUrl = TvdbUtils.BannerUrl + actor.Image, - SortOrder = actor.SortOrder - }; - - if (!string.IsNullOrWhiteSpace(personInfo.Name)) - { - result.AddPerson(personInfo); - } - } - } - - public string Name => "TheTVDB"; - - public async Task Identify(SeriesInfo info) - { - if (!string.IsNullOrWhiteSpace(info.GetProviderId(MetadataProviders.Tvdb))) - { - return; - } - - var srch = await FindSeries(info.Name, info.Year, info.MetadataLanguage, CancellationToken.None) - .ConfigureAwait(false); - - var entry = srch.FirstOrDefault(); - - if (entry != null) - { - var id = entry.GetProviderId(MetadataProviders.Tvdb); - info.SetProviderId(MetadataProviders.Tvdb, id); - } - } - - public int Order => 0; - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - return _httpClient.GetResponse(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = url, - BufferContent = false - }); - } - } -} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs deleted file mode 100644 index dd5ebf2704..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using MediaBrowser.Model.Entities; -namespace MediaBrowser.Providers.TV.TheTVDB -{ - public static class TvdbUtils - { - public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K"; - public const string TvdbBaseUrl = "https://www.thetvdb.com/"; - public const string BannerUrl = TvdbBaseUrl + "banners/"; - - public static ImageType GetImageTypeFromKeyType(string keyType) - { - switch (keyType.ToLowerInvariant()) - { - case "poster": - case "season": return ImageType.Primary; - case "series": - case "seasonwide": return ImageType.Banner; - case "fanart": return ImageType.Backdrop; - default: throw new ArgumentException($"Invalid or unknown keytype: {keyType}", nameof(keyType)); - } - } - - public static string NormalizeLanguage(string language) - { - if (string.IsNullOrWhiteSpace(language)) - { - return null; - } - - // pt-br is just pt to tvdb - return language.Split('-')[0].ToLowerInvariant(); - } - } -} diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 646dae3e0d..baf8542851 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Providers.TV.TheTVDB; +using MediaBrowser.Providers.Plugins.TheTvdb; namespace MediaBrowser.Providers.TV { -- cgit v1.2.3 From 885bc11b67f7b8131b4695000608a08a928b935e Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 17 Mar 2020 14:30:08 +0100 Subject: Log 'JELLYFIN_' environment variables at application start --- Emby.Server.Implementations/ApplicationHost.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index acae230522..3576b616e7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -897,6 +897,18 @@ namespace Emby.Server.Implementations .GetCommandLineArgs() .Distinct(); + // Get all 'JELLYFIN_' prefixed environment variables + var allEnvVars = Environment.GetEnvironmentVariables(); + var jellyfinEnvVars = new Dictionary(); + foreach (var key in allEnvVars.Keys) + { + if (key.ToString().StartsWith("JELLYFIN_", StringComparison.OrdinalIgnoreCase)) + { + jellyfinEnvVars.Add(key, allEnvVars[key]); + } + } + + logger.LogInformation("Environment Variables: {EnvVars}", jellyfinEnvVars); logger.LogInformation("Arguments: {Args}", commandLineArgs); logger.LogInformation("Operating system: {OS}", OperatingSystem.Name); logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); -- cgit v1.2.3 From d1fe28fac6ebd178c1ae450f030a1952f5edfaed Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Wed, 25 Mar 2020 19:16:12 +0100 Subject: Check for null before disposing --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 35b2cba9f1..d6a572818a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1803,7 +1803,7 @@ namespace Emby.Server.Implementations } _userRepository?.Dispose(); - _displayPreferencesRepository.Dispose(); + _displayPreferencesRepository?.Dispose(); } _userRepository = null; -- cgit v1.2.3 From 147e434634bda67ffeebd6db0790f5fd652b1fc9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 1 Apr 2020 14:50:18 +0200 Subject: Try to not crash on unsupported plugin load --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index d6a572818a..f3540e25ee 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1167,7 +1167,7 @@ namespace Emby.Server.Implementations { exportedTypes = ass.GetExportedTypes(); } - catch (TypeLoadException ex) + catch (FileNotFoundException ex) { Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); continue; -- cgit v1.2.3