Preserve multiple roles per person type instead of deduping credits by name and type
This commit is contained in:
@@ -40,7 +40,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
if (!filter.ItemId.IsEmpty())
|
||||
{
|
||||
dbQuery = dbQuery.Include(p => p.BaseItems!.Where(m => m.ItemId == filter.ItemId))
|
||||
.OrderBy(e => e.BaseItems!.First(e => e.ItemId == filter.ItemId).ListOrder)
|
||||
.OrderBy(e => e.BaseItems!.Where(m => m.ItemId == filter.ItemId).Min(m => m.ListOrder))
|
||||
.ThenBy(e => e.PersonType)
|
||||
.ThenBy(e => e.Name);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
{
|
||||
StartIndex = filter.StartIndex ?? 0,
|
||||
TotalRecordCount = count,
|
||||
Items = dbQuery.AsEnumerable().Select(Map).ToArray(),
|
||||
Items = dbQuery.AsEnumerable().SelectMany(MapCredits).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,9 +117,13 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
person.Role = person.Role?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
// multiple metadata providers can provide the _same_ person; dedupe case-insensitively.
|
||||
people = people.DistinctBy(e => e.Name.ToLowerInvariant() + "-" + e.Type).ToArray();
|
||||
var personKeys = people.Select(e => e.Name.ToLowerInvariant() + "-" + e.Type).ToArray();
|
||||
// multiple metadata providers can provide the _same_ credit; dedupe case-insensitively.
|
||||
// The role is part of the key because one person can hold several credits of the same type
|
||||
// on an item, e.g. a Writer credited for both the Novel and the Screenplay.
|
||||
people = people.DistinctBy(e => e.Name.ToLowerInvariant() + "-" + e.Type + "-" + e.Role.ToLowerInvariant()).ToArray();
|
||||
|
||||
var distinctPersons = people.DistinctBy(e => e.Name.ToLowerInvariant() + "-" + e.Type).ToArray();
|
||||
var personKeys = distinctPersons.Select(e => e.Name.ToLowerInvariant() + "-" + e.Type).ToArray();
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
@@ -132,9 +136,10 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
.Select(f => f.item)
|
||||
.ToArray();
|
||||
|
||||
var toAdd = people
|
||||
var toAdd = distinctPersons
|
||||
.Where(e => !existingPersons.Any(f => string.Equals(f.Name, e.Name, StringComparison.OrdinalIgnoreCase) && f.PersonType == e.Type.ToString()))
|
||||
.Select(Map);
|
||||
.Select(Map)
|
||||
.ToArray();
|
||||
context.Peoples.AddRange(toAdd);
|
||||
context.SaveChanges();
|
||||
|
||||
@@ -215,9 +220,19 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
return result;
|
||||
}
|
||||
|
||||
private PersonInfo Map(People people)
|
||||
private IEnumerable<PersonInfo> MapCredits(People people)
|
||||
{
|
||||
var mappings = people.BaseItems;
|
||||
if (mappings is null || mappings.Count == 0)
|
||||
{
|
||||
return [Map(people, null)];
|
||||
}
|
||||
|
||||
return mappings.OrderBy(m => m.ListOrder).Select(m => Map(people, m));
|
||||
}
|
||||
|
||||
private PersonInfo Map(People people, PeopleBaseItemMap? mapping)
|
||||
{
|
||||
var mapping = people.BaseItems?.FirstOrDefault();
|
||||
var personInfo = new PersonInfo()
|
||||
{
|
||||
Id = people.Id,
|
||||
|
||||
@@ -35,57 +35,61 @@ namespace MediaBrowser.Controller.Entities
|
||||
person.Type = PersonKind.Writer;
|
||||
}
|
||||
|
||||
// If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes
|
||||
if (person.Type == PersonKind.GuestStar)
|
||||
{
|
||||
var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type == PersonKind.Actor);
|
||||
// Check for dupes based on the combination of Name, Type and Role.
|
||||
var existing = people.FirstOrDefault(p => IsSameCredit(p, person)
|
||||
&& string.Equals(p.Role ?? string.Empty, person.Role ?? string.Empty, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existing is not null)
|
||||
{
|
||||
existing.Type = PersonKind.GuestStar;
|
||||
MergeExisting(existing, person);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (person.Type == PersonKind.Actor)
|
||||
if (existing is null)
|
||||
{
|
||||
// If the actor already exists without a role and we have one, fill it in
|
||||
var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type == PersonKind.Actor || p.Type == PersonKind.GuestStar));
|
||||
if (existing is null)
|
||||
if (string.IsNullOrEmpty(person.Role))
|
||||
{
|
||||
// Wasn't there - add it
|
||||
people.Add(person);
|
||||
existing = people.FirstOrDefault(p => IsSameCredit(p, person));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Was there, if no role and we have one - fill it in
|
||||
if (string.IsNullOrEmpty(existing.Role) && !string.IsNullOrEmpty(person.Role))
|
||||
// If the person already exists without a role and we have one, fill it in
|
||||
existing = people.FirstOrDefault(p => IsSameCredit(p, person) && string.IsNullOrEmpty(p.Role));
|
||||
if (existing is not null)
|
||||
{
|
||||
existing.Role = person.Role;
|
||||
}
|
||||
|
||||
MergeExisting(existing, person);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (existing is null)
|
||||
{
|
||||
var existing = people.FirstOrDefault(p =>
|
||||
string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase)
|
||||
&& p.Type == person.Type);
|
||||
|
||||
// Check for dupes based on the combination of Name and Type
|
||||
if (existing is null)
|
||||
{
|
||||
people.Add(person);
|
||||
}
|
||||
else
|
||||
{
|
||||
MergeExisting(existing, person);
|
||||
}
|
||||
people.Add(person);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the type is GuestStar and there's already an Actor entry, then promote it to avoid dupes
|
||||
if (person.Type == PersonKind.GuestStar)
|
||||
{
|
||||
existing.Type = PersonKind.GuestStar;
|
||||
}
|
||||
|
||||
MergeExisting(existing, person);
|
||||
}
|
||||
|
||||
private static bool IsSameCredit(PersonInfo existing, PersonInfo person)
|
||||
{
|
||||
if (!string.Equals(existing.Name, person.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Actor and GuestStar describe the same credit, a guest star is just a promoted actor.
|
||||
if (IsCastKind(existing.Type) && IsCastKind(person.Type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return existing.Type == person.Type;
|
||||
}
|
||||
|
||||
private static bool IsCastKind(PersonKind kind)
|
||||
=> kind is PersonKind.Actor or PersonKind.GuestStar;
|
||||
|
||||
private static void MergeExisting(PersonInfo existing, PersonInfo person)
|
||||
{
|
||||
existing.SortOrder = person.SortOrder ?? existing.SortOrder;
|
||||
|
||||
Reference in New Issue
Block a user