Download server.js from 000x/sinhala-subtitle-hardcoder: direct link, hf CLI and curl.
- Browser
- Download file 9.13 kB
-
https://huggingface.co/spaces/000x/sinhala-subtitle-hardcoder/resolve/main/server.js
- Command line
-
hf download hf://spaces/000x/sinhala-subtitle-hardcoder/server.js
-
curl -L -o server.js https://huggingface.co/spaces/000x/sinhala-subtitle-hardcoder/resolve/main/server.js
9.13 kB
| const express = require('express'); | |
| const sharp = require('sharp'); | |
| const crypto = require('crypto'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { Readable } = require('stream'); | |
| const { pipeline } = require('stream/promises'); | |
| const { createCanvas, loadImage, ImageData, GlobalFonts } = require('@napi-rs/canvas'); | |
| /* Works with a public/ folder or with all files uploaded flat into the repo root */ | |
| const PUB = fs.existsSync(path.join(__dirname, 'public', 'render.js')) ? path.join(__dirname, 'public') : __dirname; | |
| const R = require(path.join(PUB, 'render.js')); | |
| const PORT = process.env.PORT || 7860; | |
| const ROOT = process.env.DATA_DIR || '/tmp/mr'; | |
| const PREVIEW_EDGE = 1600; | |
| const SESSION_TTL = 3 * 3600 * 1000; | |
| fs.mkdirSync(ROOT, { recursive: true }); | |
| sharp.cache(false); | |
| /* Windows font names used by the app -> Linux look-alikes (best effort) */ | |
| for (const [real, alias] of [ | |
| ['Liberation Sans', 'Arial'], | |
| ['Liberation Mono', 'Courier New'], | |
| ['DejaVu Serif', 'Georgia'], | |
| ['DejaVu Sans', 'Impact'], | |
| ['Noto Sans Sinhala', 'Iskoola Pota'], | |
| ]) { | |
| try { GlobalFonts.setAlias(real, alias); } catch {} | |
| } | |
| /* ---------- sessions (one per browser tab, id is random and unguessable) ---------- */ | |
| const sessions = new Map(); | |
| function getSess(sid) { | |
| if (!/^[a-f0-9]{32}$/.test(sid || '')) throw Object.assign(new Error('bad session'), { code: 400 }); | |
| let s = sessions.get(sid); | |
| if (!s) { | |
| const dir = path.join(ROOT, sid); | |
| fs.mkdirSync(dir, { recursive: true }); | |
| s = { dir, items: new Map(), layers: [], t: Date.now() }; | |
| sessions.set(sid, s); | |
| } | |
| s.t = Date.now(); | |
| return s; | |
| } | |
| setInterval(() => { | |
| const now = Date.now(); | |
| for (const [sid, s] of sessions) { | |
| if (now - s.t > SESSION_TTL) { | |
| sessions.delete(sid); | |
| fs.rm(s.dir, { recursive: true, force: true }, () => {}); | |
| } | |
| } | |
| }, 10 * 60 * 1000).unref(); | |
| /* ---------- helpers ---------- */ | |
| const bearer = req => (req.headers.authorization || '').replace(/^Bearer\s+/i, ''); | |
| async function pool(list, n, fn) { | |
| let i = 0; | |
| await Promise.all(Array.from({ length: Math.min(n, list.length) }, async () => { | |
| while (i < list.length) await fn(list[i++]); | |
| })); | |
| } | |
| function ndjson(res) { | |
| res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8'); | |
| res.setHeader('Cache-Control', 'no-store'); | |
| res.setHeader('X-Accel-Buffering', 'no'); | |
| res.flushHeaders(); | |
| return o => { if (!res.writableEnded && !res.destroyed) res.write(JSON.stringify(o) + '\n'); }; | |
| } | |
| const fail = (res, e) => res.status(e.code && e.code < 600 ? e.code : 500).send(e.message || 'error'); | |
| async function driveUpload(tok, folderId, name, mime, buf) { | |
| let last; | |
| for (let attempt = 0; attempt < 3; attempt++) { | |
| try { | |
| const init = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&fields=id', { | |
| method: 'POST', | |
| headers: { | |
| Authorization: 'Bearer ' + tok, | |
| 'Content-Type': 'application/json; charset=UTF-8', | |
| 'X-Upload-Content-Type': mime, | |
| 'X-Upload-Content-Length': String(buf.length), | |
| }, | |
| body: JSON.stringify({ name, parents: [folderId] }), | |
| }); | |
| if (init.status === 401) throw new Error('Google session expired'); | |
| if (!init.ok) throw new Error('Drive ' + init.status); | |
| const put = await fetch(init.headers.get('location'), { | |
| method: 'PUT', | |
| headers: { 'Content-Type': mime, 'Content-Length': String(buf.length) }, | |
| body: buf, | |
| }); | |
| if (!put.ok) throw new Error('Drive upload ' + put.status); | |
| return; | |
| } catch (e) { | |
| last = e; | |
| if (/expired/.test(e.message)) break; | |
| await new Promise(r => setTimeout(r, 800 * (attempt + 1))); | |
| } | |
| } | |
| throw last; | |
| } | |
| /* Same drawing code as the browser preview (public/render.js), at full resolution. */ | |
| async function renderBuf(sess, rid, ov, o) { | |
| const it = sess.items.get(rid); | |
| if (!it) throw Object.assign(new Error('unknown photo'), { code: 404 }); | |
| const k = o.max ? Math.min(1, o.max / Math.max(it.w, it.h)) : 1; | |
| const w = Math.round(it.w * k), h = Math.round(it.h * k); | |
| let pipe = sharp(it.file).rotate(); | |
| if (k < 1) pipe = pipe.resize(w, h, { fit: 'fill' }); | |
| const { data, info } = await pipe.ensureAlpha().raw().toBuffer({ resolveWithObject: true }); | |
| const c = createCanvas(info.width, info.height); | |
| const ctx = c.getContext('2d'); | |
| ctx.putImageData(new ImageData(new Uint8ClampedArray(data.buffer, data.byteOffset, data.length), info.width, info.height), 0, 0); | |
| R.drawAll(ctx, info.width, info.height, sess.layers, { ov: ov || {} }); | |
| const png = o.fmt === 'png'; | |
| const q = Math.round(Math.min(1, Math.max(0.5, +o.q || 0.92)) * 100); | |
| return { buf: await c.encode(png ? 'png' : 'jpeg', q), mime: png ? 'image/png' : 'image/jpeg' }; | |
| } | |
| /* ---------- app ---------- */ | |
| const app = express(); | |
| app.disable('x-powered-by'); | |
| if (PUB === __dirname) { | |
| app.get('/', (q, r) => r.sendFile(path.join(PUB, 'index.html'))); | |
| app.get('/render.js', (q, r) => r.sendFile(path.join(PUB, 'render.js'))); | |
| } else { | |
| app.use(express.static(PUB)); | |
| } | |
| app.get('/api/config', (req, res) => res.json({ clientId: process.env.GOOGLE_CLIENT_ID || '' })); | |
| /* Download picked Drive files on the server (parallel), stream progress back */ | |
| app.post('/api/import', express.json({ limit: '2mb' }), async (req, res) => { | |
| let sess; | |
| try { sess = getSess(req.body.sid); } catch (e) { return fail(res, e); } | |
| const tok = bearer(req); | |
| const files = Array.isArray(req.body.files) ? req.body.files.slice(0, 2000) : []; | |
| if (!tok) return res.status(401).send('missing token'); | |
| const send = ndjson(res); | |
| send({ t: 'start', n: files.length }); | |
| await pool(files, 6, async f => { | |
| try { | |
| const rid = crypto.randomBytes(6).toString('hex'); | |
| const file = path.join(sess.dir, rid + '.orig'); | |
| const r = await fetch(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(f.id)}?alt=media&supportsAllDrives=true`, { | |
| headers: { Authorization: 'Bearer ' + tok }, | |
| }); | |
| if (r.status === 401) throw new Error('Google session expired, reconnect Drive'); | |
| if (!r.ok) throw new Error('Drive returned ' + r.status); | |
| await pipeline(Readable.fromWeb(r.body), fs.createWriteStream(file)); | |
| const meta = await sharp(file).metadata(); | |
| const swap = (meta.orientation || 1) >= 5; | |
| const w = swap ? meta.height : meta.width, h = swap ? meta.width : meta.height; | |
| await sharp(file).rotate() | |
| .resize({ width: PREVIEW_EDGE, height: PREVIEW_EDGE, fit: 'inside', withoutEnlargement: true }) | |
| .jpeg({ quality: 86 }).toFile(path.join(sess.dir, rid + '.prev.jpg')); | |
| sess.items.set(rid, { name: String(f.name || rid), w, h, file }); | |
| send({ t: 'item', rid, name: String(f.name || rid), w, h }); | |
| } catch (e) { | |
| send({ t: 'err', name: f.name, msg: e.message }); | |
| } | |
| }); | |
| send({ t: 'done' }); | |
| res.end(); | |
| }); | |
| app.get('/api/preview/:sid/:rid', (req, res) => { | |
| try { | |
| const s = getSess(req.params.sid); | |
| if (!s.items.has(req.params.rid)) return res.sendStatus(404); | |
| res.setHeader('Cache-Control', 'private, max-age=3600'); | |
| res.sendFile(path.join(s.dir, req.params.rid + '.prev.jpg')); | |
| } catch (e) { fail(res, e); } | |
| }); | |
| /* Watermark layers (image layers arrive as PNG data URLs) */ | |
| app.post('/api/settings/:sid', express.json({ limit: '80mb' }), async (req, res) => { | |
| try { | |
| const s = getSess(req.params.sid); | |
| const layers = Array.isArray(req.body.layers) ? req.body.layers : []; | |
| s.layers = await Promise.all(layers.map(async l => { | |
| const L = { ...l }; | |
| if (l.type === 'image') { | |
| L.cv = await loadImage(Buffer.from(String(l.png).split(',')[1] || '', 'base64')); | |
| delete L.png; | |
| } | |
| return L; | |
| })); | |
| res.json({ ok: true }); | |
| } catch (e) { fail(res, e); } | |
| }); | |
| /* Render one photo at full size and send it back (for ZIP / separate downloads) */ | |
| app.post('/api/render/:sid/:rid', express.json({ limit: '1mb' }), async (req, res) => { | |
| try { | |
| const s = getSess(req.params.sid); | |
| const { buf, mime } = await renderBuf(s, req.params.rid, req.body.ov, req.body); | |
| res.type(mime).send(buf); | |
| } catch (e) { fail(res, e); } | |
| }); | |
| /* Render + upload straight into a Drive folder (created by the browser in the chosen account) */ | |
| app.post('/api/export-drive', express.json({ limit: '5mb' }), async (req, res) => { | |
| let sess; | |
| try { sess = getSess(req.body.sid); } catch (e) { return fail(res, e); } | |
| const tok = bearer(req); | |
| const { folderId, items } = req.body; | |
| if (!tok || !folderId || !Array.isArray(items)) return res.status(400).send('bad request'); | |
| const send = ndjson(res); | |
| await pool(items, 3, async it => { | |
| try { | |
| const { buf, mime } = await renderBuf(sess, it.rid, it.ov, req.body); | |
| await driveUpload(tok, folderId, String(it.name), mime, buf); | |
| send({ t: 'ok', name: it.name }); | |
| } catch (e) { | |
| send({ t: 'err', name: it.name, msg: e.message }); | |
| } | |
| }); | |
| send({ t: 'done' }); | |
| res.end(); | |
| }); | |
| app.listen(PORT, '0.0.0.0', () => console.log('markroom listening on ' + PORT)); | |