310 lines
12 KiB
JavaScript
310 lines
12 KiB
JavaScript
const STYLE_PRESET_MAP = {
|
|
'Réaliste (style cinématographique)': {
|
|
style: 'Cinematic realism, film still, anamorphic lens, shallow depth of field',
|
|
palettes: [
|
|
'cool teal and amber highlights, muted neutrals',
|
|
'stormy blues with soft gold accents',
|
|
'charcoal, steel blue, and warm tungsten glows',
|
|
],
|
|
lighting: [
|
|
'dramatic key light with soft fill and deep shadows',
|
|
'low-key cinematic lighting with subtle haze',
|
|
],
|
|
textures: ['subtle film grain and crisp detail', 'matte finish with fine grain'],
|
|
typography: ['clean cinematic title card, subtle embossing'],
|
|
compositions: ['cinematic framing with a strong subject focus'],
|
|
},
|
|
'Artistique (style peinture moderne)': {
|
|
style: 'Modern painting, expressive brushstrokes, gallery-ready composition',
|
|
palettes: [
|
|
'rich pigments with bold contrast',
|
|
'muted pastels with a sharp accent color',
|
|
'deep jewel tones and warm highlights',
|
|
],
|
|
lighting: ['soft gallery lighting with gentle gradients'],
|
|
textures: ['visible canvas texture and layered paint', 'acrylic strokes with matte finish'],
|
|
typography: ['refined serif lettering integrated into the paint'],
|
|
compositions: ['balanced composition with painterly focal point'],
|
|
},
|
|
'Style Dessins': {
|
|
style: 'Hand-drawn illustration, clean linework, ink and colored pencil',
|
|
palettes: [
|
|
'warm paper tones with soft color fills',
|
|
'limited palette with bold accent color',
|
|
],
|
|
lighting: ['soft diffused light with minimal shadows'],
|
|
textures: ['paper texture with visible grain'],
|
|
typography: ['hand-lettered title with slight texture'],
|
|
compositions: ['centered illustration with clear silhouette'],
|
|
},
|
|
'Style rétros': {
|
|
style: 'Retro poster art, vintage print, halftone patterns, aged paper',
|
|
palettes: [
|
|
'sun-faded oranges, teal, and cream',
|
|
'muted reds, mustard, and dark green',
|
|
],
|
|
lighting: ['flat poster lighting with strong contrast'],
|
|
textures: ['distressed print texture and subtle scratches'],
|
|
typography: ['vintage serif lettering with screen print texture'],
|
|
compositions: ['poster-style layout with bold hierarchy'],
|
|
},
|
|
'Style cyberpunk': {
|
|
style: 'Cyberpunk aesthetic, futuristic city glow, reflective surfaces',
|
|
palettes: [
|
|
'neon magenta and electric blue on dark backgrounds',
|
|
'cyan, violet, and deep black with sharp highlights',
|
|
],
|
|
lighting: ['neon rim light and high-contrast highlights'],
|
|
textures: ['glossy surfaces with subtle rain speckles'],
|
|
typography: ['neon tube lettering embedded in the scene'],
|
|
compositions: ['dynamic perspective with strong depth'],
|
|
},
|
|
'Style Afro': {
|
|
style: 'Afrofuturist motifs, bold patterns, textile-inspired geometry',
|
|
palettes: [
|
|
'warm earth tones with vibrant accents',
|
|
'gold, terracotta, deep blue, and emerald',
|
|
],
|
|
lighting: ['warm directional light with rich contrast'],
|
|
textures: ['woven fabric texture and layered patterns'],
|
|
typography: ['bold lettering with subtle pattern fills'],
|
|
compositions: ['symmetrical layout with iconic central motif'],
|
|
},
|
|
'Style grunge': {
|
|
style: 'Grunge collage, distressed textures, gritty mixed media',
|
|
palettes: [
|
|
'smoky blacks with muted reds and greys',
|
|
'dirty neutrals with high-contrast highlights',
|
|
],
|
|
lighting: ['moody low-key lighting with harsh shadows'],
|
|
textures: ['torn paper, scratches, and distressed overlays'],
|
|
typography: ['distressed lettering with rough edges'],
|
|
compositions: ['layered collage with imperfect alignment'],
|
|
},
|
|
'Style pop art': {
|
|
style: 'Pop art illustration, bold outlines, Ben-Day dots, screen print',
|
|
palettes: [
|
|
'bold primary colors with black and white',
|
|
'bright yellow, red, and cyan with clean contrast',
|
|
],
|
|
lighting: ['flat graphic lighting with crisp separation'],
|
|
textures: ['screen print texture with halftone dots'],
|
|
typography: ['bold outlined letters with halftone texture'],
|
|
compositions: ['graphic layout with strong shapes and punchy hierarchy'],
|
|
},
|
|
}
|
|
|
|
const DEFAULT_STYLE_VARIANTS = [
|
|
'Conceptual album art, abstract surrealism, high fidelity',
|
|
'Graphic collage, mixed media, layered textures',
|
|
'Minimalist design, bold shapes, strong negative space',
|
|
'Dreamlike photography, cinematic framing, subtle grain',
|
|
'Geometric abstraction, clean gradients, modern design',
|
|
'Stylized illustration, crisp edges, bold composition',
|
|
]
|
|
|
|
const COMPOSITION_VARIANTS = [
|
|
'centered focal subject with balanced symmetry',
|
|
'asymmetrical layout with dynamic diagonal flow',
|
|
'minimal composition with strong negative space',
|
|
'collage-like layout with layered elements',
|
|
'close-up framing for intimacy and impact',
|
|
'wide cinematic framing with a distant focal point',
|
|
]
|
|
|
|
const LIGHTING_VARIANTS = [
|
|
'dramatic chiaroscuro with deep shadows',
|
|
'soft diffused light with gentle gradients',
|
|
'high-contrast lighting with sharp highlights',
|
|
'backlit silhouette with glowing edges',
|
|
'moody low-key lighting with selective highlights',
|
|
]
|
|
|
|
const PALETTE_VARIANTS = [
|
|
'deep navy with warm amber accents',
|
|
'muted earth tones with copper highlights',
|
|
'cool blues with silver accents',
|
|
'bold primary colors with black and white',
|
|
'monochrome with a single accent color',
|
|
'pastel gradients with a sharp contrasting highlight',
|
|
'electric blue and magenta on dark tones',
|
|
]
|
|
|
|
const TEXTURE_VARIANTS = [
|
|
'subtle film grain with a matte finish',
|
|
'canvas texture with visible brushwork',
|
|
'paper cutout layers with rough edges',
|
|
'glossy highlights with clean edges',
|
|
'distressed print texture with scratches',
|
|
]
|
|
|
|
const TYPOGRAPHY_VARIANTS = [
|
|
'embossed metallic lettering integrated into the art',
|
|
'minimalist sans-serif with generous tracking',
|
|
'hand-drawn lettering with slight texture',
|
|
'neon-style lettering embedded in the scene',
|
|
'classic serif with elegant spacing',
|
|
]
|
|
|
|
const SUBJECT_VARIANTS = [
|
|
'a single strong symbol or scene that represents the song',
|
|
'an abstract motif with a clear focal point',
|
|
'a cinematic moment captured mid-action',
|
|
'a minimalist icon or silhouette with strong presence',
|
|
'a surreal object or landscape that feels iconic',
|
|
]
|
|
|
|
const ATMOSPHERE_DARK = [
|
|
'cinematic and moody with deep contrast',
|
|
'noir ambience with subtle haze and high contrast',
|
|
'tense and dramatic with low-key depth',
|
|
]
|
|
|
|
const ATMOSPHERE_ENERGETIC = [
|
|
'dynamic and high energy with punchy contrast',
|
|
'electric and fast-paced with vivid saturation',
|
|
'bold and kinetic with sharp highlights',
|
|
]
|
|
|
|
const ATMOSPHERE_SOFT = [
|
|
'calm and spacious with balanced mood',
|
|
'introspective and gentle with airy depth',
|
|
'dreamlike and serene with soft transitions',
|
|
]
|
|
|
|
const hashSeed = (value) => {
|
|
const str = String(value || '')
|
|
let hash = 0
|
|
for (let i = 0; i < str.length; i += 1) {
|
|
hash = (hash * 31 + str.charCodeAt(i)) >>> 0
|
|
}
|
|
return hash
|
|
}
|
|
|
|
const pickVariant = (list, seed, salt = 0) => {
|
|
if (!Array.isArray(list) || list.length === 0) return ''
|
|
const idx = (seed + salt) % list.length
|
|
return list[idx]
|
|
}
|
|
|
|
exports.generatePicturePrompt = (project = {}) => {
|
|
const {
|
|
title = '',
|
|
lyrics: lyricsRaw,
|
|
musicConfig = {},
|
|
coverStyle: coverStyleRaw = '',
|
|
artistName: artistNameRaw = '',
|
|
variantSeed = '',
|
|
} = project || {}
|
|
|
|
// --- 1. Nettoyage et Normalisation ---
|
|
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 = '', mood = '' } = musicConfig || {}
|
|
const userStyle = sanitizeInline(coverStyleRaw)
|
|
|
|
const seedInput = [titleForPrompt, artistName, userStyle, String(variantSeed || '')].join('|')
|
|
const baseSeed = hashSeed(seedInput)
|
|
const seed = baseSeed || Math.floor(Math.random() * 1_000_000_000)
|
|
|
|
// --- 2. Intelligence Visuelle (Mapping & Variations) ---
|
|
const preset = STYLE_PRESET_MAP[userStyle] || null
|
|
const styleDescriptor = preset?.style || userStyle || pickVariant(DEFAULT_STYLE_VARIANTS, seed, 7)
|
|
const renderingStyle = `Art Style: ${styleDescriptor}.`
|
|
|
|
const compositionChoice =
|
|
preset?.compositions?.length > 0
|
|
? pickVariant(preset.compositions, seed, 1)
|
|
: pickVariant(COMPOSITION_VARIANTS, seed, 1)
|
|
const lightingChoice =
|
|
preset?.lighting?.length > 0
|
|
? pickVariant(preset.lighting, seed, 2)
|
|
: pickVariant(LIGHTING_VARIANTS, seed, 2)
|
|
const paletteChoice =
|
|
preset?.palettes?.length > 0
|
|
? pickVariant(preset.palettes, seed, 3)
|
|
: pickVariant(PALETTE_VARIANTS, seed, 3)
|
|
const textureChoice =
|
|
preset?.textures?.length > 0
|
|
? pickVariant(preset.textures, seed, 4)
|
|
: pickVariant(TEXTURE_VARIANTS, seed, 4)
|
|
const typographyChoice =
|
|
preset?.typography?.length > 0
|
|
? pickVariant(preset.typography, seed, 5)
|
|
: pickVariant(TYPOGRAPHY_VARIANTS, seed, 5)
|
|
|
|
// Détermination de l'énergie visuelle
|
|
const isEnergetic =
|
|
tempo && /\b(rapid|fast|vite|agité|upbeat|energ|dance|rock|metal)\b/i.test(String(tempo))
|
|
const isDark =
|
|
mood && /\b(sombre|triste|dark|sad|mélancoli|nuit|night|eerie)\b/i.test(String(mood))
|
|
|
|
const atmosphereChoice = isDark
|
|
? pickVariant(ATMOSPHERE_DARK, seed, 6)
|
|
: isEnergetic
|
|
? pickVariant(ATMOSPHERE_ENERGETIC, seed, 6)
|
|
: pickVariant(ATMOSPHERE_SOFT, seed, 6)
|
|
|
|
// --- 3. Extraction de l'Inspiration (Lyrics) ---
|
|
const sections = Array.isArray(lyricsRaw) ? lyricsRaw : []
|
|
const chorus = sections.find((s) => s.type === 'refrain' || s.type === 'chorus')
|
|
const verse = sections.find((s) => s.type === 'couplet' || s.type === 'verse')
|
|
|
|
const visualHook = (chorus?.lyrics || verse?.lyrics || '')
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 10)
|
|
.slice(0, 2)
|
|
.join('. ')
|
|
|
|
const imageryPrompt = visualHook
|
|
? `Visual Inspiration: An interpretation of these lyrics: "${visualHook}".`
|
|
: "Visual Inspiration: Abstract visual representation of the song's mood."
|
|
|
|
const subjectChoice = pickVariant(SUBJECT_VARIANTS, seed, 8)
|
|
|
|
// --- 4. Construction du Prompt Final (Structure Optimisée Imagen 3) ---
|
|
const promptParts = [
|
|
'Design a professional, high-quality music album cover.',
|
|
|
|
'**Typography & Text:**',
|
|
`The song title "${titleForPrompt}" must be the CENTERPIECE. Write it in a distinct font that matches the mood.`,
|
|
hasArtistName
|
|
? `The artist name "${artistName}" must appear smaller, elegant, and legible near the bottom or top.`
|
|
: '',
|
|
`Typography treatment: ${typographyChoice}.`,
|
|
'Ensure perfect spelling. The text should be integrated into the artwork (e.g., embossed texture, neon tube, or bold cut-out), not just pasted on top.',
|
|
|
|
'**Visual Direction:**',
|
|
renderingStyle,
|
|
`Composition: ${compositionChoice}.`,
|
|
`Surface & Texture: ${textureChoice}.`,
|
|
`Subject: ${subjectChoice}.`,
|
|
imageryPrompt,
|
|
|
|
'**Mood & Color:**',
|
|
`Atmosphere: ${atmosphereChoice}.`,
|
|
`Palette: ${paletteChoice}.`,
|
|
`Lighting: ${lightingChoice}.`,
|
|
genres.length > 0 ? `Musical Vibe Reference: ${genres.join(', ')}.` : '',
|
|
|
|
'**Constraints:**',
|
|
'Use a square 1:1 aspect ratio.',
|
|
'No watermarks or logos.',
|
|
'No blurry text or messy borders.',
|
|
'Avoid generic stock imagery and clichéd instruments unless essential to the concept.',
|
|
]
|
|
|
|
return promptParts.filter(Boolean).join('\n\n')
|
|
}
|