fix: resolve 5 bugs found during comprehensive tool testing

1. split batch 404: register split tool in batch registry via
   registerToolProcessFn() so /api/v1/tools/split/batch works

2. CodeFormer crash: inference_app() expects a file path, not a numpy
   array. Save to temp file before calling, read result back.

3. OCR fallback chain: fix case-sensitive "Segmentation fault" match
   that prevented PaddleOCR crash from triggering Tesseract fallback.
   Also add "process crashed" check. Upgrade ARM paddlepaddle to >=3.2.1.

4. blur-faces large images: downscale to 1920px max before MediaPipe
   detection, scale coordinates back. Also add rotation retry for
   portrait-oriented images where BlazeFace misses faces. Applied to
   detect_faces.py, enhance_faces.py, and restore.py.

5. color-adjustments tool ID: fix mismatch in index.ts registration
   array (was "color-adjustments", should be "adjust-colors").
This commit is contained in:
ashim-hq
2026-04-21 22:25:06 +08:00
parent c17caa42e0
commit 77a60b24cc
7 changed files with 276 additions and 64 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ export async function registerToolRoutes(app: FastifyInstance): Promise<void> {
{ id: "compress", register: registerCompress },
{ id: "strip-metadata", register: registerStripMetadata },
{ id: "edit-metadata", register: registerEditMetadata },
{ id: "color-adjustments", register: registerColorAdjustments },
{ id: "adjust-colors", register: registerColorAdjustments },
{ id: "sharpening", register: registerSharpening },
// Watermark & Overlay
+3 -2
View File
@@ -165,12 +165,13 @@ export function registerOcr(app: FastifyInstance) {
});
} catch (err) {
lastError = err;
const msg = err instanceof Error ? err.message : String(err);
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
// If the Python process crashed (segfault, dispatcher exit), try next tier
if (
msg.includes("exited unexpectedly") ||
msg.includes("exited with code") ||
msg.includes("Segmentation fault")
msg.includes("segmentation fault") ||
msg.includes("process crashed")
) {
request.log.warn(
{ toolId: "ocr", quality: tier, err },
+84
View File
@@ -7,6 +7,7 @@ import { z } from "zod";
import { autoOrient } from "../../lib/auto-orient.js";
import { formatZodErrors } from "../../lib/errors.js";
import { ensureSharpCompat } from "../../lib/heic-converter.js";
import { registerToolProcessFn } from "../tool-factory.js";
const settingsSchema = z.object({
columns: z.number().min(1).max(100).default(3),
@@ -159,4 +160,87 @@ export function registerSplit(app: FastifyInstance) {
}
}
});
registerToolProcessFn({
toolId: "split",
settingsSchema,
process: async (inputBuffer, _settings, filename) => {
const settings = _settings as z.infer<typeof settingsSchema>;
const metadata = await sharp(inputBuffer).metadata();
const fullW = metadata.width ?? 0;
const fullH = metadata.height ?? 0;
let cols = settings.columns;
let rows = settings.rows;
if (settings.tileWidth && settings.tileHeight) {
cols = Math.max(1, Math.ceil(fullW / settings.tileWidth));
rows = Math.max(1, Math.ceil(fullH / settings.tileHeight));
}
cols = Math.min(cols, 100);
rows = Math.min(rows, 100);
const cellW = Math.floor(fullW / cols);
const cellH = Math.floor(fullH / rows);
const originalExt = extname(filename) || ".png";
const baseName = filename.replace(/\.[^.]+$/, "");
const { sharpFormat, ext: outputExt } = resolveOutputFormat(
settings.outputFormat,
originalExt,
);
const archive = archiver("zip", { zlib: { level: 5 } });
const chunks: Buffer[] = [];
archive.on("data", (chunk: Buffer) => chunks.push(chunk));
const done = new Promise<void>((resolve, reject) => {
archive.on("end", resolve);
archive.on("error", reject);
});
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
let left: number;
let top: number;
let w: number;
let h: number;
if (settings.tileWidth && settings.tileHeight) {
left = col * settings.tileWidth;
top = row * settings.tileHeight;
w = col === cols - 1 ? fullW - left : Math.min(settings.tileWidth, fullW - left);
h = row === rows - 1 ? fullH - top : Math.min(settings.tileHeight, fullH - top);
} else {
left = col * cellW;
top = row * cellH;
w = col === cols - 1 ? fullW - left : cellW;
h = row === rows - 1 ? fullH - top : cellH;
}
if (left >= fullW || top >= fullH || w <= 0 || h <= 0) continue;
let pipeline = sharp(inputBuffer).extract({ left, top, width: w, height: h });
if (sharpFormat) {
const formatOpts: Record<string, unknown> = {};
if (sharpFormat === "jpeg" || sharpFormat === "webp") {
formatOpts.quality = settings.quality;
}
pipeline = pipeline.toFormat(sharpFormat, formatOpts);
}
const partBuffer = await pipeline.toBuffer();
archive.append(partBuffer, {
name: `${baseName}_r${row + 1}_c${col + 1}${outputExt}`,
});
}
}
await archive.finalize();
await done;
return {
buffer: Buffer.concat(chunks),
filename: `${baseName}_split.zip`,
contentType: "application/zip",
};
},
});
}