more tickets

This commit is contained in:
Thomas Demirdjian
2025-11-13 18:31:38 +01:00
parent dd4cf9b4d4
commit 5457030d4a
32 changed files with 913 additions and 558 deletions
+20 -3
View File
@@ -4,8 +4,19 @@ exports.generatePicturePrompt = (project = {}) => {
lyrics: lyricsRaw,
musicConfig = {},
coverStyle: coverStyleRaw = "",
artistName: artistNameRaw = "",
} = project || {};
const sanitizeInline = (value = "") => {
if (typeof value !== "string") return "";
return value.replace(/[\r\n]+/g, " ").replace(/[<>]/g, "").trim();
};
const titleForPrompt = sanitizeInline(title) || "Sans titre";
const artistName =
sanitizeInline(artistNameRaw) || sanitizeInline(project?.userName || "");
const hasArtistName = artistName.length > 0;
const {
genres = [],
tempo = "",
@@ -55,11 +66,17 @@ exports.generatePicturePrompt = (project = {}) => {
const styleLine = coverStyle
? `${coverStyle}. ${paletteHint}`
: `${paletteHint}. Style libre, artistique et lumineux.`;
const typographyLine = hasArtistName
? `Reproduis strictement le titre de la chanson ${titleForPrompt} sans modification, sans traduction et sans ajout ou suppression de caractères. Ajoute également le nom de l'artiste ${artistName} de manière artistique, parfaitement lisible et hiérarchisée, en harmonie avec le style abstrait et lumineux de la pochette.`
: `Reproduis strictement le titre de la chanson ${titleForPrompt} sans modification, sans traduction et sans ajout ou suppression de caractères. Intègre-le de manière artistique et parfaitement lisible, en harmonie avec le style abstrait et lumineux de la pochette.`;
const artistLine = hasArtistName
? ` <ARTISTE>${artistName}</ARTISTE>\n`
: "";
const prompt = `<BRIEF>
<OBJECTIF>Créer une pochette d'album en adéquation avec les paroles de la musique</OBJECTIF>
<TITRE>${title || "Sans titre"}</TITRE>
<STYLE_MUSICAL>${tagsLine}</STYLE_MUSICAL>
<TITRE>${titleForPrompt}</TITRE>
${artistLine} <STYLE_MUSICAL>${tagsLine}</STYLE_MUSICAL>
<EXTRAITS_PAROLES>${lyricsLine}</EXTRAITS_PAROLES>
</BRIEF>
@@ -67,7 +84,7 @@ exports.generatePicturePrompt = (project = {}) => {
<FORMAT>Image carrée 1024x1024 pixels, résolution haute.</FORMAT>
<STYLE>${styleLine}</STYLE>
<COMPOSITION>La composition doit être dynamique et remplir toute la surface, sans laisser de bordures ni de zones vides.</COMPOSITION>
<TYPOGRAPHIE>Reproduis strictement le titre de la chanson ${title || ""} sans modification, sans traduction et sans ajout ou suppression de caractères. Intègre-le de manière artistique et parfaitement lisible, en harmonie avec le style abstrait et lumineux de la pochette.</TYPOGRAPHIE>
<TYPOGRAPHIE>${typographyLine}</TYPOGRAPHIE>
<ORTHOGRAPHE>Aucune faute d'orthographe n'est tolérée. Respecte la casse et l'orthographe exactes du titre fourni.</ORTHOGRAPHE>
</CONTEXTES_VISUELS>
+64 -1
View File
@@ -14,6 +14,65 @@ const { sendNotification } = require("./notifications");
const bucket = admin.storage().bucket();
const LOGO_PATH = path.resolve(__dirname, "../assets/musicLandLogo.png");
const pickFirstNonEmpty = (...values) => {
for (const value of values) {
if (typeof value === "string") {
const trimmed = value.trim();
if (trimmed.length > 0) {
return trimmed;
}
}
}
return "";
};
const combineNames = (...parts) =>
parts
.map((part) => (typeof part === "string" ? part.trim() : ""))
.filter(Boolean)
.join(" ")
.trim();
async function resolveArtistName(project = {}) {
const owner = project?.owner || {};
const direct = pickFirstNonEmpty(
project?.artistName,
project?.userName,
owner?.artistName,
owner?.userName,
owner?.displayName
);
if (direct) return direct;
const userId =
typeof project?.userId === "string" && project.userId.trim()
? project.userId.trim()
: "";
if (!userId) return "";
try {
const userSnapshot = await refList.users.doc(userId).get();
if (!userSnapshot?.exists) return "";
const userData = userSnapshot.data() || {};
const fullName = combineNames(userData.firstName, userData.lastName);
return (
pickFirstNonEmpty(
userData.artistName,
userData.userName,
userData.displayName,
fullName
) || ""
);
} catch (error) {
logger.warn("⚠️ [Cover] Unable to resolve artist name", {
projectId: project?.id || null,
userId,
error: error?.message || String(error),
});
return "";
}
}
async function buildCoverWithLogo(backgroundUrl, targetPath) {
logger.info("🖼️ [Cover] Adding logo to generated background");
const [{ data: backgroundBuffer }, logoBuffer] = await Promise.all([
@@ -66,7 +125,11 @@ async function buildCoverWithLogo(backgroundUrl, targetPath) {
// Shared core for generating and saving the cover, and updating the project
async function performCoverGeneration(project) {
const t0 = Date.now();
const prompt = generatePicturePrompt(project);
const artistName = await resolveArtistName(project);
const prompt = generatePicturePrompt({
...project,
artistName,
});
// Utiliser generateImageV2 qui renvoie directement l'URL publique
logger.info("🎨 [Cover] Calling model V2", {
projectId: project.id,