|
import gradio as gr |
|
import os |
|
from PIL import Image |
|
import requests |
|
import io |
|
import gc |
|
import json |
|
from typing import Tuple, Optional, Dict, Any |
|
import logging |
|
from dotenv import load_dotenv |
|
|
|
|
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
ART_STYLES = { |
|
"Art Moderne": { |
|
"prompt_prefix": "modern art style poster, professional design", |
|
"negative_prompt": "traditional, photorealistic, cluttered, busy design" |
|
}, |
|
"Neo Vintage": { |
|
"prompt_prefix": "vintage style advertising poster, retro design", |
|
"negative_prompt": "modern, digital, contemporary style" |
|
}, |
|
"Pop Art": { |
|
"prompt_prefix": "pop art style poster, bold design", |
|
"negative_prompt": "subtle, realistic, traditional art" |
|
}, |
|
"Minimaliste": { |
|
"prompt_prefix": "minimalist design poster, clean composition", |
|
"negative_prompt": "complex, detailed, ornate, busy" |
|
}, |
|
"Cyberpunk": { |
|
"prompt_prefix": "cyberpunk style poster, neon lights, futuristic design", |
|
"negative_prompt": "vintage, natural, rustic, traditional" |
|
}, |
|
"Aquarelle": { |
|
"prompt_prefix": "watercolor art style poster, fluid artistic design", |
|
"negative_prompt": "digital, sharp, photorealistic" |
|
}, |
|
"Art Déco": { |
|
"prompt_prefix": "art deco style poster, geometric patterns, luxury design", |
|
"negative_prompt": "modern, minimalist, casual" |
|
}, |
|
"Japonais": { |
|
"prompt_prefix": "japanese art style poster, ukiyo-e inspired design", |
|
"negative_prompt": "western, modern, photographic" |
|
}, |
|
"Ultra Réaliste": { |
|
"prompt_prefix": "hyper-realistic poster, photographic quality, extremely detailed", |
|
"negative_prompt": "cartoon, illustration, stylized, abstract" |
|
}, |
|
"Bande Dessinée": { |
|
"prompt_prefix": "comic book style poster, vibrant colors, bold outlines", |
|
"negative_prompt": "realistic, photographic, subtle, muted colors" |
|
}, |
|
|
|
"Noir et Blanc": { |
|
"prompt_prefix": "black and white style poster, monochromatic design", |
|
"negative_prompt": "colorful, vibrant designs" |
|
}, |
|
} |
|
|
|
|
|
COMPOSITION_PARAMS = { |
|
|
|
"Layouts": { |
|
"Centré": "centered composition, balanced layout", |
|
"Asymétrique": "dynamic asymmetrical composition", |
|
"Grille": "grid-based layout, structured composition", |
|
"Diagonal": "diagonal dynamic composition", |
|
"Minimaliste": "minimal composition, lots of whitespace", |
|
"Radial": "radial composition, circular arrangement", |
|
"En Z": "Z-shaped composition, guiding viewer's eye", |
|
"Règle des tiers": "rule of thirds composition", |
|
"Symétrie": "symmetrical composition, mirror-like balance", |
|
"Cadre dans le cadre": "frame within a frame composition" |
|
}, |
|
|
|
"Ambiances": { |
|
"Dramatique": "dramatic lighting, high contrast", |
|
"Doux": "soft lighting, gentle atmosphere", |
|
"Vibrant": "vibrant colors, energetic mood", |
|
"Mystérieux": "mysterious atmosphere with moody lighting", |
|
"Serein": "peaceful atmosphere with calm mood", |
|
"Rétro": "retro atmosphere with nostalgic mood", |
|
"Futuriste": "futuristic atmosphere with high-tech mood", |
|
"Onirique": "dreamy atmosphere with surreal mood", |
|
"Industriel": "industrial atmosphere with raw and urban mood", |
|
"Naturel": "natural atmosphere with organic and earthy mood", |
|
|
|
'Nocturne': 'nocturnal atmosphere with dark and moody lighting' |
|
}, |
|
|
|
'Palette': { |
|
'Monochrome': 'monochromatic color scheme', |
|
'Contrasté': 'high contrast color palette', |
|
'Pastel': 'soft pastel color palette', |
|
'Terre': 'earthy color palette', |
|
'Néon': 'neon color palette', |
|
'Complémentaire': 'complementary color scheme', |
|
'Analogique': 'analogous color scheme', |
|
'Triadique': 'triadic color scheme', |
|
'Tétradique': 'tetradic color scheme', |
|
'Tons rompus': 'muted tones color palette' |
|
} |
|
} |
|
|
|
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 _build_prompt(self, params: Dict[str, Any]) -> str: |
|
style_info = ART_STYLES.get(params["style"], ART_STYLES["Neo Vintage"]) |
|
|
|
prompt = f"{style_info['prompt_prefix']}, {params['subject']}" |
|
|
|
if params.get("layout"): |
|
prompt += f", {COMPOSITION_PARAMS['Layouts'][params['layout']]}" |
|
|
|
if params.get("ambiance"): |
|
prompt += f", {COMPOSITION_PARAMS['Ambiances'][params['ambiance']]}" |
|
|
|
if params.get("palette"): |
|
prompt += f", {COMPOSITION_PARAMS['Palette'][params['palette']]}" |
|
|
|
for param, description in [ |
|
("detail_level", "highly detailed" if params.get("detail_level", 0) > 7 else "moderately detailed"), |
|
("contrast", "high contrast" if params.get("contrast", 0) > 7 else "balanced contrast"), |
|
("saturation", "vibrant colors" if params.get("saturation", 0) > 7 else "subtle colors") |
|
]: |
|
if params.get(param): |
|
prompt += f", {description}" |
|
|
|
if params.get("title"): |
|
prompt += f", with text saying '{params['title']}'" |
|
|
|
logger.debug(f"Prompt final: {prompt}") |
|
|
|
return prompt |
|
|
|
def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]: |
|
try: |
|
logger.info(f"Démarrage de génération avec paramètres: {json.dumps(params)}") |
|
|
|
if 'Bearer None' in self.headers['Authorization']: |
|
return None,"⚠️ Erreur: Token Hugging Face non configuré" |
|
|
|
prompt = self._build_prompt(params) |
|
|
|
payload = { |
|
'inputs': prompt, |
|
'parameters': { |
|
'negative_prompt': ART_STYLES[params["style"]]["negative_prompt"], |
|
'num_inference_steps': min(int(35 * (params["quality"] / 100)), 40), |
|
'guidance_scale': min(7.5 * (params["creativity"] / 10), 10.0), |
|
'width': 768, |
|
'height': 768 if params["orientation"] == 'Portrait' else 512, |
|
} |
|
} |
|
|
|
logger.debug(f'Payload: {json.dumps(payload)}') |
|
|
|
response = requests.post(self.API_URL, |
|
headers=self.headers, |
|
json=payload, |
|
timeout=30) |
|
|
|
if response.status_code == 200: |
|
image = Image.open(io.BytesIO(response.content)) |
|
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(): |
|
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: #3498db; border-radius: 10px; color: white; } |
|
.controls-group { background: #ecf0f1; padding: 15px; border-radius: 5px; margin: 10px 0; color: #2c3e50; } |
|
.advanced-controls { background: #bdc3c7; padding: 12px; border-radius: 5px; margin: 8px 0; } |
|
.gradio-slider input[type="range"] { accent-color: #3498db; } |
|
.gradio-button { transition: all 0.3s ease; } |
|
.gradio-button:hover { transform: translateY(-2px); box-shadow: 0 4px 6px rgba(52 ,152 ,219 ,0.11),0 1px 3px rgba(0 ,0 ,0 ,0.08); } |
|
""" |
|
|
|
generator = ImageGenerator() |
|
|
|
with gr.Blocks(css=css) as app: |
|
|
|
gr.HTML(""" |
|
<div class="welcome"> |
|
<h1>🎨 Equity Artisan 4.0</h1> |
|
<p>Assistant de création d'affiches professionnelles avancé</p> |
|
</div> |
|
""") |
|
|
|
with gr.Column(elem_classes="container"): |
|
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") |
|
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="Neo Vintage", 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=list(COMPOSITION_PARAMS["Palette"].keys()), value="Contrasté", label="Palette") |
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
gr.Markdown("### 📝 Contenu") |
|
subject = gr.Textbox(label="Description", placeholder="Décrivez votre vision...") |
|
title = gr.Textbox(label="Titre", placeholder="Titre de l'affiche...") |
|
|
|
with gr.Group(elem_classes="advanced-controls"): |
|
gr.Markdown("### 🎯 Ajustements Fins") |
|
with gr.Row(): |
|
detail_level = gr.Slider(minimum=1 ,maximum=10 ,value=7 ,step=1 ,label="Niveau de Détail") |
|
contrast = gr.Slider(minimum=1 ,maximum=10 ,value=5 ,step=1 ,label="Contraste") |
|
saturation = gr.Slider(minimum=1 ,maximum=10 ,value=5 ,step=1 ,label="Saturation") |
|
|
|
with gr.Group(elem_classes="controls-group"): |
|
with gr.Row(): |
|
quality = gr.Slider(minimum=30,maximum=50,value=35,label="Qualité") |
|
creativity = gr.Slider(minimum=5,maximum=15,value=7.5,label="Créativité") |
|
|
|
with gr.Row(): |
|
generate_btn = gr.Button("✨ Générer", variant="primary") |
|
clear_btn = gr.Button("🗑️ Effacer", variant="secondary") |
|
|
|
image_output = gr.Image(label="Aperçu") |
|
status = gr.Textbox(label="Statut", interactive=False) |
|
|
|
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() |