Vue 3 + Vite + Tailwind frontend, Fastify API server, execa shell bridge. All components built, server validated, 41 unit tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.3 KiB
JavaScript
67 lines
2.3 KiB
JavaScript
import { randomUUID } from 'node:crypto'
|
|
import { writeFile, unlink } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { preprocess } from '../lib/imagemagick.js'
|
|
import {
|
|
runChafa,
|
|
runJp2a,
|
|
runAsciiImageConverter,
|
|
runImgToTxt,
|
|
} from '../lib/converters.js'
|
|
|
|
const RUNNERS = {
|
|
chafa: runChafa,
|
|
jp2a: runJp2a,
|
|
'ascii-image-converter': runAsciiImageConverter,
|
|
img2txt: runImgToTxt,
|
|
}
|
|
|
|
export async function convertRoute(fastify) {
|
|
fastify.post('/convert', async (request, reply) => {
|
|
let tempInput = null
|
|
let tempProcessed = null
|
|
|
|
try {
|
|
const parts = request.parts()
|
|
let tool = null
|
|
let flags = {}
|
|
|
|
for await (const part of parts) {
|
|
if (part.type === 'file' && part.fieldname === 'image') {
|
|
const SAFE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.tiff', '.tif', '.avif'])
|
|
const rawExt = path.extname(part.filename || '').toLowerCase()
|
|
const ext = SAFE_EXTS.has(rawExt) ? rawExt : '.jpg'
|
|
tempInput = `/tmp/asciinator-${randomUUID()}${ext}`
|
|
const buf = await part.toBuffer()
|
|
await writeFile(tempInput, buf)
|
|
} else if (part.type === 'field') {
|
|
if (part.fieldname === 'tool') tool = part.value
|
|
if (part.fieldname === 'flags') {
|
|
try { flags = JSON.parse(part.value) } catch { flags = {} }
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!tempInput) return reply.code(400).type('text/plain').send('No image provided')
|
|
if (!tool) return reply.code(400).type('text/plain').send('No tool specified')
|
|
|
|
const runner = RUNNERS[tool]
|
|
if (!runner) return reply.code(400).type('text/plain').send(`Unknown tool: ${tool}`)
|
|
|
|
const format = tool === 'jp2a' ? 'jpg' : 'png'
|
|
tempProcessed = await preprocess(tempInput, format)
|
|
const result = await runner(tempProcessed, flags)
|
|
|
|
return reply.type('text/plain').send(result.stdout)
|
|
} catch (err) {
|
|
if (err.exitCode !== undefined) {
|
|
return reply.code(422).type('text/plain').send(err.stderr || err.message)
|
|
}
|
|
return reply.code(400).type('text/plain').send(err.message)
|
|
} finally {
|
|
if (tempInput) await unlink(tempInput).catch(() => {})
|
|
if (tempProcessed) await unlink(tempProcessed).catch(() => {})
|
|
}
|
|
})
|
|
}
|