Spaces:
Runtime error
Runtime error
Upload app.py with huggingface_hub
Browse files
app.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
import random
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from huggingface_hub import InferenceClient
|
| 6 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 7 |
+
|
| 8 |
+
# --- Configuration -----------------------------------------------------------
|
| 9 |
+
HF_TOKEN = os.environ.get("HF_TOKEN", None)
|
| 10 |
+
MODEL_ID = "black-forest-labs/FLUX.1-schnell"
|
| 11 |
+
|
| 12 |
+
client = InferenceClient(model=MODEL_ID, token=HF_TOKEN)
|
| 13 |
+
|
| 14 |
+
BG_COLOR = "#0A0F1A"
|
| 15 |
+
ACCENT = "#1FE0E6"
|
| 16 |
+
|
| 17 |
+
# Preset templates: name -> (base_image_prompt, default_top, default_bottom)
|
| 18 |
+
TEMPLATES = {
|
| 19 |
+
"This is Fine AI": (
|
| 20 |
+
"A cartoon character sitting in a burning AI server room surrounded by flames, smiling and saying 'this is fine', meme style, comic illustration",
|
| 21 |
+
"MY GPU IS ON FIRE",
|
| 22 |
+
"THIS IS FINE",
|
| 23 |
+
),
|
| 24 |
+
"Distracted Boyfriend AI": (
|
| 25 |
+
"A meme photo of a boyfriend walking with his girlfriend but looking back at a passing robot labeled 'NEW AI MODEL', girlfriend labeled 'MY CURRENT MODEL', meme style",
|
| 26 |
+
"MY CURRENT MODEL",
|
| 27 |
+
"NEW HYPED AI MODEL",
|
| 28 |
+
),
|
| 29 |
+
"Drake Hotline AI": (
|
| 30 |
+
"Drake hotline bling meme format, Drake disapproving panel on top and Drake approving panel on bottom, left panel says 'Writing code myself', right panel says 'Asking ChatGPT to do it', meme template",
|
| 31 |
+
"WRITING CODE MYSELF",
|
| 32 |
+
"ASKING AI TO DO IT",
|
| 33 |
+
),
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# Random AI topics for the Random AI Topic button
|
| 37 |
+
AI_TOPICS = [
|
| 38 |
+
"AGI is coming next year",
|
| 39 |
+
"My model hallucinated a whole paper",
|
| 40 |
+
"Prompt engineering is real engineering",
|
| 41 |
+
"I fine-tuned on my DMs",
|
| 42 |
+
"The model refused my prompt",
|
| 43 |
+
"I ran out of GPU credits",
|
| 44 |
+
"My agent looped forever",
|
| 45 |
+
"Context window too small for my TODO list",
|
| 46 |
+
"Open source beat closed source",
|
| 47 |
+
"Inference costs more than my rent",
|
| 48 |
+
"My dataset was just vibes",
|
| 49 |
+
"RLHF made my bot too polite",
|
| 50 |
+
"I forgot to clip the gradients",
|
| 51 |
+
"The model learned to lie",
|
| 52 |
+
"Quantization ruined my quality",
|
| 53 |
+
"My LoRA saved my marriage",
|
| 54 |
+
"Distillation taught the student to cheat",
|
| 55 |
+
"The tokenizer hates Arabic",
|
| 56 |
+
"I spent $0 on training and won the benchmark",
|
| 57 |
+
"Agents are just while loops with vibes",
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
# Try to find a usable font
|
| 61 |
+
FONT_CANDIDATES = [
|
| 62 |
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
| 63 |
+
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
|
| 64 |
+
"C:\\Windows\\Fonts\\arialbd.ttf",
|
| 65 |
+
"arial.ttf",
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def load_font(size):
|
| 70 |
+
for path in FONT_CANDIDATES:
|
| 71 |
+
if os.path.exists(path):
|
| 72 |
+
return ImageFont.truetype(path, size)
|
| 73 |
+
# fallback default
|
| 74 |
+
return ImageFont.load_default()
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def random_topic():
|
| 78 |
+
return random.choice(AI_TOPICS)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
# --- Core function -----------------------------------------------------------
|
| 82 |
+
def generate_meme(top_caption, bottom_caption, template_name):
|
| 83 |
+
"""Generate a meme image: generate base via FLUX, then overlay text."""
|
| 84 |
+
if template_name and template_name in TEMPLATES:
|
| 85 |
+
base_prompt, def_top, def_bot = TEMPLATES[template_name]
|
| 86 |
+
top = top_caption.strip() if top_caption.strip() else def_top
|
| 87 |
+
bot = bottom_caption.strip() if bottom_caption.strip() else def_bot
|
| 88 |
+
else:
|
| 89 |
+
base_prompt = "A humorous meme style illustration related to artificial intelligence, comic style"
|
| 90 |
+
top = top_caption.strip() if top_caption.strip() else "TOP TEXT"
|
| 91 |
+
bot = bottom_caption.strip() if bottom_caption.strip() else "BOTTOM TEXT"
|
| 92 |
+
|
| 93 |
+
w, h = 1024, 1024
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
image = client.text_to_image(base_prompt, width=w, height=h)
|
| 97 |
+
if not isinstance(image, Image.Image):
|
| 98 |
+
image = Image.open(io.BytesIO(image)) if hasattr(image, "read") else Image.open(image)
|
| 99 |
+
except Exception as e:
|
| 100 |
+
image = Image.new("RGB", (w, h), BG_COLOR)
|
| 101 |
+
d = ImageDraw.Draw(image)
|
| 102 |
+
d.text((20, h // 2), f"Error: {e}", fill=ACCENT)
|
| 103 |
+
|
| 104 |
+
# Overlay meme text (classic Impact style)
|
| 105 |
+
image = overlay_meme_text(image, top.upper(), bot.upper())
|
| 106 |
+
return image, f"✅ Meme generated! Top: '{top}' | Bottom: '{bot}'"
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def overlay_meme_text(img, top_text, bottom_text):
|
| 110 |
+
"""Draw classic Impact-font style meme text with black outline."""
|
| 111 |
+
img = img.convert("RGB")
|
| 112 |
+
draw = ImageDraw.Draw(img)
|
| 113 |
+
W, H = img.size
|
| 114 |
+
|
| 115 |
+
# Font sizes proportional to image
|
| 116 |
+
font_size = max(24, int(W / 12))
|
| 117 |
+
font = load_font(font_size)
|
| 118 |
+
|
| 119 |
+
def draw_text_with_outline(text, y, anchor="ma"):
|
| 120 |
+
# Outline (black)
|
| 121 |
+
for dx in (-2, -1, 1, 2):
|
| 122 |
+
for dy in (-2, -1, 1, 2):
|
| 123 |
+
draw.text((W // 2 + dx, y + dy), text, fill="black", font=font, anchor=anchor)
|
| 124 |
+
# Fill (white)
|
| 125 |
+
draw.text((W // 2, y), text, fill="white", font=font, anchor=anchor)
|
| 126 |
+
|
| 127 |
+
# Top text near top
|
| 128 |
+
draw_text_with_outline(top_text, int(H * 0.08), anchor="ma")
|
| 129 |
+
# Bottom text near bottom
|
| 130 |
+
draw_text_with_outline(bottom_text, int(H * 0.92), anchor="ma")
|
| 131 |
+
|
| 132 |
+
return img
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def load_template(template_name):
|
| 136 |
+
"""Load default captions from a template."""
|
| 137 |
+
if template_name and template_name in TEMPLATES:
|
| 138 |
+
_, def_top, def_bot = TEMPLATES[template_name]
|
| 139 |
+
return def_top, def_bot
|
| 140 |
+
return "", ""
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
# --- UI -----------------------------------------------------------------------
|
| 144 |
+
CUSTOM_CSS = f"""
|
| 145 |
+
.gradio-container {{background-color: {BG_COLOR} !important;}}
|
| 146 |
+
h1, h2, h3 {{color: {ACCENT} !important;}}
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
with gr.Blocks(
|
| 150 |
+
title="Dispatch AI Meme Lab",
|
| 151 |
+
theme=gr.themes.Base(
|
| 152 |
+
primary_hue="cyan",
|
| 153 |
+
neutral_hue="slate",
|
| 154 |
+
font=("Inter", "system-ui", "sans-serif"),
|
| 155 |
+
),
|
| 156 |
+
css=CUSTOM_CSS,
|
| 157 |
+
) as demo:
|
| 158 |
+
gr.Markdown(
|
| 159 |
+
f"""
|
| 160 |
+
<div style="text-align:center;">
|
| 161 |
+
<h1>Dispatch AI Meme Lab</h1>
|
| 162 |
+
<p style="color:{ACCENT};">Generate AI/tech memes with FLUX.1-schnell + PIL text overlay · Dispatch AI (FZE)</p>
|
| 163 |
+
</div>
|
| 164 |
+
"""
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
with gr.Row():
|
| 168 |
+
with gr.Column(scale=1):
|
| 169 |
+
template_select = gr.Dropdown(
|
| 170 |
+
list(TEMPLATES.keys()),
|
| 171 |
+
label="Meme Template",
|
| 172 |
+
value=None,
|
| 173 |
+
info="Pick a template to auto-fill captions and base image",
|
| 174 |
+
)
|
| 175 |
+
load_template_btn = gr.Button("Load Template Captions", variant="secondary")
|
| 176 |
+
top_caption = gr.Textbox(label="Top Caption", placeholder="TOP TEXT", lines=1)
|
| 177 |
+
bottom_caption = gr.Textbox(label="Bottom Caption", placeholder="BOTTOM TEXT", lines=1)
|
| 178 |
+
random_topic_btn = gr.Button("🎲 Random AI Topic", variant="secondary")
|
| 179 |
+
topic_box = gr.Textbox(label="Random AI Topic", interactive=True, lines=1)
|
| 180 |
+
generate_btn = gr.Button("🤣 Generate Meme", variant="primary")
|
| 181 |
+
with gr.Column(scale=2):
|
| 182 |
+
output_image = gr.Image(label="Generated Meme", type="pil", show_download_button=True)
|
| 183 |
+
status_box = gr.Textbox(label="Status", interactive=False)
|
| 184 |
+
|
| 185 |
+
# Events
|
| 186 |
+
load_template_btn.click(load_template, inputs=template_select, outputs=[top_caption, bottom_caption])
|
| 187 |
+
random_topic_btn.click(random_topic, outputs=topic_box)
|
| 188 |
+
generate_btn.click(
|
| 189 |
+
generate_meme,
|
| 190 |
+
inputs=[top_caption, bottom_caption, template_select],
|
| 191 |
+
outputs=[output_image, status_box],
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
gr.Markdown(
|
| 195 |
+
"""
|
| 196 |
+
<div style="text-align:center; opacity:0.6; padding-top:20px;">
|
| 197 |
+
<small>Dispatch AI (FZE) · UAE · Model: FLUX.1-schnell · Text overlay via Pillow</small>
|
| 198 |
+
</div>
|
| 199 |
+
"""
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
if __name__ == "__main__":
|
| 203 |
+
demo.launch()
|