fix: convert input buffer to PNG before passing to face landmarks Python sidecar

AVIF (and other Sharp-native formats) were written as raw bytes to a
.png temp file, causing PIL to fail with "cannot identify image file".
Every other AI module wrapper already converts via sharp().png().toBuffer()
before writing; face-landmarks was the only one that skipped this step.
This commit is contained in:
SnapOtter
2026-05-13 14:18:49 +08:00
parent 0b3f46407a
commit 648c8c12f5
4 changed files with 15 additions and 10 deletions
+3 -1
View File
@@ -1,6 +1,7 @@
import { unlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import sharp from "sharp";
import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js";
export interface FaceLandmarkPoint {
@@ -33,7 +34,8 @@ export async function detectFaceLandmarks(
const inputPath = join(tmpdir(), `face_landmarks_${Date.now()}.png`);
try {
await writeFile(inputPath, inputBuffer);
const pngBuffer = await sharp(inputBuffer).png().toBuffer();
await writeFile(inputPath, pngBuffer);
const { stdout } = await runPythonWithProgress(
"face_landmarks.py",
[inputPath, "unused", "{}"],
+4 -3
View File
@@ -68,13 +68,14 @@ describe("detectFaceLandmarks", () => {
);
});
it("writes input buffer directly without sharp conversion", async () => {
it("converts input buffer to PNG before writing", async () => {
const sharp = (await import("sharp")).default;
await detectFaceLandmarks(FAKE_INPUT);
// face-landmarks.ts does NOT use sharp -- it writes inputBuffer directly
expect(sharp).toHaveBeenCalledWith(FAKE_INPUT);
expect(writeFile).toHaveBeenCalledWith(
expect.stringContaining("face_landmarks_"),
FAKE_INPUT,
Buffer.from("mock-png-data"),
);
});
+5 -3
View File
@@ -580,11 +580,13 @@ describe("detectFaceLandmarks", () => {
expect(result.landmarks).toBeNull();
});
it("does not use sharp to convert to PNG (writes buffer directly)", async () => {
it("converts input buffer to PNG before writing", async () => {
await detectFaceLandmarks(FAKE_INPUT);
// face-landmarks writes inputBuffer directly, no sharp conversion
expect(writeFile).toHaveBeenCalledWith(expect.stringContaining("face_landmarks_"), FAKE_INPUT);
expect(writeFile).toHaveBeenCalledWith(
expect.stringContaining("face_landmarks_"),
Buffer.from("mock-png-data"),
);
});
it("cleans up temp file in finally block", async () => {
+3 -3
View File
@@ -630,13 +630,13 @@ describe("detectFaceLandmarks", () => {
await expect(detectFaceLandmarks(INPUT_BUFFER)).rejects.toThrow("MediaPipe not found");
});
it("writes raw input buffer (no sharp conversion)", async () => {
it("converts input buffer to PNG before writing", async () => {
mockParseStdoutJson.mockReturnValue({ success: true, faceDetected: false });
await detectFaceLandmarks(INPUT_BUFFER);
// face-landmarks writes inputBuffer directly, no sharp pipeline
expect(mockWriteFile).toHaveBeenCalledWith(expect.any(String), INPUT_BUFFER);
expect(mockSharp).toHaveBeenCalledWith(INPUT_BUFFER);
expect(mockWriteFile).toHaveBeenCalledWith(expect.any(String), Buffer.from("mock-png"));
});
});