mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
The upscale function called runPythonWithProgress without a timeout parameter, defaulting to the bridge's 10-minute hard limit. On CPU-only systems like Synology NAS devices, Real-ESRGAN 4x upscaling easily exceeds this for modest images. Additionally, when the timeout fired on the dispatcher path, the Python process was left running and blocked all subsequent AI operations. This fix adds an adaptive timeout based on input megapixels, scale factor, and GPU availability (180s/effective-MP on CPU, 30s/effective-MP on GPU, floor of 10 minutes). It also kills the dispatcher on timeout so subsequent requests can proceed via a fresh restart. Closes #119
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
import { readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import sharp from "sharp";
|
|
import {
|
|
isGpuAvailable,
|
|
type ProgressCallback,
|
|
parseStdoutJson,
|
|
runPythonWithProgress,
|
|
} from "./bridge.js";
|
|
|
|
export interface UpscaleOptions {
|
|
scale?: number;
|
|
model?: string;
|
|
faceEnhance?: boolean;
|
|
denoise?: number;
|
|
format?: string;
|
|
quality?: number;
|
|
}
|
|
|
|
export interface UpscaleResult {
|
|
buffer: Buffer;
|
|
width: number;
|
|
height: number;
|
|
method: string;
|
|
format: string;
|
|
}
|
|
|
|
export async function upscale(
|
|
inputBuffer: Buffer,
|
|
outputDir: string,
|
|
options: UpscaleOptions = {},
|
|
onProgress?: ProgressCallback,
|
|
): Promise<UpscaleResult> {
|
|
const inputPath = join(outputDir, "input_upscale.png");
|
|
const outputPath = join(outputDir, "output_upscale.png");
|
|
|
|
const pngBuffer = await sharp(inputBuffer).png().toBuffer();
|
|
await writeFile(inputPath, pngBuffer);
|
|
|
|
const meta = await sharp(pngBuffer).metadata();
|
|
const megapixels = ((meta.width ?? 0) * (meta.height ?? 0)) / 1_000_000;
|
|
const scale = options.scale ?? 2;
|
|
const effectiveMp = megapixels * scale ** 2;
|
|
// CPU inference is ~50-100x slower than GPU; be generous for self-hosted NAS hardware
|
|
const rateMs = isGpuAvailable() ? 30_000 : 180_000;
|
|
const timeout = Math.max(600_000, effectiveMp * rateMs);
|
|
|
|
const { stdout } = await runPythonWithProgress(
|
|
"upscale.py",
|
|
[inputPath, outputPath, JSON.stringify(options)],
|
|
{ onProgress, timeout },
|
|
);
|
|
|
|
const result = parseStdoutJson(stdout);
|
|
if (!result.success) {
|
|
throw new Error(result.error || "Upscaling failed");
|
|
}
|
|
|
|
// Python may write to a different path when the output format changes
|
|
const actualOutputPath = result.output_path || outputPath;
|
|
const buffer = await readFile(actualOutputPath);
|
|
return {
|
|
buffer,
|
|
width: result.width,
|
|
height: result.height,
|
|
method: result.method ?? "unknown",
|
|
format: result.format ?? "png",
|
|
};
|
|
}
|