mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: add exotic format decoding and upload progress to stitch tool
The stitch route only decoded HEIC via ensureSharpCompat, so exotic formats (DNG, PSD, TGA, BMP, JXL, HDR, QOI, DDS, ICO, JP2, DPX, etc.) crashed Sharp at metadata read time, causing "Stitch creation failed". Add the full CLI decode pipeline (decodeToSharpCompat) matching the tool-factory pattern, plus SVG sanitization with proper error handling for each format category. Replace fetch() with XMLHttpRequest in the frontend to surface upload progress via onprogress events, and add a progress bar that shows upload percentage then pulses during server-side stitching.
This commit is contained in:
@@ -9,8 +9,10 @@ import { autoOrient } from "../../lib/auto-orient.js";
|
||||
import { formatZodErrors } from "../../lib/errors.js";
|
||||
import { validateImageBuffer } from "../../lib/file-validation.js";
|
||||
import { sanitizeFilename } from "../../lib/filename.js";
|
||||
import { decodeToSharpCompat, needsCliDecode } from "../../lib/format-decoders.js";
|
||||
import { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
||||
import { decodeHeic } from "../../lib/heic-converter.js";
|
||||
import { decompressSvgz, sanitizeSvg } from "../../lib/svg-sanitize.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
|
||||
const settingsSchema = z.object({
|
||||
@@ -85,7 +87,46 @@ export function registerStitch(app: FastifyInstance) {
|
||||
.status(400)
|
||||
.send({ error: `Invalid file "${file.filename}": ${validation.reason}` });
|
||||
}
|
||||
file.buffer = await autoOrient(await ensureSharpCompat(file.buffer));
|
||||
|
||||
if (validation.format === "heif") {
|
||||
try {
|
||||
file.buffer = await decodeHeic(file.buffer);
|
||||
} catch (err) {
|
||||
return reply.status(422).send({
|
||||
error: `Failed to decode "${file.filename}" (HEIC). Ensure libheif-examples is installed.`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (needsCliDecode(validation.format)) {
|
||||
const fileExt = file.filename.split(".").pop()?.toLowerCase();
|
||||
try {
|
||||
file.buffer = await decodeToSharpCompat(file.buffer, validation.format, fileExt);
|
||||
} catch {
|
||||
try {
|
||||
await sharp(file.buffer).metadata();
|
||||
} catch (err) {
|
||||
return reply.status(422).send({
|
||||
error: `Failed to decode "${file.filename}" (${validation.format.toUpperCase()})`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validation.format === "svg") {
|
||||
try {
|
||||
file.buffer = decompressSvgz(file.buffer);
|
||||
file.buffer = sanitizeSvg(file.buffer);
|
||||
} catch (err) {
|
||||
return reply.status(400).send({
|
||||
error: `Invalid SVG "${file.filename}": ${err instanceof Error ? err.message : "Unknown error"}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
file.buffer = await autoOrient(file.buffer);
|
||||
}
|
||||
|
||||
let settings: z.infer<typeof settingsSchema>;
|
||||
|
||||
Reference in New Issue
Block a user