fix cover generation

This commit is contained in:
Thomas Demirdjian
2025-09-04 15:33:04 +02:00
parent ce786d4750
commit dd19172804
4 changed files with 128 additions and 50 deletions
+69 -30
View File
@@ -191,36 +191,56 @@ exports.CombineCoverAndPicture = async (
options = {},
) => {
const size = Number(options.size || 1024);
console.log("🧩 [CombineCoverAndPicture] Start", { size });
const toDataUrl = (input, fallbackMime = "image/png") => {
// Convertit diverses entrées (URL http(s), data URL, {base64, mimeType}) en data URL
const toDataUrl = async (input, fallbackMime = "image/png") => {
if (!input) return null;
if (typeof input === "string") {
if (input.startsWith("data:")) return input;
// http(s) URL - laissez le middleware télécharger
if (/^https?:\/\//i.test(input)) return input;
// Base64 brut (non recommandé sans mimeType) -> tenter fallback
if (/^[A-Za-z0-9+/=]+$/.test(input) && input.length > 256) {
return `data:${fallbackMime};base64,${input}`;
try {
if (typeof input === "string") {
if (input.startsWith("data:")) return input;
if (/^https?:\/\//i.test(input)) {
const axios = require("axios");
const resp = await axios.get(input, { responseType: "arraybuffer" });
const mime = resp.headers["content-type"] || fallbackMime;
const b64 = Buffer.from(resp.data).toString("base64");
return `data:${mime};base64,${b64}`;
}
// Base64 brut -> fallback
if (/^[A-Za-z0-9+/=]+$/.test(input) && input.length > 256) {
return `data:${fallbackMime};base64,${input}`;
}
return input;
}
return input; // dernier recours
}
if (typeof input === "object") {
if (input.url) {
if (input.url.startsWith("data:")) return input.url;
if (/^https?:\/\//i.test(input.url)) return input.url; // middleware download
if (input.base64)
if (typeof input === "object") {
if (input.url) {
if (input.url.startsWith("data:")) return input.url;
if (/^https?:\/\//i.test(input.url)) {
const axios = require("axios");
const resp = await axios.get(input.url, {
responseType: "arraybuffer",
});
const mime =
input.mimeType || resp.headers["content-type"] || fallbackMime;
const b64 = Buffer.from(resp.data).toString("base64");
return `data:${mime};base64,${b64}`;
}
if (input.base64)
return `data:${input.mimeType || fallbackMime};base64,${input.base64}`;
return input.url;
}
if (input.base64) {
return `data:${input.mimeType || fallbackMime};base64,${input.base64}`;
return input.url;
}
if (input.base64) {
return `data:${input.mimeType || fallbackMime};base64,${input.base64}`;
}
}
} catch (e) {
console.warn("toDataUrl: échec de conversion en data URL", e?.message);
}
return null;
};
const bgUrl = toDataUrl(backgroundImage, "image/png");
const fgUrl = toDataUrl(foregroundImage, "image/png");
const bgUrl = await toDataUrl(backgroundImage, "image/png");
const fgUrl = await toDataUrl(foregroundImage, "image/png");
if (!bgUrl || !fgUrl) {
throw new Error(
"CombineCoverAndPicture: images d'entrée invalides (background/foreground requis)",
@@ -234,23 +254,38 @@ exports.CombineCoverAndPicture = async (
// Instruction en FR pour un compositing propre
const instruction = `
Combine les deux images fournies sur une toile carrée ${size}x${size}.
Utilise la première image comme arrière-plan en plein cadre (recadrer au besoin sans bords vides).
Détoure précisément le sujet principal de la seconde image (suppression d'arrière-plan),
puis place-le par-dessus, centré, avec une légère ombre portée douce pour le détacher.
Préserve les détails/contours, couleurs naturelles, et une intégration réaliste (échelle, lumière).
L'image générer sera une pochette d'album de musique.
Aucun texte, aucun logo, aucun filigrane. Export en PNG.
Tu reçois 2 images: la première est l'arrière-plan, la seconde est une photo à incruster.
1) Crée une toile carrée ${size}x${size} et place l'arrière-plan en plein cadre, sans bords vides (recadrer si nécessaire).
2) Détoure le sujet principal de la seconde image.
3) Place le sujet détouré par-dessus l'arrière-plan, et essaie de faire une jolie pochette.
4) Aucun texte, aucun logo, aucun filigrane. Export en PNG.
`.trim();
// Construire le prompt avec parties media
// Utiliser des data URLs pour garantir la prise en compte des 2 images
const parts = [
{ text: instruction },
{ media: { url: bgUrl } },
{ media: { url: fgUrl } },
];
const res = await ai.generate({ model, prompt: parts });
console.log("🧩 [CombineCoverAndPicture] Entrées prêtes", {
bgIsDataUrl: typeof bgUrl === "string" && bgUrl.startsWith("data:"),
fgIsDataUrl: typeof fgUrl === "string" && fgUrl.startsWith("data:"),
bgLen: (bgUrl || "").length,
fgLen: (fgUrl || "").length,
});
let res;
try {
res = await ai.generate({ model, prompt: parts });
} catch (err) {
console.error(
"❌ [CombineCoverAndPicture] ai.generate failed",
err?.message || err,
);
throw err;
}
const media = res?.media;
if (!media || !media.url) {
throw new Error("La génération n'a pas retourné d'image composite.");
@@ -289,6 +324,10 @@ Aucun texte, aucun logo, aucun filigrane. Export en PNG.
const publicUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
path,
)}?alt=media&token=${token}`;
console.log("✅ [CombineCoverAndPicture] Uploaded", {
path,
mimeType,
urlLen: publicUrl.length,
});
return publicUrl;
};