From 1a709bb2b6665501bdd36246faa75f846ff4491f Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 02:01:45 +0800 Subject: [PATCH 1/9] Start boxset support --- .../Providers/TvdbBoxSetProvider.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs new file mode 100644 index 0000000..6925566 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.Tvdb.Providers +{ + /// + /// The TvdbBoxSetProvider class. + /// + public class TvdbBoxSetProvider : IRemoteMetadataProvider + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly TvdbClientManager _tvdbClientManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of . + /// Instance of . + /// Instance of . + public TvdbBoxSetProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + _tvdbClientManager = tvdbClientManager; + } + + /// + public string Name => TvdbPlugin.ProviderName; + + /// + public Task> GetMetadata(BoxSetInfo info, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); + } + } +} From fc76dee8cd1bc07bf49891b2b290940934c168f9 Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 02:15:46 +0800 Subject: [PATCH 2/9] Add required apis --- Jellyfin.Plugin.Tvdb/TvdbClientManager.cs | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs b/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs index edfdfca..207e95a 100644 --- a/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs +++ b/Jellyfin.Plugin.Tvdb/TvdbClientManager.cs @@ -607,6 +607,56 @@ public async Task> GetArtworkTypeAsync(CancellationTo } } + /// + /// Get BoxSet by name. + /// + /// BoxSet Name. + /// Cancellation Token. + /// The box set search result. + public async Task> GetBoxSetByNameAsync( + string name, + CancellationToken cancellationToken) + { + var key = $"TvdbBoxSetSearch_{name}"; + if (_memoryCache.TryGetValue(key, out IReadOnlyList? boxSets) + && boxSets is not null) + { + return boxSets; + } + + var searchClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var searchResult = await searchClient.GetSearchResultsAsync(query: name, type: "list", limit: 5, cancellationToken: cancellationToken) + .ConfigureAwait(false); + _memoryCache.Set(key, searchResult.Data, TimeSpan.FromHours(CacheDurationInHours)); + return searchResult.Data; + } + + /// + /// Get BoxSet by remoteId. + /// + /// The BoxSet tvdb id. + /// Cancellation Token. + /// The box set response. + public async Task GetBoxSetExtendedByIdAsync( + int tvdbId, + CancellationToken cancellationToken) + { + var key = $"TvdbBoxSet_{tvdbId.ToString(CultureInfo.InvariantCulture)}"; + if (_memoryCache.TryGetValue(key, out ListExtendedRecord? boxSet) + && boxSet is not null) + { + return boxSet; + } + + var boxSetClient = _serviceProvider.GetRequiredService(); + await LoginAsync().ConfigureAwait(false); + var boxSetResult = await boxSetClient.GetListExtendedAsync(id: tvdbId, cancellationToken: cancellationToken) + .ConfigureAwait(false); + _memoryCache.Set(key, boxSetResult.Data, TimeSpan.FromHours(CacheDurationInHours)); + return boxSetResult.Data; + } + /// /// Gets updates from tvdb since a given time. No caching. /// @@ -698,6 +748,7 @@ private ServiceProvider ConfigureService(IApplicationHost applicationHost) services.AddTransient(_ => new LanguagesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); services.AddTransient(_ => new UpdatesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); services.AddTransient(_ => new MoviesClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); + services.AddTransient(_ => new ListsClient(_sdkClientSettings, _httpClientFactory.CreateClient(TvdbHttpClient))); return services.BuildServiceProvider(); } From 1b4b51ee563152212e6f930d2e610cd3dffa4c54 Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 03:28:51 +0800 Subject: [PATCH 3/9] Add preliminary Boxset provider --- .../ExternalId/TvdbBoxSetExternalId.cs | 28 +++ .../ExternalId/TvdbBoxSetSlugExternalId.cs | 28 +++ .../Providers/TvdbBoxSetProvider.cs | 187 +++++++++++++++++- 3 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs create mode 100644 Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs diff --git a/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs new file mode 100644 index 0000000..df2ea46 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace Jellyfin.Plugin.Tvdb.Providers.ExternalId +{ + /// + /// The TvdbBoxSetExternalId class. + /// + public class TvdbBoxSetExternalId : IExternalId + { + /// + public string ProviderName => TvdbPlugin.ProviderName; + + /// + public string Key => TvdbPlugin.ProviderId; + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet; + + /// + public string? UrlFormatString => null; + + /// + public bool Supports(IHasProviderIds item) => item is BoxSet; + } +} diff --git a/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs new file mode 100644 index 0000000..ec79993 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace Jellyfin.Plugin.Tvdb.Providers.ExternalId +{ + /// + /// The TvdbBoxSetSlugExternalId class. + /// + public class TvdbBoxSetSlugExternalId : IExternalId + { + /// + public string ProviderName => TvdbPlugin.ProviderName + " Slug"; + + /// + public string Key => TvdbPlugin.SlugProviderId; + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet; + + /// + public string? UrlFormatString => TvdbUtils.TvdbBaseUrl + "lists/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is BoxSet; + } +} diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs index 6925566..8042c63 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Text; @@ -7,9 +8,12 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; +using Tvdb.Sdk; namespace Jellyfin.Plugin.Tvdb.Providers { @@ -20,6 +24,7 @@ public class TvdbBoxSetProvider : IRemoteMetadataProvider { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; private readonly TvdbClientManager _tvdbClientManager; /// @@ -27,11 +32,13 @@ public class TvdbBoxSetProvider : IRemoteMetadataProvider /// /// Instance of . /// Instance of . + /// Instance of . /// Instance of . - public TvdbBoxSetProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager) + public TvdbBoxSetProvider(IHttpClientFactory httpClientFactory, ILogger logger, ILibraryManager libraryManager, TvdbClientManager tvdbClientManager) { _httpClientFactory = httpClientFactory; _logger = logger; + _libraryManager = libraryManager; _tvdbClientManager = tvdbClientManager; } @@ -39,15 +46,185 @@ public TvdbBoxSetProvider(IHttpClientFactory httpClientFactory, ILogger TvdbPlugin.ProviderName; /// - public Task> GetMetadata(BoxSetInfo info, CancellationToken cancellationToken) + public async Task> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) { - throw new NotImplementedException(); + if (!string.IsNullOrEmpty(searchInfo.Name)) + { + // Search for box sets + return await FindBoxSet(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); + } + + // Return empty list if no results found + return Enumerable.Empty(); } /// - public Task> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) + public async Task> GetMetadata(BoxSetInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + QueriedById = true, + }; + // Tvdb Box sets are only supported by tvdb ids + if (!info.HasTvdbId()) + { + result.QueriedById = false; + await Identify(info).ConfigureAwait(false); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (info.HasTvdbId()) + { + result.Item = new BoxSet(); + result.HasMetadata = true; + + await FetchBoxSetMetadata(result, info, cancellationToken) + .ConfigureAwait(false); + } + + return result; + } + + private async Task> FindBoxSet(string name, string language, CancellationToken cancellationToken) + { + _logger.LogDebug("TvdbSearch: Finding id for item: {Name}", name); + var results = await FindBoxSetsInternal(name, language, cancellationToken).ConfigureAwait(false); + + return results; + } + + private async Task> FindBoxSetsInternal(string name, string language, CancellationToken cancellationToken) + { + var parsedName = _libraryManager.ParseName(name); + var comparableName = TvdbUtils.GetComparableName(parsedName.Name); + + var list = new List, RemoteSearchResult>>(); + IReadOnlyList result; + try + { + result = await _tvdbClientManager.GetBoxSetByNameAsync(comparableName, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogError(e, "No movie results found for {Name}", comparableName); + return new List(); + } + + foreach (var boxSetSearchResult in result) + { + var tvdbTitles = new List + { + boxSetSearchResult.Translations.GetTranslatedNamedOrDefault(language) ?? boxSetSearchResult.Name + }; + if (boxSetSearchResult.Aliases is not null) + { + tvdbTitles.AddRange(boxSetSearchResult.Aliases); + } + + var remoteSearchResult = new RemoteSearchResult + { + Name = tvdbTitles.FirstOrDefault(), + SearchProviderName = Name + }; + + if (!string.IsNullOrEmpty(boxSetSearchResult.Image_url)) + { + remoteSearchResult.ImageUrl = boxSetSearchResult.Image_url; + } + + try + { + var movieResult = + await _tvdbClientManager.GetBoxSetExtendedByIdAsync(Convert.ToInt32(boxSetSearchResult.Tvdb_id, CultureInfo.InvariantCulture), cancellationToken) + .ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogError(e, "Unable to retrieve movie with id {TvdbId}:{MovieName}", boxSetSearchResult.Tvdb_id, boxSetSearchResult.Name); + } + + remoteSearchResult.SetTvdbId(boxSetSearchResult.Tvdb_id); + list.Add(new Tuple, RemoteSearchResult>(tvdbTitles, remoteSearchResult)); + } + + return list + .OrderBy(i => i.Item1.Contains(name, StringComparer.OrdinalIgnoreCase) ? 0 : 1) + .ThenBy(i => i.Item1.Any(title => title.Contains(parsedName.Name, StringComparison.OrdinalIgnoreCase)) ? 0 : 1) + .ThenBy(i => i.Item1.Any(title => title.Contains(comparableName, StringComparison.OrdinalIgnoreCase)) ? 0 : 1) + .ThenBy(i => list.IndexOf(i)) + .Select(i => i.Item2) + .ToList(); + } + + private async Task Identify(BoxSetInfo info) + { + if (info.HasTvdbId()) + { + return; + } + + var remoteSearchResults = await FindBoxSet(info.Name, info.MetadataLanguage, CancellationToken.None) + .ConfigureAwait(false); + + var entry = remoteSearchResults.FirstOrDefault(); + if (entry.HasTvdbId(out var tvdbId)) + { + info.SetTvdbId(tvdbId); + } + } + + private async Task FetchBoxSetMetadata( + MetadataResult result, + BoxSetInfo boxSetInfo, + CancellationToken cancellationToken) + { + var boxSetMetadata = result.Item; + + if (boxSetInfo.HasTvdbId(out var tvdbIdTxt)) + { + boxSetMetadata.SetTvdbId(tvdbIdTxt); + } + + if (string.IsNullOrWhiteSpace(tvdbIdTxt)) + { + _logger.LogWarning("No valid tvdb id found for movie {TvdbId}:{MovieName}", tvdbIdTxt, boxSetInfo.Name); + return; + } + + var tvdbId = Convert.ToInt32(tvdbIdTxt, CultureInfo.InvariantCulture); + try + { + var boxSetResult = + await _tvdbClientManager + .GetBoxSetExtendedByIdAsync(tvdbId, cancellationToken) + .ConfigureAwait(false); + MapBoxSetToResult(result, boxSetResult, boxSetInfo); + + result.ResetPeople(); + + List people = new List(); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to retrieve movie with id {TvdbId}:{MovieName}", tvdbId, boxSetInfo.Name); + return; + } + } + + private void MapBoxSetToResult(MetadataResult result, ListExtendedRecord tvdbBoxSet, BoxSetInfo info) { - throw new NotImplementedException(); + BoxSet boxSet = result.Item; + boxSet.SetTvdbId(tvdbBoxSet.Id); + // Tvdb uses 3 letter code for language (prob ISO 639-2) + // Reverts to OriginalName if no translation is found + // boxSet.Name = tvdbBoxSet.Translations.GetTranslatedNamedOrDefault(info.MetadataLanguage) ?? TvdbUtils.ReturnOriginalLanguageOrDefault(tvdbMovie.Name); + // boxSet.Overview = tvdbBoxSet.Translations.GetTranslatedOverviewOrDefault(info.MetadataLanguage); + boxSet.OriginalTitle = tvdbBoxSet.Name; + result.ResultLanguage = info.MetadataLanguage; + // Attempts to default to USA if not found + boxSet.SetProviderIdIfHasValue(TvdbPlugin.SlugProviderId, tvdbBoxSet.Url); } /// From 4c1cd57b4d859458785ff9a05ddd82e3ca8d4b35 Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 13:24:41 +0800 Subject: [PATCH 4/9] Add some support for boxset --- .../ExternalId/TvdbBoxSetExternalId.cs | 2 +- .../ExternalId/TvdbBoxSetSlugExternalId.cs | 2 +- .../Providers/TvdbBoxSetImageProvider.cs | 86 +++++++++++++++++++ .../Providers/TvdbBoxSetProvider.cs | 2 + 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs diff --git a/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs index df2ea46..d544e97 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetExternalId.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Plugin.Tvdb.Providers.ExternalId public class TvdbBoxSetExternalId : IExternalId { /// - public string ProviderName => TvdbPlugin.ProviderName; + public string ProviderName => TvdbPlugin.ProviderName + " Numerical"; /// public string Key => TvdbPlugin.ProviderId; diff --git a/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs index ec79993..8dbbbd2 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/ExternalId/TvdbBoxSetSlugExternalId.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Plugin.Tvdb.Providers.ExternalId public class TvdbBoxSetSlugExternalId : IExternalId { /// - public string ProviderName => TvdbPlugin.ProviderName + " Slug"; + public string ProviderName => TvdbPlugin.ProviderName; /// public string Key => TvdbPlugin.SlugProviderId; diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs new file mode 100644 index 0000000..abd8932 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using Tvdb.Sdk; + +namespace Jellyfin.Plugin.Tvdb.Providers +{ + /// + /// The TvdbBoxSetProvider class. + /// + public class TvdbBoxSetImageProvider : IRemoteImageProvider + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly TvdbClientManager _tvdbClientManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + public TvdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, ILibraryManager libraryManager, TvdbClientManager tvdbClientManager) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + _libraryManager = libraryManager; + _tvdbClientManager = tvdbClientManager; + } + + /// + public string Name => TvdbPlugin.ProviderName; + + /// + public bool Supports(BaseItem item) + { + return item is BoxSet; + } + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + yield return ImageType.Primary; + } + + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + if (!item.HasTvdbId()) + { + return Enumerable.Empty(); + } + + var boxSetRecord = await _tvdbClientManager.GetBoxSetExtendedByIdAsync(item.GetTvdbId(), cancellationToken).ConfigureAwait(false); + var remoteImageInfo = new RemoteImageInfo + { + ProviderName = Name, + Type = ImageType.Primary, + Url = boxSetRecord.Image, + }; + + return new List { remoteImageInfo }; + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken); + } + } +} diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs index 8042c63..31f1c3c 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs @@ -220,7 +220,9 @@ private void MapBoxSetToResult(MetadataResult result, ListExtendedRecord // Tvdb uses 3 letter code for language (prob ISO 639-2) // Reverts to OriginalName if no translation is found // boxSet.Name = tvdbBoxSet.Translations.GetTranslatedNamedOrDefault(info.MetadataLanguage) ?? TvdbUtils.ReturnOriginalLanguageOrDefault(tvdbMovie.Name); + boxSet.Name = tvdbBoxSet.Name; // boxSet.Overview = tvdbBoxSet.Translations.GetTranslatedOverviewOrDefault(info.MetadataLanguage); + boxSet.Overview = tvdbBoxSet.Overview; boxSet.OriginalTitle = tvdbBoxSet.Name; result.ResultLanguage = info.MetadataLanguage; // Attempts to default to USA if not found From ae03e2b592c02bc91060cf75ae6f5693e1da59cf Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 13:30:57 +0800 Subject: [PATCH 5/9] Cleanup TvdbBoxSetImageprovider --- Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs index abd8932..25af289 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs @@ -25,7 +25,6 @@ public class TvdbBoxSetImageProvider : IRemoteImageProvider { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; private readonly TvdbClientManager _tvdbClientManager; /// @@ -33,13 +32,11 @@ public class TvdbBoxSetImageProvider : IRemoteImageProvider /// /// Instance of . /// Instance of . - /// Instance of . /// Instance of . - public TvdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, ILibraryManager libraryManager, TvdbClientManager tvdbClientManager) + public TvdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager) { _httpClientFactory = httpClientFactory; _logger = logger; - _libraryManager = libraryManager; _tvdbClientManager = tvdbClientManager; } From e165c3bd53fbe78167e08cfa9c7098b0e04c9187 Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 14:48:53 +0800 Subject: [PATCH 6/9] Add scheduled task to create collections --- .../ScheduledTasks/ScanForBoxSetsTask.cs | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs diff --git a/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs b/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs new file mode 100644 index 0000000..8a00d29 --- /dev/null +++ b/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.Tvdb.ScheduledTasks +{ + /// + /// Scan for box sets task. + /// + public class ScanForBoxSetsTask : IScheduledTask + { + private readonly ILogger _logger; + private readonly ICollectionManager _collectionManager; + private readonly ILibraryManager _libraryManager; + private readonly TvdbClientManager _tvdbClientManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of . + /// Instance of . + /// Instance of . + public ScanForBoxSetsTask( + ILogger logger, + ICollectionManager collectionManager, + ILibraryManager libraryManager, + TvdbClientManager tvdbClientManager) + { + _logger = logger; + _collectionManager = collectionManager; + _libraryManager = libraryManager; + _tvdbClientManager = tvdbClientManager; + } + + /// + public string Name => "Scan library for new Tvdb box sets"; + + /// + public string Key => "TVDBBoxSetsRefreshLibraryTask"; + + /// + public string Description => "Scans all libraries for movies and series and adds them to box sets if the conditions are met."; + + /// + public string Category => "TheTVDB"; + + /// + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + _logger.LogInformation("Starting TMDbBoxSets refresh library task"); + await ScanForBoxSets(progress).ConfigureAwait(false); + _logger.LogInformation("TMDbBoxSets refresh library task finished"); + } + + /// + public IEnumerable GetDefaultTriggers() + { + return Enumerable.Empty(); + } + + private async Task ScanForBoxSets(IProgress progress) + { + var items = GetItemsWithCollectionId(); + _logger.LogInformation("Found {0} items with tvdb collection id", items.Count); + var boxSets = GetBoxSets(); + _logger.LogInformation("Found {0} box sets with tvdb id", boxSets.Count); + + var itemsByCollectionId = items.SelectMany(i => + { + var collectionId = i.GetProviderId(TvdbPlugin.CollectionProviderId); + return collectionId!.Split(';') + .Where(c => !string.IsNullOrEmpty(c)) + .Select(c => new { CollectionId = c, Item = i }); + }).GroupBy(i => i.CollectionId) + .ToList(); + int index = 0; + foreach (var itemCollection in itemsByCollectionId) + { + progress.Report(100.0 * index / itemsByCollectionId.Count); + var collectionId = itemCollection.Key; + var boxSet = boxSets.FirstOrDefault(b => b.GetProviderId(TvdbPlugin.ProviderId) == collectionId); + await AddItemToCollection(itemCollection.Select(i => i.Item).ToList(), collectionId, boxSet).ConfigureAwait(false); + index++; + } + + progress.Report(100); + } + + private List GetItemsWithCollectionId() + { + var items = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Series }, + IsVirtualItem = false, + OrderBy = new List> + { + new (ItemSortBy.Name, SortOrder.Ascending) + }, + Recursive = true, + HasTvdbId = true, + }); + + return items.Where(i => i.HasProviderId(TvdbPlugin.CollectionProviderId) + && !string.IsNullOrEmpty(i.GetProviderId(TvdbPlugin.CollectionProviderId))).ToList(); + } + + private List GetBoxSets() + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.BoxSet }, + CollapseBoxSetItems = false, + Recursive = true, + HasTvdbId = true, + }).OfType().ToList(); + } + + private async Task AddItemToCollection(IReadOnlyList items, string collectionId, BoxSet? boxSet) + { + if (boxSet == null) + { + var collectionName = await GetBoxSetName(collectionId).ConfigureAwait(false); + boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions + { + Name = collectionName, + ProviderIds = new Dictionary + { + { TvdbPlugin.ProviderId, collectionId }, + }, + }).ConfigureAwait(false); + } + + var itemIds = items + .Where(i => !boxSet.ContainsLinkedChildByItemId(i.Id)) + .Select(i => i.Id) + .ToList(); + + if (items.Count == 0) + { + return; + } + + await _collectionManager.AddToCollectionAsync(boxSet.Id, itemIds).ConfigureAwait(false); + } + + private async Task GetBoxSetName(string collectionId) + { + var collectionIdInt = int.Parse(collectionId, CultureInfo.InvariantCulture); + var collection = await _tvdbClientManager.GetBoxSetExtendedByIdAsync(collectionIdInt, CancellationToken.None).ConfigureAwait(false); + return collection.Name; + } + } +} From ecb848501980bcaceaefea09f001dfabc5388197 Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 14:54:26 +0800 Subject: [PATCH 7/9] Cleanup --- .../Providers/TvdbBoxSetImageProvider.cs | 4 ---- .../Providers/TvdbBoxSetProvider.cs | 23 +++---------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs index 25af289..70023a6 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetImageProvider.cs @@ -1,20 +1,16 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; -using Tvdb.Sdk; namespace Jellyfin.Plugin.Tvdb.Providers { diff --git a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs index 31f1c3c..f90dc43 100644 --- a/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs +++ b/Jellyfin.Plugin.Tvdb/Providers/TvdbBoxSetProvider.cs @@ -3,14 +3,12 @@ using System.Globalization; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; using Tvdb.Sdk; @@ -108,7 +106,7 @@ private async Task> FindBoxSetsInternal(string name, st } catch (Exception e) { - _logger.LogError(e, "No movie results found for {Name}", comparableName); + _logger.LogError(e, "No BoxSet results found for {Name}", comparableName); return new List(); } @@ -134,17 +132,6 @@ private async Task> FindBoxSetsInternal(string name, st remoteSearchResult.ImageUrl = boxSetSearchResult.Image_url; } - try - { - var movieResult = - await _tvdbClientManager.GetBoxSetExtendedByIdAsync(Convert.ToInt32(boxSetSearchResult.Tvdb_id, CultureInfo.InvariantCulture), cancellationToken) - .ConfigureAwait(false); - } - catch (Exception e) - { - _logger.LogError(e, "Unable to retrieve movie with id {TvdbId}:{MovieName}", boxSetSearchResult.Tvdb_id, boxSetSearchResult.Name); - } - remoteSearchResult.SetTvdbId(boxSetSearchResult.Tvdb_id); list.Add(new Tuple, RemoteSearchResult>(tvdbTitles, remoteSearchResult)); } @@ -189,7 +176,7 @@ private async Task FetchBoxSetMetadata( if (string.IsNullOrWhiteSpace(tvdbIdTxt)) { - _logger.LogWarning("No valid tvdb id found for movie {TvdbId}:{MovieName}", tvdbIdTxt, boxSetInfo.Name); + _logger.LogWarning("No valid tvdb id found for BoxSet {TvdbId}:{BoxSetName}", tvdbIdTxt, boxSetInfo.Name); return; } @@ -201,14 +188,10 @@ await _tvdbClientManager .GetBoxSetExtendedByIdAsync(tvdbId, cancellationToken) .ConfigureAwait(false); MapBoxSetToResult(result, boxSetResult, boxSetInfo); - - result.ResetPeople(); - - List people = new List(); } catch (Exception e) { - _logger.LogError(e, "Failed to retrieve movie with id {TvdbId}:{MovieName}", tvdbId, boxSetInfo.Name); + _logger.LogError(e, "Failed to retrieve BoxSet with id {TvdbId}:{BoxSetName}", tvdbId, boxSetInfo.Name); return; } } From 035e23a6e879590ac023f9e8b36f705bfba7cdcd Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 15:50:01 +0800 Subject: [PATCH 8/9] Dont create collection if only 2 items --- Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs b/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs index 8a00d29..5b20d1e 100644 --- a/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs +++ b/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs @@ -130,6 +130,11 @@ private List GetBoxSets() private async Task AddItemToCollection(IReadOnlyList items, string collectionId, BoxSet? boxSet) { + if (items.Count < 2) + { + return; + } + if (boxSet == null) { var collectionName = await GetBoxSetName(collectionId).ConfigureAwait(false); From 291acf329bf17b688427313e181ed7231b93d21d Mon Sep 17 00:00:00 2001 From: LJQ Date: Fri, 2 Aug 2024 15:53:31 +0800 Subject: [PATCH 9/9] use CancellationToken --- .../ScheduledTasks/ScanForBoxSetsTask.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs b/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs index 5b20d1e..f4f5966 100644 --- a/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs +++ b/Jellyfin.Plugin.Tvdb/ScheduledTasks/ScanForBoxSetsTask.cs @@ -61,7 +61,7 @@ public ScanForBoxSetsTask( public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { _logger.LogInformation("Starting TMDbBoxSets refresh library task"); - await ScanForBoxSets(progress).ConfigureAwait(false); + await ScanForBoxSets(progress, cancellationToken).ConfigureAwait(false); _logger.LogInformation("TMDbBoxSets refresh library task finished"); } @@ -71,7 +71,7 @@ public IEnumerable GetDefaultTriggers() return Enumerable.Empty(); } - private async Task ScanForBoxSets(IProgress progress) + private async Task ScanForBoxSets(IProgress progress, CancellationToken cancellationToken) { var items = GetItemsWithCollectionId(); _logger.LogInformation("Found {0} items with tvdb collection id", items.Count); @@ -92,7 +92,7 @@ private async Task ScanForBoxSets(IProgress progress) progress.Report(100.0 * index / itemsByCollectionId.Count); var collectionId = itemCollection.Key; var boxSet = boxSets.FirstOrDefault(b => b.GetProviderId(TvdbPlugin.ProviderId) == collectionId); - await AddItemToCollection(itemCollection.Select(i => i.Item).ToList(), collectionId, boxSet).ConfigureAwait(false); + await AddItemToCollection(itemCollection.Select(i => i.Item).ToList(), collectionId, boxSet, cancellationToken).ConfigureAwait(false); index++; } @@ -128,7 +128,7 @@ private List GetBoxSets() }).OfType().ToList(); } - private async Task AddItemToCollection(IReadOnlyList items, string collectionId, BoxSet? boxSet) + private async Task AddItemToCollection(IReadOnlyList items, string collectionId, BoxSet? boxSet, CancellationToken cancellationToken) { if (items.Count < 2) { @@ -137,7 +137,7 @@ private async Task AddItemToCollection(IReadOnlyList items, string col if (boxSet == null) { - var collectionName = await GetBoxSetName(collectionId).ConfigureAwait(false); + var collectionName = await GetBoxSetName(collectionId, cancellationToken).ConfigureAwait(false); boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions { Name = collectionName, @@ -161,10 +161,10 @@ private async Task AddItemToCollection(IReadOnlyList items, string col await _collectionManager.AddToCollectionAsync(boxSet.Id, itemIds).ConfigureAwait(false); } - private async Task GetBoxSetName(string collectionId) + private async Task GetBoxSetName(string collectionId, CancellationToken cancellationToken) { var collectionIdInt = int.Parse(collectionId, CultureInfo.InvariantCulture); - var collection = await _tvdbClientManager.GetBoxSetExtendedByIdAsync(collectionIdInt, CancellationToken.None).ConfigureAwait(false); + var collection = await _tvdbClientManager.GetBoxSetExtendedByIdAsync(collectionIdInt, cancellationToken).ConfigureAwait(false); return collection.Name; } }