Libros de texto: fuente Project Gutenberg y lectura continua
Los EPUB se convierten en el servidor en una lista cerrada de bloques y el navegador los maqueta en páginas según su pantalla. Cada página se dibuja en un canvas y llega al lector como una imagen más, así que hoja, sombra, hendidura y sonido no cambian. El libro se lee de corrido: cada capítulo empieza debajo del anterior y el progreso se sigue guardando por capítulo. El catálogo sale del OPDS de gutenberg.org, con reintentos. Ajustes: mostrar solo manga, solo libros o ambos, por usuario. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -56,8 +56,14 @@ export function writeCbz(file, entries) {
|
||||
fs.renameSync(tmp, file)
|
||||
}
|
||||
|
||||
// Devuelve las imágenes del CBZ ordenadas por nombre: [{ name, method, start, size }]
|
||||
// Devuelve las imágenes del CBZ ordenadas por nombre: [{ name, method, start, size, unzipped }]
|
||||
export function readCbzIndex(file) {
|
||||
return readZipIndex(file).filter(e => IMAGE_EXT.test(e.name) && !e.name.startsWith('__MACOSX'))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }))
|
||||
}
|
||||
|
||||
// Todas las entradas del ZIP, en el orden del archivo (también lo usan los EPUB).
|
||||
export function readZipIndex(file) {
|
||||
const fd = fs.openSync(file, 'r')
|
||||
try {
|
||||
const { size } = fs.fstatSync(fd)
|
||||
@@ -65,7 +71,7 @@ export function readCbzIndex(file) {
|
||||
const tail = Buffer.alloc(tailLen)
|
||||
fs.readSync(fd, tail, 0, tailLen, size - tailLen)
|
||||
const eocd = tail.lastIndexOf(Buffer.from([0x50, 0x4b, 0x05, 0x06]))
|
||||
if (eocd < 0) throw new Error('CBZ inválido')
|
||||
if (eocd < 0) throw new Error('ZIP inválido')
|
||||
const count = tail.readUInt16LE(eocd + 10)
|
||||
const cdSize = tail.readUInt32LE(eocd + 12)
|
||||
const cdOffset = tail.readUInt32LE(eocd + 16)
|
||||
@@ -76,18 +82,18 @@ export function readCbzIndex(file) {
|
||||
for (let p = 0, i = 0; i < count; i++) {
|
||||
const method = cd.readUInt16LE(p + 10)
|
||||
const compSize = cd.readUInt32LE(p + 20)
|
||||
const unzipped = cd.readUInt32LE(p + 24)
|
||||
const nameLen = cd.readUInt16LE(p + 28)
|
||||
const extraLen = cd.readUInt16LE(p + 30)
|
||||
const commentLen = cd.readUInt16LE(p + 32)
|
||||
const localOffset = cd.readUInt32LE(p + 42)
|
||||
const name = cd.toString('utf8', p + 46, p + 46 + nameLen)
|
||||
p += 46 + nameLen + extraLen + commentLen
|
||||
if (!IMAGE_EXT.test(name) || name.startsWith('__MACOSX')) continue
|
||||
fs.readSync(fd, lh, 0, 30, localOffset)
|
||||
const start = localOffset + 30 + lh.readUInt16LE(26) + lh.readUInt16LE(28)
|
||||
entries.push({ name, method, start, size: compSize })
|
||||
entries.push({ name, method, start, size: compSize, unzipped })
|
||||
}
|
||||
return entries.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }))
|
||||
return entries
|
||||
} finally {
|
||||
fs.closeSync(fd)
|
||||
}
|
||||
@@ -98,13 +104,13 @@ export function imageType(name) {
|
||||
return { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp', gif: 'image/gif', avif: 'image/avif' }[ext] || 'application/octet-stream'
|
||||
}
|
||||
|
||||
// Lee una entrada completa del CBZ a memoria.
|
||||
export function readCbzEntry(file, entry) {
|
||||
// Lee una entrada completa del CBZ a memoria. maxBytes corta una entrada que al descomprimirse crezca de más.
|
||||
export function readCbzEntry(file, entry, maxBytes) {
|
||||
if (entry.method !== 0 && entry.method !== 8) throw new Error(`Compresión ZIP no soportada (${entry.method})`)
|
||||
const fd = fs.openSync(file, 'r')
|
||||
const buf = Buffer.alloc(entry.size)
|
||||
try { fs.readSync(fd, buf, 0, entry.size, entry.start) } finally { fs.closeSync(fd) }
|
||||
return entry.method === 8 ? zlib.inflateRawSync(buf) : buf
|
||||
return entry.method === 8 ? zlib.inflateRawSync(buf, maxBytes ? { maxOutputLength: maxBytes } : {}) : buf
|
||||
}
|
||||
|
||||
// Envía una entrada del CBZ por la respuesta HTTP.
|
||||
|
||||
Reference in New Issue
Block a user