File size: 18,091 Bytes
cc5958e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc5958e
 
 
 
 
 
 
 
 
 
6287e96
 
 
 
fb609fe
 
6287e96
 
 
fb609fe
cc5958e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb609fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
 
b67b56b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
cc5958e
 
 
 
 
 
 
 
 
6287e96
 
 
 
 
 
 
 
 
 
cc5958e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
 
fb609fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
 
 
 
fb609fe
6287e96
 
fb609fe
6287e96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb609fe
 
 
 
 
 
 
 
 
6287e96
fb609fe
 
 
 
 
 
 
 
 
 
 
 
6287e96
 
 
 
 
 
 
 
5f8c423
711e100
6287e96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ea8db7
6287e96
 
 
 
 
 
b67b56b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
fb609fe
 
 
 
 
 
6287e96
fb609fe
 
 
6287e96
 
fb609fe
6287e96
fb609fe
 
 
 
 
 
6287e96
 
 
 
 
fb609fe
7537a36
fb609fe
6287e96
 
 
 
711e100
6287e96
fb609fe
 
 
6287e96
 
fb609fe
 
 
6287e96
fb609fe
 
 
 
 
6287e96
b67b56b
 
 
6287e96
 
2ea8db7
6287e96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ea8db7
6287e96
 
 
 
 
 
 
 
 
 
 
 
 
 
2ea8db7
6287e96
 
 
 
 
 
 
 
 
 
 
 
 
fb609fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6287e96
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
"""Pepe the Frog Meme Generator - Main Streamlit Application.

This is the main entry point for the web application. It provides a user-friendly
interface for generating Pepe memes using AI-powered Stable Diffusion models.

The application features:
- Model selection (multiple LoRA variants, LCM support)
- Style presets and raw prompt mode
- Advanced generation settings (steps, guidance, seed)
- Text overlay capability for meme creation
- Gallery system for viewing generated images
- Download functionality
- Progress tracking during generation

Application Structure:
    1. Page configuration and styling
    2. Session state initialization
    3. Model loading and caching
    4. Sidebar UI (model selection, settings)
    5. Main content area (prompt input, generation)
    6. Results display and download
    7. Gallery view

Usage:
    Run with: streamlit run src/app.py
    Access at: http://localhost:8501

Author: MJaheen
License: MIT
"""

import streamlit as st
from PIL import Image
import io
from datetime import datetime

# Import our modules
from model.generator import PepeGenerator
from model.config import ModelConfig
from utils.image_processor import ImageProcessor

# Page config
st.set_page_config(
    page_title="🐸 Pepe Meme Generator",
    page_icon="🐸",
    layout="wide",
)

# Custom CSS
st.markdown("""
    <style>
    .stButton>button {
        width: 100%;
        background-color: #4CAF50;
        color: white;
        height: 3em;
        border-radius: 10px;
        font-weight: bold;
    }
    .stButton>button:hover {
        background-color: #45a049;
    }
    </style>
""", unsafe_allow_html=True)


def init_session_state():
    """
    Initialize Streamlit session state variables.
    
    This function sets up persistent state across app reruns:
    - generated_images: List of all generated images in current session
    - generation_count: Counter for tracking total generations
    - current_model: Currently selected model name for cache invalidation
    
    Session state persists across reruns but is reset when the page is refreshed.
    """
    if 'generated_images' not in st.session_state:
        st.session_state.generated_images = []
    if 'generation_count' not in st.session_state:
        st.session_state.generation_count = 0
    if 'current_model' not in st.session_state:
        st.session_state.current_model = None


@st.cache_resource
def load_generator(model_name: str = "Pepe Fine-tuned (LoRA)"):
    """
    Load and cache the Stable Diffusion generator.
    
    This function loads a PepeGenerator instance configured with the selected
    model. It's cached using @st.cache_resource to avoid reloading the model
    on every interaction, which would be very slow.
    
    The cache is automatically invalidated when:
    - The model_name parameter changes
    - The user manually clears cache
    
    Args:
        model_name: Name of the model from AVAILABLE_MODELS dict.
            Examples: "Pepe Fine-tuned (LoRA)", "Pepe + LCM (FAST)"
    
    Returns:
        PepeGenerator: Configured generator instance with loaded model.
    
    Note:
        Model loading can take 30-60 seconds on first load as it downloads
        weights from Hugging Face (~4GB for base model + LoRA).
    """
    config = ModelConfig()
    model_config = config.AVAILABLE_MODELS[model_name]
    
    # Update config with selected model settings
    config.BASE_MODEL = model_config['base']
    config.LORA_PATH = model_config.get('lora')
    config.USE_LORA = model_config.get('use_lora', False)
    config.TRIGGER_WORD = model_config.get('trigger_word', 'pepe the frog')
    
    # LCM settings
    config.USE_LCM = model_config.get('use_lcm', False)
    config.LCM_LORA_PATH = model_config.get('lcm_lora')
    
    # Log which model is being loaded
    import logging
    logger = logging.getLogger(__name__)
    logger.info(f"Loading model: {model_name}")
    logger.info(f"Base: {config.BASE_MODEL}, LoRA: {config.USE_LORA}, LCM: {config.USE_LCM}")
    
    return PepeGenerator(config)


def debug_generation_inputs(timestamp, prompt, style, steps, guidance, seed, model, top_text, bottom_text, num_vars, raw_prompt=False, use_seed=False, add_text=False, font_size=40, font_path=""):
    """
    Debug function to print all generation inputs when 'Generate Meme' is pressed.

    Args:
        timestamp: Current timestamp when generation started
        prompt: The user's text prompt
        style: Selected style preset
        steps: Number of inference steps
        guidance: Guidance scale value
        seed: Random seed (if used)
        model: Selected model name
        top_text: Top meme text
        bottom_text: Bottom meme text
        num_vars: Number of variations to generate
        raw_prompt: Whether raw prompt mode is enabled
        use_seed: Whether fixed seed is enabled
        add_text: Whether text overlay is enabled
        font_size: Font size for text overlay
        font_path: Path to font file
    """
    print("=" * 80)
    print("🎨 MEME GENERATION DEBUG INFO")
    print("=" * 80)
    print(f"⏰ Timestamp: {timestamp}")
    print(f"πŸ€– Model: {model}")
    print(f"πŸ“ Prompt: {prompt}")
    print(f"🎨 Style: {style}")
    print(f"βš™οΈ  Steps: {steps}")
    print(f"🎯 Guidance Scale: {guidance}")
    print(f"πŸ”’ Seed Enabled: {use_seed}")
    if use_seed:
        print(f"🎲 Seed Value: {seed}")
    print(f"πŸ”„ Variations: {num_vars}")
    print(f"πŸ“ Raw Prompt Mode: {raw_prompt}")
    print(f"πŸ’¬ Text Overlay: {add_text}")
    if add_text:
        print(f"πŸ“ Top Text: '{top_text}'")
        print(f"πŸ“ Bottom Text: '{bottom_text}'")
        print(f"πŸ”€ Font Size: {font_size}")
        print(f"πŸ“ Font Path: {font_path}")
    print("=" * 80)
    print("πŸš€ Starting image generation...")
    print("=" * 80)
    return datetime.now()  # Return start time for timing calculation


def debug_generation_complete(start_time, num_vars):
    """
    Debug function to print generation completion time and performance metrics.

    Args:
        start_time: The datetime when generation started
        num_vars: Number of variations generated
    """
    end_time = datetime.now()
    total_time = end_time - start_time
    total_seconds = total_time.total_seconds()

    print("=" * 80)
    print("βœ… MEME GENERATION COMPLETED")
    print("=" * 80)
    print(f"⏱️  Total Time: {total_seconds:.2f} seconds")
    print(f"πŸ–ΌοΈ  Images Generated: {num_vars}")
    if num_vars > 1:
        print(f"⏱️  Average Time per Image: {total_seconds/num_vars:.2f} seconds")
    print(f"🏁 Finished at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 80)


def get_example_prompts():
    """
    Return a list of example prompts for inspiration.
    
    These prompts are designed to work well with the fine-tuned Pepe model
    and demonstrate various styles, activities, and scenarios.
    
    Returns:
        list: List of example prompt strings with trigger word and descriptions.
    """
    return [
        "pepe the frog as a wizard casting spells",
        "pepe the frog coding on a laptop",
        "pepe the frog drinking coffee",
        "pepe the frog as a superhero",
        "pepe the frog reading a book",
    ]


def main():
    """
    Main application function that builds and runs the Streamlit UI.
    
    This function orchestrates the entire application flow:
    1. Initializes session state
    2. Loads configuration and sets up sidebar controls
    3. Handles model selection and switching
    4. Processes user input (prompts, settings)
    5. Generates images when requested
    6. Displays results with download options
    7. Shows gallery of previous generations
    
    The UI is organized into:
    - Sidebar: Model selection, style presets, advanced settings
    - Main area: Prompt input, generation button, results
    - Bottom: Gallery view (expandable)
    
    Flow:
        User selects model β†’ Enters prompt β†’ Adjusts settings β†’ 
        Clicks generate β†’ Shows progress β†’ Displays result β†’ 
        Offers download β†’ Adds to gallery
    """
    # Initialize session state for persistent data across reruns
    init_session_state()
    
    # Sidebar (needs to be first to define selected_model)
    st.sidebar.header("βš™οΈ Settings")
    
    # Model selection
    st.sidebar.subheader("πŸ€– Model Selection")
    config = ModelConfig()
    available_models = list(config.AVAILABLE_MODELS.keys())
    selected_model = st.sidebar.selectbox(
        "Choose Model",
        available_models,
        index=0,
        help="Select which model to use for generation"
    )
    
    # Detect model change and auto-clear cache
    if st.session_state.current_model is not None and st.session_state.current_model != selected_model:
        st.cache_resource.clear()
        st.sidebar.success(f"βœ… Switched to: {selected_model}")
    
    # Update current model in session state
    st.session_state.current_model = selected_model
    
    # Show LCM mode indicator if enabled
    model_config = config.AVAILABLE_MODELS[selected_model]
    if model_config.get('use_lcm', False):
        st.sidebar.success("⚑ LCM Mode: 8x Faster! (6-8 steps optimal)")
    
    # Header
    st.title("🐸 Pepe the Frog Meme Generator")
    st.markdown("Create custom Pepe memes using AI! Powered by Stable Diffusion.")
    
    st.sidebar.divider()
    
    # Style selection
    st.sidebar.subheader("🎨 Style & Prompt")
    style_options = {
        "Default": "default",
        "😊 Happy": "happy",
        "😒 Sad": "sad",
        "😏 Smug": "smug",
        "😠 Angry": "angry",
        "πŸ€” Thinking": "thinking",
        "😲 Surprised": "surprised",
    }
    
    selected_style = st.sidebar.selectbox(
        "Choose Style",
        list(style_options.keys())
    )
    style = style_options[selected_style]
    
    # Raw prompt mode
    use_raw_prompt = st.sidebar.checkbox(
        "Raw Prompt Mode",
        help="Use your exact prompt without trigger words or style modifiers"
    )
    
    # Advanced settings - adjust defaults based on LCM mode
    is_lcm_mode = model_config.get('use_lcm', False)
    
    with st.sidebar.expander("πŸ”§ Advanced Settings"):
        if is_lcm_mode:
            # LCM needs fewer steps and lower guidance
            steps = st.slider("Steps", 4, 12, 6, 1, 
                            help="⚑ LCM Mode: 4-8 steps optimal. Recommended: 6")
            guidance = st.slider("Guidance Scale", 1.0, 2.5, 1.5, 0.1,
                               help="⚑ LCM Mode: Lower guidance (1.0-2.0). Recommended: 1.5")
        else:
            # Normal mode settings
            steps = st.slider("Steps", 15, 50, 25, 5, 
                            help="Fewer steps = faster generation. 20-25 recommended for CPU")
            guidance = st.slider("Guidance Scale", 1.0, 20.0, 7.5, 0.5)
        
        use_seed = st.checkbox("Fixed Seed")
        seed = st.number_input("Seed", 0, 999999, 42) if use_seed else None
    
    # Text overlay settings
    with st.sidebar.expander("πŸ’¬ Add Text"):
        add_text = st.checkbox("Add Meme Text")
        top_text = st.text_input("Top Text") if add_text else ""
        bottom_text = st.text_input("Bottom Text") if add_text else ""
        font_size = st.slider("Font Size", 10, 100, 40, 1)
        font_path = config.FONT_PATH
    
    # Main area
    col1, col2 = st.columns([1, 1])
    
    with col1:
        st.subheader("✏️ Create Your Meme")
        
        # Prompt input
        prompt = st.text_area(
            "Describe your meme",
            height=100,
            placeholder="e.g., pepe the frog celebrating victory"
        )
        
        # Examples
        with st.expander("πŸ’‘ Example Prompts"):
            for example in get_example_prompts():
                st.write(f"β€’ {example}")
        
        # Generate button
        col_btn1, col_btn2 = st.columns([3, 1])
        with col_btn1:
            generate = st.button("🎨 Generate Meme", type="primary")
        with col_btn2:
            num_vars = st.number_input("Variations", 1, 4, 1)
    
    with col2:
        st.subheader("πŸ–ΌοΈ Generated Meme")
        placeholder = st.empty()
        
        if st.session_state.generated_images:
            placeholder.image(
                st.session_state.generated_images[-1],
                use_column_width=True
            )
        else:
            placeholder.info("Your meme will appear here...")
    
    # Generate
    if generate and prompt:
        # Debug: Print all generation inputs and get start time
        start_time = debug_generation_inputs(
            timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            prompt=prompt,
            style=style,
            steps=steps,
            guidance=guidance,
            seed=seed,
            model=selected_model,
            top_text=top_text,
            bottom_text=bottom_text,
            num_vars=num_vars,
            raw_prompt=use_raw_prompt,
            use_seed=use_seed,
            add_text=add_text,
            font_size=font_size,
            font_path=font_path
        )

        try:
            generator = load_generator(selected_model)
            processor = ImageProcessor()
            
            # Overall progress for multiple images
            overall_progress = st.progress(0)
            overall_status = st.empty()
            
            # Progress for current image generation steps
            step_progress = st.progress(0)
            step_status = st.empty()
            
            for i in range(num_vars):
                overall_status.text(f"Generating image {i+1}/{num_vars}...")
                
                # Define callback for step-by-step progress
                def progress_callback(current_step: int, total_steps: int):
                    step_progress.progress(current_step / total_steps)
                    step_status.text(f"Step {current_step}/{total_steps}")
                
                # Generate with progress callback
                image = generator.generate(
                    prompt=prompt,
                    style=style,
                    num_inference_steps=steps,
                    guidance_scale=guidance,
                    seed=seed,
                    progress_callback=progress_callback,
                    raw_prompt=use_raw_prompt
                )
                
                # Add text if requested
                if add_text and (top_text or bottom_text):
                    image = processor.add_meme_text(image, top_text, bottom_text,font_size,font_path)
                
                # Always add MJ signature
                image = processor.add_signature(image, signature="MJaheen", font_size=10, opacity=200)
                
                st.session_state.generated_images.append(image)
                st.session_state.generation_count += 1
                
                # Update overall progress
                overall_progress.progress((i + 1) / num_vars)
            
            # Clear progress indicators
            overall_progress.empty()
            overall_status.empty()
            step_progress.empty()
            step_status.empty()
            
            # Debug: Print completion time and performance metrics
            debug_generation_complete(start_time, num_vars)
            
            # Show result
            if num_vars == 1:
                placeholder.image(image, use_column_width=True)
                
                # Download
                buf = io.BytesIO()
                image.save(buf, format="PNG")
                st.download_button(
                    "⬇️ Download",
                    buf.getvalue(),
                    f"pepe_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png",
                    "image/png"
                )
            else:
                st.subheader("All Variations")
                cols = st.columns(min(num_vars, 2))
                for idx, img in enumerate(st.session_state.generated_images[-num_vars:]):
                    with cols[idx % 2]:
                        st.image(img, use_column_width=True)
        
        except Exception as e:
            st.error(f"Error: {str(e)}")
    
    elif generate and not prompt:
        st.error("Please enter a prompt!")
    
    # Gallery
    if st.session_state.generated_images:
        st.divider()
        with st.expander(f"πŸ–ΌοΈ Gallery ({len(st.session_state.generated_images)} images)"):
            cols = st.columns(4)
            for idx, img in enumerate(reversed(st.session_state.generated_images[-8:])):
                with cols[idx % 4]:
                    st.image(img, use_column_width=True)
    
    # Footer
    st.divider()
    col_a, col_b, col_c = st.columns(3)
    with col_a:
        st.metric("Total Generated", st.session_state.generation_count)
    with col_b:
        st.metric("In Gallery", len(st.session_state.generated_images))
    with col_c:
        if st.button("πŸ—‘οΈ Clear"):
            st.session_state.generated_images = []
            st.session_state.generation_count = 0
            st.rerun()
    
    # Personal Information
    st.divider()
    st.markdown("### πŸ‘¨β€πŸ’» About the Engineer")
    info_col1, info_col2 = st.columns(2)
    
    with info_col1:
        st.markdown("""
        **Contact Information:**
        - πŸ“§ Email: [Mohamed.a.jaheen@gmail.com](mailto:Mohamed.a.jaheen@gmail.com)
        - πŸ”— LinkedIn: [Mohamed Jaheen](https://www.linkedin.com/in/mohamedjaheen/)
        """)
    
    with info_col2:
        st.markdown("""
        **About this App:**
        - supported by worldquant university
        - Built with Streamlit & Stable Diffusion
        - Fine-tuned Pepe model available
        - Open source and customizable
        - MIT licences
        """)
    
    st.caption("© 2025 - AI Meme Generator (Pepe the Frog) | Made with ❀️ using Python and MJ")


if __name__ == "__main__":
    main()