diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index cc85f09d23..f075d2f649 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -3862,5 +3862,17 @@ namespace Emby.Server.Implementations.Library
{
return _mediaStreamRepository.GetMediaStreamLanguages(mediaStreamType);
}
+
+ ///
+ public IReadOnlyList GetMediaStreamLanguages(MediaStreamType mediaStreamType, InternalItemsQuery query)
+ {
+ if (query.User is not null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+
+ SetTopParentOrAncestorIds(query);
+ return _itemRepository.GetMediaStreamLanguages(query, mediaStreamType);
+ }
}
}
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index cfc8be28ae..07ebe83e86 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -158,13 +158,42 @@ public class FilterController : BaseJellyfinApiController
IsSeries = isSeries
};
+ var streamLanguageQuery = new InternalItemsQuery(user)
+ {
+ // It's possible that different langauges are only available on alternative versions.
+ // To fetch them all, owned items are inlcluded.
+ IncludeOwnedItems = true,
+ IncludeItemTypes = includeItemTypes,
+ DtoOptions = new DtoOptions
+ {
+ Fields = Array.Empty(),
+ EnableImages = false,
+ EnableUserData = false
+ },
+ IsAiring = isAiring,
+ IsMovie = isMovie,
+ IsSports = isSports,
+ IsKids = isKids,
+ IsNews = isNews,
+ IsSeries = isSeries
+ };
+
if ((recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
{
genreQuery.AncestorIds = parentItem is null ? Array.Empty() : new[] { parentItem.Id };
+ streamLanguageQuery.AncestorIds = parentItem is null ? Array.Empty() : new[] { parentItem.Id };
}
else
{
genreQuery.Parent = parentItem;
+ streamLanguageQuery.Parent = parentItem;
+ }
+
+ if ((includeItemTypes.Contains(BaseItemKind.Series) || includeItemTypes.Contains(BaseItemKind.Season))
+ && !includeItemTypes.Contains(BaseItemKind.Episode))
+ {
+ // streams are joined on epsiodes not shows or seasons
+ streamLanguageQuery.IncludeItemTypes = [..includeItemTypes, BaseItemKind.Episode];
}
if (includeItemTypes.Length == 1
@@ -188,10 +217,13 @@ public class FilterController : BaseJellyfinApiController
}).ToArray();
}
- if (includeItemTypes.Contains(BaseItemKind.Movie) || includeItemTypes.Contains(BaseItemKind.Series))
+ if (includeItemTypes.Contains(BaseItemKind.Movie)
+ || includeItemTypes.Contains(BaseItemKind.Series)
+ || includeItemTypes.Contains(BaseItemKind.Season)
+ || includeItemTypes.Contains(BaseItemKind.Episode))
{
filters.AudioLanguages = _libraryManager
- .GetMediaStreamLanguages(MediaStreamType.Audio)
+ .GetMediaStreamLanguages(MediaStreamType.Audio, streamLanguageQuery)
.Select(language =>
{
var culture = _localization.FindLanguageInfo(language);
@@ -204,7 +236,7 @@ public class FilterController : BaseJellyfinApiController
.OrderBy(l => l.Name)
.ToArray();
filters.SubtitleLanguages = _libraryManager
- .GetMediaStreamLanguages(MediaStreamType.Subtitle)
+ .GetMediaStreamLanguages(MediaStreamType.Subtitle, streamLanguageQuery)
.Select(language =>
{
var culture = _localization.FindLanguageInfo(language);
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs
index e4fd3204e1..00ffd984f5 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs
@@ -7,6 +7,7 @@ using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.EntityFrameworkCore;
using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
@@ -81,6 +82,46 @@ public sealed partial class BaseItemRepository
_itemTypeLookup.MusicGenreTypes);
}
+ ///
+ public IReadOnlyList GetMediaStreamLanguages(InternalItemsQuery filter, MediaStreamType mediaStreamType)
+ {
+ ArgumentNullException.ThrowIfNull(filter);
+
+ if (!filter.Limit.HasValue)
+ {
+ filter.EnableTotalRecordCount = false;
+ }
+
+ using var context = _dbProvider.CreateDbContext();
+
+ return TranslateQuery(
+ context.BaseItems.Include(e => e.MediaStreams).Where(e => e.Id != EF.Constant(PlaceholderId)),
+ context,
+ new InternalItemsQuery(filter.User)
+ {
+ IncludeOwnedItems = filter.IncludeOwnedItems,
+ ExcludeItemTypes = filter.ExcludeItemTypes,
+ IncludeItemTypes = filter.IncludeItemTypes,
+ MediaTypes = filter.MediaTypes,
+ AncestorIds = filter.AncestorIds,
+ ItemIds = filter.ItemIds,
+ TopParentIds = filter.TopParentIds,
+ ParentId = filter.ParentId,
+ IsAiring = filter.IsAiring,
+ IsMovie = filter.IsMovie,
+ IsSports = filter.IsSports,
+ IsKids = filter.IsKids,
+ IsNews = filter.IsNews,
+ IsSeries = filter.IsSeries
+ })
+ .Where(e => e.MediaStreams != null)
+ .SelectMany(e => e.MediaStreams!)
+ .Where(e => e.StreamType == (MediaStreamTypeEntity)mediaStreamType)
+ .Select(s => string.IsNullOrEmpty(s.Language) ? "und" : s.Language) // und = undetermined
+ .Distinct()
+ .ToArray();
+ }
+
private string[] GetItemValueNames(IReadOnlyList itemValueTypes, IReadOnlyList withItemTypes, IReadOnlyList excludeItemTypes)
{
using var context = _dbProvider.CreateDbContext();
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index c23eba75ef..c37b13ea4f 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -800,5 +800,13 @@ namespace MediaBrowser.Controller.Library
/// The stream type.
/// List of language codes.
IReadOnlyList GetMediaStreamLanguages(MediaStreamType mediaStreamType);
+
+ ///
+ /// Gets a list of all language codes for the matching items and the the provided stream type.
+ ///
+ /// The stream type.
+ /// The query filter.
+ /// List of language codes.
+ IReadOnlyList GetMediaStreamLanguages(MediaStreamType mediaStreamType, InternalItemsQuery query);
}
}
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 291916ab25..d44fe57bed 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -7,6 +7,7 @@ using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Persistence;
@@ -119,6 +120,14 @@ public interface IItemRepository
/// The list of genre names.
IReadOnlyList GetGenreNames();
+ ///
+ /// Gets all language codes of the matching base items and the provided stream type.
+ ///
+ /// The query filter.
+ /// The type of the media stream.
+ /// List of language codes.
+ public IReadOnlyList GetMediaStreamLanguages(InternalItemsQuery filter, MediaStreamType mediaStreamType);
+
///
/// Gets all artist names.
///