From 95e5efa533940895cd0bd2bc40de796d2807c154 Mon Sep 17 00:00:00 2001 From: Fishandchips321 Date: Mon, 9 Mar 2026 18:19:09 +0000 Subject: [PATCH] added audio and subtitle language info to search results --- backend/src/Models/ItemDTO.cs | 2 + backend/src/Models/JellyfinApi/Item.cs | 2 + backend/src/Models/JellyfinApi/MediaStream.cs | 10 +++ backend/src/Repositories/JellyfinApiClient.cs | 26 ++++++- backend/src/Services/ClientService.cs | 17 +++++ backend/src/Services/IClientService.cs | 1 + backend/src/Services/SearchService.cs | 70 +++++++++++-------- 7 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 backend/src/Models/JellyfinApi/MediaStream.cs diff --git a/backend/src/Models/ItemDTO.cs b/backend/src/Models/ItemDTO.cs index b9e26d6..a339783 100644 --- a/backend/src/Models/ItemDTO.cs +++ b/backend/src/Models/ItemDTO.cs @@ -28,4 +28,6 @@ public class ItemDTO public string? ParentId { get; set; } public string? ThumbnailUrl { get; set; } public string? ProductionYear { get; set; } + public string[]? SubtitleLanguages { get; set; } + public string[]? AudioLanguages { get; set; } } \ No newline at end of file diff --git a/backend/src/Models/JellyfinApi/Item.cs b/backend/src/Models/JellyfinApi/Item.cs index 9d0bc38..ab15b28 100644 --- a/backend/src/Models/JellyfinApi/Item.cs +++ b/backend/src/Models/JellyfinApi/Item.cs @@ -21,4 +21,6 @@ public class Item public string Type { get; set; } = string.Empty; public double PrimaryImageAspectRatio { get; set; } public string? CollectionType { get; set; } + public bool? HasSubtitles { get; set; } + public List? MediaStreams { get; set; } } \ No newline at end of file diff --git a/backend/src/Models/JellyfinApi/MediaStream.cs b/backend/src/Models/JellyfinApi/MediaStream.cs new file mode 100644 index 0000000..376f217 --- /dev/null +++ b/backend/src/Models/JellyfinApi/MediaStream.cs @@ -0,0 +1,10 @@ +namespace JellyGlass.Models.JellyfinApi; + +public class MediaStream +{ + public string Title { get; set; } + public string DisplayTitle { get; set; } + public string Type { get; set; } + public string? LocalizedDefault { get; set; } + public string Language { get; set; } = "undefined"; +} \ No newline at end of file diff --git a/backend/src/Repositories/JellyfinApiClient.cs b/backend/src/Repositories/JellyfinApiClient.cs index 4132ffc..acd6d85 100644 --- a/backend/src/Repositories/JellyfinApiClient.cs +++ b/backend/src/Repositories/JellyfinApiClient.cs @@ -46,9 +46,31 @@ public class JellyfinApiClient return apiResponse!.Items.ToArray(); } - public async Task GetItems(string searchTerm = "", string years = "", string itemTypes = "", string limit = "", string parentId = "") + public async Task Search(string searchTerm = "", string itemTypes = "Series,Movie", string limit = "") { - var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/items?searchTerm={searchTerm}&recursive=true&includeItemTypes=Series,Movie"); + var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/items?searchTerm={searchTerm}&recursive=true&includeItemTypes={itemTypes}&limit={limit}"); + + var response = await MakeRequest(request); + + var apiResponse = await response.Content.ReadFromJsonAsync(); + + return apiResponse!.Items.ToArray(); + } + + public async Task GetMediaInfoFromTvSeries(string seriesId) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/items?parentId={seriesId}&recursive=true&includeItemTypes=Episode&fields=MediaStreams"); + + var response = await MakeRequest(request); + + var apiResponse = await response.Content.ReadFromJsonAsync(); + + return apiResponse!.Items.ToArray(); + } + + public async Task GetMediaInfoFromMovie(string movieId) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/items?ids={movieId}&fields=MediaStreams"); var response = await MakeRequest(request); diff --git a/backend/src/Services/ClientService.cs b/backend/src/Services/ClientService.cs index 23d1cc4..6f00aec 100644 --- a/backend/src/Services/ClientService.cs +++ b/backend/src/Services/ClientService.cs @@ -43,6 +43,23 @@ public class ClientService : IClientService return client; } + public async Task GetClientFromJellyfinServerID(string jellyfinServerID) + { + if (!_clients.Any()) + { + await LoadClients(); + } + + var client = _clients.FirstOrDefault(c => c.ID == jellyfinServerID); + + if (client == null) + { + throw new Exception($"Could not find a client with ID of {jellyfinServerID}"); + } + + return client; + } + public async Task LoadNewClient(Server server) { var client = new JellyfinApiClient(server.Url, server.ApiToken); diff --git a/backend/src/Services/IClientService.cs b/backend/src/Services/IClientService.cs index f4d3191..b1edec7 100644 --- a/backend/src/Services/IClientService.cs +++ b/backend/src/Services/IClientService.cs @@ -7,6 +7,7 @@ public interface IClientService { public Task GetClients(); public Task GetClientFromUrl(string url); + public Task GetClientFromJellyfinServerID(string jellyfinServerID); //the ID of the jellyfin server, not the server stored in our db public Task LoadNewClient(Server server); public void UnloadClient(JellyfinApiClient client); } \ No newline at end of file diff --git a/backend/src/Services/SearchService.cs b/backend/src/Services/SearchService.cs index 2632e56..49d74ad 100644 --- a/backend/src/Services/SearchService.cs +++ b/backend/src/Services/SearchService.cs @@ -5,12 +5,12 @@ namespace JellyGlass.Services; public class SearchService : ISearchService { - private ILibraryService _libraryService; + private ILogger _logger; private IClientService _clientService; - public SearchService(ILibraryService libraryService, IClientService clientService) + public SearchService(ILogger logger, IClientService clientService) { - _libraryService = libraryService; + _logger = logger; _clientService = clientService; } @@ -18,48 +18,62 @@ public class SearchService : ISearchService { var client = await _clientService.GetClientFromUrl(serverUrl); - var items = await client.GetItems(searchTerm: searchTerm); + var items = await client.Search(searchTerm: searchTerm); var dtos = new List(); foreach (var item in items) { - dtos.Add(new ItemDTO(item, client.InstanceUrl)); + var dto = new ItemDTO(item, client.InstanceUrl); + var languages = await GetLanguagesForItem(item); + dto.SubtitleLanguages = languages.Item2; + dto.AudioLanguages = languages.Item1; + dtos.Add(dto); } return dtos.ToArray(); } - // public async Task Search2(string searchTerm, int serverId) - // { - // var libraries = await _libraryService.GetLibrariesFromServer(serverId); - - // var foundItems = new List(); - - // foreach (var library in libraries) - // { - // var found = await SearchLibraryForTerm(searchTerm, serverId, library); - - // foundItems.AddRange(found); - // } - - // return foundItems.ToArray(); - // } - - private async Task SearchLibraryForTerm(string searchTerm, string serverUrl, Library library) + private async Task> GetLanguagesForItem(Item item) { - var items = await _libraryService.GetItemsFromLibrary(library.Name, serverUrl); + _logger.LogInformation($"Getting subtitle languages for {item.Name}"); - var foundItems = new List(); + var subtitleLanguages = new List(); + var audioLanguages = new List(); + var client = await _clientService.GetClientFromJellyfinServerID(item.ServerId); - foreach (var item in items) + Item[] infoList; + + if (item.Type == "Movie") { - if (item.Name.Contains(searchTerm, StringComparison.CurrentCultureIgnoreCase)) + infoList = await client.GetMediaInfoFromMovie(item.Id); + } + else if (item.Type == "Series") + { + infoList = await client.GetMediaInfoFromTvSeries(item.Id); + } + else + { + throw new Exception(); + } + + _logger.LogInformation($"Found {infoList.Count()} items"); + + foreach (var info in infoList) + { + foreach (var mediaStream in info.MediaStreams!) { - foundItems.Add(item); + if (mediaStream.Type == "Subtitle" && !subtitleLanguages.Contains(mediaStream.Language)) + { + subtitleLanguages.Add(mediaStream.Language); + } + else if (mediaStream.Type == "Audio" && !audioLanguages.Contains(mediaStream.Language)) + { + audioLanguages.Add(mediaStream.Language); + } } } - return foundItems.ToArray(); + return new Tuple(audioLanguages.ToArray(), subtitleLanguages.ToArray()); } } \ No newline at end of file