Download server.js from kipeles/apexkit2: direct link, hf CLI and curl.
- Browser
- Download file 4.79 kB
-
https://huggingface.co/spaces/kipeles/apexkit2/resolve/main/server.js
- Command line
-
hf download hf://spaces/kipeles/apexkit2/server.js
-
curl -L -o server.js https://huggingface.co/spaces/kipeles/apexkit2/resolve/main/server.js
4.79 kB
| import express from 'express'; | |
| import multer from 'multer'; | |
| import { exec as execCallback } from 'child_process'; | |
| import { promisify } from 'util'; | |
| import fs from 'fs/promises'; | |
| import crypto from 'crypto'; | |
| const exec = promisify(execCallback); | |
| const app = express(); | |
| const upload = multer({ storage: multer.memoryStorage() }); | |
| app.post('/process', upload.single('file'), async (req, res) => { | |
| const file = req.file; | |
| const format = (req.query.format || 'pdf').toLowerCase(); | |
| const page = parseInt(req.query.page || '1'); | |
| const quality = parseInt(req.query.quality || '80'); | |
| if (!file) return res.status(400).json({ error: 'No file provided' }); | |
| const id = crypto.randomBytes(16).toString('hex'); | |
| const tmpPdf = `/tmp/${id}.pdf`; | |
| let outputFiles = [tmpPdf]; | |
| try { | |
| // 1. Send file to local Gotenberg using standard native multipart | |
| const formData = new FormData(); | |
| const fileBlob = new Blob([file.buffer], { type: file.mimetype }); | |
| formData.append('files', fileBlob, file.originalname); | |
| formData.append('pdfa', 'PDF/A-1b'); | |
| const gotenbergRes = await fetch('http://127.0.0.1:3000/forms/libreoffice/convert', { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| if (!gotenbergRes.ok) throw new Error(`Gotenberg Error: ${await gotenbergRes.text()}`); | |
| const pdfBuffer = await gotenbergRes.arrayBuffer(); | |
| await fs.writeFile(tmpPdf, Buffer.from(pdfBuffer)); | |
| // 2. Extract total pages count using pdfinfo (highly efficient) | |
| const { stdout: pdfInfoStdout } = await exec(`pdfinfo ${tmpPdf}`); | |
| const pagesMatch = pdfInfoStdout.match(/Pages:\s+(\d+)/); | |
| const totalPages = pagesMatch ? parseInt(pagesMatch[1], 10) : 1; | |
| // 3. Apply circular modulo wrapping if requested page is out of bounds | |
| let targetPage = page; | |
| if (page > totalPages) { | |
| targetPage = ((page - 1) % totalPages) + 1; | |
| } | |
| // Return headers to inform Edge router of page layouts | |
| res.setHeader('X-Total-Pages', totalPages.toString()); | |
| res.setHeader('X-Actual-Page', targetPage.toString()); | |
| // 4. Return PDF immediately if requested | |
| if (format === 'pdf') { | |
| res.setHeader('Content-Type', 'application/pdf'); | |
| return res.send(Buffer.from(pdfBuffer)); | |
| } | |
| // 5. Extract specific target page | |
| let finalOutput = ''; | |
| let mimeType = ''; | |
| if (format === 'svg') { | |
| finalOutput = `/tmp/${id}.svg`; | |
| outputFiles.push(finalOutput); | |
| await exec(`pdftocairo -svg -f ${targetPage} -l ${targetPage} ${tmpPdf} ${finalOutput}`); | |
| mimeType = 'image/svg+xml'; | |
| } else if (format === 'png' || format === 'jpeg' || format === 'webp' || format === 'avif') { | |
| const tmpImg = `/tmp/${id}`; | |
| if (format === 'jpeg') { | |
| finalOutput = `/tmp/${id}.jpg`; | |
| outputFiles.push(finalOutput); | |
| await exec(`pdftocairo -jpeg -singlefile -f ${targetPage} -l ${targetPage} ${tmpPdf} ${tmpImg}`); | |
| mimeType = 'image/jpeg'; | |
| } else { | |
| const intermediatePng = `/tmp/${id}.png`; | |
| outputFiles.push(intermediatePng); | |
| await exec(`pdftocairo -png -singlefile -f ${targetPage} -l ${targetPage} ${tmpPdf} ${tmpImg}`); | |
| if (format === 'png') { | |
| finalOutput = intermediatePng; | |
| mimeType = 'image/png'; | |
| } else if (format === 'webp') { | |
| finalOutput = `/tmp/${id}.webp`; | |
| outputFiles.push(finalOutput); | |
| await exec(`cwebp -q ${quality} ${intermediatePng} -o ${finalOutput}`); | |
| mimeType = 'image/webp'; | |
| } else if (format === 'avif') { | |
| finalOutput = `/tmp/${id}.avif`; | |
| outputFiles.push(finalOutput); | |
| await exec(`avifenc -q ${quality} ${intermediatePng} ${finalOutput}`); | |
| mimeType = 'image/avif'; | |
| } | |
| } | |
| } else { | |
| throw new Error('Unsupported format'); | |
| } | |
| const finalBuffer = await fs.readFile(finalOutput); | |
| res.setHeader('Content-Type', mimeType); | |
| res.setHeader('Cache-Control', 'public, max-age=31536000'); | |
| res.send(finalBuffer); | |
| } catch (error) { | |
| console.error(error); | |
| res.status(500).json({ error: error.message }); | |
| } finally { | |
| for (const f of outputFiles) { | |
| fs.unlink(f).catch(() => {}); | |
| } | |
| } | |
| }); | |
| app.listen(7860, () => console.log('Extractor API running on 7860')); |