inoculatemedia commited on
Commit
a944363
·
verified ·
1 Parent(s): c3ba721

Update app.py from anycoder

Browse files
Files changed (1) hide show
  1. app.py +591 -0
app.py ADDED
@@ -0,0 +1,591 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ from PIL import Image
4
+ import io
5
+ import time
6
+ import random
7
+ from typing import List, Dict, Any
8
+
9
+ # Configuration
10
+ CONFIG = {
11
+ "required_images": 10,
12
+ "playback_fps": 30,
13
+ "image_size": (512, 512)
14
+ }
15
+
16
+ class ImageSequenceGenerator:
17
+ def __init__(self):
18
+ self.images: List[str] = []
19
+ self.is_playing: bool = False
20
+ self.current_frame: int = 0
21
+ self.playback_interval: Any = None
22
+ self.is_generating: bool = False
23
+
24
+ def generate_random_image(self, prompt: str) -> str:
25
+ """Generate a random colorful image with the prompt text"""
26
+ width, height = CONFIG["image_size"]
27
+ canvas = Image.new('RGB', (width, height))
28
+
29
+ # Generate random colors
30
+ colors = []
31
+ for _ in range(5):
32
+ colors.append((random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
33
+
34
+ # Create gradient
35
+ for y in range(height):
36
+ # Calculate gradient position
37
+ pos = y / height
38
+ # Get interpolated color
39
+ if len(colors) == 1:
40
+ color = colors[0]
41
+ else:
42
+ # Find which segment we're in
43
+ segment = pos * (len(colors) - 1)
44
+ idx = int(segment)
45
+ fraction = segment - idx
46
+
47
+ # Interpolate between colors
48
+ c1 = colors[idx]
49
+ c2 = colors[idx + 1] if idx + 1 < len(colors) else colors[idx]
50
+
51
+ color = (
52
+ int(c1[0] + (c2[0] - c1[0]) * fraction),
53
+ int(c1[1] + (c2[1] - c1[1]) * fraction),
54
+ int(c1[2] + (c2[2] - c1[2]) * fraction)
55
+ )
56
+
57
+ # Draw horizontal line
58
+ for x in range(width):
59
+ canvas.putpixel((x, y), color)
60
+
61
+ # Add random shapes
62
+ draw = ImageDraw.Draw(canvas)
63
+ for _ in range(10):
64
+ color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), 180)
65
+ size = random.randint(50, 150)
66
+ x = random.randint(0, width - size)
67
+ y = random.randint(0, height - size)
68
+
69
+ if random.random() > 0.5:
70
+ # Circle
71
+ draw.ellipse([x, y, x + size, y + size], fill=color)
72
+ else:
73
+ # Rectangle
74
+ draw.rectangle([x, y, x + size, y + size], fill=color)
75
+
76
+ # Add prompt text
77
+ if prompt:
78
+ try:
79
+ font_size = 24
80
+ font = ImageFont.truetype("arial.ttf", font_size) if hasattr(ImageFont, 'truetype') else ImageFont.load_default()
81
+ text = prompt[:30] + ("..." if len(prompt) > 30 else "")
82
+ text_width, text_height = draw.textsize(text, font=font)
83
+
84
+ # Add text with white outline
85
+ for dx in [-1, 0, 1]:
86
+ for dy in [-1, 0, 1]:
87
+ if dx != 0 or dy != 0:
88
+ draw.text(
89
+ ((width - text_width) // 2 + dx, (height - text_height) // 2 + dy),
90
+ text,
91
+ font=font,
92
+ fill=(0, 0, 0)
93
+ )
94
+
95
+ draw.text(
96
+ ((width - text_width) // 2, (height - text_height) // 2),
97
+ text,
98
+ font=font,
99
+ fill=(255, 255, 255)
100
+ )
101
+ except:
102
+ pass # Skip text if font issues
103
+
104
+ # Convert to base64 string
105
+ buffered = io.BytesIO()
106
+ canvas.save(buffered, format="PNG")
107
+ img_str = f"data:image/png;base64,{base64.b64encode(buffered.getvalue()).decode()}"
108
+ return img_str
109
+
110
+ def add_image_to_buffer(self, image_data: str):
111
+ """Add image to buffer"""
112
+ self.images.append(image_data)
113
+ return self.get_state()
114
+
115
+ def remove_image_from_buffer(self, index: int):
116
+ """Remove image from buffer"""
117
+ if 0 <= index < len(self.images):
118
+ self.images.pop(index)
119
+ return self.get_state()
120
+
121
+ def clear_buffer(self):
122
+ """Clear the entire buffer"""
123
+ self.images = []
124
+ self.stop_sequence()
125
+ return self.get_state()
126
+
127
+ def play_sequence(self):
128
+ """Start playing the sequence"""
129
+ if self.is_playing or len(self.images) < CONFIG["required_images"]:
130
+ return self.get_state()
131
+
132
+ self.is_playing = True
133
+ self.current_frame = 0
134
+ return self.get_state()
135
+
136
+ def stop_sequence(self):
137
+ """Stop playing the sequence"""
138
+ if not self.is_playing:
139
+ return self.get_state()
140
+
141
+ self.is_playing = False
142
+ self.current_frame = 0
143
+ return self.get_state()
144
+
145
+ def get_current_frame(self):
146
+ """Get the current frame for display"""
147
+ if not self.is_playing or not self.images:
148
+ return None
149
+
150
+ frame_data = self.images[self.current_frame]
151
+ self.current_frame = (self.current_frame + 1) % len(self.images)
152
+ return frame_data
153
+
154
+ def get_state(self) -> Dict[str, Any]:
155
+ """Get current state for UI updates"""
156
+ return {
157
+ "buffer_count": len(self.images),
158
+ "can_play": len(self.images) >= CONFIG["required_images"] and not self.is_playing,
159
+ "is_playing": self.is_playing,
160
+ "is_generating": self.is_generating,
161
+ "images": self.images
162
+ }
163
+
164
+ def generate_image(self, prompt: str, negative_prompt: str, steps: int, guidance_scale: float, seed: int, batch_size: int):
165
+ """Generate a new image"""
166
+ if self.is_generating:
167
+ return self.get_state()
168
+
169
+ if not prompt.strip():
170
+ raise gr.Error("Please enter a prompt")
171
+
172
+ self.is_generating = True
173
+
174
+ # Simulate generation progress
175
+ for i in range(10):
176
+ time.sleep(0.3)
177
+ yield self.get_state()
178
+
179
+ try:
180
+ # Generate image
181
+ image_data = self.generate_random_image(prompt)
182
+ self.add_image_to_buffer(image_data)
183
+ except Exception as e:
184
+ raise gr.Error(f"Failed to generate image: {str(e)}")
185
+ finally:
186
+ self.is_generating = False
187
+
188
+ return self.get_state()
189
+
190
+ # Create the Gradio interface
191
+ def create_gradio_app():
192
+ generator = ImageSequenceGenerator()
193
+
194
+ # Custom CSS for styling
195
+ custom_css = """
196
+ .gradio-container {
197
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
198
+ }
199
+
200
+ .main-title {
201
+ text-align: center;
202
+ color: #6c5ce7;
203
+ margin-bottom: 2rem;
204
+ }
205
+
206
+ .status-container {
207
+ display: flex;
208
+ justify-content: space-between;
209
+ margin: 1rem 0;
210
+ gap: 1rem;
211
+ }
212
+
213
+ .status-item {
214
+ flex: 1;
215
+ padding: 1rem;
216
+ background: #f8f9fa;
217
+ border-radius: 8px;
218
+ text-align: center;
219
+ }
220
+
221
+ .status-label {
222
+ font-size: 0.9rem;
223
+ color: #666;
224
+ margin-bottom: 0.3rem;
225
+ }
226
+
227
+ .status-value {
228
+ font-weight: 700;
229
+ color: #6c5ce7;
230
+ font-size: 1.2rem;
231
+ }
232
+
233
+ .image-buffer {
234
+ display: grid;
235
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
236
+ gap: 1rem;
237
+ margin: 1rem 0;
238
+ max-height: 400px;
239
+ overflow-y: auto;
240
+ padding: 0.5rem;
241
+ border: 2px dashed #e0e0e0;
242
+ border-radius: 8px;
243
+ }
244
+
245
+ .buffer-item {
246
+ position: relative;
247
+ aspect-ratio: 1;
248
+ border-radius: 8px;
249
+ overflow: hidden;
250
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
251
+ transition: transform 0.3s ease;
252
+ }
253
+
254
+ .buffer-item:hover {
255
+ transform: scale(1.05);
256
+ z-index: 10;
257
+ }
258
+
259
+ .buffer-item img {
260
+ width: 100%;
261
+ height: 100%;
262
+ object-fit: cover;
263
+ }
264
+
265
+ .player-display {
266
+ width: 100%;
267
+ aspect-ratio: 16/9;
268
+ background: #f0f0f0;
269
+ border-radius: 8px;
270
+ overflow: hidden;
271
+ position: relative;
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: center;
275
+ margin: 1rem 0;
276
+ }
277
+
278
+ .player-display img {
279
+ width: 100%;
280
+ height: 100%;
281
+ object-fit: contain;
282
+ }
283
+
284
+ .empty-state {
285
+ text-align: center;
286
+ padding: 2rem;
287
+ color: #999;
288
+ }
289
+
290
+ .empty-state i {
291
+ font-size: 3rem;
292
+ margin-bottom: 1rem;
293
+ color: #ddd;
294
+ }
295
+
296
+ .progress-bar {
297
+ height: 8px;
298
+ background: #e0e0e0;
299
+ border-radius: 4px;
300
+ overflow: hidden;
301
+ margin: 1rem 0;
302
+ }
303
+
304
+ .progress {
305
+ height: 100%;
306
+ background: #6c5ce7;
307
+ width: 0%;
308
+ transition: width 0.3s ease;
309
+ }
310
+
311
+ .player-controls {
312
+ display: flex;
313
+ gap: 1rem;
314
+ margin: 1rem 0;
315
+ }
316
+
317
+ .settings-panel {
318
+ background: white;
319
+ border-radius: 15px;
320
+ padding: 1.5rem;
321
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
322
+ margin: 1rem 0;
323
+ }
324
+
325
+ .settings-grid {
326
+ display: grid;
327
+ grid-template-columns: 1fr 1fr;
328
+ gap: 1rem;
329
+ }
330
+
331
+ @media (max-width: 768px) {
332
+ .settings-grid {
333
+ grid-template-columns: 1fr;
334
+ }
335
+ }
336
+ """
337
+
338
+ with gr.Blocks(css=custom_css) as demo:
339
+ gr.Markdown("""
340
+ <div style="text-align: center; margin-bottom: 2rem;">
341
+ <h1 style="color: #6c5ce7; font-size: 2.5rem; margin-bottom: 0.5rem;">
342
+ <i class="fas fa-images"></i> Image Sequence Generator
343
+ </h1>
344
+ <p style="color: #666; font-size: 1.1rem;">
345
+ Generate images and play them back as a sequence at 30 FPS
346
+ </p>
347
+ <div style="margin-top: 1rem; font-size: 0.9rem; color: #666;">
348
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #6c5ce7; font-weight: 600;">anycoder</a>
349
+ </div>
350
+ </div>
351
+ """)
352
+
353
+ with gr.Row():
354
+ with gr.Column(scale=1):
355
+ # Controls Section
356
+ gr.Markdown("### Generation Controls")
357
+
358
+ prompt = gr.Textbox(
359
+ label="Prompt",
360
+ placeholder="Describe the image you want to generate",
361
+ lines=2
362
+ )
363
+
364
+ negative_prompt = gr.Textbox(
365
+ label="Negative Prompt",
366
+ placeholder="What you don't want to see",
367
+ lines=2
368
+ )
369
+
370
+ with gr.Accordion("Advanced Settings", open=False):
371
+ with gr.Row():
372
+ steps = gr.Slider(
373
+ label="Steps",
374
+ minimum=1,
375
+ maximum=20,
376
+ value=4,
377
+ step=1
378
+ )
379
+ guidance_scale = gr.Slider(
380
+ label="Guidance Scale",
381
+ minimum=0,
382
+ maximum=20,
383
+ value=0,
384
+ step=0.1
385
+ )
386
+
387
+ with gr.Row():
388
+ seed = gr.Number(
389
+ label="Seed",
390
+ value=-1,
391
+ minimum=-1
392
+ )
393
+ batch_size = gr.Number(
394
+ label="Batch Size",
395
+ value=1,
396
+ minimum=1,
397
+ maximum=4
398
+ )
399
+
400
+ generate_btn = gr.Button(
401
+ value="Generate Image",
402
+ variant="primary",
403
+ size="lg"
404
+ )
405
+
406
+ progress_bar = gr.HTML("""
407
+ <div class="progress-bar">
408
+ <div class="progress" id="progress-bar" style="width: 0%;"></div>
409
+ </div>
410
+ """)
411
+
412
+ # State components
413
+ state_display = gr.JSON(visible=False)
414
+ buffer_state = gr.State(generator.get_state())
415
+
416
+ with gr.Column(scale=1):
417
+ # Preview Section
418
+ gr.Markdown("### Image Buffer & Player")
419
+
420
+ # Status display
421
+ status_html = gr.HTML("""
422
+ <div class="status-container">
423
+ <div class="status-item">
424
+ <div class="status-label">Images in Buffer</div>
425
+ <div class="status-value" id="buffer-count">0</div>
426
+ </div>
427
+ <div class="status-item">
428
+ <div class="status-label">Required for Playback</div>
429
+ <div class="status-value">10</div>
430
+ </div>
431
+ <div class="status-item">
432
+ <div class="status-label">Playback Speed</div>
433
+ <div class="status-value">30 FPS</div>
434
+ </div>
435
+ </div>
436
+ """)
437
+
438
+ with gr.Row():
439
+ clear_btn = gr.Button("Clear Buffer", variant="secondary")
440
+ play_btn = gr.Button("Play Sequence", variant="primary")
441
+ stop_btn = gr.Button("Stop", variant="stop")
442
+
443
+ player_display = gr.Image(
444
+ label="Player Display",
445
+ type="pil",
446
+ height=300,
447
+ visible=True
448
+ )
449
+
450
+ gr.Markdown("#### Image Buffer")
451
+ image_buffer = gr.Gallery(
452
+ label="Generated Images",
453
+ columns=4,
454
+ height="auto",
455
+ object_fit="cover"
456
+ )
457
+
458
+ # Event handlers
459
+ def update_ui(state):
460
+ # Update status display
461
+ status_html.value = f"""
462
+ <div class="status-container">
463
+ <div class="status-item">
464
+ <div class="status-label">Images in Buffer</div>
465
+ <div class="status-value">{state['buffer_count']}</div>
466
+ </div>
467
+ <div class="status-item">
468
+ <div class="status-label">Required for Playback</div>
469
+ <div class="status-value">{CONFIG['required_images']}</div>
470
+ </div>
471
+ <div class="status-item">
472
+ <div class="status-label">Playback Speed</div>
473
+ <div class="status-value">30 FPS</div>
474
+ </div>
475
+ </div>
476
+ """
477
+
478
+ # Update button states
479
+ play_btn.interactive = state['can_play']
480
+ stop_btn.interactive = state['is_playing']
481
+ generate_btn.interactive = not state['is_generating']
482
+
483
+ # Update image buffer
484
+ if state['images']:
485
+ images = [gr.Image(value=img, visible=True) for img in state['images']]
486
+ return {
487
+ status_html: status_html,
488
+ play_btn: gr.Button(interactive=state['can_play']),
489
+ stop_btn: gr.Button(interactive=state['is_playing']),
490
+ generate_btn: gr.Button(interactive=not state['is_generating']),
491
+ image_buffer: gr.Gallery(value=state['images'], visible=True)
492
+ }
493
+ else:
494
+ return {
495
+ status_html: status_html,
496
+ play_btn: gr.Button(interactive=state['can_play']),
497
+ stop_btn: gr.Button(interactive=state['is_playing']),
498
+ generate_btn: gr.Button(interactive=not state['is_generating']),
499
+ image_buffer: gr.Gallery(value=[], visible=True)
500
+ }
501
+
502
+ def generate_image_wrapper(prompt, negative_prompt, steps, guidance_scale, seed, batch_size):
503
+ for state in generator.generate_image(prompt, negative_prompt, steps, guidance_scale, seed, batch_size):
504
+ yield update_ui(state)
505
+
506
+ def play_sequence_wrapper():
507
+ state = generator.play_sequence()
508
+ return update_ui(state)
509
+
510
+ def stop_sequence_wrapper():
511
+ state = generator.stop_sequence()
512
+ return update_ui(state)
513
+
514
+ def clear_buffer_wrapper():
515
+ state = generator.clear_buffer()
516
+ return update_ui(state)
517
+
518
+ # Connect event handlers
519
+ generate_btn.click(
520
+ fn=generate_image_wrapper,
521
+ inputs=[prompt, negative_prompt, steps, guidance_scale, seed, batch_size],
522
+ outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
523
+ )
524
+
525
+ play_btn.click(
526
+ fn=play_sequence_wrapper,
527
+ outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
528
+ )
529
+
530
+ stop_btn.click(
531
+ fn=stop_sequence_wrapper,
532
+ outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
533
+ )
534
+
535
+ clear_btn.click(
536
+ fn=clear_buffer_wrapper,
537
+ outputs=[status_html, play_btn, stop_btn, generate_btn, image_buffer]
538
+ )
539
+
540
+ # Timer for playback
541
+ timer = gr.Timer(1.0 / CONFIG["playback_fps"])
542
+
543
+ def update_player_display():
544
+ if generator.is_playing and generator.images:
545
+ frame_data = generator.get_current_frame()
546
+ if frame_data:
547
+ # Convert base64 to PIL Image
548
+ from PIL import Image
549
+ import io
550
+ import base64
551
+
552
+ # Extract base64 data
553
+ header, encoded = frame_data.split(",", 1)
554
+ binary_data = base64.b64decode(encoded)
555
+ image = Image.open(io.BytesIO(binary_data))
556
+ return image
557
+ return None
558
+
559
+ player_display.change(
560
+ fn=update_player_display,
561
+ every=timer,
562
+ outputs=player_display
563
+ )
564
+
565
+ return demo
566
+
567
+ # Create and launch the app
568
+ if __name__ == "__main__":
569
+ demo = create_gradio_app()
570
+ demo.launch(
571
+ theme=gr.themes.Soft(
572
+ primary_hue="purple",
573
+ secondary_hue="indigo",
574
+ neutral_hue="slate",
575
+ font=[gr.themes.GoogleFont("Segoe UI"), "sans-serif"],
576
+ text_size="lg",
577
+ spacing_size="lg",
578
+ radius_size="md"
579
+ ).set(
580
+ button_primary_background_fill="*primary_600",
581
+ button_primary_background_fill_hover="*primary_700",
582
+ block_title_text_weight="600",
583
+ ),
584
+ footer_links=[
585
+ {
586
+ "label": "Built with anycoder",
587
+ "url": "https://huggingface.co/spaces/akhaliq/anycoder"
588
+ }
589
+ ],
590
+ show_error=True
591
+ )