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 },
|
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.removeContentTypeParser("application/json");
|
||||||
app.addContentTypeParser("application/json", { parseAs: "string" }, (_request, body, done) => {
|
app.addContentTypeParser("application/json", { parseAs: "string" }, (_request, body, done) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -235,16 +235,29 @@ async function decodeTga(buffer: Buffer): Promise<Buffer> {
|
|||||||
* because EXR files are typically stored in linear light.
|
* because EXR files are typically stored in linear light.
|
||||||
*/
|
*/
|
||||||
async function decodeExr(buffer: Buffer): Promise<Buffer> {
|
async function decodeExr(buffer: Buffer): Promise<Buffer> {
|
||||||
const cmd = await findMagickCmd();
|
|
||||||
const id = randomUUID();
|
const id = randomUUID();
|
||||||
const inputPath = join(tmpdir(), `exr-in-${id}.exr`);
|
const inputPath = join(tmpdir(), `exr-in-${id}.exr`);
|
||||||
const outputPath = join(tmpdir(), `exr-out-${id}.png`);
|
const outputPath = join(tmpdir(), `exr-out-${id}.png`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writeFile(inputPath, buffer);
|
await writeFile(inputPath, buffer);
|
||||||
|
|
||||||
|
// ImageMagick needs the OpenEXR delegate which is often missing on macOS
|
||||||
|
try {
|
||||||
|
const cmd = await findMagickCmd();
|
||||||
await execFileAsync(
|
await execFileAsync(
|
||||||
cmd,
|
cmd,
|
||||||
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", `png:${outputPath}`]),
|
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", "-depth", "8", `png:${outputPath}`]),
|
||||||
|
{ timeout: 120_000 },
|
||||||
|
);
|
||||||
|
return await readFile(outputPath);
|
||||||
|
} catch {
|
||||||
|
// ImageMagick failed, try ffmpeg
|
||||||
|
}
|
||||||
|
|
||||||
|
await execFileAsync(
|
||||||
|
"ffmpeg",
|
||||||
|
["-y", "-i", inputPath, "-pix_fmt", "rgba", "-update", "1", outputPath],
|
||||||
{ timeout: 120_000 },
|
{ timeout: 120_000 },
|
||||||
);
|
);
|
||||||
return await readFile(outputPath);
|
return await readFile(outputPath);
|
||||||
@@ -267,7 +280,7 @@ async function decodeHdr(buffer: Buffer): Promise<Buffer> {
|
|||||||
await writeFile(inputPath, buffer);
|
await writeFile(inputPath, buffer);
|
||||||
await execFileAsync(
|
await execFileAsync(
|
||||||
cmd,
|
cmd,
|
||||||
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", `png:${outputPath}`]),
|
magickArgs(cmd, [inputPath, "-colorspace", "sRGB", "-depth", "8", `png:${outputPath}`]),
|
||||||
{ timeout: 120_000 },
|
{ timeout: 120_000 },
|
||||||
);
|
);
|
||||||
return await readFile(outputPath);
|
return await readFile(outputPath);
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ export async function registerBatchRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
async (request: FastifyRequest<{ Params: { toolId: string } }>, reply: FastifyReply) => {
|
async (request: FastifyRequest<{ Params: { toolId: string } }>, reply: FastifyReply) => {
|
||||||
const { toolId } = request.params;
|
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
|
// Look up the tool config from the registry
|
||||||
const toolConfig = getToolConfig(toolId);
|
const toolConfig = getToolConfig(toolId);
|
||||||
if (!toolConfig) {
|
if (!toolConfig) {
|
||||||
|
|||||||
Reference in New Issue
Block a user