From 7d18f3d6edf54421ffcecf233d7cb1f873b8a408 Mon Sep 17 00:00:00 2001 From: theguymadmax Date: Tue, 24 Jun 2025 19:48:36 -0400 Subject: [PATCH] Improve cast and crew handling (#14370) --- .../Tmdb/Configuration/PluginConfiguration.cs | 15 +++ .../Plugins/Tmdb/Configuration/config.html | 34 +++++- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 60 ++++++--- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 115 +++++++++++++----- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 85 +++++++++---- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 64 +++++++--- 6 files changed, 282 insertions(+), 91 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index 99b759ae29..f11b1d95aa 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -38,6 +38,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// public int MaxCastMembers { get; set; } = 15; + /// + /// Gets or sets a value indicating the maximum number of crew members to fetch for an item. + /// + public int MaxCrewMembers { get; set; } = 15; + + /// + /// Gets or sets a value indicating whether to hide cast members without profile images. + /// + public bool HideMissingCastMembers { get; set; } + + /// + /// Gets or sets a value indicating whether to hide crew members without profile images. + /// + public bool HideMissingCrewMembers { get; set; } + /// /// Gets or sets a value indicating the poster image size to fetch. /// diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index f3c24e7b42..89d380ec1f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -25,9 +25,24 @@ Import season name from metadata fetched for series. -
- -
The maximum number of cast members to fetch for an item.
+
+

Cast & Crew Settings

+
+ +
The maximum number of cast members to fetch for an item.
+
+
+ +
The maximum number of crew members to fetch for an item.
+
+ +

Image Scaling

@@ -129,6 +144,8 @@ document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries; document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies; document.querySelector('#importSeasonName').checked = config.ImportSeasonName; + document.querySelector('#hideMissingCastMembers').checked = config.HideMissingCastMembers; + document.querySelector('#hideMissingCrewMembers').checked = config.HideMissingCrewMembers; var maxCastMembers = document.querySelector('#maxCastMembers'); maxCastMembers.value = config.MaxCastMembers; @@ -137,12 +154,18 @@ cancelable: false })); + var maxCrewMembers = document.querySelector('#maxCrewMembers'); + maxCrewMembers.value = config.MaxCrewMembers; + maxCrewMembers.dispatchEvent(new Event('change', { + bubbles: true, + cancelable: false + })); + pluginConfig = config; configureImageScaling(); }); }); - document.querySelector('.configForm') .addEventListener('submit', function (e) { Dashboard.showLoadingMsg(); @@ -153,6 +176,9 @@ config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked; config.ImportSeasonName = document.querySelector('#importSeasonName').checked; config.MaxCastMembers = document.querySelector('#maxCastMembers').value; + config.MaxCrewMembers = document.querySelector('#maxCrewMembers').value; + config.HideMissingCastMembers = document.querySelector('#hideMissingCastMembers').checked; + config.HideMissingCrewMembers = document.querySelector('#hideMissingCrewMembers').checked; config.PosterSize = document.querySelector('#selectPosterSize').value; config.BackdropSize = document.querySelector('#selectBackdropSize').value; config.LogoSize = document.querySelector('#selectLogoSize').value; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 9bb6507fe6..2f8cb68ef5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -144,6 +144,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); var imdbId = info.GetProviderId(MetadataProvider.Imdb); + var config = Plugin.Instance.Configuration; if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId)) { @@ -249,12 +250,26 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movieResult.Credits?.Cast is not null) { - foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + var castQuery = movieResult.Credits.Cast.AsEnumerable(); + + if (config.HideMissingCastMembers) { + castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath)); + } + + castQuery = castQuery.OrderBy(a => a.Order).Take(config.MaxCastMembers); + + foreach (var actor in castQuery) + { + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + var personInfo = new PersonInfo { Name = actor.Name.Trim(), - Role = actor.Character.Trim(), + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, SortOrder = actor.Order }; @@ -275,32 +290,47 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movieResult.Credits?.Crew is not null) { - foreach (var person in movieResult.Credits.Crew) - { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + var crewQuery = movieResult.Credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (config.HideMissingCrewMembers) + { + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } + + crewQuery = crewQuery.Take(config.MaxCrewMembers); + + foreach (var entry in crewQuery) + { + var crewMember = entry.CrewMember; + + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job?.Trim(), - Type = type + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType }; - if (!string.IsNullOrWhiteSpace(person.ProfilePath)) + if (!string.IsNullOrWhiteSpace(crewMember.ProfilePath)) { - personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath); + personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath); } - if (person.Id > 0) + if (crewMember.Id > 0) { - personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture)); + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); } metadataResult.AddPerson(personInfo); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 73c3b4f16f..7d0900cfda 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -81,6 +81,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { var metadataResult = new MetadataResult(); + var config = Plugin.Instance.Configuration; // Allowing this will dramatically increase scan times if (info.IsMissingEpisode) @@ -206,52 +207,106 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (credits?.Cast is not null) { - foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + var castQuery = config.HideMissingCastMembers + ? credits.Cast.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order) + : credits.Cast.OrderBy(a => a.Order); + + foreach (var actor in castQuery.Take(config.MaxCastMembers)) { - metadataResult.AddPerson(new PersonInfo + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + + var personInfo = new PersonInfo { Name = actor.Name.Trim(), - Role = actor.Character.Trim(), + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, - SortOrder = actor.Order - }); + SortOrder = actor.Order, + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) + }; + + if (actor.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); + } + + metadataResult.AddPerson(personInfo); } } if (credits?.GuestStars is not null) { - foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) - { - metadataResult.AddPerson(new PersonInfo - { - Name = guest.Name.Trim(), - Role = guest.Character.Trim(), - Type = PersonKind.GuestStar, - SortOrder = guest.Order - }); - } - } + var guestQuery = config.HideMissingCastMembers + ? credits.GuestStars.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order) + : credits.GuestStars.OrderBy(a => a.Order); - // and the rest from crew - if (credits?.Crew is not null) - { - foreach (var person in credits.Crew) + foreach (var guest in guestQuery.Take(config.MaxCastMembers)) { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); - - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(guest.Name)) { continue; } - metadataResult.AddPerson(new PersonInfo + var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job?.Trim(), - Type = type - }); + Name = guest.Name.Trim(), + Role = guest.Character?.Trim() ?? string.Empty, + Type = PersonKind.GuestStar, + SortOrder = guest.Order, + ImageUrl = _tmdbClientManager.GetProfileUrl(guest.ProfilePath) + }; + + if (guest.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, guest.Id.ToString(CultureInfo.InvariantCulture)); + } + + metadataResult.AddPerson(personInfo); + } + } + + if (credits?.Crew is not null) + { + var crewQuery = credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + + if (config.HideMissingCrewMembers) + { + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } + + foreach (var entry in crewQuery.Take(config.MaxCrewMembers)) + { + var crewMember = entry.CrewMember; + + if (string.IsNullOrWhiteSpace(crewMember.Name)) + { + continue; + } + + var personInfo = new PersonInfo + { + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType, + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) + }; + + if (crewMember.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); + } + + metadataResult.AddPerson(personInfo); } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index b0a1e00df9..cfef0d6561 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -42,6 +42,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) { var result = new MetadataResult(); + var config = Plugin.Instance.Configuration; info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId); @@ -65,10 +66,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.Item = new Season { IndexNumber = seasonNumber, - Overview = seasonResult.Overview + Overview = seasonResult.Overview, + PremiereDate = seasonResult.AirDate, + ProductionYear = seasonResult.AirDate?.Year }; - if (Plugin.Instance.Configuration.ImportSeasonName) + if (config.ImportSeasonName) { result.Item.Name = seasonResult.Name; } @@ -77,47 +80,81 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO why was this disabled? var credits = seasonResult.Credits; + if (credits?.Cast is not null) { - var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList(); - for (var i = 0; i < cast.Count; i++) + var castQuery = config.HideMissingCastMembers + ? credits.Cast.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order) + : credits.Cast.OrderBy(a => a.Order); + + foreach (var actor in castQuery.Take(config.MaxCastMembers)) { - var member = cast[i]; - result.AddPerson(new PersonInfo + if (string.IsNullOrWhiteSpace(actor.Name)) { - Name = member.Name.Trim(), - Role = member.Character.Trim(), + continue; + } + + var personInfo = new PersonInfo + { + Name = actor.Name.Trim(), + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, - SortOrder = member.Order - }); + SortOrder = actor.Order, + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) + }; + + if (actor.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); + } + + result.AddPerson(personInfo); } } if (credits?.Crew is not null) { - foreach (var person in credits.Crew) - { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + var crewQuery = credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (config.HideMissingCrewMembers) + { + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } + + foreach (var entry in crewQuery.Take(config.MaxCrewMembers)) + { + var crewMember = entry.CrewMember; + + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } - result.AddPerson(new PersonInfo + var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job?.Trim(), - Type = type - }); + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType, + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) + }; + + if (crewMember.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); + } + + result.AddPerson(personInfo); } } - result.Item.PremiereDate = seasonResult.AirDate; - result.Item.ProductionYear = seasonResult.AirDate?.Year; - return result; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 9ace9c6743..8791712c71 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -323,17 +323,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV private IEnumerable GetPersons(TvShow seriesResult) { + var config = Plugin.Instance.Configuration; + if (seriesResult.Credits?.Cast is not null) { - foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + IEnumerable castQuery = seriesResult.Credits.Cast.OrderBy(a => a.Order); + + if (config.HideMissingCastMembers) { + castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath)); + } + + foreach (var actor in castQuery.Take(config.MaxCastMembers)) + { + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + var personInfo = new PersonInfo { Name = actor.Name.Trim(), - Role = actor.Character.Trim(), + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, SortOrder = actor.Order, - ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath) + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) }; if (actor.Id > 0) @@ -347,30 +361,44 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (seriesResult.Credits?.Crew is not null) { - var keepTypes = new[] - { - PersonType.Director, - PersonType.Writer, - PersonType.Producer - }; + var crewQuery = seriesResult.Credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); - foreach (var person in seriesResult.Credits.Crew) + if (config.HideMissingCrewMembers) { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + foreach (var entry in crewQuery.Take(config.MaxCrewMembers)) + { + var crewMember = entry.CrewMember; + + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } - yield return new PersonInfo + var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job?.Trim(), - Type = type + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType, + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) }; + + if (crewMember.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); + } + + yield return personInfo; } } }