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>
80 lines
2.5 KiB
JavaScript
80 lines
2.5 KiB
JavaScript
import { execa } from 'execa'
|
|
|
|
const TIMEOUT = 30_000
|
|
|
|
const SCHEMAS = {
|
|
chafa: {
|
|
size: { type: 'value' },
|
|
colors: { type: 'value', valid: ['none', '2', '8', '16/8', '16', '240', '256', 'full'] },
|
|
symbols: { type: 'value' },
|
|
dither: { type: 'value', valid: ['none', 'ordered', 'diffusion', 'noise'] },
|
|
threshold: { type: 'value' },
|
|
'font-ratio': { type: 'value' },
|
|
},
|
|
jp2a: {
|
|
width: { type: 'value' },
|
|
height: { type: 'value' },
|
|
chars: { type: 'value' },
|
|
background: { type: 'value', valid: ['light', 'dark'] },
|
|
},
|
|
'ascii-image-converter': {
|
|
width: { type: 'value' },
|
|
height: { type: 'value' },
|
|
color: { type: 'bool' },
|
|
braille: { type: 'bool' },
|
|
threshold: { type: 'value' },
|
|
},
|
|
img2txt: {
|
|
width: { type: 'value' },
|
|
height: { type: 'value' },
|
|
format: { type: 'value', valid: ['ansi', 'utf8', 'html'] },
|
|
dither: { type: 'value' },
|
|
gamma: { type: 'value' },
|
|
},
|
|
}
|
|
|
|
export function buildArgs(tool, flags) {
|
|
const schema = SCHEMAS[tool]
|
|
if (!schema) throw new Error(`Unknown tool: ${tool}`)
|
|
|
|
const args = []
|
|
for (const [key, value] of Object.entries(flags)) {
|
|
const def = schema[key]
|
|
if (!def) throw new Error(`Unknown flag for ${tool}: --${key}`)
|
|
if (def.valid && !def.valid.includes(String(value))) {
|
|
throw new Error(`Invalid value for --${key}: ${value}. Expected one of: ${def.valid.join(', ')}`)
|
|
}
|
|
if (def.type === 'bool') {
|
|
if (value) args.push(`--${key}`)
|
|
} else {
|
|
args.push(`--${key}`, String(value))
|
|
}
|
|
}
|
|
return args
|
|
}
|
|
|
|
export async function runChafa(imagePath, flags) {
|
|
const { width, height, ...rest } = flags
|
|
const processed = { ...rest }
|
|
if (width && height) processed.size = `${width}x${height}`
|
|
else if (width) processed.size = `${width}x`
|
|
else if (height) processed.size = `x${height}`
|
|
const args = buildArgs('chafa', processed)
|
|
return execa('chafa', [...args, imagePath], { timeout: TIMEOUT })
|
|
}
|
|
|
|
export async function runJp2a(imagePath, flags) {
|
|
const args = buildArgs('jp2a', flags)
|
|
return execa('jp2a', [...args, imagePath], { timeout: TIMEOUT })
|
|
}
|
|
|
|
export async function runAsciiImageConverter(imagePath, flags) {
|
|
const args = buildArgs('ascii-image-converter', flags)
|
|
return execa('ascii-image-converter', [...args, imagePath], { timeout: TIMEOUT })
|
|
}
|
|
|
|
export async function runImgToTxt(imagePath, flags) {
|
|
const args = buildArgs('img2txt', flags)
|
|
return execa('img2txt', [...args, imagePath], { timeout: TIMEOUT })
|
|
}
|