mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add support for JXL, Camera RAW, ICO, TGA, PSD, EXR, HDR image formats
Extends the platform to handle 7 new image format families alongside the existing AVIF support gap-fill. Uses the established HEIC decoder pattern (CLI decode → PNG → Sharp) for formats Sharp can't handle natively: Camera RAW via dcraw_emu/LibRaw, PSD/TGA/EXR/HDR via ImageMagick. JXL and ICO are Sharp-native. Adds server-side preview for non-browser-displayable formats and JXL as a new convert output target. All 27 validateImageBuffer callers updated with filename for extension-based format detection.
This commit is contained in:
@@ -23,6 +23,7 @@ import { formatZodErrors } from "../lib/errors.js";
|
||||
import { isToolInstalled } from "../lib/feature-status.js";
|
||||
import { validateImageBuffer } from "../lib/file-validation.js";
|
||||
import { sanitizeFilename } from "../lib/filename.js";
|
||||
import { decodeToSharpCompat, needsCliDecode } from "../lib/format-decoders.js";
|
||||
import { decodeHeic } from "../lib/heic-converter.js";
|
||||
import { createWorkspace } from "../lib/workspace.js";
|
||||
import { requireAuth } from "../plugins/auth.js";
|
||||
@@ -101,7 +102,7 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
||||
}
|
||||
|
||||
// Validate the initial image
|
||||
const validation = await validateImageBuffer(fileBuffer);
|
||||
const validation = await validateImageBuffer(fileBuffer, filename);
|
||||
if (!validation.valid) {
|
||||
return reply.status(400).send({
|
||||
error: `Invalid image: ${validation.reason}`,
|
||||
@@ -123,6 +124,20 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
||||
}
|
||||
}
|
||||
|
||||
// Decode CLI-decoded formats (RAW, TGA, PSD, EXR, HDR)
|
||||
if (needsCliDecode(validation.format)) {
|
||||
try {
|
||||
fileBuffer = await decodeToSharpCompat(fileBuffer, validation.format);
|
||||
const ext = filename.match(/\.[^.]+$/)?.[0];
|
||||
if (ext) filename = `${filename.slice(0, -ext.length)}.png`;
|
||||
} catch (err) {
|
||||
return reply.status(422).send({
|
||||
error: `Failed to decode ${validation.format} file`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize EXIF orientation before passing to pipeline steps
|
||||
fileBuffer = await autoOrient(fileBuffer);
|
||||
|
||||
@@ -517,7 +532,7 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
||||
updateJobProgress({ ...progress });
|
||||
|
||||
// Validate the image
|
||||
const validation = await validateImageBuffer(file.buffer);
|
||||
const validation = await validateImageBuffer(file.buffer, file.filename);
|
||||
if (!validation.valid) {
|
||||
progress.failedFiles++;
|
||||
progress.errors.push({
|
||||
@@ -540,6 +555,13 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
||||
if (ext) currentFilename = `${currentFilename.slice(0, -ext.length)}.png`;
|
||||
}
|
||||
|
||||
// Decode CLI-decoded formats (RAW, TGA, PSD, EXR, HDR)
|
||||
if (needsCliDecode(validation.format)) {
|
||||
currentBuffer = await decodeToSharpCompat(currentBuffer, validation.format);
|
||||
const ext = currentFilename.match(/\.[^.]+$/)?.[0];
|
||||
if (ext) currentFilename = `${currentFilename.slice(0, -ext.length)}.png`;
|
||||
}
|
||||
|
||||
// Normalize EXIF orientation
|
||||
currentBuffer = await autoOrient(currentBuffer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user