ASCIInator/server/lib/converters.js
dev 9a4e501be9 fix: jp2a requires --key=value arg format
jp2a 1.3.x does not accept space-separated long option args.
Added _argFormat: 'equals' to jp2a schema so buildArgs emits
--key=value for value flags. Also mirrors chafa's width/height →
--size=WxH combining in runJp2a.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 11:31:37 +00:00

113 lines
4.1 KiB
JavaScript

import { execa } from 'execa'
const TIMEOUT = 30_000
export const SCHEMAS = {
chafa: {
size: { type: 'value' },
colors: { type: 'value', valid: ['none', '2', '8', '16/8', '16', '240', '256', 'full'] },
'color-extractor': { type: 'value', valid: ['average', 'median', 'mode'] },
'color-space': { type: 'value', valid: ['rgb', 'din99d'] },
symbols: { type: 'value' },
fill: { type: 'value' },
dither: { type: 'value', valid: ['none', 'ordered', 'diffusion', 'noise'] },
'dither-grain': { type: 'value', valid: ['1x1', '2x2', '4x4', '8x8'] },
'dither-intensity':{ type: 'value' },
threshold: { type: 'value' },
'font-ratio': { type: 'value' },
work: { type: 'value' },
zoom: { type: 'bool' },
stretch: { type: 'bool' },
'fg-only': { type: 'bool' },
format: { type: 'value', valid: ['symbols', 'utf8'] },
},
jp2a: {
_argFormat: 'equals',
size: { type: 'value' },
width: { type: 'value' },
height: { type: 'value' },
chars: { type: 'value' },
background: { type: 'value', valid: ['light', 'dark'] },
colors: { type: 'bool' },
'color-depth': { type: 'value', valid: ['4', '8', '24'] },
fill: { type: 'bool' },
grayscale: { type: 'bool' },
invert: { type: 'bool' },
border: { type: 'bool' },
flipx: { type: 'bool' },
flipy: { type: 'bool' },
red: { type: 'value' },
green: { type: 'value' },
blue: { type: 'value' },
'edge-threshold': { type: 'value' },
'edges-only': { type: 'bool' },
},
'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', valid: ['none', 'ordered2', 'ordered4', 'ordered8', 'random', 'fstein'] },
gamma: { type: 'value' },
},
}
export function buildArgs(tool, flags) {
const schema = SCHEMAS[tool]
if (!schema) throw new Error(`Unknown tool: ${tool}`)
const equals = schema._argFormat === 'equals'
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 if (equals) {
args.push(`--${key}=${String(value)}`)
} 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 { 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('jp2a', processed)
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 })
}