Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from transformers import pipeline, set_seed | |
| from PIL import Image, ImageDraw, ImageFont | |
| import textwrap | |
| import io | |
| # Lazy-load models | |
| _captioner = None | |
| _generator = None | |
| def get_captioner(): | |
| global _captioner | |
| if _captioner is None: | |
| _captioner = pipeline("image-to-text", model="Salesforce/blip-image-captioning-base") | |
| return _captioner | |
| def get_generator(): | |
| global _generator | |
| if _generator is None: | |
| _generator = pipeline("text-generation", model="distilgpt2") | |
| return _generator | |
| def wrap_text(text, width=22): | |
| return "\n".join(textwrap.wrap(text, width=width)) | |
| def draw_meme(img: Image.Image, caption: str, position: str = "bottom", uppercase: bool = True) -> Image.Image: | |
| if uppercase: | |
| caption = caption.upper() | |
| # Prepare drawing | |
| draw = ImageDraw.Draw(img) | |
| W, H = img.size | |
| # Choose a font (fallback to default if Impact is not available) | |
| try: | |
| font = ImageFont.truetype("Impact.ttf", size=max(24, W // 15)) | |
| except Exception: | |
| font = ImageFont.load_default() | |
| # Wrap caption to fit width | |
| wrapped = wrap_text(caption, width=max(18, W // 25)) | |
| # Determine text size | |
| bbox = draw.multiline_textbbox((0, 0), wrapped, font=font, align="center") | |
| text_w = bbox[2] - bbox[0] | |
| text_h = bbox[3] - bbox[1] | |
| # Choose y coordinate | |
| margin = int(0.03 * H) | |
| if position == "top": | |
| y = margin | |
| elif position == "center": | |
| y = (H - text_h) // 2 | |
| else: | |
| y = H - text_h - margin | |
| # Draw black outline then white text (classic meme style) | |
| x = (W - text_w) // 2 | |
| outline = max(2, W // 200) | |
| for dx in (-outline, outline): | |
| for dy in (-outline, outline): | |
| draw.multiline_text((x + dx, y + dy), wrapped, font=font, fill="black", align="center") | |
| draw.multiline_text((x, y), wrapped, font=font, fill="white", align="center") | |
| return img | |
| def explain_caption(caption: str) -> str: | |
| cap = caption.lower() | |
| cues = [] | |
| if any(w in cap for w in ["when ", "me:", "you ", "pov", "that moment"]): | |
| cues.append("relatable setup") | |
| if any(w in cap for w in ["but", "yet", "although", "instead"]): | |
| cues.append("incongruity/twist") | |
| if any(w in cap for w in ["too", "very", "super", "always", "never"]): | |
| cues.append("mild exaggeration") | |
| if any(w in cap for w in ["monday", "wifi", "coffee", "deadline", "sleep"]): | |
| cues.append("everyday theme") | |
| pieces = [ | |
| "Short, high-contrast phrasing (classic meme style).", | |
| "Focuses on a common situation to trigger recognition.", | |
| "Adds a small twist that creates incongruity (the core of the joke)." | |
| ] | |
| if cues: | |
| pieces.append("Detected humor cues: " + ", ".join(cues) + ".") | |
| return " ".join(pieces) | |
| def generate_meme(image, style, uppercase, seed): | |
| if image is None: | |
| return None, "Please upload an image.", "" | |
| if seed is not None and str(seed).strip() != "": | |
| try: | |
| set_seed(int(seed)) | |
| except Exception: | |
| pass | |
| # Step 1: caption the image | |
| captioner = get_captioner() | |
| raw_caption = captioner(image)[0]["generated_text"] | |
| # Step 2: create a witty meme caption from the caption | |
| prompt = ( | |
| "Write a short, witty meme caption (max 12 words) based on this image description: " | |
| f"'{raw_caption}'. Use relatable internet humor. Avoid names, no offensive content." | |
| ) | |
| generator = get_generator() | |
| gen = generator(prompt, max_new_tokens=24, temperature=0.9, top_p=0.95, do_sample=True)[0]["generated_text"] | |
| # Get only the newly generated tail after the prompt | |
| meme_caption = gen[len(prompt):].strip().split("\n")[0] | |
| # Clean fallbacks | |
| if len(meme_caption) < 3 or len(meme_caption.split()) < 2: | |
| meme_caption = "When Monday hits before the coffee" | |
| # Step 3: draw caption onto the image | |
| img = image.convert("RGB").copy() | |
| img_out = draw_meme(img, meme_caption, position=style, uppercase=uppercase) | |
| # Step 4: generate a simple explanation | |
| explanation = explain_caption(meme_caption) | |
| return img_out, meme_caption, explanation | |
| with gr.Blocks(title="AI Meme Generator + Explainer") as demo: | |
| gr.Markdown( | |
| """ | |
| # AI Meme Generator + Explainer | |
| Upload an image → the app captions it, writes a short meme line, and explains why it works. | |
| Runs on CPU using lightweight models (BLIP-base + DistilGPT2). | |
| """ | |
| ) | |
| with gr.Row(): | |
| image = gr.Image(type="pil", label="Upload image") | |
| with gr.Column(): | |
| style = gr.Radio(choices=["top", "center", "bottom"], value="bottom", label="Caption position") | |
| uppercase = gr.Checkbox(value=True, label="Uppercase caption (meme style)") | |
| seed = gr.Textbox(value="", label="Seed (optional)", placeholder="e.g., 42") | |
| btn = gr.Button("Generate Meme") | |
| out_img = gr.Image(label="Meme") | |
| out_caption = gr.Textbox(label="Generated caption", lines=2) | |
| out_expl = gr.Textbox(label="Why it’s funny (explanation)", lines=4) | |
| btn.click(generate_meme, inputs=[image, style, uppercase, seed], outputs=[out_img, out_caption, out_expl]) | |
| if __name__ == "__main__": | |
| demo.launch() | |