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
This commit is contained in:
neon
2026-10-20 03:54:29 +00:00
parent 5878c96254
commit 1214152bd5
3 changed files with 34 additions and 8 deletions
@@ -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'],
@@ -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 }),
+2 -1
View File
@@ -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"),