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>
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>
/// Gets or sets a value indicating the poster image size to fetch.
/// </summary>

View File

@ -25,10 +25,25 @@
<input is="emby-checkbox" type="checkbox" id="importSeasonName" />
<span>Import season name from metadata fetched for series.</span>
</label>
<div class="verticalSection">
<h2>Cast & Crew Settings</h2>
<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 class="verticalSection verticalSection-extrabottompadding">
<h2>Image Scaling</h2>
<div class="selectContainer">
@ -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;

View File

@ -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)
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)
&& !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);

View File

@ -81,6 +81,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
{
var metadataResult = new MetadataResult<Episode>();
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 guest in guestQuery.Take(config.MaxCastMembers))
{
foreach (var person in credits.Crew)
{
// 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);
}
}

View File

@ -42,6 +42,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Season>();
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,46 +80,80 @@ 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)
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)
&& !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.Item.PremiereDate = seasonResult.AirDate;
result.Item.ProductionYear = seasonResult.AirDate?.Year;
result.AddPerson(personInfo);
}
}
return result;
}

View File

@ -323,17 +323,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private IEnumerable<PersonInfo> 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<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
{
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[]
var crewQuery = seriesResult.Credits.Crew
.Select(crewMember => new
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer
};
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;
}
}
}