fix: EXR/HDR decode failures and batch network timeout

EXR: add ffmpeg fallback when ImageMagick lacks the OpenEXR delegate
(common on macOS Homebrew installs). HDR: force 8-bit depth output to
prevent CLAHE crash (hist_local requires VIPS_FORMAT_UCHAR). Batch:
disable socket timeout and increase server requestTimeout to 30 min
so large AI batches don't get killed by Node.js defaults.
This commit is contained in:
SnapOtter
2026-05-13 14:19:51 +08:00
parent 648c8c12f5
commit 917c1ff773
3 changed files with 28 additions and 4 deletions
+6
View File
@@ -86,6 +86,12 @@ const app = Fastify({
routerOptions: { maxParamLength: 500 },
});
// Image processing (especially AI batch) can run for tens of minutes.
// Node.js defaults to a 5-minute requestTimeout which kills long-running
// connections. Set a generous default; per-route overrides disable it entirely.
app.server.requestTimeout = 30 * 60 * 1000;
app.server.headersTimeout = 60 * 1000;
app.removeContentTypeParser("application/json");
app.addContentTypeParser("application/json", { parseAs: "string" }, (_request, body, done) => {
try {
+17 -4
View File
@@ -235,16 +235,29 @@ async function decodeTga(buffer: Buffer): Promise<Buffer> {
* because EXR files are typically stored in linear light.
*/
async function decodeExr(buffer: Buffer): Promise<Buffer> {
const cmd = await findMagickCmd();
const id = randomUUID();
const inputPath = join(tmpdir(), `exr-in-${id}.exr`);
const outputPath = join(tmpdir(), `exr-out-${id}.png`);
try {
await writeFile(inputPath, buffer);
// ImageMagick needs the OpenEXR delegate which is often missing on macOS
try {
const cmd = await findMagickCmd();
await execFileAsync(
cmd,
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", "-depth", "8", `png:${outputPath}`]),
{ timeout: 120_000 },
);
return await readFile(outputPath);
} catch {
// ImageMagick failed, try ffmpeg
}
await execFileAsync(
cmd,
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", `png:${outputPath}`]),
"ffmpeg",
["-y", "-i", inputPath, "-pix_fmt", "rgba", "-update", "1", outputPath],
{ timeout: 120_000 },
);
return await readFile(outputPath);
@@ -267,7 +280,7 @@ async function decodeHdr(buffer: Buffer): Promise<Buffer> {
await writeFile(inputPath, buffer);
await execFileAsync(
cmd,
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", `png:${outputPath}`]),
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", "-depth", "8", `png:${outputPath}`]),
{ timeout: 120_000 },
);
return await readFile(outputPath);
+5
View File
@@ -37,6 +37,11 @@ export async function registerBatchRoutes(app: FastifyInstance): Promise<void> {
async (request: FastifyRequest<{ Params: { toolId: string } }>, reply: FastifyReply) => {
const { toolId } = request.params;
// Batch processing (especially with AI) can take tens of minutes.
// Disable the Node.js HTTP socket timeout so the connection is not
// dropped while images are still being processed.
request.raw.socket?.setTimeout?.(0);
// Look up the tool config from the registry
const toolConfig = getToolConfig(toolId);
if (!toolConfig) {