- Updated main.ts to include 'PDF to CBZ' in the tools list. - Added new types for CBZ options and comic metadata in pdf-to-cbz-type.ts. - Implemented comic-info utility functions for generating ComicInfo.xml and metadata OPF files. - Created pdf-to-cbz.html page with UI for PDF to CBZ conversion, including options for image format, quality, and metadata. - Updated vite.config.ts to route to the new PDF to CBZ page.
191 lines
4.5 KiB
TypeScript
191 lines
4.5 KiB
TypeScript
import type { ComicMetadata } from '@/types';
|
|
|
|
function escapeXml(str: string): string {
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
export function generateComicInfoXml(params: ComicMetadata): string {
|
|
const lines: string[] = [
|
|
'<?xml version="1.0" encoding="utf-8"?>',
|
|
'<ComicInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">',
|
|
];
|
|
|
|
if (params.title) {
|
|
lines.push(` <Title>${escapeXml(params.title)}</Title>`);
|
|
}
|
|
|
|
if (params.series) {
|
|
lines.push(` <Series>${escapeXml(params.series)}</Series>`);
|
|
}
|
|
|
|
if (params.number) {
|
|
lines.push(` <Number>${escapeXml(params.number)}</Number>`);
|
|
}
|
|
|
|
if (params.volume) {
|
|
lines.push(` <Volume>${escapeXml(params.volume)}</Volume>`);
|
|
}
|
|
|
|
if (params.writer) {
|
|
lines.push(` <Writer>${escapeXml(params.writer)}</Writer>`);
|
|
}
|
|
|
|
if (params.publisher) {
|
|
lines.push(` <Publisher>${escapeXml(params.publisher)}</Publisher>`);
|
|
}
|
|
|
|
if (params.genre) {
|
|
lines.push(` <Genre>${escapeXml(params.genre)}</Genre>`);
|
|
}
|
|
|
|
if (params.year) {
|
|
const yearNum = parseInt(params.year, 10);
|
|
if (!isNaN(yearNum)) {
|
|
lines.push(` <Year>${yearNum}</Year>`);
|
|
}
|
|
}
|
|
|
|
if (params.communityRating) {
|
|
const rating = parseFloat(params.communityRating);
|
|
if (!isNaN(rating) && rating >= 0 && rating <= 5) {
|
|
lines.push(` <CommunityRating>${rating}</CommunityRating>`);
|
|
}
|
|
}
|
|
|
|
lines.push(` <PageCount>${params.pageCount}</PageCount>`);
|
|
|
|
if (params.blackAndWhite) {
|
|
lines.push(' <BlackAndWhite>Yes</BlackAndWhite>');
|
|
}
|
|
|
|
if (params.manga) {
|
|
lines.push(' <Manga>YesAndRightToLeft</Manga>');
|
|
}
|
|
|
|
lines.push('</ComicInfo>');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export function generateMetadataOpf(params: ComicMetadata): string {
|
|
const lines: string[] = [
|
|
'<?xml version="1.0" encoding="utf-8"?>',
|
|
'<package xmlns="http://www.idpf.org/2007/opf" version="2.0">',
|
|
' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">',
|
|
];
|
|
|
|
if (params.title) {
|
|
lines.push(` <dc:title>${escapeXml(params.title)}</dc:title>`);
|
|
}
|
|
|
|
if (params.writer) {
|
|
lines.push(
|
|
` <dc:creator opf:role="aut">${escapeXml(params.writer)}</dc:creator>`
|
|
);
|
|
}
|
|
|
|
if (params.publisher) {
|
|
lines.push(
|
|
` <dc:publisher>${escapeXml(params.publisher)}</dc:publisher>`
|
|
);
|
|
}
|
|
|
|
if (params.year) {
|
|
const yearNum = parseInt(params.year, 10);
|
|
if (!isNaN(yearNum)) {
|
|
lines.push(` <dc:date>${yearNum}-01-01</dc:date>`);
|
|
}
|
|
}
|
|
|
|
if (params.genre) {
|
|
lines.push(` <dc:subject>${escapeXml(params.genre)}</dc:subject>`);
|
|
}
|
|
|
|
if (params.series) {
|
|
lines.push(
|
|
` <meta name="calibre:series" content="${escapeXml(params.series)}" />`
|
|
);
|
|
if (params.number) {
|
|
lines.push(
|
|
` <meta name="calibre:series_index" content="${escapeXml(params.number)}" />`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (params.communityRating) {
|
|
const rating = parseFloat(params.communityRating);
|
|
if (!isNaN(rating) && rating >= 0 && rating <= 5) {
|
|
lines.push(
|
|
` <meta name="calibre:rating" content="${(rating / 5) * 10}" />`
|
|
);
|
|
}
|
|
}
|
|
|
|
lines.push(' </metadata>');
|
|
lines.push('</package>');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export function generateComicBookInfoJson(params: ComicMetadata): string {
|
|
const info: Record<string, unknown> = {};
|
|
|
|
if (params.title) {
|
|
info.title = params.title;
|
|
}
|
|
|
|
if (params.series) {
|
|
info.series = params.series;
|
|
}
|
|
|
|
if (params.number) {
|
|
info.issue = params.number;
|
|
}
|
|
|
|
if (params.volume) {
|
|
info.volume = parseInt(params.volume, 10) || undefined;
|
|
}
|
|
|
|
if (params.publisher) {
|
|
info.publisher = params.publisher;
|
|
}
|
|
|
|
if (params.year) {
|
|
const yearNum = parseInt(params.year, 10);
|
|
if (!isNaN(yearNum)) {
|
|
info.publicationYear = yearNum;
|
|
}
|
|
}
|
|
|
|
if (params.genre) {
|
|
info.tags = params.genre
|
|
.split(',')
|
|
.map((t) => t.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
if (params.writer) {
|
|
info.credits = [{ person: params.writer, role: 'Writer' }];
|
|
}
|
|
|
|
if (params.communityRating) {
|
|
const rating = parseFloat(params.communityRating);
|
|
if (!isNaN(rating) && rating >= 0 && rating <= 5) {
|
|
info.rating = rating;
|
|
}
|
|
}
|
|
|
|
const wrapper = {
|
|
appID: 'BentoPDF/1.0',
|
|
lastModified: new Date().toISOString(),
|
|
'ComicBookInfo/1.0': info,
|
|
};
|
|
|
|
return JSON.stringify(wrapper);
|
|
}
|