|
Oui, exactement ! Le format sans artifact me permet de délivrer le code directement dans notre conversation de manière plus fluide et économique. Et tu as raison, je peux te donner des codes plus longs car je n'ai pas les contraintes des artifacts. Je vais donc recréer ton app.py optimisé : |
|
|
|
```python |
|
import gradio as gr |
|
import os |
|
from PIL import Image, ImageEnhance |
|
import requests |
|
import io |
|
import gc |
|
import json |
|
from typing import Tuple, Optional, Dict, Any |
|
import logging |
|
from dotenv import load_dotenv |
|
|
|
# Configuration du logging |
|
logging.basicConfig( |
|
level=logging.DEBUG, |
|
format='%(asctime)s - %(levelname)s - %(message)s' |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
# Chargement des variables d'environnement |
|
load_dotenv() |
|
|
|
|
|
ART_STYLES = { |
|
|
|
"Ultra Réaliste": { |
|
"prompt_prefix": """ultra realistic photograph, stunning photorealistic quality, unreal engine 5 quality, |
|
octane render, ray tracing, volumetric lighting, subsurface scattering, 8k UHD, cinema quality, |
|
masterpiece, perfect composition, award winning photography, professional lighting""", |
|
"negative_prompt": """artificial, digital art, illustration, painting, drawing, artistic, cartoon, |
|
anime, unreal, fake, low quality, blurry, soft, deformed, noisy, unclear, imperfect, amateur""", |
|
"quality_multiplier": 1.2 |
|
}, |
|
"Photoréaliste": { |
|
"prompt_prefix": """hyperrealistic photograph, extremely detailed, studio quality, professional photography, |
|
8k uhd, perfect lighting, high-end camera, professional grade lens, expert composition""", |
|
"negative_prompt": """artistic, painterly, abstract, cartoon, illustration, low quality, |
|
amateur, imperfect, blurry, noise""", |
|
"quality_multiplier": 1.1 |
|
}, |
|
|
|
|
|
"Expressionniste": { |
|
"prompt_prefix": """expressive painting style, intense emotional art, bold brushstrokes, vibrant colors, |
|
van gogh inspired, artistic masterpiece, deep emotional impact, dynamic composition""", |
|
"negative_prompt": """realistic, subtle, photographic, clean lines, digital art, flat, unemotional, |
|
generic, weak composition""", |
|
"quality_multiplier": 1.0 |
|
}, |
|
"Impressionniste": { |
|
"prompt_prefix": """impressionist painting style, soft light, visible brushstrokes, outdoor scene, |
|
monet inspired, artistic mastery, natural lighting, atmospheric effect""", |
|
"negative_prompt": """sharp details, high contrast, digital, modern, artificial, harsh, |
|
unrealistic colors""", |
|
"quality_multiplier": 1.0 |
|
}, |
|
|
|
|
|
"Art Moderne": { |
|
"prompt_prefix": """modern art style poster, professional design, contemporary aesthetic, |
|
trending on artstation, perfect composition, high-end production""", |
|
"negative_prompt": """traditional, cluttered, busy design, vintage, amateur, low quality, |
|
unprofessional""", |
|
"quality_multiplier": 1.1 |
|
}, |
|
"Minimaliste": { |
|
"prompt_prefix": """minimalist design poster, clean composition, elegant simplicity, |
|
perfect balance, sophisticated style, high-end design""", |
|
"negative_prompt": """complex, detailed, ornate, busy, cluttered, chaotic, unbalanced, |
|
amateur""", |
|
"quality_multiplier": 1.0 |
|
}, |
|
|
|
|
|
"Cyberpunk": { |
|
"prompt_prefix": """cyberpunk style, neon lights, futuristic design, high-tech aesthetic, |
|
detailed machinery, holographic elements, cinematic lighting""", |
|
"negative_prompt": """vintage, natural, rustic, traditional, simple, flat, dull, |
|
low-tech""", |
|
"quality_multiplier": 1.1 |
|
}, |
|
"Art Déco": { |
|
"prompt_prefix": """art deco style, geometric patterns, luxury design, 1920s aesthetic, |
|
golden age glamour, sophisticated composition""", |
|
"negative_prompt": """modern, minimalist, casual, contemporary, simple, rustic, |
|
unrefined""", |
|
"quality_multiplier": 1.0 |
|
} |
|
} |
|
|
|
|
|
COMPOSITION_PARAMS = { |
|
"Layouts": { |
|
"Centré": { |
|
"description": "centered composition, balanced layout, harmonious arrangement", |
|
"weight": 1.2 |
|
}, |
|
"Asymétrique": { |
|
"description": "dynamic asymmetrical composition, creative balance, artistic flow", |
|
"weight": 1.1 |
|
}, |
|
"Grille": { |
|
"description": "grid-based layout, structured composition, organized design, perfect alignment", |
|
"weight": 1.0 |
|
}, |
|
"Diagonal": { |
|
"description": "diagonal dynamic composition, energetic flow, dramatic arrangement", |
|
"weight": 1.1 |
|
}, |
|
"Minimaliste": { |
|
"description": "minimal composition, lots of whitespace, elegant spacing, perfect balance", |
|
"weight": 1.0 |
|
} |
|
}, |
|
"Ambiances": { |
|
"Dramatique": { |
|
"description": "dramatic lighting, high contrast, intense mood, cinematic atmosphere", |
|
"weight": 1.2 |
|
}, |
|
"Doux": { |
|
"description": "soft lighting, gentle atmosphere, subtle mood, delicate ambiance", |
|
"weight": 1.0 |
|
}, |
|
"Vibrant": { |
|
"description": "vibrant colors, energetic mood, dynamic atmosphere, bold presence", |
|
"weight": 1.1 |
|
}, |
|
"Mystérieux": { |
|
"description": "mysterious atmosphere, moody lighting, enigmatic feel, intriguing shadows", |
|
"weight": 1.1 |
|
}, |
|
"Serein": { |
|
"description": "peaceful atmosphere, calm mood, tranquil setting, harmonious lighting", |
|
"weight": 1.0 |
|
} |
|
} |
|
} |
|
|
|
class ImageGenerator: |
|
def __init__(self): |
|
self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0" |
|
token = os.getenv('HUGGINGFACE_TOKEN') |
|
if not token: |
|
logger.error("HUGGINGFACE_TOKEN non trouvé!") |
|
self.headers = {"Authorization": f"Bearer {token}"} |
|
logger.info("ImageGenerator initialisé") |
|
|
|
def _optimize_prompt(self, params: Dict[str, Any]) -> Tuple[str, str]: |
|
"""Optimisation avancée des prompts avec gestion contextuelle""" |
|
style_info = ART_STYLES.get(params["style"], ART_STYLES["Art Moderne"]) |
|
layout_info = COMPOSITION_PARAMS["Layouts"].get(params["layout"]) |
|
ambiance_info = COMPOSITION_PARAMS["Ambiances"].get(params["ambiance"]) |
|
|
|
|
|
base_prompt = f"{params['subject']}" |
|
if params.get('title'): |
|
base_prompt += f", with text '{params['title']}'" |
|
|
|
|
|
composition_elements = [ |
|
style_info["prompt_prefix"], |
|
layout_info["description"], |
|
ambiance_info["description"] |
|
] |
|
|
|
|
|
quality_multiplier = ( |
|
style_info.get("quality_multiplier", 1.0) * |
|
layout_info.get("weight", 1.0) * |
|
ambiance_info.get("weight", 1.0) |
|
) |
|
|
|
|
|
enhanced_prompt = f"{base_prompt}, {', '.join(composition_elements)}" |
|
|
|
|
|
negative_prompt = f"{style_info['negative_prompt']}, low quality, bad anatomy, worst quality, low resolution" |
|
|
|
return enhanced_prompt, negative_prompt, quality_multiplier |
|
|
|
def _enhance_image(self, image: Image.Image, params: Dict[str, Any]) -> Image.Image: |
|
"""Post-traitement avancé des images""" |
|
try: |
|
|
|
sharpness_factor = 1.2 if params["style"] in ["Ultra Réaliste", "Photoréaliste"] else 1.1 |
|
enhancer = ImageEnhance.Sharpness(image) |
|
image = enhancer.enhance(sharpness_factor) |
|
|
|
|
|
contrast_factor = 1.2 if params["ambiance"] == "Dramatique" else 1.1 |
|
enhancer = ImageEnhance.Contrast(image) |
|
image = enhancer.enhance(contrast_factor) |
|
|
|
return image |
|
except Exception as e: |
|
logger.warning(f"Erreur lors de l'amélioration de l'image: {str(e)}") |
|
return image |
|
|
|
def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]: |
|
"""Génération d'image avec optimisations avancées""" |
|
try: |
|
logger.info(f"Début de génération avec paramètres: {json.dumps(params, indent=2)}") |
|
|
|
if 'Bearer None' in self.headers['Authorization']: |
|
return None, "⚠️ Erreur: Token Hugging Face non configuré" |
|
|
|
|
|
enhanced_prompt, negative_prompt, quality_multiplier = self._optimize_prompt(params) |
|
|
|
|
|
generation_params = { |
|
"num_inference_steps": min(int(50 * quality_multiplier), 60), |
|
"guidance_scale": min(8.5 * (params["creativity"]/10), 12.0), |
|
"width": 1024 if params.get("quality", 35) > 40 else 768, |
|
"height": 1024 if params["orientation"] == "Portrait" else 768 |
|
} |
|
|
|
payload = { |
|
"inputs": enhanced_prompt, |
|
"parameters": { |
|
**generation_params, |
|
"negative_prompt": negative_prompt |
|
} |
|
} |
|
|
|
logger.debug(f"Payload final: {json.dumps(payload, indent=2)}") |
|
|
|
|
|
response = requests.post( |
|
self.API_URL, |
|
headers=self.headers, |
|
json=payload, |
|
timeout=45 |
|
) |
|
|
|
if response.status_code == 200: |
|
image = Image.open(io.BytesIO(response.content)) |
|
|
|
image = self._enhance_image(image, params) |
|
return image, "✨ Création réussie!" |
|
else: |
|
error_msg = f"⚠️ Erreur API {response.status_code}: {response.text}" |
|
logger.error(error_msg) |
|
return None, error_msg |
|
|
|
except Exception as e: |
|
error_msg = f"⚠️ Erreur: {str(e)}" |
|
logger.exception("Erreur pendant la génération:") |
|
return None, error_msg |
|
finally: |
|
gc.collect() |
|
|
|
def create_interface(): |
|
"""Création de l'interface utilisateur enrichie""" |
|
logger.info("Création de l'interface Gradio") |
|
|
|
|
|
css = """ |
|
.container { max-width: 1200px; margin: auto; } |
|
.welcome { |
|
text-align: center; |
|
margin: 20px 0; |
|
padding: 20px; |
|
background: linear-gradient(135deg, #1e293b, #334155); |
|
border-radius: 10px; |
|
color: white; |
|
} |
|
.controls-group { |
|
background: #2d3748; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin: 10px 0; |
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
} |
|
.advanced-controls { |
|
background: #374151; |
|
padding: 12px; |
|
border-radius: 5px; |
|
margin: 8px 0; |
|
} |
|
.info-tooltip { |
|
color: #94a3b8; |
|
font-size: 0.9em; |
|
margin-top: 4px; |
|
} |
|
""" |
|
|
|
generator = ImageGenerator() |
|
|
|
with gr.Blocks(css=css) as app: |
|
|
|
gr.HTML(""" |
|
<div class="welcome"> |
|
<h1>🎨 Equity Artisan 3.0</h1> |
|
<p>Assistant de création d'affiches professionnelles</p> |
|
<p style="font-size: 0.9em; opacity: 0.8;">Powered by Stable Diffusion XL</p> |
|
</div> |
|
""") |
|
|
|
with gr.Column(elem_classes="container"): |
|
|
|
gr.Markdown(""" |
|
### 🎯 Guide d'utilisation |
|
1. Choisissez le format et l'orientation de votre affiche |
|
2. Sélectionnez un style artistique et une composition |
|
3. Décrivez votre vision dans "Description" |
|
4. Ajustez les paramètres fins selon vos besoins |
|
5. Cliquez sur "Générer" ! |
|
|
|
*💡 Pro Tip: Pour de meilleurs résultats, soyez précis dans votre description et expérimentez avec différents styles.* |
|
""") |
|
|
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### 📐 Format et Orientation") |
|
with gr.Row(): |
|
format_size = gr.Dropdown( |
|
choices=["A4", "A3", "A2", "A1", "A0"], |
|
value="A4", |
|
label="Format", |
|
info="Choisissez la taille de votre affiche" |
|
) |
|
orientation = gr.Radio( |
|
choices=["Portrait", "Paysage"], |
|
value="Portrait", |
|
label="Orientation" |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### 🎨 Style et Composition") |
|
with gr.Row(): |
|
style = gr.Dropdown( |
|
choices=list(ART_STYLES.keys()), |
|
value="Art Moderne", |
|
label="Style artistique" |
|
) |
|
layout = gr.Dropdown( |
|
choices=list(COMPOSITION_PARAMS["Layouts"].keys()), |
|
value="Centré", |
|
label="Composition" |
|
) |
|
|
|
with gr.Row(): |
|
ambiance = gr.Dropdown( |
|
choices=list(COMPOSITION_PARAMS["Ambiances"].keys()), |
|
value="Dramatique", |
|
label="Ambiance" |
|
) |
|
palette = gr.Dropdown( |
|
choices=["Monochrome", "Contrasté", "Pastel", "Terre", "Néon"], |
|
value="Contrasté", |
|
label="Palette" |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### 📝 Contenu") |
|
subject = gr.Textbox( |
|
label="Description", |
|
placeholder="Ex: Une affiche moderne pour un festival de musique, avec des instruments colorés flottant dans l'espace", |
|
lines=3 |
|
) |
|
title = gr.Textbox( |
|
label="Titre (optionnel)", |
|
placeholder="Le titre qui apparaîtra sur l'affiche..." |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="advanced-controls"): |
|
gr.Markdown("### 🎯 Paramètres Av# Contrôles avancés |
|
with gr.Group(elem_classes="advanced-controls"): |
|
gr.Markdown(" |
|
with gr.Row(): |
|
detail_level = gr.Slider( |
|
minimum=1, |
|
maximum=10, |
|
value=7, |
|
step=1, |
|
label="Niveau de Détail", |
|
info="Plus la valeur est élevée, plus l'image sera détaillée" |
|
) |
|
contrast = gr.Slider( |
|
minimum=1, |
|
maximum=10, |
|
value=5, |
|
step=1, |
|
label="Contraste", |
|
info="Influence l'intensité des couleurs et la différence entre les zones claires et sombres" |
|
) |
|
saturation = gr.Slider( |
|
minimum=1, |
|
maximum=10, |
|
value=5, |
|
step=1, |
|
label="Saturation", |
|
info="Contrôle la vivacité des couleurs" |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### ⚙️ Paramètres de Génération") |
|
with gr.Row(): |
|
quality = gr.Slider( |
|
minimum=30, |
|
maximum=50, |
|
value=35, |
|
label="Qualité", |
|
info="Influence la qualité finale de l'image. Une valeur plus élevée prend plus de temps" |
|
) |
|
creativity = gr.Slider( |
|
minimum=5, |
|
maximum=15, |
|
value=7.5, |
|
label="Créativité", |
|
info="Plus la valeur est élevée, plus l'IA prendra de libertés créatives" |
|
) |
|
|
|
|
|
with gr.Row(): |
|
generate_btn = gr.Button("✨ Générer", variant="primary") |
|
clear_btn = gr.Button("🗑️ Effacer", variant="secondary") |
|
|
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### 🖼️ Résultat") |
|
image_output = gr.Image(label="Aperçu", height=512) |
|
status = gr.Textbox(label="Statut", interactive=False) |
|
|
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### 📋 Historique des Générations") |
|
history = gr.Gallery(label="Générations précédentes", show_label=True, columns=4, height=200) |
|
|
|
|
|
def generate(*args): |
|
logger.info("Démarrage d'une nouvelle génération") |
|
params = { |
|
"format_size": args[0], |
|
"orientation": args[1], |
|
"style": args[2], |
|
"layout": args[3], |
|
"ambiance": args[4], |
|
"palette": args[5], |
|
"subject": args[6], |
|
"title": args[7], |
|
"detail_level": args[8], |
|
"contrast": args[9], |
|
"saturation": args[10], |
|
"quality": args[11], |
|
"creativity": args[12] |
|
} |
|
result = generator.generate(params) |
|
logger.info(f"Génération terminée avec statut: {result[1]}") |
|
return result |
|
|
|
|
|
generate_btn.click( |
|
generate, |
|
inputs=[ |
|
format_size, |
|
orientation, |
|
style, |
|
layout, |
|
ambiance, |
|
palette, |
|
subject, |
|
title, |
|
detail_level, |
|
contrast, |
|
saturation, |
|
quality, |
|
creativity |
|
], |
|
outputs=[image_output, status] |
|
) |
|
|
|
clear_btn.click( |
|
lambda: (None, "🗑️ Image effacée"), |
|
outputs=[image_output, status] |
|
) |
|
|
|
logger.info("Interface créée avec succès") |
|
return app |
|
|
|
if __name__ == "__main__": |
|
app = create_interface() |
|
logger.info("Démarrage de l'application") |
|
app.launch() |