more tickets
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user