aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheMelmacian <76712303+TheMelmacian@users.noreply.github.com>2026-05-12 01:47:07 +0200
committerTheMelmacian <76712303+TheMelmacian@users.noreply.github.com>2026-05-12 02:12:14 +0200
commit39049a726e1a88e8acf1d8cc5c217bc8d86be9ae (patch)
tree5239b36e5511781bbb4abea70054b3c7600bbb92
parent5701cdce684dbbcdfdd5cc4c79586fe623e9f2d0 (diff)
move language filters from QueryFiltersLegacy to QueryFilters
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs13
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs37
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs2
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs36
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs1
-rw-r--r--Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs11
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs7
-rw-r--r--MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs7
-rw-r--r--MediaBrowser.Model/Querying/QueryFilters.cs6
-rw-r--r--MediaBrowser.Model/Querying/QueryFiltersLegacy.cs6
10 files changed, 82 insertions, 44 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 11f1496086..15d51cf35f 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -87,6 +87,7 @@ namespace Emby.Server.Implementations.Library
private readonly IPathManager _pathManager;
private readonly FastConcurrentLru<Guid, BaseItem> _cache;
private readonly DotIgnoreIgnoreRule _dotIgnoreIgnoreRule;
+ private readonly IMediaStreamRepository _mediaStreamRepository;
/// <summary>
/// The _root folder sync lock.
@@ -129,6 +130,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="peopleRepository">The people repository.</param>
/// <param name="pathManager">The path manager.</param>
/// <param name="dotIgnoreIgnoreRule">The .ignore rule handler.</param>
+ /// <param name="mediaStreamRepository">The media stream repository.</param>
public LibraryManager(
IServerApplicationHost appHost,
ILoggerFactory loggerFactory,
@@ -151,7 +153,8 @@ namespace Emby.Server.Implementations.Library
IDirectoryService directoryService,
IPeopleRepository peopleRepository,
IPathManager pathManager,
- DotIgnoreIgnoreRule dotIgnoreIgnoreRule)
+ DotIgnoreIgnoreRule dotIgnoreIgnoreRule,
+ IMediaStreamRepository mediaStreamRepository)
{
_appHost = appHost;
_logger = loggerFactory.CreateLogger<LibraryManager>();
@@ -181,6 +184,8 @@ namespace Emby.Server.Implementations.Library
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
+ _mediaStreamRepository = mediaStreamRepository;
+
RecordConfigurationValues(_configurationManager.Configuration);
}
@@ -3800,5 +3805,11 @@ namespace Emby.Server.Implementations.Library
SetTopParentOrAncestorIds(query);
return _itemRepository.GetQueryFiltersLegacy(query);
}
+
+ /// <inheritdoc />
+ public IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType)
+ {
+ return _mediaStreamRepository.GetMediaStreamLanguages(mediaStreamType);
+ }
}
}
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 2f53784db1..740423ef04 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -8,6 +8,8 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -24,16 +26,19 @@ public class FilterController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="FilterController"/> class.
/// </summary>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- public FilterController(ILibraryManager libraryManager, IUserManager userManager)
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ public FilterController(ILibraryManager libraryManager, IUserManager userManager, ILocalizationManager localization)
{
_libraryManager = libraryManager;
_userManager = userManager;
+ _localization = localization;
}
/// <summary>
@@ -183,6 +188,36 @@ public class FilterController : BaseJellyfinApiController
}).ToArray();
}
+ if (includeItemTypes.Contains(BaseItemKind.Movie) || includeItemTypes.Contains(BaseItemKind.Series))
+ {
+ filters.AudioLanguages = _libraryManager
+ .GetMediaStreamLanguages(MediaStreamType.Audio)
+ .Select(language =>
+ {
+ var culture = _localization.FindLanguageInfo(language);
+ return new NameValuePair
+ {
+ Name = culture != null ? $"{culture.DisplayName} ({language})" : language,
+ Value = language
+ };
+ })
+ .OrderBy(l => l.Name)
+ .ToArray();
+ filters.SubtitleLanguages = _libraryManager
+ .GetMediaStreamLanguages(MediaStreamType.Subtitle)
+ .Select(language =>
+ {
+ var culture = _localization.FindLanguageInfo(language);
+ return new NameValuePair
+ {
+ Name = culture != null ? $"{culture.DisplayName} ({language})" : language,
+ Value = language
+ };
+ })
+ .OrderBy(l => l.Name)
+ .ToArray();
+ }
+
return filters;
}
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index a813109c96..8eca2787b1 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -421,7 +421,7 @@ public class ItemsController : BaseJellyfinApiController
}
else
{
- // if we want to know if an item has no subtitles we don't need to check for subtitles of a specific language
+ // if we search for items without subtitles, we don't need to check for subtitles of a specific language
query.SubtitleLanguages = [];
}
}
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
index 71b46b3cb5..dc16c3b1b3 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
@@ -535,46 +535,12 @@ public sealed partial class BaseItemRepository
.OrderBy(g => g)
.ToArray();
- // At the moment language filters are only available for video types (Movie and Series libraries).
- // They are fetched directly from the MediaStreamInfos table and only filtered by StreamType.
- // This is the fastest and most perfomant way to get the list of available languages,
- // but the filter values can include language tags that are not linked to any item in the current library.
- var subtitleLanguages = IncludesVideoTypes(filter)
- ? context.MediaStreamInfos
- .Where(s => s.StreamType == MediaStreamTypeEntity.Subtitle)
- .Select(s => string.IsNullOrEmpty(s.Language) ? "und" : s.Language) // und = undetermined
- .Distinct()
- .OrderBy(l => l)
- .ToArray()
- : [];
-
- var audioLanguages = IncludesVideoTypes(filter)
- ? context.MediaStreamInfos
- .Where(s => s.StreamType == MediaStreamTypeEntity.Audio)
- .Select(s => string.IsNullOrEmpty(s.Language) ? "und" : s.Language) // und = undetermined
- .Distinct()
- .OrderBy(l => l)
- .ToArray()
- : [];
-
return new QueryFiltersLegacy
{
Years = years,
OfficialRatings = officialRatings,
Tags = tags,
- Genres = genres,
- SubtitleLanguages = subtitleLanguages,
- AudioLanguages = audioLanguages
+ Genres = genres
};
}
-
- private bool IncludesVideoTypes(InternalItemsQuery filter)
- {
- return filter.IncludeItemTypes.Contains(BaseItemKind.Movie)
- || filter.IncludeItemTypes.Contains(BaseItemKind.Video)
- || filter.IncludeItemTypes.Contains(BaseItemKind.Series)
- || filter.IncludeItemTypes.Contains(BaseItemKind.Season)
- || filter.IncludeItemTypes.Contains(BaseItemKind.Episode)
- || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer);
- }
}
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
index b58e7fffe3..3d1aafd72e 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
@@ -1078,6 +1078,7 @@ public sealed partial class BaseItemRepository
{
// Dvds and Blu-rays can either be stored in a folder structure or as an iso file
// => to find all matches we need to check both: VideoType and IsoType
+ // alternatively, we could provide specific IsoType filters
var videoTypeBs = filter.VideoTypes.Select(vt => $"\"VideoType\":\"{vt}\"").ToArray();
var isoTypeBs = filter.VideoTypes.Select(vt => $"\"IsoType\":\"{vt}\"").ToArray();
Expression<Func<BaseItemEntity, bool>> hasVideoType = e => videoTypeBs.Any(f => e.Data!.Contains(f)) || isoTypeBs.Any(f => e.Data!.Contains(f));
diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs
index dd0446f49a..7fa33c8639 100644
--- a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs
@@ -55,6 +55,17 @@ public class MediaStreamRepository : IMediaStreamRepository
return TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter).AsEnumerable().Select(Map).ToArray();
}
+ /// <inheritdoc />
+ public IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType)
+ {
+ using var context = _dbProvider.CreateDbContext();
+ return context.MediaStreamInfos
+ .Where(e => e.StreamType == (MediaStreamTypeEntity)mediaStreamType)
+ .Select(s => string.IsNullOrEmpty(s.Language) ? "und" : s.Language) // und = undetermined
+ .Distinct()
+ .ToArray();
+ }
+
private string? GetPathToSave(string? path)
{
if (path is null)
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index f5e3d7034e..f4c2196400 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -784,5 +784,12 @@ namespace MediaBrowser.Controller.Library
/// <param name="query">The query filter.</param>
/// <returns>Aggregated filter values.</returns>
QueryFiltersLegacy GetQueryFiltersLegacy(InternalItemsQuery query);
+
+ /// <summary>
+ /// Gets a list of all language codes of the provided stream type.
+ /// </summary>
+ /// <param name="mediaStreamType">The stream type.</param>
+ /// <returns>List of language codes.</returns>
+ IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType);
}
}
diff --git a/MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs b/MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs
index 665129eafd..de04ff021d 100644
--- a/MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs
@@ -22,6 +22,13 @@ public interface IMediaStreamRepository
IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter);
/// <summary>
+ /// Gets all language codes of the provided stream type.
+ /// </summary>
+ /// <param name="mediaStreamType">The type of the media stream.</param>
+ /// <returns>IEnumerable{string}.</returns>
+ IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType);
+
+ /// <summary>
/// Saves the media streams.
/// </summary>
/// <param name="id">The identifier.</param>
diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs
index 73b27a7b06..b877af71c6 100644
--- a/MediaBrowser.Model/Querying/QueryFilters.cs
+++ b/MediaBrowser.Model/Querying/QueryFilters.cs
@@ -12,10 +12,16 @@ namespace MediaBrowser.Model.Querying
{
Tags = Array.Empty<string>();
Genres = Array.Empty<NameGuidPair>();
+ AudioLanguages = Array.Empty<NameValuePair>();
+ SubtitleLanguages = Array.Empty<NameValuePair>();
}
public NameGuidPair[] Genres { get; set; }
public string[] Tags { get; set; }
+
+ public NameValuePair[] AudioLanguages { get; set; }
+
+ public NameValuePair[] SubtitleLanguages { get; set; }
}
}
diff --git a/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs b/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs
index aa1ca85cad..fcb450ed30 100644
--- a/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs
+++ b/MediaBrowser.Model/Querying/QueryFiltersLegacy.cs
@@ -13,8 +13,6 @@ namespace MediaBrowser.Model.Querying
Tags = Array.Empty<string>();
OfficialRatings = Array.Empty<string>();
Years = Array.Empty<int>();
- AudioLanguages = Array.Empty<string>();
- SubtitleLanguages = Array.Empty<string>();
}
public string[] Genres { get; set; }
@@ -24,9 +22,5 @@ namespace MediaBrowser.Model.Querying
public string[] OfficialRatings { get; set; }
public int[] Years { get; set; }
-
- public string[] AudioLanguages { get; set; }
-
- public string[] SubtitleLanguages { get; set; }
}
}