Download app.py from Equityone/generart: direct link, hf CLI and curl.
- Browser
- Download file 13.6 kB
-
https://huggingface.co/spaces/Equityone/generart/resolve/main/app.py
- Command line
-
hf download hf://spaces/Equityone/generart/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Equityone/generart/resolve/main/app.py
13.6 kB
| 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 | |
| # 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() | |
| # Styles artistiques étendus | |
| 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" | |
| }, | |
| # New styles added here | |
| "Noir et Blanc": { | |
| "prompt_prefix": "black and white style poster, monochromatic design", | |
| "negative_prompt": "colorful, vibrant designs" | |
| }, | |
| } | |
| # Paramètres de composition étendus | |
| COMPOSITION_PARAMS = { | |
| # Layouts remain unchanged | |
| "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" | |
| }, | |
| # New ambiance added here including Nocturne | |
| "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", | |
| # Correctly added Nocturne ambiance here without syntax error | |
| 'Nocturne': 'nocturnal atmosphere with dark and moody lighting' | |
| }, | |
| # Palette remains unchanged | |
| '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é") # Fixed typo from maximun to maximum | |
| creativity = gr.Slider(minimum=5,maximum=15,value=7.5,label="Créativité") # Fixed typo from maximun to maximum | |
| 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() |