diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 30ff1bd333..662e28ec1d 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -3394,6 +3394,12 @@ namespace Emby.Server.Implementations.Library
return _peopleRepository.GetPeopleNames(query);
}
+ ///
+ public IReadOnlyDictionary> GetPeopleNamesByItem(IReadOnlyList itemIds, IReadOnlyList personTypes)
+ {
+ return _peopleRepository.GetPeopleNamesByItem(itemIds, personTypes);
+ }
+
public void UpdatePeople(BaseItem item, List people)
{
UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
index b612112d49..d84a59850d 100644
--- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
@@ -165,6 +165,64 @@ public class PeopleRepository(IDbContextFactory dbProvider, I
transaction.Commit();
}
+ ///
+ public IReadOnlyDictionary> GetPeopleNamesByItem(IReadOnlyList itemIds, IReadOnlyList personTypes)
+ {
+ if (itemIds.Count == 0)
+ {
+ return new Dictionary>();
+ }
+
+ using var context = _dbProvider.CreateDbContext();
+ var query = context.PeopleBaseItemMap
+ .AsNoTracking()
+ .Where(m => itemIds.Contains(m.ItemId));
+
+ if (personTypes.Count > 0)
+ {
+ query = query.Where(m => personTypes.Contains(m.People.PersonType));
+ }
+
+ // One round-trip: pull (ItemId, ListOrder, Name) sorted by ItemId+ListOrder, group in memory.
+ var rows = query
+ .OrderBy(m => m.ItemId)
+ .ThenBy(m => m.ListOrder)
+ .Select(m => new { m.ItemId, m.People.Name })
+ .ToArray();
+
+ var result = new Dictionary>();
+ List? current = null;
+ var currentId = Guid.Empty;
+ var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var row in rows)
+ {
+ if (row.ItemId != currentId)
+ {
+ if (current is { Count: > 0 })
+ {
+ result[currentId] = current;
+ }
+
+ currentId = row.ItemId;
+ current = new List();
+ seen.Clear();
+ }
+
+ if (!string.IsNullOrWhiteSpace(row.Name) && seen.Add(row.Name))
+ {
+ current!.Add(row.Name);
+ }
+ }
+
+ if (current is { Count: > 0 })
+ {
+ result[currentId] = current;
+ }
+
+ return result;
+ }
+
private PersonInfo Map(People people)
{
var mapping = people.BaseItems?.FirstOrDefault();
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index f4c2196400..d794205f00 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -597,6 +597,14 @@ namespace MediaBrowser.Controller.Library
/// List<System.String>.
IReadOnlyList GetPeopleNames(InternalPeopleQuery query);
+ ///
+ /// Gets the people names per item for a batch of item IDs in a single DB round-trip.
+ ///
+ /// The item IDs to look up.
+ /// Optional person types to include. Empty for all.
+ /// Dictionary keyed by item id; values are the per-item people names. Items with no people are absent.
+ IReadOnlyDictionary> GetPeopleNamesByItem(IReadOnlyList itemIds, IReadOnlyList personTypes);
+
///
/// Queries the items.
///
diff --git a/MediaBrowser.Controller/Persistence/IPeopleRepository.cs b/MediaBrowser.Controller/Persistence/IPeopleRepository.cs
index a89f3ef9ee..3a3b2bfb1f 100644
--- a/MediaBrowser.Controller/Persistence/IPeopleRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IPeopleRepository.cs
@@ -32,4 +32,14 @@ public interface IPeopleRepository
/// The query.
/// The list of people names matching the filter.
IReadOnlyList GetPeopleNames(InternalPeopleQuery filter);
+
+ ///
+ /// Gets the people names per item for a batch of item IDs, preserving per-item list order.
+ /// One database round-trip for the whole batch; grouped by item id in memory.
+ /// Items with no people are omitted from the returned dictionary.
+ ///
+ /// The item IDs to get people for.
+ /// Optional person types to include (e.g. "Actor", "Director"). Empty for all.
+ /// Dictionary keyed by item id; values are the per-item people names.
+ IReadOnlyDictionary> GetPeopleNamesByItem(IReadOnlyList itemIds, IReadOnlyList personTypes);
}