mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user