mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
refactor: improve tool processing, dropzone, seam carving, and format encoding
- Refactor use-tool-processor and use-pipeline-processor hooks - Enhance dropzone component with improved UX - Improve seam carving with better error handling and tests - Add JXL format encoding support to format-encoders - Update tool routes for consistent format handling - Add dropzone unit tests
This commit is contained in:
@@ -8,6 +8,7 @@ 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 { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
|
||||
@@ -643,7 +644,7 @@ export function registerCollage(app: FastifyInstance) {
|
||||
outputExt = "avif";
|
||||
break;
|
||||
case "jxl":
|
||||
pipeline = pipeline.jxl({ quality: settings.quality });
|
||||
pipeline = pipeline.png();
|
||||
outputExt = "jxl";
|
||||
break;
|
||||
default:
|
||||
@@ -653,18 +654,19 @@ export function registerCollage(app: FastifyInstance) {
|
||||
}
|
||||
|
||||
const result = await pipeline.toBuffer();
|
||||
const finalBuffer = outputExt === "jxl" ? await encodeJxl(result, settings.quality) : result;
|
||||
|
||||
const jobId = randomUUID();
|
||||
const workspacePath = await createWorkspace(jobId);
|
||||
const filename = `collage.${outputExt}`;
|
||||
const outputPath = join(workspacePath, "output", filename);
|
||||
await writeFile(outputPath, result);
|
||||
await writeFile(outputPath, finalBuffer);
|
||||
|
||||
return reply.send({
|
||||
jobId,
|
||||
downloadUrl: `/api/v1/download/${jobId}/${filename}`,
|
||||
originalSize: files.reduce((s, f) => s + f.buffer.length, 0),
|
||||
processedSize: result.length,
|
||||
processedSize: finalBuffer.length,
|
||||
});
|
||||
} catch (err) {
|
||||
return reply.status(422).send({
|
||||
|
||||
@@ -8,7 +8,13 @@ import { convert } from "@snapotter/image-engine";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { encodeBmp, encodeIco, encodeJp2, encodeQoi } from "../../lib/format-encoders.js";
|
||||
import {
|
||||
encodeBmp,
|
||||
encodeIco,
|
||||
encodeJp2,
|
||||
encodeJxl,
|
||||
encodeQoi,
|
||||
} from "../../lib/format-encoders.js";
|
||||
import { encodeHeic } from "../../lib/heic-converter.js";
|
||||
import { isSvgBuffer } from "../../lib/svg-sanitize.js";
|
||||
import { createToolRoute } from "../tool-factory.js";
|
||||
@@ -56,6 +62,7 @@ const CLI_ENCODERS: Record<string, (buf: Buffer, quality?: number) => Promise<Bu
|
||||
bmp: encodeBmp,
|
||||
ico: encodeIco,
|
||||
jp2: encodeJp2,
|
||||
jxl: encodeJxl,
|
||||
qoi: encodeQoi,
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ 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 { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { decodeHeic, encodeHeic } from "../../lib/heic-converter.js";
|
||||
import { resolveOutputFormat } from "../../lib/output-format.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
@@ -213,7 +214,7 @@ export function registerEraseObject(app: FastifyInstance) {
|
||||
outputBuffer = await encodeHeic(resultBuffer, quality);
|
||||
finalFormat = format;
|
||||
} else if (format === "jxl") {
|
||||
outputBuffer = await sharp(resultBuffer).jxl({ quality }).toBuffer();
|
||||
outputBuffer = await encodeJxl(resultBuffer, quality);
|
||||
finalFormat = "jxl";
|
||||
} else {
|
||||
outputBuffer = await sharp(resultBuffer).avif({ quality }).toBuffer();
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { formatZodErrors } from "../../lib/errors.js";
|
||||
import { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
||||
|
||||
const settingsSchema = z.object({
|
||||
@@ -145,10 +146,12 @@ export function registerImageToBase64(app: FastifyInstance) {
|
||||
outputBuffer = await pipeline.avif({ quality: opts.quality, effort: 4 }).toBuffer();
|
||||
mimeType = "image/avif";
|
||||
break;
|
||||
case "jxl":
|
||||
outputBuffer = await pipeline.jxl({ quality: opts.quality }).toBuffer();
|
||||
case "jxl": {
|
||||
const pngBuf = await pipeline.png().toBuffer();
|
||||
outputBuffer = await encodeJxl(pngBuf, opts.quality);
|
||||
mimeType = "image/jxl";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
outputBuffer = await pipeline.toBuffer();
|
||||
mimeType = detectMimeType(ext);
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 { decodeHeic } from "../../lib/heic-converter.js";
|
||||
import { sanitizeSvg } from "../../lib/svg-sanitize.js";
|
||||
import { createToolRoute } from "../tool-factory.js";
|
||||
@@ -40,9 +41,16 @@ const settingsSchema = z.object({
|
||||
type Settings = z.infer<typeof settingsSchema>;
|
||||
|
||||
async function processImage(inputBuffer: Buffer, settings: Settings, filename: string) {
|
||||
const isJxl = settings.format === "jxl";
|
||||
const engineSettings = isJxl ? { ...settings, format: "png" as const } : settings;
|
||||
|
||||
const image = sharp(inputBuffer);
|
||||
const result = await optimizeForWeb(image, settings);
|
||||
const buffer = await result.toBuffer();
|
||||
const result = await optimizeForWeb(image, engineSettings);
|
||||
let buffer = await result.toBuffer();
|
||||
|
||||
if (isJxl) {
|
||||
buffer = await encodeJxl(buffer, settings.quality);
|
||||
}
|
||||
|
||||
const ext = extname(filename);
|
||||
const baseName = ext ? filename.slice(0, -ext.length) : filename;
|
||||
|
||||
@@ -9,6 +9,7 @@ import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { env } from "../../config.js";
|
||||
import { formatZodErrors } from "../../lib/errors.js";
|
||||
import { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { encodeHeic } from "../../lib/heic-converter.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
|
||||
@@ -117,8 +118,10 @@ async function convertWithSharp(
|
||||
return s.tiff().toBuffer();
|
||||
case "gif":
|
||||
return s.gif().toBuffer();
|
||||
case "jxl":
|
||||
return s.jxl({ quality }).toBuffer();
|
||||
case "jxl": {
|
||||
const pngBuf = await s.png().toBuffer();
|
||||
return encodeJxl(pngBuf, quality);
|
||||
}
|
||||
case "heic":
|
||||
case "heif": {
|
||||
const pngBuf = await s.png().toBuffer();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { z } from "zod";
|
||||
import { autoOrient } from "../../lib/auto-orient.js";
|
||||
import { formatZodErrors } from "../../lib/errors.js";
|
||||
import { sanitizeFilename } from "../../lib/filename.js";
|
||||
import { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
||||
import { registerToolProcessFn } from "../tool-factory.js";
|
||||
|
||||
@@ -31,7 +32,7 @@ function resolveOutputFormat(
|
||||
jpg: { sharpFormat: "jpeg", ext: ".jpg" },
|
||||
webp: { sharpFormat: "webp", ext: ".webp" },
|
||||
avif: { sharpFormat: "avif", ext: ".avif" },
|
||||
jxl: { sharpFormat: "jxl", ext: ".jxl" },
|
||||
jxl: { sharpFormat: "png", ext: ".jxl" },
|
||||
};
|
||||
return map[outputFormat] ?? { sharpFormat: null, ext: originalExt };
|
||||
}
|
||||
@@ -150,7 +151,11 @@ export function registerSplit(app: FastifyInstance) {
|
||||
}
|
||||
|
||||
const partBuffer = await pipeline.toBuffer();
|
||||
archive.append(partBuffer, {
|
||||
const finalBuffer =
|
||||
settings.outputFormat === "jxl"
|
||||
? await encodeJxl(partBuffer, settings.quality)
|
||||
: partBuffer;
|
||||
archive.append(finalBuffer, {
|
||||
name: `${baseName}_r${row + 1}_c${col + 1}${outputExt}`,
|
||||
});
|
||||
}
|
||||
@@ -236,7 +241,11 @@ export function registerSplit(app: FastifyInstance) {
|
||||
}
|
||||
|
||||
const partBuffer = await pipeline.toBuffer();
|
||||
archive.append(partBuffer, {
|
||||
const finalBuffer =
|
||||
settings.outputFormat === "jxl"
|
||||
? await encodeJxl(partBuffer, settings.quality)
|
||||
: partBuffer;
|
||||
archive.append(finalBuffer, {
|
||||
name: `${baseName}_r${row + 1}_c${col + 1}${outputExt}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
|
||||
@@ -204,13 +205,17 @@ export function registerStitch(app: FastifyInstance) {
|
||||
} else if (settings.format === "avif") {
|
||||
pipeline = pipeline.avif({ quality: settings.quality, effort: 4 });
|
||||
} else if (settings.format === "jxl") {
|
||||
pipeline = pipeline.jxl({ quality: settings.quality });
|
||||
pipeline = pipeline.png();
|
||||
} else {
|
||||
pipeline = pipeline.png();
|
||||
}
|
||||
|
||||
let result = await pipeline.toBuffer();
|
||||
|
||||
if (settings.format === "jxl") {
|
||||
result = await encodeJxl(result, settings.quality);
|
||||
}
|
||||
|
||||
if (settings.cornerRadius > 0) {
|
||||
const meta = await sharp(result).metadata();
|
||||
if (!meta.width || !meta.height) throw new Error("Cannot read image dimensions");
|
||||
@@ -238,7 +243,7 @@ export function registerStitch(app: FastifyInstance) {
|
||||
} else if (settings.format === "avif") {
|
||||
result = await sharp(result).avif({ quality: settings.quality, effort: 4 }).toBuffer();
|
||||
} else if (settings.format === "jxl") {
|
||||
result = await sharp(result).jxl({ quality: settings.quality }).toBuffer();
|
||||
result = await encodeJxl(result, settings.quality);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { env } from "../../config.js";
|
||||
import { resolveConcurrency } from "../../lib/env.js";
|
||||
import { formatZodErrors } from "../../lib/errors.js";
|
||||
import { sanitizeFilename } from "../../lib/filename.js";
|
||||
import { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { decodeHeic, encodeHeic } from "../../lib/heic-converter.js";
|
||||
import { isSvgBuffer, sanitizeSvg } from "../../lib/svg-sanitize.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
@@ -77,10 +78,12 @@ async function convertSvg(
|
||||
buffer = await image.gif().toBuffer();
|
||||
ext = "gif";
|
||||
break;
|
||||
case "jxl":
|
||||
buffer = await image.jxl({ quality: settings.quality }).toBuffer();
|
||||
case "jxl": {
|
||||
const pngBuf = await image.png().toBuffer();
|
||||
buffer = await encodeJxl(pngBuf, settings.quality);
|
||||
ext = "jxl";
|
||||
break;
|
||||
}
|
||||
case "heif": {
|
||||
const pngBuffer = await image.png().toBuffer();
|
||||
buffer = await encodeHeic(pngBuffer, settings.quality);
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 { encodeJxl } from "../../lib/format-encoders.js";
|
||||
import { decodeHeic, encodeHeic } from "../../lib/heic-converter.js";
|
||||
import { resolveOutputFormat } from "../../lib/output-format.js";
|
||||
import { createWorkspace } from "../../lib/workspace.js";
|
||||
@@ -186,7 +187,7 @@ export function registerUpscale(app: FastifyInstance) {
|
||||
outputBuffer = await encodeHeic(result.buffer, outputQuality);
|
||||
finalFormat = format;
|
||||
} else if (format === "jxl") {
|
||||
outputBuffer = await sharp(result.buffer).jxl({ quality: outputQuality }).toBuffer();
|
||||
outputBuffer = await encodeJxl(result.buffer, outputQuality);
|
||||
finalFormat = "jxl";
|
||||
} else if (format === "avif") {
|
||||
outputBuffer = await sharp(result.buffer).avif({ quality: outputQuality }).toBuffer();
|
||||
|
||||
Reference in New Issue
Block a user