Fuente M440, modo sin conexión (servidor sin internet y app sin servidor), cola del dispositivo en Colas y arreglo del reescalado de tiras altas
- M440 (m440.in, español): búsqueda, populares, actualizados paginados, ficha, capítulos, descargas y seguimiento. La lista de capítulos viene cifrada: la clave se saca de un script ofuscado del sitio ejecutándolo en un proceso aislado con permisos mínimos (m440-key.mjs) y se renueva sola. - Servidor sin internet (connectivity.js): se detecta en segundos; las fuentes fallan al instante, la portada muestra lo descargado en el servidor con buscador, portadas locales de respaldo, descargas y comprobaciones en pausa y reanudación automática al volver la conexión. - App sin conexión con el servidor: el service worker usa la copia guardada si el servidor no contesta en 3 s, las peticiones comprueban si el servidor sigue vivo en vez de quedarse colgadas, aviso arriba e inicio con lo guardado en el dispositivo; al volver, se reconecta sola y envía el progreso pendiente. - Colas: sección «En este dispositivo» con el guardado en curso en directo y cancelación (sin dejar capítulos a medias); también visible sin servidor. - Imágenes: el proxy y el guardado en el dispositivo reintentan con tiempo límite cuando el CDN de la fuente falla. - Reescalado HD: las páginas que superan 16383 px al reescalar (tiras de webtoon) se reducen al límite de WebP en vez de hacer fallar el capítulo. - Entidades HTML con nombre en las sinopsis; pestañas de fuentes cortas en móvil; portadas rotas con icono de respaldo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+15
-1
@@ -12,6 +12,17 @@ export const MODELS = process.env.UPSCALER_MODELS || path.join(path.dirname(BIN)
|
||||
export const MODEL = process.env.UPSCALER_MODEL || 'realesr-animevideov3'
|
||||
const SCALE = Number(process.env.UPSCALER_SCALE || 2)
|
||||
const WEBP_QUALITY = Number(process.env.UPSCALER_QUALITY || 85)
|
||||
// WebP no admite más de 16383 px por lado: las tiras de webtoon muy altas se pasan al reescalarlas.
|
||||
const WEBP_MAX_SIDE = 16383
|
||||
// Ancho y alto de un PNG (cabecera IHDR).
|
||||
function pngSize(file) {
|
||||
const fd = fs.openSync(file, 'r')
|
||||
try {
|
||||
const b = Buffer.alloc(24)
|
||||
fs.readSync(fd, b, 0, 24, 0)
|
||||
return b.readUInt32BE(0) === 0x89504e47 ? { w: b.readUInt32BE(16), h: b.readUInt32BE(20) } : null
|
||||
} finally { fs.closeSync(fd) }
|
||||
}
|
||||
const HD_DIR = '.hd'
|
||||
const UPSCALABLE = /\.(jpe?g|png|webp)$/i // lo demás (gif, avif) se copia tal cual
|
||||
|
||||
@@ -86,7 +97,10 @@ export function setupUpscale({ db, MANGA_DIR, HttpError, json, readBody, MANGA_I
|
||||
const png = path.join(outDir, `${base}.png`)
|
||||
if (UPSCALABLE.test(name) && fs.existsSync(png)) {
|
||||
const webp = path.join(work, `${base}.webp`)
|
||||
await run('cwebp', ['-quiet', '-q', String(WEBP_QUALITY), '-m', '4', '-mt', png, '-o', webp])
|
||||
// Demasiado grande para WebP: se reduce justo hasta el límite (sigue mejor que la original).
|
||||
const size = pngSize(png)
|
||||
const fit = size && Math.max(size.w, size.h) > WEBP_MAX_SIDE ? (size.h >= size.w ? ['-resize', '0', String(WEBP_MAX_SIDE)] : ['-resize', String(WEBP_MAX_SIDE), '0']) : []
|
||||
await run('cwebp', ['-quiet', '-q', String(WEBP_QUALITY), '-m', '4', '-mt', ...fit, png, '-o', webp])
|
||||
out.push({ name: `${base}.webp`, data: fs.readFileSync(webp) })
|
||||
fs.rmSync(webp); fs.rmSync(png)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user