diff --git a/src/content/__tests__/loadArtistReleaseEditorial.test.ts b/src/content/__tests__/loadArtistReleaseEditorial.test.ts index e1f2748..906acb2 100644 --- a/src/content/__tests__/loadArtistReleaseEditorial.test.ts +++ b/src/content/__tests__/loadArtistReleaseEditorial.test.ts @@ -34,6 +34,16 @@ describe("loadTemplate artist-release-editorial", () => { expect(typeof sg.customInstruction).toBe("string"); }); + it("customInstruction includes face-swap language referencing the headshot", async () => { + const template = await loadTemplate("artist-release-editorial"); + const sg = template.styleGuide as Record; + const instruction = sg.customInstruction as string; + const lower = instruction.toLowerCase(); + + expect(lower).toContain("headshot"); + expect(lower).toContain("face"); + }); + it("customInstruction generates only the artist portrait with no composited elements", async () => { const template = await loadTemplate("artist-release-editorial"); const sg = template.styleGuide as Record; diff --git a/src/content/templates/artist-release-editorial/style-guide.json b/src/content/templates/artist-release-editorial/style-guide.json index 5ef7954..c9f2714 100644 --- a/src/content/templates/artist-release-editorial/style-guide.json +++ b/src/content/templates/artist-release-editorial/style-guide.json @@ -3,7 +3,7 @@ "description": "Editorial promo featuring artist press photo with playlist covers and DSP branding — the kind of polished-but-organic visual an artist's team drops alongside a new release", "usesFaceGuide": true, "usesImageOverlay": true, - "customInstruction": "Generate a clean editorial-style press photo of the artist. The image should contain ONLY the artist — no text, no overlays, no graphics, no album art, no branding. Just a professional press photo that looks like it was taken for a magazine feature or editorial spread. Polished lighting but still feeling authentic and personal.", + "customInstruction": "Replace the person in the scene with the person from the white background headshot. Use the face, hair, and features exactly as they appear in the headshot. Generate a clean editorial-style press photo of the artist. The image should contain ONLY the artist — no text, no overlays, no graphics, no album art, no branding. Just a professional press photo that looks like it was taken for a magazine feature or editorial spread. Polished lighting but still feeling authentic and personal. Remove any text, captions, watermarks, or overlays. The image should be clean with no text on it.", "imagePrompt": "A professional editorial press photo of an artist. Clean, intentional lighting — soft key light with subtle rim light separation. The artist is posed naturally, looking directly at camera or slightly off-axis. The background is a solid or subtly textured surface (concrete wall, draped fabric, muted gradient) that does not distract from the subject. The mood is confident and polished but not sterile. Shot on a DSLR or medium format camera, shallow depth of field, cinematic color grade leaning warm or desaturated depending on the artist's aesthetic. The image contains ONLY the artist — no text, no graphics, no overlays.", "camera": { diff --git a/src/tasks/__tests__/createContentTask.test.ts b/src/tasks/__tests__/createContentTask.test.ts index da511ab..d0a63d3 100644 --- a/src/tasks/__tests__/createContentTask.test.ts +++ b/src/tasks/__tests__/createContentTask.test.ts @@ -32,6 +32,17 @@ vi.mock("../../content/fetchGithubFile", () => ({ fetchGithubFile: vi.fn().mockResolvedValue(Buffer.from("fake-png")), })); +vi.mock("../../content/downloadImageBuffer", () => ({ + downloadImageBuffer: vi.fn().mockResolvedValue({ + buffer: Buffer.from("fake-image"), + contentType: "image/png", + }), +})); + +vi.mock("../../content/detectFace", () => ({ + detectFace: vi.fn().mockResolvedValue(false), +})); + vi.mock("../../content/generateContentImage", () => ({ generateContentImage: vi.fn().mockResolvedValue("https://fal.ai/image.png"), })); @@ -138,6 +149,32 @@ describe("createContentTask", () => { await expect(mockRun(VALID_PAYLOAD)).rejects.toThrow("face-guide.png not found"); }); + it("does not pass additionalImageUrls to generateContentImage when usesImageOverlay is true", async () => { + const { loadTemplate } = await import("../../content/loadTemplate"); + vi.mocked(loadTemplate).mockResolvedValueOnce({ + name: "artist-release-editorial", + imagePrompt: "editorial scene", + usesFaceGuide: true, + usesImageOverlay: true, + styleGuide: { customInstruction: "Generate editorial photo." }, + captionGuide: null, + captionExamples: [], + videoMoods: [], + videoMovements: [], + referenceImagePaths: [], + }); + + await mockRun({ + ...VALID_PAYLOAD, + template: "artist-release-editorial", + images: ["https://example.com/cover1.png", "https://example.com/cover2.png"], + }); + + const { generateContentImage } = await import("../../content/generateContentImage"); + const callArgs = vi.mocked(generateContentImage).mock.calls[0][0]; + expect(callArgs.additionalImageUrls).toBeUndefined(); + }); + it("throws when FAL_KEY is missing", async () => { delete process.env.FAL_KEY; await expect(mockRun(VALID_PAYLOAD)).rejects.toThrow("FAL_KEY"); diff --git a/src/tasks/createContentTask.ts b/src/tasks/createContentTask.ts index b23c837..0ac1285 100644 --- a/src/tasks/createContentTask.ts +++ b/src/tasks/createContentTask.ts @@ -100,7 +100,7 @@ export const createContentTask = schemaTask({ faceGuideUrl: faceGuideUrl ?? undefined, referenceImagePath, prompt: fullPrompt, - additionalImageUrls, + additionalImageUrls: template.usesImageOverlay ? undefined : additionalImageUrls, }); // --- Step 6: Upscale image (optional) ---