fix cover generation
This commit is contained in:
+69
-30
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user