Improve cast and crew handling (#14370)

This commit is contained in:
theguymadmax 2025-06-24 19:48:36 -04:00 committed by GitHub
parent 9b8c12d433
commit 7d18f3d6ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 282 additions and 91 deletions

View File

@ -38,6 +38,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary> /// </summary>
public int MaxCastMembers { get; set; } = 15; public int MaxCastMembers { get; set; } = 15;
/// <summary>
/// Gets or sets a value indicating the maximum number of crew members to fetch for an item.
/// </summary>
public int MaxCrewMembers { get; set; } = 15;
/// <summary>
/// Gets or sets a value indicating whether to hide cast members without profile images.
/// </summary>
public bool HideMissingCastMembers { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to hide crew members without profile images.
/// </summary>
public bool HideMissingCrewMembers { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating the poster image size to fetch. /// Gets or sets a value indicating the poster image size to fetch.
/// </summary> /// </summary>

View File

@ -25,9 +25,24 @@
<input is="emby-checkbox" type="checkbox" id="importSeasonName" /> <input is="emby-checkbox" type="checkbox" id="importSeasonName" />
<span>Import season name from metadata fetched for series.</span> <span>Import season name from metadata fetched for series.</span>
</label> </label>
<div class="inputContainer"> <div class="verticalSection">
<input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" /> <h2>Cast & Crew Settings</h2>
<div class="fieldDescription">The maximum number of cast members to fetch for an item.</div> <div class="inputContainer">
<input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" />
<div class="fieldDescription">The maximum number of cast members to fetch for an item.</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="maxCrewMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Crew Members" />
<div class="fieldDescription">The maximum number of crew members to fetch for an item.</div>
</div>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="hideMissingCastMembers" />
<span>Hide cast members without profile images.</span>
</label>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="hideMissingCrewMembers" />
<span>Hide crew members without profile images.</span>
</label>
</div> </div>
<div class="verticalSection verticalSection-extrabottompadding"> <div class="verticalSection verticalSection-extrabottompadding">
<h2>Image Scaling</h2> <h2>Image Scaling</h2>
@ -129,6 +144,8 @@
document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries; document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries;
document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies; document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies;
document.querySelector('#importSeasonName').checked = config.ImportSeasonName; document.querySelector('#importSeasonName').checked = config.ImportSeasonName;
document.querySelector('#hideMissingCastMembers').checked = config.HideMissingCastMembers;
document.querySelector('#hideMissingCrewMembers').checked = config.HideMissingCrewMembers;
var maxCastMembers = document.querySelector('#maxCastMembers'); var maxCastMembers = document.querySelector('#maxCastMembers');
maxCastMembers.value = config.MaxCastMembers; maxCastMembers.value = config.MaxCastMembers;
@ -137,12 +154,18 @@
cancelable: false cancelable: false
})); }));
var maxCrewMembers = document.querySelector('#maxCrewMembers');
maxCrewMembers.value = config.MaxCrewMembers;
maxCrewMembers.dispatchEvent(new Event('change', {
bubbles: true,
cancelable: false
}));
pluginConfig = config; pluginConfig = config;
configureImageScaling(); configureImageScaling();
}); });
}); });
document.querySelector('.configForm') document.querySelector('.configForm')
.addEventListener('submit', function (e) { .addEventListener('submit', function (e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
@ -153,6 +176,9 @@
config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked; config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked;
config.ImportSeasonName = document.querySelector('#importSeasonName').checked; config.ImportSeasonName = document.querySelector('#importSeasonName').checked;
config.MaxCastMembers = document.querySelector('#maxCastMembers').value; 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.PosterSize = document.querySelector('#selectPosterSize').value;
config.BackdropSize = document.querySelector('#selectBackdropSize').value; config.BackdropSize = document.querySelector('#selectBackdropSize').value;
config.LogoSize = document.querySelector('#selectLogoSize').value; config.LogoSize = document.querySelector('#selectLogoSize').value;

View File

@ -144,6 +144,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{ {
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
var imdbId = info.GetProviderId(MetadataProvider.Imdb); var imdbId = info.GetProviderId(MetadataProvider.Imdb);
var config = Plugin.Instance.Configuration;
if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId)) if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
{ {
@ -249,12 +250,26 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResult.Credits?.Cast is not null) 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 var personInfo = new PersonInfo
{ {
Name = actor.Name.Trim(), Name = actor.Name.Trim(),
Role = actor.Character.Trim(), Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor, Type = PersonKind.Actor,
SortOrder = actor.Order SortOrder = actor.Order
}; };
@ -275,32 +290,47 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResult.Credits?.Crew is not null) if (movieResult.Credits?.Crew is not null)
{ {
foreach (var person in movieResult.Credits.Crew) var crewQuery = movieResult.Credits.Crew
{ .Select(crewMember => new
// Normalize this {
var type = TmdbUtils.MapCrewToPersonType(person); 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) if (config.HideMissingCrewMembers)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) {
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; continue;
} }
var personInfo = new PersonInfo var personInfo = new PersonInfo
{ {
Name = person.Name.Trim(), Name = crewMember.Name.Trim(),
Role = person.Job?.Trim(), Role = crewMember.Job?.Trim() ?? string.Empty,
Type = type 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); metadataResult.AddPerson(personInfo);

View File

@ -81,6 +81,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
{ {
var metadataResult = new MetadataResult<Episode>(); var metadataResult = new MetadataResult<Episode>();
var config = Plugin.Instance.Configuration;
// Allowing this will dramatically increase scan times // Allowing this will dramatically increase scan times
if (info.IsMissingEpisode) if (info.IsMissingEpisode)
@ -206,52 +207,106 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (credits?.Cast is not null) 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(), Name = actor.Name.Trim(),
Role = actor.Character.Trim(), Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor, 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) if (credits?.GuestStars is not null)
{ {
foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) var guestQuery = config.HideMissingCastMembers
{ ? credits.GuestStars.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order)
metadataResult.AddPerson(new PersonInfo : credits.GuestStars.OrderBy(a => a.Order);
{
Name = guest.Name.Trim(),
Role = guest.Character.Trim(),
Type = PersonKind.GuestStar,
SortOrder = guest.Order
});
}
}
// and the rest from crew foreach (var guest in guestQuery.Take(config.MaxCastMembers))
if (credits?.Crew is not null)
{
foreach (var person in credits.Crew)
{ {
// Normalize this if (string.IsNullOrWhiteSpace(guest.Name))
var type = TmdbUtils.MapCrewToPersonType(person);
if (!TmdbUtils.WantedCrewKinds.Contains(type)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{ {
continue; continue;
} }
metadataResult.AddPerson(new PersonInfo var personInfo = new PersonInfo
{ {
Name = person.Name.Trim(), Name = guest.Name.Trim(),
Role = person.Job?.Trim(), Role = guest.Character?.Trim() ?? string.Empty,
Type = type 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);
} }
} }

View File

@ -42,6 +42,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{ {
var result = new MetadataResult<Season>(); var result = new MetadataResult<Season>();
var config = Plugin.Instance.Configuration;
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId); info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId);
@ -65,10 +66,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.Item = new Season result.Item = new Season
{ {
IndexNumber = seasonNumber, 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; result.Item.Name = seasonResult.Name;
} }
@ -77,47 +80,81 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// TODO why was this disabled? // TODO why was this disabled?
var credits = seasonResult.Credits; var credits = seasonResult.Credits;
if (credits?.Cast is not null) if (credits?.Cast is not null)
{ {
var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList(); var castQuery = config.HideMissingCastMembers
for (var i = 0; i < cast.Count; i++) ? 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]; if (string.IsNullOrWhiteSpace(actor.Name))
result.AddPerson(new PersonInfo
{ {
Name = member.Name.Trim(), continue;
Role = member.Character.Trim(), }
var personInfo = new PersonInfo
{
Name = actor.Name.Trim(),
Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor, 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) if (credits?.Crew is not null)
{ {
foreach (var person in credits.Crew) var crewQuery = credits.Crew
{ .Select(crewMember => new
// Normalize this {
var type = TmdbUtils.MapCrewToPersonType(person); 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) if (config.HideMissingCrewMembers)
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) {
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; continue;
} }
result.AddPerson(new PersonInfo var personInfo = new PersonInfo
{ {
Name = person.Name.Trim(), Name = crewMember.Name.Trim(),
Role = person.Job?.Trim(), Role = crewMember.Job?.Trim() ?? string.Empty,
Type = type 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; return result;
} }

View File

@ -323,17 +323,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult) private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult)
{ {
var config = Plugin.Instance.Configuration;
if (seriesResult.Credits?.Cast is not null) if (seriesResult.Credits?.Cast is not null)
{ {
foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) IEnumerable<Cast> 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 var personInfo = new PersonInfo
{ {
Name = actor.Name.Trim(), Name = actor.Name.Trim(),
Role = actor.Character.Trim(), Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor, Type = PersonKind.Actor,
SortOrder = actor.Order, SortOrder = actor.Order,
ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath) ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath)
}; };
if (actor.Id > 0) if (actor.Id > 0)
@ -347,30 +361,44 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (seriesResult.Credits?.Crew is not null) if (seriesResult.Credits?.Crew is not null)
{ {
var keepTypes = new[] var crewQuery = seriesResult.Credits.Crew
{ .Select(crewMember => new
PersonType.Director, {
PersonType.Writer, CrewMember = crewMember,
PersonType.Producer 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 crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
var type = TmdbUtils.MapCrewToPersonType(person); }
if (!TmdbUtils.WantedCrewKinds.Contains(type) foreach (var entry in crewQuery.Take(config.MaxCrewMembers))
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) {
var crewMember = entry.CrewMember;
if (string.IsNullOrWhiteSpace(crewMember.Name))
{ {
continue; continue;
} }
yield return new PersonInfo var personInfo = new PersonInfo
{ {
Name = person.Name.Trim(), Name = crewMember.Name.Trim(),
Role = person.Job?.Trim(), Role = crewMember.Job?.Trim() ?? string.Empty,
Type = type Type = entry.PersonType,
ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath)
}; };
if (crewMember.Id > 0)
{
personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture));
}
yield return personInfo;
} }
} }
} }