mirror of
https://github.com/danbulant/Mangades
synced 2026-07-08 12:30:36 +00:00
fix multichapter epub generation
This commit is contained in:
parent
56562f88ca
commit
b79647df02
3 changed files with 57 additions and 52 deletions
|
|
@ -1,38 +1,37 @@
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef Chapter
|
|
||||||
* @property {string} id
|
|
||||||
* @property {number} number
|
|
||||||
* @property {number?} volume
|
|
||||||
* @property {string[]} links
|
|
||||||
* @property {string} hash
|
|
||||||
* @property {string} baseUrl
|
|
||||||
* @property {string} title
|
|
||||||
*/
|
|
||||||
|
|
||||||
import request, { proxy, imageproxy } from "./request";
|
import request, { proxy, imageproxy } from "./request";
|
||||||
|
|
||||||
const RETRY_LIMIT = 10;
|
const RETRY_LIMIT = 10;
|
||||||
|
|
||||||
|
interface Chapter {
|
||||||
|
id: string,
|
||||||
|
number: string,
|
||||||
|
volume?: string,
|
||||||
|
links: string[],
|
||||||
|
hash: string,
|
||||||
|
hashes: string[],
|
||||||
|
baseUrl: string,
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Opts {
|
||||||
|
quality: string,
|
||||||
|
file: WritableStream,
|
||||||
|
title: string,
|
||||||
|
id: string,
|
||||||
|
language: string,
|
||||||
|
author: string,
|
||||||
|
updatedAt: string | Date,
|
||||||
|
chapters: Chapter[],
|
||||||
|
callback?: (chapter: string | number, link: number, finished: boolean) => void,
|
||||||
|
onerror?: (error: Error) => void
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base generator, to be extended
|
* Base generator, to be extended
|
||||||
*/
|
*/
|
||||||
export class BaseGenerator {
|
export class BaseGenerator {
|
||||||
/**
|
opts: Opts;
|
||||||
*
|
constructor(opts: Opts) {
|
||||||
* @param {object} opts
|
|
||||||
* @param {string} opts.quality
|
|
||||||
* @param {WritableStream} opts.file
|
|
||||||
* @param {string} opts.title
|
|
||||||
* @param {string} opts.id
|
|
||||||
* @param {string} opts.language
|
|
||||||
* @param {string} opts.author
|
|
||||||
* @param {string|Date} opts.updatedAt
|
|
||||||
* @param {Chapter[]} opts.chapters
|
|
||||||
* @param {(chapter: number, link: number, finished: boolean) => void} [opts.callback]
|
|
||||||
* @param {(error: Error) => void} [opts.onerror]
|
|
||||||
*/
|
|
||||||
constructor(opts) {
|
|
||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
this.opts.quality = "data";
|
this.opts.quality = "data";
|
||||||
}
|
}
|
||||||
|
|
@ -83,9 +82,9 @@ export class BaseGenerator {
|
||||||
* @param {number} link
|
* @param {number} link
|
||||||
* @param {boolean} finished
|
* @param {boolean} finished
|
||||||
*/
|
*/
|
||||||
callback(chapter = -1, link = -1, finished = false) {
|
callback(chapter: string | number = -1, link: number = -1, finished = false) {
|
||||||
if(this.opts.callback) {
|
if(this.opts.callback) {
|
||||||
this.opts.callback(chapter, parseInt(link), finished);
|
this.opts.callback(chapter, Number(link), finished);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,10 @@ const enc = new TextEncoder();
|
||||||
* Handles epub generation
|
* Handles epub generation
|
||||||
*/
|
*/
|
||||||
export class EpubGenerator extends BaseGenerator {
|
export class EpubGenerator extends BaseGenerator {
|
||||||
|
writer: WritableStreamDefaultWriter<Uint8Array>;
|
||||||
|
zip: Zip;
|
||||||
|
hashes: { hash: string, chapter: string }[];
|
||||||
|
|
||||||
async generate() {
|
async generate() {
|
||||||
this.writer = this.opts.file.getWriter();
|
this.writer = this.opts.file.getWriter();
|
||||||
this.zip = new Zip();
|
this.zip = new Zip();
|
||||||
|
|
@ -34,7 +38,7 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
chapter.links = data.urls;
|
chapter.links = data.urls;
|
||||||
chapter.hashes = data.hashes;
|
chapter.hashes = data.hashes;
|
||||||
chapter.hash = data.hash;
|
chapter.hash = data.hash;
|
||||||
this.hashes.push(...chapter.hashes);
|
this.hashes.push(...(chapter.hashes.map(t => ({ hash: t, chapter: chapterI }))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +56,7 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
for(const i in chapter.links) {
|
for(const i in chapter.links) {
|
||||||
let url = chapter.links[i];
|
let url = chapter.links[i];
|
||||||
let hash = chapter.hashes[i];
|
let hash = chapter.hashes[i];
|
||||||
this.callback(chapterI, i, false);
|
this.callback(chapterI, Number(i), false);
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const res = await this.fetchImage(url, chapter);
|
const res = await this.fetchImage(url, chapter);
|
||||||
const image = new ZipPassThrough("OEBPS/" + hash);
|
const image = new ZipPassThrough("OEBPS/" + hash);
|
||||||
|
|
@ -67,12 +71,13 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
url: url
|
url: url
|
||||||
});
|
});
|
||||||
image.push(data, true);
|
image.push(data, true);
|
||||||
const textContent = new ZipPassThrough("OEBPS/" + i + ".xhtml");
|
let fileI = this.hashes.findIndex(t => t.hash === hash);
|
||||||
|
const textContent = new ZipPassThrough(`OEBPS/${fileI}.xhtml`);
|
||||||
this.zip.add(textContent);
|
this.zip.add(textContent);
|
||||||
textContent.push(enc.encode(`<?xml version="1.0" encoding="UTF-8"?>
|
textContent.push(enc.encode(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Page ${i + 1}</title>
|
<title>Chapter ${chapterI} Page ${Number(i) + 1}</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
<meta name="EPB-UUID" content=""/>
|
<meta name="EPB-UUID" content=""/>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -80,7 +85,7 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
<img style="margin:auto;height:100%;" src="${hash}" />
|
<img style="margin:auto;height:100%;" src="${hash}" />
|
||||||
</body>
|
</body>
|
||||||
</html>`), true);
|
</html>`), true);
|
||||||
this.callback(chapterI, i, true);
|
this.callback(chapterI, Number(i), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,28 +118,28 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
|
|
||||||
<metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:dc="http://purl.org/dc/elements/1.1/"
|
<metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
xmlns:dcterms="http://purl.org/dc/terms/">
|
xmlns:dcterms="http://purl.org/dc/terms/">
|
||||||
<dc:title>${this.opts.title}</dc:title>
|
<dc:title>${this.opts.title}</dc:title>
|
||||||
<dc:language>${this.opts.language || "en"}</dc:language>
|
<dc:language>${this.opts.language || "en"}</dc:language>
|
||||||
<dc:creator>${this.opts.author}</dc:creator>
|
<dc:creator>${this.opts.author}</dc:creator>
|
||||||
<dc:identifier id="bookid">${this.opts.id}</dc:identifier>
|
<dc:identifier id="bookid">${this.opts.id}</dc:identifier>
|
||||||
<dc:type>Image</dc:type>
|
<dc:type>Image</dc:type>
|
||||||
|
|
||||||
<meta property="dcterms:modified">${this.opts.updatedAt.toString().split("+")[0]}Z</meta>
|
<meta property="dcterms:modified">${this.opts.updatedAt.toString().split("+")[0]}Z</meta>
|
||||||
<meta property="rendition:layout">pre-paginated</meta>
|
<meta property="rendition:layout">pre-paginated</meta>
|
||||||
<meta property="rendition:orientation">portrait</meta>
|
<meta property="rendition:orientation">portrait</meta>
|
||||||
<meta property="rendition:spread">landscape</meta>
|
<meta property="rendition:spread">landscape</meta>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
||||||
<manifest>
|
<manifest>
|
||||||
${this.hashes.map((t, i) => ` <item id="i${i}" href="${t}" fallback="fallback" media-type="image/${t.substr(t.lastIndexOf(".") + 1) === "jpg" ? "jpeg" : t.substr(t.lastIndexOf(".") + 1)}"/>`).join("\n")}
|
${this.hashes.map((t, i) => ` <item id="i${i}" href="${t.hash}" fallback="fallback" media-type="image/${t.hash.substr(t.hash.lastIndexOf(".") + 1) === "jpg" ? "jpeg" : t.hash.substr(t.hash.lastIndexOf(".") + 1)}"/>`).join("\n")}
|
||||||
${this.hashes.map((t, i) => ` <item id="p${i}" href="${i}.xhtml" media-type="application/xhtml+xml" />`).join("\n")}
|
${this.hashes.map((t, i) => ` <item id="p${i}" href="${i}.xhtml" media-type="application/xhtml+xml" />`).join("\n")}
|
||||||
|
|
||||||
<item id="ncxtoc" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
|
<item id="ncxtoc" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
||||||
<spine toc="ncxtoc">
|
<spine toc="ncxtoc">
|
||||||
${this.hashes.map((t, i) => ` <itemref idref="p${i}" linear="yes" />`).join("\n")}
|
${this.hashes.map((t, i) => ` <itemref idref="p${i}" linear="yes" />`).join("\n")}
|
||||||
<itemref idref="fallback" linear="no" />
|
<itemref idref="fallback" linear="no" />
|
||||||
</spine>
|
</spine>
|
||||||
|
|
||||||
</package>`), true);
|
</package>`), true);
|
||||||
|
|
@ -142,6 +147,7 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
|
|
||||||
toc() {
|
toc() {
|
||||||
const ncx = new ZipPassThrough("OEBPS/toc.ncx");
|
const ncx = new ZipPassThrough("OEBPS/toc.ncx");
|
||||||
|
|
||||||
this.zip.add(ncx);
|
this.zip.add(ncx);
|
||||||
ncx.push(enc.encode(`<?xml version="1.0" encoding="utf-8"?>
|
ncx.push(enc.encode(`<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
|
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
|
||||||
|
|
@ -159,14 +165,14 @@ export class EpubGenerator extends BaseGenerator {
|
||||||
</docAuthor>
|
</docAuthor>
|
||||||
|
|
||||||
<navMap>
|
<navMap>
|
||||||
${this.opts.chapters.map((t, i) => t.links.map((link, i) => `
|
${this.opts.chapters.map((t, i) => `
|
||||||
<navPoint id="p${i}" playOrder="${i + 1}">
|
<navPoint id="p${this.hashes.findIndex(h => h.hash === t.hashes[0])}" playOrder="${i + 1}">
|
||||||
<navLabel>
|
<navLabel>
|
||||||
<text>${this.opts.title} Chapter ${t.number} Page ${i + 1}</text>
|
<text>${this.opts.title} Chapter ${t.number}</text>
|
||||||
</navLabel>
|
</navLabel>
|
||||||
<content src="${i}.xhtml"/>
|
<content src="${this.hashes.findIndex(h => h.hash === t.hashes[0])}.xhtml"/>
|
||||||
</navPoint>
|
</navPoint>
|
||||||
`)).flat().join("\n")}
|
`).flat().join("\n")}
|
||||||
</navMap>
|
</navMap>
|
||||||
</ncx>`), true);
|
</ncx>`), true);
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
"$lib/*": ["src/lib/*"]
|
"$lib/*": ["src/lib/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"],
|
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "src/**/*.ts"],
|
||||||
"extends": "./.svelte-kit/tsconfig.json"
|
"extends": "./.svelte-kit/tsconfig.json"
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue