From 1214152bd5f306dcf9ffbffbdbc9ac6d5016cb5b Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Tue, 20 Oct 2026 03:54:29 +0000 Subject: [PATCH] fix(series): group books by media from content files only Count only primary and content files when profiling a book's media, so a side image, cover or .nfo in an audiobook folder no longer adds it to the Books group on the series page. Fixes #1862 --- .../src/features/book/lib/book-media.test.ts | 11 ++++++++ .../useSeriesBookMediaGroups.test.ts | 28 ++++++++++++++----- packages/types/src/book.ts | 3 +- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/client/src/features/book/lib/book-media.test.ts b/client/src/features/book/lib/book-media.test.ts index c9f361a6c..0d310df19 100644 --- a/client/src/features/book/lib/book-media.test.ts +++ b/client/src/features/book/lib/book-media.test.ts @@ -25,6 +25,17 @@ describe('book media profile', () => { expect(getBookMediaProfile(files).primaryMediaKind).toBe('audiobook') }) + it('derives media capabilities from content files only', () => { + const files = [file(1, 'm4b', 'primary'), file(2, 'jpg', 'supplement'), file(3, 'jpg', 'cover'), file(4, 'nfo', 'metadata')] + + expect(getBookMediaProfile(files)).toEqual({ + primaryMediaKind: 'audiobook', + hasEbook: false, + hasAudio: true, + hasComic: false, + }) + }) + it.each([ [' EPUB ', 'ebook'], ['m4A', 'audiobook'], diff --git a/client/src/features/series/composables/useSeriesBookMediaGroups.test.ts b/client/src/features/series/composables/useSeriesBookMediaGroups.test.ts index 61a12c462..fae51f0de 100644 --- a/client/src/features/series/composables/useSeriesBookMediaGroups.test.ts +++ b/client/src/features/series/composables/useSeriesBookMediaGroups.test.ts @@ -54,21 +54,35 @@ describe('series book media groups', () => { it('groups a book by every media kind it contains regardless of its primary file', () => { const book = makeBook('epub', { files: [ - { id: 1, format: 'mp3', role: 'secondary', sizeBytes: null }, + { id: 1, format: 'mp3', role: 'content', sizeBytes: null }, { id: 2, format: 'epub', role: 'primary', sizeBytes: null }, - { id: 3, format: 'cbz', role: 'secondary', sizeBytes: null }, + { id: 3, format: 'cbz', role: 'content', sizeBytes: null }, ], }) expect(getSeriesBookMediaGroupKeys(book)).toEqual(['books', 'audiobooks', 'comics']) }) + it('ignores cover, supplement and metadata files when grouping an audiobook', () => { + const book = makeBook(undefined, { + files: [ + { id: 1, format: 'mp3', role: 'primary', sizeBytes: null }, + { id: 2, format: 'jpg', role: 'supplement', sizeBytes: null }, + { id: 3, format: 'jpg', role: 'cover', sizeBytes: null }, + { id: 4, format: 'nfo', role: 'metadata', sizeBytes: null }, + { id: 5, format: 'cue', role: 'supplement', sizeBytes: null }, + ], + }) + + expect(getSeriesBookMediaGroupKeys(book)).toEqual(['audiobooks']) + }) + it('does not add a book to the same group more than once', () => { const book = makeBook(undefined, { files: [ { id: 1, format: 'mp3', role: 'primary', sizeBytes: null }, - { id: 2, format: 'm4b', role: 'secondary', sizeBytes: null }, - { id: 3, format: ' M4A ', role: 'secondary', sizeBytes: null }, + { id: 2, format: 'm4b', role: 'content', sizeBytes: null }, + { id: 3, format: ' M4A ', role: 'content', sizeBytes: null }, ], }) @@ -90,15 +104,15 @@ describe('series book media groups', () => { id: 3, files: [ { id: 30, format: 'epub', role: 'primary', sizeBytes: null }, - { id: 31, format: 'm4b', role: 'secondary', sizeBytes: null }, + { id: 31, format: 'm4b', role: 'content', sizeBytes: null }, ], }), makeBook('pdf', { id: 4, files: [ { id: 40, format: 'pdf', role: 'primary', sizeBytes: null }, - { id: 41, format: 'flac', role: 'secondary', sizeBytes: null }, - { id: 42, format: 'cbr', role: 'secondary', sizeBytes: null }, + { id: 41, format: 'flac', role: 'content', sizeBytes: null }, + { id: 42, format: 'cbr', role: 'content', sizeBytes: null }, ], }), makeBook(undefined, { id: 5 }), diff --git a/packages/types/src/book.ts b/packages/types/src/book.ts index c367dd72a..e96fc2970 100644 --- a/packages/types/src/book.ts +++ b/packages/types/src/book.ts @@ -166,7 +166,8 @@ export function getBookMediaKind(format: string | null | undefined): BookMediaKi } export function getBookMediaProfile(files: readonly BookMediaFile[]): BookMediaProfile { - const mediaKinds = files.map((file) => getBookMediaKind(file.format)); + // Covers and sidecars such as a folder image or an .nfo fall through getBookMediaKind as "ebook". + const mediaKinds = files.filter((file) => file.role === "content" || file.role === "primary").map((file) => getBookMediaKind(file.format)); return { primaryMediaKind: getBookMediaKind(getPrimaryBookFile(files)?.format), hasEbook: mediaKinds.includes("ebook"),