feat: expand camera RAW support with exiftool-first decode strategy

- Add CR3 (Canon) brand detection in ftyp magic bytes, returning "raw"
  to route through the RAW decode pipeline
- Add magic bytes for Fujifilm RAF, Sigma X3F, and Minolta MRW formats
- Expand RAW_EXTENSIONS set from 6 to 24 formats (CR3, NRW, RAF, PEF,
  3FR, IIQ, SRW, X3F, RWL, GPR, FFF, MRW, MEF, KDC, DCR, ERF, PTX)
- Rewrite decodeRaw() with two-tier strategy: ExifTool embedded JPEG
  extraction (fast path) with ImageMagick+LibRaw fallback (full decode)
- Add ext parameter to decodeToSharpCompat/decodeRaw so temp files use
  the original extension for correct format identification
- Pass fileExt from tool-factory when calling decodeToSharpCompat
- Add JXL, ICO, PSD, EXR, HDR, TGA MIME entries to CONTENT_TYPE_TO_EXT
This commit is contained in:
SnapOtter
2026-05-07 23:59:47 +08:00
parent f66ce9add8
commit 5205f4a215
3 changed files with 103 additions and 8 deletions
+10 -1
View File
@@ -192,9 +192,12 @@ export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig
// Decode CLI-decoded formats (RAW, PSD, TGA, EXR, HDR) via external tools.
// The decoded buffer is PNG, so update the filename extension to match.
// Pass the original file extension so RAW decoder can use the correct
// temp file suffix (e.g. .cr3, .nef) for format identification.
if (needsCliDecode(validation.format)) {
try {
fileBuffer = await decodeToSharpCompat(fileBuffer, validation.format);
const fileExt = filename.split(".").pop()?.toLowerCase();
fileBuffer = await decodeToSharpCompat(fileBuffer, validation.format, fileExt);
const ext = filename.match(/\.[^.]+$/)?.[0];
if (ext) filename = `${filename.slice(0, -ext.length)}.png`;
} catch (err) {
@@ -314,6 +317,12 @@ export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig
"image/bmp": ".bmp",
"image/heic": ".heic",
"image/heif": ".heif",
"image/jxl": ".jxl",
"image/x-icon": ".ico",
"image/vnd.adobe.photoshop": ".psd",
"image/x-exr": ".exr",
"image/vnd.radiance": ".hdr",
"image/x-targa": ".tga",
};
const expectedExt = CONTENT_TYPE_TO_EXT[result.contentType];
if (expectedExt) {