mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(books): respect library cover aspect ratio for combined media books
Books containing both ebook and audiobook formats were incorrectly rendered with square covers in dashboard widgets, series views, and cards because aspect ratio logic checked for any audio format file instead of relying on the configured cover aspect ratio or primary media profile. Closes #712
This commit is contained in:
@@ -4,6 +4,7 @@ import type { AudiobookChapter, NarratorRef } from "./audiobook";
|
||||
import type { ComicMetadataFields } from "./metadata-fetch";
|
||||
import type { BookFileWriteField, WriteResult } from "./file-write";
|
||||
import type { CustomMetadataBookValue } from "./custom-metadata";
|
||||
import type { CoverAspectRatio } from "./library";
|
||||
|
||||
export const BOOK_FORMATS = ["epub", "pdf", "mobi", "azw3", "cbz", "cbr", "cb7", "fb2", "m4b", "mp3", "m4a", "opus", "ogg", "flac"] as const;
|
||||
export type BookFormat = (typeof BOOK_FORMATS)[number];
|
||||
@@ -75,6 +76,39 @@ export type BookFileRef = {
|
||||
sizeBytes: number | null;
|
||||
};
|
||||
|
||||
export type BookMediaKind = "ebook" | "audiobook" | "comic" | "unknown";
|
||||
|
||||
export type BookMediaProfile = {
|
||||
primaryMediaKind: BookMediaKind;
|
||||
hasEbook: boolean;
|
||||
hasAudio: boolean;
|
||||
hasComic: boolean;
|
||||
};
|
||||
|
||||
type BookMediaFile = Pick<BookFileRef, "format" | "role">;
|
||||
|
||||
export function getPrimaryBookFile<T extends BookMediaFile>(files: readonly T[]): T | null {
|
||||
return files.find((file) => file.role === "primary") ?? files.find((file) => file.format != null) ?? files[0] ?? null;
|
||||
}
|
||||
|
||||
export function getBookMediaKind(format: string | null | undefined): BookMediaKind {
|
||||
const normalized = format?.trim().toLowerCase();
|
||||
if (!normalized) return "unknown";
|
||||
if (isAudioFormat(normalized)) return "audiobook";
|
||||
if (isComicFormat(normalized)) return "comic";
|
||||
return "ebook";
|
||||
}
|
||||
|
||||
export function getBookMediaProfile(files: readonly BookMediaFile[]): BookMediaProfile {
|
||||
const mediaKinds = files.map((file) => getBookMediaKind(file.format));
|
||||
return {
|
||||
primaryMediaKind: getBookMediaKind(getPrimaryBookFile(files)?.format),
|
||||
hasEbook: mediaKinds.includes("ebook"),
|
||||
hasAudio: mediaKinds.includes("audiobook"),
|
||||
hasComic: mediaKinds.includes("comic"),
|
||||
};
|
||||
}
|
||||
|
||||
export type BookSeriesMembership = {
|
||||
seriesId: number;
|
||||
seriesName: string;
|
||||
@@ -85,6 +119,7 @@ export type BookSeriesMembership = {
|
||||
export type BookCard = {
|
||||
id: number;
|
||||
status: string;
|
||||
coverAspectRatio: CoverAspectRatio;
|
||||
title: string | null;
|
||||
authors: string[];
|
||||
seriesId?: number | null;
|
||||
@@ -282,6 +317,7 @@ export type BooksPage = {
|
||||
export type BookRecommendation = {
|
||||
id: number;
|
||||
title: string | null;
|
||||
coverAspectRatio: CoverAspectRatio;
|
||||
updatedAt: string | null;
|
||||
hasCover: boolean;
|
||||
authors: string[];
|
||||
@@ -292,6 +328,7 @@ export type BookRecommendation = {
|
||||
export type SeriesBookRecommendation = {
|
||||
id: number;
|
||||
title: string | null;
|
||||
coverAspectRatio: CoverAspectRatio;
|
||||
updatedAt: string | null;
|
||||
seriesIndex: number | null;
|
||||
hasCover: boolean;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export type OrganizationMode = "book_per_file" | "book_per_folder";
|
||||
export type CoverAspectRatio = "2/3" | "1/1";
|
||||
|
||||
export function normalizeCoverAspectRatio(value: unknown): CoverAspectRatio {
|
||||
return value === "1/1" ? "1/1" : "2/3";
|
||||
}
|
||||
|
||||
export const DEFAULT_FORMAT_PRIORITY = [
|
||||
"epub",
|
||||
"kepub",
|
||||
|
||||
Reference in New Issue
Block a user