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