Files
convert/src/mime.js
T
anthony 86b555b334 feat: register ebook converter in the app so the API serves the new family
Also fixes a real bug found via the fb2 API test: file-type sniffs a
real fb2 file's XML declaration as generic "xml", not "fb2" and not
undetected, so it never reached the undetectable-format fallback added
in the previous commit. Added an fb2->xml alias in normalizeFormat,
same pattern as the existing azw3->mobi one.
2026-07-31 13:29:06 +02:00

94 lines
2.6 KiB
JavaScript

import { fileTypeFromFile } from 'file-type';
import fs from 'node:fs/promises';
import { probeDfont } from './converters/dfont.js';
const OUTPUT_MIME_TYPES = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp',
gif: 'image/gif',
tiff: 'image/tiff',
avif: 'image/avif',
bmp: 'image/bmp',
ico: 'image/x-icon',
pdf: 'application/pdf',
html: 'text/html',
txt: 'text/plain',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
md: 'text/markdown',
csv: 'text/csv',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ttf: 'font/ttf',
otf: 'font/otf',
woff: 'font/woff',
epub: 'application/epub+zip',
fb2: 'application/x-fictionbook+xml',
lrf: 'application/octet-stream',
mobi: 'application/x-mobipocket-ebook',
pdb: 'application/vnd.palm',
rb: 'application/octet-stream',
snb: 'application/octet-stream',
tcr: 'application/octet-stream',
azw3: 'application/vnd.amazon.ebook',
};
export async function detectInputMime(filePath) {
const result = await fileTypeFromFile(filePath);
return result ?? null;
}
export function outputMimeType(targetFormat) {
const mime = OUTPUT_MIME_TYPES[targetFormat];
if (!mime) {
throw new Error(`No known MIME type for target format "${targetFormat}"`);
}
return mime;
}
const UNDETECTABLE_TEXT_FORMATS = {
txt: 'text/plain',
html: 'text/html',
md: 'text/markdown',
csv: 'text/csv',
};
const UNDETECTABLE_EBOOK_FORMATS = {
fb2: 'application/x-fictionbook+xml',
lrf: 'application/octet-stream',
pdb: 'application/vnd.palm',
rb: 'application/octet-stream',
snb: 'application/octet-stream',
tcr: 'application/octet-stream',
};
function normalizeFormat(format) {
if (format === 'jpg') return 'jpeg';
if (format === 'heif') return 'heic';
if (format === 'azw3') return 'mobi';
if (format === 'fb2') return 'xml';
return format;
}
export async function resolveInputFormat(filePath, declaredFormat) {
if (declaredFormat === 'dfont') {
const buffer = await fs.readFile(filePath);
return probeDfont(buffer)
? { mime: 'application/x-dfont', valid: true }
: { mime: null, valid: false };
}
const detected = await detectInputMime(filePath);
if (!detected) {
const fallbackMime = UNDETECTABLE_TEXT_FORMATS[declaredFormat] ?? UNDETECTABLE_EBOOK_FORMATS[declaredFormat];
if (fallbackMime) {
return { mime: fallbackMime, valid: true };
}
return { mime: null, valid: false };
}
const valid = normalizeFormat(detected.ext) === normalizeFormat(declaredFormat);
return { mime: detected.mime, valid };
}