forked from akai/readest
feat(metadata): parse Calibre series info from PDF and CBZ (#4066)
Surface series name and index from Calibre-written metadata so the existing belongsTo.series → metadata.seriesIndex pipeline picks them up. - PDF: read calibre:series + calibreSI:series_index from XMP - CBZ: read ComicInfo.xml (Series/Number/Count); fall back to ComicBookInfo/1.0 (series/issue) in the ZIP comment Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve, join } from 'path';
|
||||
|
||||
import { DocumentLoader } from '@/libs/document';
|
||||
import type { BookDoc } from '@/libs/document';
|
||||
import type { Collection } from '@/utils/book';
|
||||
|
||||
const vendorDir = join(process.cwd(), 'public/vendor');
|
||||
|
||||
const loadFixture = async (
|
||||
filename: string,
|
||||
mimeType: string,
|
||||
expectedFormat: string,
|
||||
): Promise<BookDoc> => {
|
||||
const filePath = resolve(__dirname, '../fixtures/data', filename);
|
||||
const buffer = readFileSync(filePath);
|
||||
const file = new File([buffer], filename, { type: mimeType });
|
||||
const loader = new DocumentLoader(file);
|
||||
const result = await loader.open();
|
||||
expect(result.format).toBe(expectedFormat);
|
||||
return result.book;
|
||||
};
|
||||
|
||||
const getSeries = (book: BookDoc): Collection | undefined => {
|
||||
const belongsTo = book.metadata.belongsTo?.series;
|
||||
if (!belongsTo) return undefined;
|
||||
return Array.isArray(belongsTo) ? belongsTo[0] : belongsTo;
|
||||
};
|
||||
|
||||
describe('Calibre series metadata', () => {
|
||||
describe('PDF (XMP calibre:series)', () => {
|
||||
let book: BookDoc;
|
||||
|
||||
beforeAll(async () => {
|
||||
await import('foliate-js/pdf.js');
|
||||
const pdfjsLib = (globalThis as Record<string, unknown>)['pdfjsLib'] as {
|
||||
GlobalWorkerOptions: { workerSrc: string };
|
||||
};
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
`file://${join(vendorDir, 'pdfjs/pdf.worker.min.mjs')}`,
|
||||
).href;
|
||||
|
||||
book = await loadFixture('sample-metadata.pdf', 'application/pdf', 'PDF');
|
||||
}, 30_000);
|
||||
|
||||
it('extracts series name and position from XMP', () => {
|
||||
const series = getSeries(book);
|
||||
expect(series).toBeTruthy();
|
||||
expect(series!.name).toBe('Metadata Series');
|
||||
expect(series!.position).toBe('1.00');
|
||||
});
|
||||
|
||||
it('preserves the title', () => {
|
||||
expect(book.metadata.title).toBe('PDF Metadata');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CBZ (ComicInfo.xml + ComicBookInfo)', () => {
|
||||
let book: BookDoc;
|
||||
|
||||
beforeAll(async () => {
|
||||
book = await loadFixture('sample-metadata.cbz', 'application/vnd.comicbook+zip', 'CBZ');
|
||||
});
|
||||
|
||||
it('extracts series name and position', () => {
|
||||
const series = getSeries(book);
|
||||
expect(series).toBeTruthy();
|
||||
expect(series!.name).toBe('Metadata Series');
|
||||
expect(series!.position).toBe('2.0');
|
||||
});
|
||||
|
||||
it('preserves the title', () => {
|
||||
expect(book.metadata.title).toBe('CBZ Metadata');
|
||||
});
|
||||
});
|
||||
});
|
||||
Binary file not shown.
Binary file not shown.
+1
-1
Submodule packages/foliate-js updated: 0a0cedabb6...96dac68155
Reference in New Issue
Block a user