From f03d88cd8ca54c1105f692bfa65d0016f698b6bf Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 7 May 2025 01:42:32 -0700 Subject: [PATCH] batch jellyfin song list requests when fetching by albumId (#922) --- .../api/jellyfin/jellyfin-controller.ts | 122 ++++++++++++------ 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index e6ff74e5..2c3b4385 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -695,58 +695,98 @@ export const JellyfinController: ControllerEndpoint = { } const yearsFilter = yearsGroup.length ? formatCommaDelimitedString(yearsGroup) : undefined; - const albumIdsFilter = query.albumIds - ? formatCommaDelimitedString(query.albumIds) - : undefined; const artistIdsFilter = query.artistIds ? formatCommaDelimitedString(query.artistIds) : query.albumArtistIds ? formatCommaDelimitedString(query.albumArtistIds) : undefined; - const res = await jfApiClient(apiClientProps).getSongList({ - params: { - userId: apiClientProps.server?.userId, - }, - query: { - AlbumIds: albumIdsFilter, - ArtistIds: artistIdsFilter, - Fields: 'Genres, DateCreated, MediaSources, ParentId', - GenreIds: query.genreIds?.join(','), - IncludeItemTypes: 'Audio', - IsFavorite: query.favorite, - Limit: query.limit, - ParentId: query.musicFolderId, - Recursive: true, - SearchTerm: query.searchTerm, - SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName', - SortOrder: sortOrderMap.jellyfin[query.sortOrder], - StartIndex: query.startIndex, - ...query._custom?.jellyfin, - Years: yearsFilter, - }, - }); + let items: z.infer[] = []; + let totalRecordCount = 0; + const batchSize = 50; - if (res.status !== 200) { - throw new Error('Failed to get song list'); - } + // Handle albumIds fetches in batches to prevent HTTP 414 errors + if (query.albumIds && query.albumIds.length > batchSize) { + const albumIdBatches = chunk(query.albumIds, batchSize); - let items: z.infer[]; + for (const batch of albumIdBatches) { + const albumIdsFilter = formatCommaDelimitedString(batch); - // Jellyfin Bodge because of code from https://github.com/jellyfin/jellyfin/blob/c566ccb63bf61f9c36743ddb2108a57c65a2519b/Emby.Server.Implementations/Data/SqliteItemRepository.cs#L3622 - // If the Album ID filter is passed, Jellyfin will search for - // 1. the matching album id - // 2. An album with the name of the album. - // It is this second condition causing issues, - if (query.albumIds) { - const albumIdSet = new Set(query.albumIds); - items = res.body.Items.filter((item) => albumIdSet.has(item.AlbumId!)); + const res = await jfApiClient(apiClientProps).getSongList({ + params: { + userId: apiClientProps.server?.userId, + }, + query: { + AlbumIds: albumIdsFilter, + ArtistIds: artistIdsFilter, + Fields: 'Genres, DateCreated, MediaSources, ParentId', + GenreIds: query.genreIds?.join(','), + IncludeItemTypes: 'Audio', + IsFavorite: query.favorite, + Limit: query.limit, + ParentId: query.musicFolderId, + Recursive: true, + SearchTerm: query.searchTerm, + SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName', + SortOrder: sortOrderMap.jellyfin[query.sortOrder], + StartIndex: query.startIndex, + ...query._custom?.jellyfin, + Years: yearsFilter, + }, + }); - if (items.length < res.body.Items.length) { - res.body.TotalRecordCount -= res.body.Items.length - items.length; + if (res.status !== 200) { + throw new Error('Failed to get song list'); + } + + items = [...items, ...res.body.Items]; + totalRecordCount += res.body.Items.length; } } else { - items = res.body.Items; + const albumIdsFilter = query.albumIds + ? formatCommaDelimitedString(query.albumIds) + : undefined; + + const res = await jfApiClient(apiClientProps).getSongList({ + params: { + userId: apiClientProps.server?.userId, + }, + query: { + AlbumIds: albumIdsFilter, + ArtistIds: artistIdsFilter, + Fields: 'Genres, DateCreated, MediaSources, ParentId', + GenreIds: query.genreIds?.join(','), + IncludeItemTypes: 'Audio', + IsFavorite: query.favorite, + Limit: query.limit, + ParentId: query.musicFolderId, + Recursive: true, + SearchTerm: query.searchTerm, + SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName', + SortOrder: sortOrderMap.jellyfin[query.sortOrder], + StartIndex: query.startIndex, + ...query._custom?.jellyfin, + Years: yearsFilter, + }, + }); + + if (res.status !== 200) { + throw new Error('Failed to get song list'); + } + + // Jellyfin Bodge because of code from https://github.com/jellyfin/jellyfin/blob/c566ccb63bf61f9c36743ddb2108a57c65a2519b/Emby.Server.Implementations/Data/SqliteItemRepository.cs#L3622 + // If the Album ID filter is passed, Jellyfin will search for + // 1. the matching album id + // 2. An album with the name of the album. + // It is this second condition causing issues, + if (query.albumIds) { + const albumIdSet = new Set(query.albumIds); + items = res.body.Items.filter((item) => albumIdSet.has(item.AlbumId!)); + totalRecordCount = items.length; + } else { + items = res.body.Items; + totalRecordCount = res.body.TotalRecordCount; + } } return { @@ -754,7 +794,7 @@ export const JellyfinController: ControllerEndpoint = { jfNormalize.song(item, apiClientProps.server, '', query.imageSize), ), startIndex: query.startIndex, - totalRecordCount: res.body.TotalRecordCount, + totalRecordCount, }; }, getSongListCount: async ({ apiClientProps, query }) =>