diff options
10 files changed, 123 insertions, 46 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 31cef1437e..9012e35f1d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -231,6 +231,7 @@ - [bjorntp](https://github.com/bjorntp) - [martenumberto](https://github.com/martenumberto) - [ZeusCraft10](https://github.com/ZeusCraft10) + - [AliveDevil](https://github.com/AliveDevil) - [MarcoCoreDuo](https://github.com/MarcoCoreDuo) - [LiHRaM](https://github.com/LiHRaM) - [MSalman5230](https://github.com/MSalman5230) diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 126f4361ba..4983dbb874 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -546,6 +546,8 @@ namespace MediaBrowser.Controller.Entities.TV { var info = GetItemLookupInfo<SeriesInfo>(); + info.DisplayOrder = DisplayOrder; + return info; } diff --git a/MediaBrowser.Controller/Providers/SeriesInfo.cs b/MediaBrowser.Controller/Providers/SeriesInfo.cs index 5ca5f0a534..84a6d71d4f 100644 --- a/MediaBrowser.Controller/Providers/SeriesInfo.cs +++ b/MediaBrowser.Controller/Providers/SeriesInfo.cs @@ -5,5 +5,9 @@ namespace MediaBrowser.Controller.Providers /// </summary> public class SeriesInfo : ItemLookupInfo { + /// <summary> + /// Gets or sets the canned display order group. + /// </summary> + public string? DisplayOrder { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeGroupId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeGroupId.cs new file mode 100644 index 0000000000..7ce92949fe --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeGroupId.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.Tmdb.TV +{ + /// <summary> + /// TMDb series or season group id. + /// </summary> + public class TmdbEpisodeGroupId : IExternalId + { + internal const string ProviderKey = "TmdbEpisodeGroup"; + + /// <inheritdoc /> + public string ProviderName => TmdbUtils.ProviderName; + + /// <inheritdoc /> + public string Key => ProviderKey; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) + { + return item is Series or Season; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index 1f8c87397d..6d3b9a4283 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -69,11 +69,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty<RemoteImageInfo>(); } + var episodeGroup = series.GetProviderId(TmdbEpisodeGroupId.ProviderKey); var language = item.GetPreferredMetadataLanguage(); // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, null, cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, episodeGroup, null, null, null, cancellationToken) .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 83630bb20f..778a28b292 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -104,6 +104,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return metadataResult; } + var episodeGroup = info.SeriesProviderIds.GetValueOrDefault(TmdbEpisodeGroupId.ProviderKey); TvEpisode? episodeResult = null; if (info.IndexNumberEnd.HasValue) { @@ -112,7 +113,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV List<TvEpisode>? result = null; for (int? episode = startindex; episode <= endindex; episode++) { - var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, episodeGroup, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); if (episodeInfo is not null) { (result ??= new List<TvEpisode>()).Add(episodeInfo); @@ -157,7 +158,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV else { episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, episodeGroup, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken) .ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 9b8803f171..0eb8bb1513 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using TMDbLib.Objects.TvShows; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { @@ -64,6 +65,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return result; } + TvGroup? groupResult = null; + if (info.SeriesProviderIds.TryGetValue(TmdbEpisodeGroupId.ProviderKey, out var episodeGroup) && !string.IsNullOrWhiteSpace(episodeGroup)) + { + groupResult = await _tmdbClientManager.GetSeriesGroupAsync(episodeGroup, seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + } + result.HasMetadata = true; result.Item = new Season { @@ -81,6 +88,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.Item.TrySetProviderId(MetadataProvider.Tmdb, seasonResult.Id?.ToString(CultureInfo.InvariantCulture)); result.Item.TrySetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds?.TvdbId); + if (groupResult is not null && !string.IsNullOrWhiteSpace(groupResult.Id)) + { + result.Item.TrySetProviderId(TmdbEpisodeGroupId.ProviderKey, groupResult.Id); + } + var credits = seasonResult.Credits; if (credits?.Cast is not null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index b6bb7aa013..155548ec1e 100755 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (series is not null) { - var remoteResult = MapTvShowToRemoteSearchResult(series); + var remoteResult = MapTvShowToRemoteSearchResult(series, searchInfo); return new[] { remoteResult }; } @@ -126,7 +126,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return remoteResults; } - private RemoteSearchResult MapTvShowToRemoteSearchResult(TvShow series) + private RemoteSearchResult MapTvShowToRemoteSearchResult(TvShow series, SeriesInfo search) { var remoteResult = new RemoteSearchResult { @@ -144,6 +144,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV remoteResult.TrySetProviderId(MetadataProvider.Tvdb, series.ExternalIds.TvdbId); } + WithDisplayOrder(series, remoteResult, search); + remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime(); remoteResult.ProductionYear = series.FirstAirDate?.Year; @@ -231,7 +233,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result = new MetadataResult<Series> { - Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), + Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode, info), ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage }; @@ -245,7 +247,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return result; } - private static Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode) + private static Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode, SeriesInfo search) { var series = new Series { @@ -359,6 +361,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV series.OriginalLanguage = seriesResult.OriginalLanguage; } + WithDisplayOrder(seriesResult, series, search); + return series; } @@ -451,5 +455,39 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } + + private static TvGroupType? MapDisplayOrderToTvGroupType(string? displayOrder) + { + return displayOrder switch + { + "originalAirDate" => TvGroupType.OriginalAirDate, + "absolute" => TvGroupType.Absolute, + "dvd" => TvGroupType.DVD, + "digital" => TvGroupType.Digital, + "storyArc" => TvGroupType.StoryArc, + "production" => TvGroupType.Production, + "tv" => TvGroupType.TV, + _ => null, + }; + } + + private static void WithDisplayOrder(TvShow searchResult, IHasProviderIds providerIds, SeriesInfo search) + { + if (!search.TryGetProviderId(TmdbEpisodeGroupId.ProviderKey, out var episodeGroupId) + && searchResult.EpisodeGroups?.Results is not null) + { + var tvGroupType = MapDisplayOrderToTvGroupType(search.DisplayOrder); + if (tvGroupType is not null) + { + var episodeGroup = searchResult.EpisodeGroups.Results.Find(g => g.Type == tvGroupType); + if (episodeGroup is not null) + { + episodeGroupId = episodeGroup.Id; + } + } + } + + providerIds.TrySetProviderId(TmdbEpisodeGroupId.ProviderKey, episodeGroupId); + } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 8dd1a546fa..7d119e8b84 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -170,53 +170,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// <summary> - /// Gets a tv show episode group from the TMDb API based on the show id and the display order. + /// Gets a tv show episode group from the TMDb API based on the episode group. /// </summary> - /// <param name="tvShowId">The tv show's TMDb id.</param> - /// <param name="displayOrder">The display order.</param> + /// <param name="episodeGroupId">The Tmdb episode group.</param> + /// <param name="seasonNumber">The season number of the episode group.</param> /// <param name="language">The tv show's language.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="countryCode">The country code, ISO 3166-1.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv show episode group information or null if not found.</returns> - private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken) + public async Task<TvGroup?> GetSeriesGroupAsync(string episodeGroupId, int seasonNumber, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken) { - TvGroupType? groupType = - string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate : - string.Equals(displayOrder, "absolute", StringComparison.Ordinal) ? TvGroupType.Absolute : - string.Equals(displayOrder, "dvd", StringComparison.Ordinal) ? TvGroupType.DVD : - string.Equals(displayOrder, "digital", StringComparison.Ordinal) ? TvGroupType.Digital : - string.Equals(displayOrder, "storyArc", StringComparison.Ordinal) ? TvGroupType.StoryArc : - string.Equals(displayOrder, "production", StringComparison.Ordinal) ? TvGroupType.Production : - string.Equals(displayOrder, "tv", StringComparison.Ordinal) ? TvGroupType.TV : - null; - - if (groupType is null) - { - return null; - } - - var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; - if (_memoryCache.TryGetValue(key, out TvGroupCollection? group)) + var key = $"group-{episodeGroupId}-{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}"; + if (_memoryCache.TryGetValue(key, out TvGroup? group)) { return group; } await EnsureClientConfigAsync().ConfigureAwait(false); - var series = await GetSeriesAsync(tvShowId, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false); - var episodeGroupId = series?.EpisodeGroups?.Results?.Find(g => g.Type == groupType)?.Id; - - if (episodeGroupId is null) - { - return null; - } - - group = await _tmDbClient.GetTvEpisodeGroupsAsync( + var groups = await _tmDbClient.GetTvEpisodeGroupsAsync( episodeGroupId, language: TmdbUtils.NormalizeLanguage(language, countryCode), cancellationToken: cancellationToken).ConfigureAwait(false); + group = groups?.Groups?.Find(s => s.Order == seasonNumber); if (group is not null) { Cache(key, group); @@ -262,20 +240,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// <summary> - /// Gets a movie from the TMDb API based on the tv show's TMDb id. + /// Gets an episode from the TMDb API based on the tv show's TMDb id. /// </summary> /// <param name="tvShowId">The tv show's TMDb id.</param> /// <param name="seasonNumber">The season number.</param> /// <param name="episodeNumber">The episode number.</param> - /// <param name="displayOrder">The display order.</param> + /// <param name="episodeGroup">The containing episode group.</param> /// <param name="language">The episode's language.</param> /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="countryCode">The country code, ISO 3166-1.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv episode information or null if not found.</returns> - public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, long episodeNumber, string displayOrder, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken) + public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, long episodeNumber, string? episodeGroup, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken) { - var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; + var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{episodeGroup}-{language}"; if (_memoryCache.TryGetValue(key, out TvEpisode? episode)) { return episode; @@ -283,12 +261,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); - var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false); - if (group is not null) + if (!string.IsNullOrWhiteSpace(episodeGroup)) { - var season = group.Groups?.Find(s => s.Order == seasonNumber); - // Episode order starts at 0 - var ep = season?.Episodes?.Find(e => e.Order == episodeNumber - 1); + var group = await GetSeriesGroupAsync(episodeGroup, seasonNumber, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false); + var ep = group?.Episodes?.Find(e => e.Order == episodeNumber - 1); if (ep is not null) { seasonNumber = ep.SeasonNumber; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs index 8d9ec10c11..83bbf2f808 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.Plugins.Tmdb.TV; using TMDbLib.Objects.TvShows; namespace MediaBrowser.Providers.Plugins.Tmdb; @@ -26,6 +27,11 @@ public class TmdbExternalUrlProvider : IExternalUrlProvider if (item.TryGetProviderId(MetadataProvider.Tmdb, out var externalId)) { yield return TmdbUtils.BaseTmdbUrl + $"tv/{externalId}"; + + if (item.TryGetProviderId(TmdbEpisodeGroupId.ProviderKey, out var episodeGroup)) + { + yield return TmdbUtils.BaseTmdbUrl + $"tv/{externalId}/episode_group/{episodeGroup}"; + } } break; @@ -47,6 +53,12 @@ public class TmdbExternalUrlProvider : IExternalUrlProvider yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}"; } } + + if (season.Series?.TryGetProviderId(TmdbEpisodeGroupId.ProviderKey, out var episodeGroup) == true + && season.TryGetProviderId(TmdbEpisodeGroupId.ProviderKey, out var group)) + { + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/episode_group/{episodeGroup}/group/{group}"; + } } break; |
