codelion commited on
Commit
7f3893e
·
verified ·
1 Parent(s): 66346a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +242 -340
app.py CHANGED
@@ -10,12 +10,6 @@ import random
10
  import urllib.parse
11
  import time
12
 
13
- # Check Gradio version
14
- required_version = "4.44.0"
15
- current_version = gr.__version__
16
- if current_version < required_version:
17
- raise ValueError(f"Gradio version {current_version} is outdated. Please upgrade to {required_version} or later using 'pip install gradio=={required_version}'.")
18
-
19
  # Initialize the Google Generative AI client with the API key from environment variables
20
  try:
21
  api_key = os.environ['GEMINI_API_KEY']
@@ -47,6 +41,40 @@ SAFETY_SETTINGS = [
47
  ),
48
  ]
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  def clean_response_text(response_text):
51
  """
52
  Clean the API response by removing Markdown code block markers.
@@ -61,10 +89,7 @@ def clean_response_text(response_text):
61
  def generate_ideas(user_input):
62
  """
63
  Generate a diverse set of ideas based on the user's input concept using the LLM.
64
- Yields progress updates for the loading UI.
65
  """
66
- yield (10, f"Brainstorming epic ideas for {user_input}... 🌟")
67
-
68
  prompt = f"""
69
  The user has provided the concept: "{user_input}". You must generate 5 diverse and creative ideas for a TikTok video that are directly and explicitly related to "{user_input}".
70
  Each idea must clearly incorporate and focus on the core theme of "{user_input}" without deviating into unrelated topics.
@@ -87,14 +112,9 @@ def generate_ideas(user_input):
87
  response_json = json.loads(cleaned_text)
88
  if 'ideas' not in response_json or not isinstance(response_json['ideas'], list) or len(response_json['ideas']) != 5:
89
  raise ValueError("Invalid JSON format: 'ideas' key missing, not a list, or incorrect length")
90
-
91
- ideas = response_json['ideas']
92
- yield (20, f"Ideas locked in for {user_input}! 🚀")
93
- return ideas
94
-
95
  except Exception as e:
96
  print(f"Error generating ideas: {e}")
97
- yield (20, f"Oops, tweaking the plan for {user_input}... 🔧")
98
  return [
99
  f"A dramatic {user_input} scene with cinematic lighting",
100
  f"A close-up of {user_input} in a futuristic setting",
@@ -105,8 +125,8 @@ def generate_ideas(user_input):
105
 
106
  def generate_item(user_input, ideas, generate_video=False, max_retries=3):
107
  """
108
- Generate a single feed item (image and optionally one video) using one of the ideas.
109
- Yields progress updates for the loading UI.
110
  """
111
  video_base64 = None
112
  max_total_attempts = 3
@@ -114,7 +134,8 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
114
  total_attempts = 0
115
  while total_attempts < max_total_attempts:
116
  total_attempts += 1
117
- yield (20 + total_attempts * 10, f"Attempt {total_attempts} to craft your {user_input} masterpiece... 🎨")
 
118
 
119
  generated_image = None
120
  text = None
@@ -122,7 +143,8 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
122
  image_prompt = None
123
 
124
  for image_attempt in range(max_retries):
125
- yield (20 + total_attempts * 10 + image_attempt * 5, f"Crafting a stunning image for {user_input}... 📸")
 
126
  selected_idea = random.choice(ideas)
127
  prompt = f"""
128
  The user has provided the concept: "{user_input}". Based on this concept and the specific idea "{selected_idea}", create content for a TikTok video.
@@ -141,21 +163,16 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
141
  safety_settings=SAFETY_SETTINGS
142
  )
143
  )
144
- if not response.text or response.text.isspace():
145
- raise ValueError("Empty response from API")
146
  cleaned_text = clean_response_text(response.text)
147
  response_json = json.loads(cleaned_text)
148
- if 'caption' not in response_json or 'image_prompt' not in response_json:
149
- raise ValueError("Invalid JSON format: 'caption' or 'image_prompt' key missing")
150
  text = response_json['caption']
151
  image_prompt = response_json['image_prompt']
152
  except Exception as e:
153
- print(f"Error generating item (image attempt {image_attempt + 1}): {e}")
154
  text = f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}"
155
  image_prompt = f"A vivid scene of {selected_idea} related to {user_input}, in a vibrant pop art style, no text or letters"
156
 
157
  try:
158
- yield (40 + image_attempt * 5, f"Rendering your {user_input} vision... ✨")
159
  imagen = client.models.generate_images(
160
  model='imagen-3.0-generate-002',
161
  prompt=image_prompt,
@@ -173,17 +190,15 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
173
  buffered = BytesIO()
174
  image.save(buffered, format="PNG")
175
  img_str = base64.b64encode(buffered.getvalue()).decode()
176
- yield (50, f"Image for {user_input} is ready! 🎉")
177
  break
178
  else:
179
  if image_attempt == max_retries - 1:
180
- yield (50, f"Tweaking the image for {user_input}... 🔄")
181
  if total_attempts == max_total_attempts:
182
  image = Image.new('RGB', (360, 640), color='gray')
183
  buffered = BytesIO()
184
  image.save(buffered, format="PNG")
185
  img_str = base64.b64encode(buffered.getvalue()).decode()
186
- yield (60, f"Using a placeholder for {user_input}... 🖼️")
187
  return {
188
  'text': text,
189
  'image_base64': img_str,
@@ -191,16 +206,16 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
191
  'ideas': ideas
192
  }
193
  break
 
194
  except Exception as e:
195
- print(f"Error generating image (image attempt {image_attempt + 1}): {e}")
196
  if image_attempt == max_retries - 1:
197
- yield (50, f"Retrying image for {user_input}... 🔄")
198
  if total_attempts == max_total_attempts:
199
  image = Image.new('RGB', (360, 640), color='gray')
200
  buffered = BytesIO()
201
  image.save(buffered, format="PNG")
202
  img_str = base64.b64encode(buffered.getvalue()).decode()
203
- yield (60, f"Using a placeholder for {user_input}... 🖼️")
204
  return {
205
  'text': text,
206
  'image_base64': img_str,
@@ -208,20 +223,16 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
208
  'ideas': ideas
209
  }
210
  break
 
211
 
212
  if generate_video and generated_image is not None:
213
- max_video_retries_per_image = 2
214
- video_generated = False
215
-
216
- # Image-to-video generation
217
  try:
218
- yield (60, f"Filming a viral video for {user_input}... 🎥")
219
  video_prompt = f"""
220
  The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
221
  Use a close-up shot with a slow dolly shot circling around the subject,
222
  using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
223
  """
224
- print(f"Attempting image-to-video generation: {video_prompt}")
225
  operation = client.models.generate_videos(
226
  model="veo-2.0-generate-001",
227
  prompt=video_prompt,
@@ -236,114 +247,62 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
236
  while not operation.done:
237
  time.sleep(20)
238
  operation = client.operations.get(operation)
239
-
240
- print(f"Image-to-video operation: done={operation.done}, error={operation.error}, response={operation.response}")
241
  if operation.error:
242
  raise ValueError(f"Video generation failed: {operation.error.message}")
243
- if operation.response is None or not hasattr(operation.response, 'generated_videos') or operation.response.generated_videos is None:
244
- raise ValueError("Video generation failed: No generated_videos in response")
245
-
246
- if len(operation.response.generated_videos) > 0:
247
  video = operation.response.generated_videos[0]
248
- if video is None or not hasattr(video, 'video'):
249
- raise ValueError("Video is invalid or missing video data")
250
  video_data = client.files.download(file=video.video)
251
- if isinstance(video_data, bytes):
252
- video_bytes = video_data
253
- else:
254
- video_buffer = BytesIO()
255
- for chunk in video_data:
256
- video_buffer.write(chunk)
257
- video_bytes = video_buffer.getvalue()
258
  video_base64 = base64.b64encode(video_bytes).decode()
259
- video_generated = True
260
- yield (90, f"Video for {user_input} is a wrap! 🎬")
261
  return {
262
  'text': text,
263
  'image_base64': img_str,
264
  'video_base64': video_base64,
265
  'ideas': ideas
266
  }
267
- else:
268
- raise ValueError("No video was generated")
269
  except Exception as e:
270
- print(f"Error generating video (image-to-video): {e}")
271
- yield (70, f"Switching to a new video approach for {user_input}... 🎞️")
272
-
273
- # Text-to-video generation (fallback)
274
- if not video_generated:
275
- for video_attempt in range(max_video_retries_per_image):
276
- try:
277
- yield (75 + video_attempt * 5, f"Trying a fresh video take for {user_input}... 📹")
278
- video_prompt_base = f"""
279
- The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
280
- Use a close-up shot with a slow dolly shot circling around the subject,
281
- using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
282
- """
283
- video_prompt = video_prompt_base if video_attempt == 0 else f"""
284
- The user concept is "{user_input}". Based on this and a simplified scene: {image_prompt}, create a video.
285
- Use a static close-up shot of the subject in a realistic style.
286
- """
287
- print(f"Attempting text-to-video generation (attempt {video_attempt + 1}): {video_prompt}")
288
- operation = client.models.generate_videos(
289
- model="veo-2.0-generate-001",
290
- prompt=video_prompt,
291
- config=types.GenerateVideosConfig(
292
- aspect_ratio="9:16",
293
- number_of_videos=1,
294
- duration_seconds=8,
295
- negative_prompt="blurry, low quality, text, letters"
296
- )
297
  )
298
- while not operation.done:
299
- time.sleep(20)
300
- operation = client.operations.get(operation)
301
-
302
- print(f"Text-to-video operation (attempt {video_attempt + 1}): done={operation.done}, error={operation.error}, response={operation.response}")
303
- if operation.error:
304
- raise ValueError(f"Video generation failed: {operation.error.message}")
305
- if operation.response is None or not hasattr(operation.response, 'generated_videos') or operation.response.generated_videos is None:
306
- raise ValueError("Video generation failed: No generated_videos in response")
307
-
308
- if len(operation.response.generated_videos) > 0:
309
- video = operation.response.generated_videos[0]
310
- if video is None or not hasattr(video, 'video'):
311
- raise ValueError("Video is invalid or missing video data")
312
- video_data = client.files.download(file=video.video)
313
- if isinstance(video_data, bytes):
314
- video_bytes = video_data
315
- else:
316
- video_buffer = BytesIO()
317
- for chunk in video_data:
318
- video_buffer.write(chunk)
319
- video_bytes = video_buffer.getvalue()
320
- video_base64 = base64.b64encode(video_bytes).decode()
321
- video_generated = True
322
- yield (90, f"Video for {user_input} is a wrap! 🎬")
323
- return {
324
- 'text': text,
325
- 'image_base64': img_str,
326
- 'video_base64': video_base64,
327
- 'ideas': ideas
328
- }
329
- else:
330
- raise ValueError("No video was generated")
331
- except Exception as e:
332
- print(f"Error generating video (text-to-video attempt {video_attempt + 1}): {e}")
333
- if video_attempt == max_video_retries_per_image - 1:
334
- yield (85, f"Finalizing without video for {user_input}... 📌")
335
- if total_attempts == max_total_attempts:
336
- yield (95, f"Polishing your {user_input} masterpiece... ✨")
337
- return {
338
- 'text': text,
339
- 'image_base64': img_str,
340
- 'video_base64': video_base64,
341
- 'ideas': ideas
342
- }
343
- break
344
 
345
  if img_str is not None:
346
- yield (95, f"Polishing your {user_input} masterpiece... ✨")
347
  return {
348
  'text': text,
349
  'image_base64': img_str,
@@ -351,13 +310,11 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
351
  'ideas': ideas
352
  }
353
 
354
- print("Max total attempts reached without successful image generation. Using placeholder.")
355
- yield (95, f"Falling back to a placeholder for {user_input}... 🖼️")
356
  image = Image.new('RGB', (360, 640), color='gray')
357
  buffered = BytesIO()
358
  image.save(buffered, format="PNG")
359
  img_str = base64.b64encode(buffered.getvalue()).decode()
360
- yield (100, f"Ready to roll with {user_input}! 🚀")
361
  return {
362
  'text': f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}",
363
  'image_base64': img_str,
@@ -365,128 +322,41 @@ def generate_item(user_input, ideas, generate_video=False, max_retries=3):
365
  'ideas': ideas
366
  }
367
 
368
- def generate_progress_html(progress, message, user_input):
369
- """
370
- Generate HTML for the progress bar and witty text.
371
- """
372
- return f"""
373
- <div id="progress-container" style="
374
- display: flex;
375
- flex-direction: column;
376
- align-items: center;
377
- justify-content: center;
378
- max-width: 360px;
379
- margin: 0 auto;
380
- background-color: #000;
381
- height: 200px;
382
- border: 1px solid #333;
383
- border-radius: 10px;
384
- color: white;
385
- font-family: Arial, sans-serif;
386
- position: relative;
387
- ">
388
- <div id="loading-message" style="
389
- font-size: 18px;
390
- font-weight: bold;
391
- text-align: center;
392
- margin-bottom: 20px;
393
- text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
394
- ">
395
- {message}
396
- </div>
397
- <div style="
398
- width: 80%;
399
- height: 10px;
400
- background-color: #333;
401
- border-radius: 5px;
402
- overflow: hidden;
403
- ">
404
- <div id="progress-bar" style="
405
- width: {progress}%;
406
- height: 100%;
407
- background: linear-gradient(to right, #ff2d55, #ff5e78);
408
- transition: width 0.5s ease-in-out;
409
- "></div>
410
- </div>
411
- <div style="
412
- margin-top: 10px;
413
- font-size: 14px;
414
- color: #ccc;
415
- ">
416
- {int(progress)}% Complete
417
- </div>
418
- <style>
419
- @keyframes pulse {{
420
- 0% {{ opacity: 1; }}
421
- 50% {{ opacity: 0.5; }}
422
- 100% {{ opacity: 1; }}
423
- }}
424
- #loading-message {{
425
- animation: pulse 2s infinite;
426
- }}
427
- </style>
428
- </div>
429
- """
430
-
431
- def process_generator(generator):
432
- """
433
- Process a generator to collect progress updates and the final result.
434
- Returns a tuple of (progress_updates, final_result).
435
- """
436
- progress_updates = []
437
- final_result = None
438
- for item in generator:
439
- if isinstance(item, tuple):
440
- progress_updates.append(item)
441
- else:
442
- final_result = item
443
- return progress_updates, final_result
444
-
445
  def start_feed(user_input, generate_video, current_index, feed_items):
446
  """
447
- Start or update the feed based on the user input.
448
- Returns all outputs expected by Gradio.
449
  """
450
  if not user_input.strip():
451
  user_input = "trending"
452
-
453
  current_user_input = user_input
454
  is_loading = True
455
- progress_html = generate_progress_html(0, f"Getting started with {user_input}... 🚀", user_input)
456
  share_links = ""
457
 
458
- # Ensure feed_items is a list
459
- feed_items = feed_items if isinstance(feed_items, list) else []
460
- print(f"start_feed: feed_items type={type(feed_items)}, value={feed_items}")
461
 
462
  try:
463
- # Generate ideas
464
- ideas_gen = generate_ideas(user_input)
465
- progress_updates, ideas = process_generator(ideas_gen)
466
- for progress, message in progress_updates:
467
- progress_html = generate_progress_html(progress, message, user_input)
468
- yield (progress_html, None, None, None, None, is_loading, progress_html)
469
-
470
- # Generate item
471
- item_gen = generate_item(user_input, ideas, generate_video=generate_video)
472
- progress_updates, item = process_generator(item_gen)
473
- for progress, message in progress_updates:
474
- progress_html = generate_progress_html(progress, message, user_input)
475
- yield (progress_html, None, None, None, None, is_loading, progress_html)
476
 
477
- feed_items = [item]
478
- current_index = 0
479
- feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
480
- share_links = generate_share_links(
481
- item['image_base64'],
482
- item['video_base64'],
483
- item['text']
484
- )
 
 
 
 
 
 
485
  except Exception as e:
486
  print(f"Error in start_feed: {e}")
487
- feed_items = []
488
- current_index = 0
489
- feed_html = """
490
  <div style="
491
  display: flex;
492
  flex-direction: column;
@@ -504,61 +374,60 @@ def start_feed(user_input, generate_video, current_index, feed_items):
504
  <p>Error generating content. Please try again!</p>
505
  </div>
506
  """
507
- progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
508
  is_loading = False
509
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
510
-
511
- is_loading = False
512
- progress_html = ""
513
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
514
 
515
  def load_next(user_input, generate_video, current_index, feed_items):
516
  """
517
- Load the next item in the feed.
518
- Returns all outputs expected by Gradio.
519
  """
520
  current_user_input = user_input if user_input.strip() else "trending"
521
  user_input = current_user_input
522
  is_loading = True
523
- progress_html = generate_progress_html(0, f"Loading next {user_input} vibe... 🚀", user_input)
524
  share_links = ""
525
-
526
- # Ensure feed_items is a list
527
- feed_items = feed_items if isinstance(feed_items, list) else []
528
- print(f"load_next: feed_items type={type(feed_items)}, value={feed_items}, current_index={current_index}")
529
 
530
  try:
531
  if current_index + 1 < len(feed_items):
532
  current_index += 1
533
- progress_html = generate_progress_html(50, f"Switching to the next {user_input} moment... 🔄", user_input)
534
- yield (progress_html, None, None, None, None, is_loading, progress_html)
 
 
 
 
 
 
 
535
  else:
536
- ideas = feed_items[-1]['ideas'] if feed_items else None
537
- if not ideas:
538
- ideas_gen = generate_ideas(user_input)
539
- progress_updates, ideas = process_generator(ideas_gen)
540
- for progress, message in progress_updates:
541
- progress_html = generate_progress_html(progress, message, user_input)
542
- yield (progress_html, None, None, None, None, is_loading, progress_html)
543
 
544
- new_item_gen = generate_item(user_input, ideas, generate_video=generate_video)
545
- progress_updates, new_item = process_generator(new_item_gen)
546
- for progress, message in progress_updates:
547
- progress_html = generate_progress_html(progress, message, user_input)
548
- yield (progress_html, None, None, None, None, is_loading, progress_html)
549
-
550
- feed_items.append(new_item)
551
- current_index = len(feed_items) - 1
552
-
553
- feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
554
- share_links = generate_share_links(
555
- feed_items[current_index]['image_base64'],
556
- feed_items[current_index]['video_base64'],
557
- feed_items[current_index]['text']
558
- )
 
 
 
559
  except Exception as e:
560
  print(f"Error in load_next: {e}")
561
- feed_html = """
562
  <div style="
563
  display: flex;
564
  flex-direction: column;
@@ -576,13 +445,9 @@ def load_next(user_input, generate_video, current_index, feed_items):
576
  <p>Error generating content. Please try again!</p>
577
  </div>
578
  """
579
- progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
580
  is_loading = False
581
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
582
-
583
- is_loading = False
584
- progress_html = ""
585
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
586
 
587
  def load_previous(user_input, generate_video, current_index, feed_items):
588
  """
@@ -590,26 +455,22 @@ def load_previous(user_input, generate_video, current_index, feed_items):
590
  """
591
  current_user_input = user_input if user_input.strip() else "trending"
592
 
593
- # Ensure feed_items is a list
594
- feed_items = feed_items if isinstance(feed_items, list) else []
595
- print(f"load_previous: feed_items type={type(feed_items)}, value={feed_items}, current_index={current_index}")
596
-
597
  if current_index > 0:
598
  current_index -= 1
599
- feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
600
  share_links = generate_share_links(
601
- feed_items[current_index]['image_base64'] if feed_items else "",
602
- feed_items[current_index]['video_base64'] if feed_items else None,
603
- feed_items[current_index]['text'] if feed_items else ""
604
  )
605
- return current_user_input, current_index, feed_items, feed_html, share_links, False, ""
606
 
607
  def generate_share_links(image_base64, video_base64, caption):
608
  """
609
  Generate share links for social media platforms with download links for image and video.
610
  """
611
- image_data_url = f"data:image/png;base64,{image_base64}" if image_base64 else "#"
612
- encoded_caption = urllib.parse.quote(caption if caption else "")
613
 
614
  download_links = f"""
615
  <p style="text-align: center; margin-bottom: 10px;">Download the media to share:</p>
@@ -716,7 +577,7 @@ def generate_share_links(image_base64, video_base64, caption):
716
  justify-content: center;
717
  margin-top: 10px;
718
  ">
719
- <a href="https://studio.youtube.com/channel/UC/videos/upload?description={caption}" target="_blank" style="
720
  background-color: #ff0000;
721
  color: white;
722
  padding: 8px 16px;
@@ -746,20 +607,18 @@ def generate_share_links(image_base64, video_base64, caption):
746
  </div>
747
  """.format(caption=encoded_caption)
748
 
749
- def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_input="", is_loading=False, progress=0, message=""):
750
  """
751
- Generate an HTML string to display the current feed item or loading state.
752
  """
753
- # Ensure feed_items is a list
754
- feed_items = feed_items if isinstance(feed_items, list) else []
755
- print(f"generate_html: feed_items type={type(feed_items)}, value={feed_items}, current_index={current_index}")
756
-
757
  if is_loading:
758
- return """
 
759
  <div id="feed-container" style="
760
  display: flex;
761
  flex-direction: column;
762
  align-items: center;
 
763
  max-width: 360px;
764
  margin: 0 auto;
765
  background-color: #000;
@@ -768,13 +627,61 @@ def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_inpu
768
  border-radius: 10px;
769
  color: white;
770
  font-family: Arial, sans-serif;
 
771
  ">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  </div>
773
  """
774
 
775
  if not feed_items or current_index >= len(feed_items):
776
  return """
777
- <div id="feed-container" style="
778
  display: flex;
779
  flex-direction: column;
780
  align-items: center;
@@ -793,33 +700,30 @@ def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_inpu
793
  """
794
 
795
  item = feed_items[current_index]
796
- if item['video_base64']:
797
- media_element = f"""
798
- <video id="feed-video" controls style="
799
- width: 100%;
800
- height: 100%;
801
- object-fit: cover;
802
- position: absolute;
803
- top: 0;
804
- left: 0;
805
- z-index: 1;
806
- ">
807
- <source src="data:video/mp4;base64,{item['video_base64']}" type="video/mp4">
808
- Your browser does not support the video tag.
809
- </video>
810
- """
811
- else:
812
- media_element = f"""
813
- <img id="feed-image" src="data:image/png;base64,{item['image_base64']}" style="
814
- width: 100%;
815
- height: 100%;
816
- object-fit: cover;
817
- position: absolute;
818
- top: 0;
819
- left: 0;
820
- z-index: 1;
821
- ">
822
- """
823
 
824
  html_str = f"""
825
  <div id="feed-container" style="
@@ -871,7 +775,6 @@ def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_inpu
871
  }}
872
  }}
873
  </script>
874
- <button id="previous-button" style="display: none;" onclick="document.getElementById('previous-button').click()"></button>
875
  """
876
  return html_str
877
 
@@ -890,7 +793,7 @@ with gr.Blocks(
890
  ) as demo:
891
  current_user_input = gr.State(value="")
892
  current_index = gr.State(value=0)
893
- feed_items = gr.State(value=[]) # Ensure initial value is an empty list
894
  is_loading = gr.State(value=False)
895
  share_links = gr.State(value="")
896
 
@@ -908,29 +811,28 @@ with gr.Blocks(
908
  )
909
  magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
910
 
911
- progress_html = gr.HTML(label="Progress", visible=True)
912
  feed_html = gr.HTML()
913
  share_html = gr.HTML(label="Share this item:")
914
 
915
  user_input.submit(
916
  fn=start_feed,
917
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
918
- outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
 
919
  )
920
 
921
  magic_button.click(
922
  fn=load_next,
923
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
924
- outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
 
925
  )
926
 
927
  previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
928
-
929
  previous_button.click(
930
  fn=load_previous,
931
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
932
- outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
933
  )
934
 
935
- # Launch the app
936
  demo.launch()
 
10
  import urllib.parse
11
  import time
12
 
 
 
 
 
 
 
13
  # Initialize the Google Generative AI client with the API key from environment variables
14
  try:
15
  api_key = os.environ['GEMINI_API_KEY']
 
41
  ),
42
  ]
43
 
44
+ # Define progress stages and corresponding witty messages
45
+ PROGRESS_STAGES = {
46
+ "initializing": [
47
+ "Warming up the creativity engine... 🚀",
48
+ "Getting ready to spark some magic... ✨",
49
+ "Loading the idea generator... ⚡"
50
+ ],
51
+ "generating_ideas": [
52
+ "Brainstorming brilliant ideas... 💡",
53
+ "Cooking up some viral concepts... 🍳",
54
+ "Mixing creativity with a pinch of flair... 🎨"
55
+ ],
56
+ "creating_image": [
57
+ "Painting a stunning visual... 🖌️",
58
+ "Snapping the perfect shot... 📸",
59
+ "Crafting a masterpiece pixel by pixel... 🖼️"
60
+ ],
61
+ "retrying_image": [
62
+ "Tweaking the image for perfection... 🔧",
63
+ "Giving it another go for that wow factor... 🔄",
64
+ "Adjusting the lens for a better shot... 🎥"
65
+ ],
66
+ "generating_video": [
67
+ "Rolling the cameras for a viral video... 🎬",
68
+ "Animating the scene with cinematic vibes... 🌟",
69
+ "Producing a TikTok-worthy clip... 🎞️"
70
+ ],
71
+ "finalizing": [
72
+ "Putting the final touches... 🎀",
73
+ "Polishing your content to shine... 💎",
74
+ "Almost ready to blow up the feed... 🔥"
75
+ ]
76
+ }
77
+
78
  def clean_response_text(response_text):
79
  """
80
  Clean the API response by removing Markdown code block markers.
 
89
  def generate_ideas(user_input):
90
  """
91
  Generate a diverse set of ideas based on the user's input concept using the LLM.
 
92
  """
 
 
93
  prompt = f"""
94
  The user has provided the concept: "{user_input}". You must generate 5 diverse and creative ideas for a TikTok video that are directly and explicitly related to "{user_input}".
95
  Each idea must clearly incorporate and focus on the core theme of "{user_input}" without deviating into unrelated topics.
 
112
  response_json = json.loads(cleaned_text)
113
  if 'ideas' not in response_json or not isinstance(response_json['ideas'], list) or len(response_json['ideas']) != 5:
114
  raise ValueError("Invalid JSON format: 'ideas' key missing, not a list, or incorrect length")
115
+ return response_json['ideas']
 
 
 
 
116
  except Exception as e:
117
  print(f"Error generating ideas: {e}")
 
118
  return [
119
  f"A dramatic {user_input} scene with cinematic lighting",
120
  f"A close-up of {user_input} in a futuristic setting",
 
125
 
126
  def generate_item(user_input, ideas, generate_video=False, max_retries=3):
127
  """
128
+ Generate a single feed item with progress updates.
129
+ Yields progress stage and message for UI updates.
130
  """
131
  video_base64 = None
132
  max_total_attempts = 3
 
134
  total_attempts = 0
135
  while total_attempts < max_total_attempts:
136
  total_attempts += 1
137
+
138
+ yield {"stage": "initializing", "message": random.choice(PROGRESS_STAGES["initializing"]), "progress": 10}
139
 
140
  generated_image = None
141
  text = None
 
143
  image_prompt = None
144
 
145
  for image_attempt in range(max_retries):
146
+ yield {"stage": "creating_image", "message": random.choice(PROGRESS_STAGES["creating_image"]), "progress": 30 + (image_attempt * 10)}
147
+
148
  selected_idea = random.choice(ideas)
149
  prompt = f"""
150
  The user has provided the concept: "{user_input}". Based on this concept and the specific idea "{selected_idea}", create content for a TikTok video.
 
163
  safety_settings=SAFETY_SETTINGS
164
  )
165
  )
 
 
166
  cleaned_text = clean_response_text(response.text)
167
  response_json = json.loads(cleaned_text)
 
 
168
  text = response_json['caption']
169
  image_prompt = response_json['image_prompt']
170
  except Exception as e:
171
+ print(f"Error generating item: {e}")
172
  text = f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}"
173
  image_prompt = f"A vivid scene of {selected_idea} related to {user_input}, in a vibrant pop art style, no text or letters"
174
 
175
  try:
 
176
  imagen = client.models.generate_images(
177
  model='imagen-3.0-generate-002',
178
  prompt=image_prompt,
 
190
  buffered = BytesIO()
191
  image.save(buffered, format="PNG")
192
  img_str = base64.b64encode(buffered.getvalue()).decode()
 
193
  break
194
  else:
195
  if image_attempt == max_retries - 1:
 
196
  if total_attempts == max_total_attempts:
197
  image = Image.new('RGB', (360, 640), color='gray')
198
  buffered = BytesIO()
199
  image.save(buffered, format="PNG")
200
  img_str = base64.b64encode(buffered.getvalue()).decode()
201
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
202
  return {
203
  'text': text,
204
  'image_base64': img_str,
 
206
  'ideas': ideas
207
  }
208
  break
209
+ yield {"stage": "retrying_image", "message": random.choice(PROGRESS_STAGES["retrying_image"]), "progress": 40 + (image_attempt * 10)}
210
  except Exception as e:
211
+ print(f"Error generating image: {e}")
212
  if image_attempt == max_retries - 1:
 
213
  if total_attempts == max_total_attempts:
214
  image = Image.new('RGB', (360, 640), color='gray')
215
  buffered = BytesIO()
216
  image.save(buffered, format="PNG")
217
  img_str = base64.b64encode(buffered.getvalue()).decode()
218
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
219
  return {
220
  'text': text,
221
  'image_base64': img_str,
 
223
  'ideas': ideas
224
  }
225
  break
226
+ yield {"stage": "retrying_image", "message": random.choice(PROGRESS_STAGES["retrying_image"]), "progress": 40 + (image_attempt * 10)}
227
 
228
  if generate_video and generated_image is not None:
229
+ yield {"stage": "generating_video", "message": random.choice(PROGRESS_STAGES["generating_video"]), "progress": 70}
 
 
 
230
  try:
 
231
  video_prompt = f"""
232
  The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
233
  Use a close-up shot with a slow dolly shot circling around the subject,
234
  using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
235
  """
 
236
  operation = client.models.generate_videos(
237
  model="veo-2.0-generate-001",
238
  prompt=video_prompt,
 
247
  while not operation.done:
248
  time.sleep(20)
249
  operation = client.operations.get(operation)
 
 
250
  if operation.error:
251
  raise ValueError(f"Video generation failed: {operation.error.message}")
252
+ if operation.response and len(operation.response.generated_videos) > 0:
 
 
 
253
  video = operation.response.generated_videos[0]
 
 
254
  video_data = client.files.download(file=video.video)
255
+ video_bytes = video_data if isinstance(video_data, bytes) else BytesIO(video_data).getvalue()
 
 
 
 
 
 
256
  video_base64 = base64.b64encode(video_bytes).decode()
257
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
 
258
  return {
259
  'text': text,
260
  'image_base64': img_str,
261
  'video_base64': video_base64,
262
  'ideas': ideas
263
  }
 
 
264
  except Exception as e:
265
+ print(f"Error generating video: {e}")
266
+ yield {"stage": "generating_video", "message": random.choice(PROGRESS_STAGES["generating_video"]), "progress": 80}
267
+ try:
268
+ operation = client.models.generate_videos(
269
+ model="veo-2.0-generate-001",
270
+ prompt=video_prompt,
271
+ config=types.GenerateVideosConfig(
272
+ aspect_ratio="9:16",
273
+ number_of_videos=1,
274
+ duration_seconds=8,
275
+ negative_prompt="blurry, low quality, text, letters"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  )
277
+ )
278
+ while not operation.done:
279
+ time.sleep(20)
280
+ operation = client.operations.get(operation)
281
+ if operation.response and len(operation.response.generated_videos) > 0:
282
+ video = operation.response.generated_videos[0]
283
+ video_data = client.files.download(file=video.video)
284
+ video_bytes = video_data if isinstance(video_data, bytes) else BytesIO(video_data).getvalue()
285
+ video_base64 = base64.b64encode(video_bytes).decode()
286
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
287
+ return {
288
+ 'text': text,
289
+ 'image_base64': img_str,
290
+ 'video_base64': video_base64,
291
+ 'ideas': ideas
292
+ }
293
+ except Exception as e:
294
+ print(f"Error generating text-to-video: {e}")
295
+ if total_attempts == max_total_attempts:
296
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
297
+ return {
298
+ 'text': text,
299
+ 'image_base64': img_str,
300
+ 'video_base64': None,
301
+ 'ideas': ideas
302
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  if img_str is not None:
305
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
306
  return {
307
  'text': text,
308
  'image_base64': img_str,
 
310
  'ideas': ideas
311
  }
312
 
 
 
313
  image = Image.new('RGB', (360, 640), color='gray')
314
  buffered = BytesIO()
315
  image.save(buffered, format="PNG")
316
  img_str = base64.b64encode(buffered.getvalue()).decode()
317
+ yield {"stage": "finalizing", "message": random.choice(PROGRESS_STAGES["finalizing"]), "progress": 90}
318
  return {
319
  'text': f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}",
320
  'image_base64': img_str,
 
322
  'ideas': ideas
323
  }
324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  def start_feed(user_input, generate_video, current_index, feed_items):
326
  """
327
+ Start or update the feed based on the user input with progress updates.
 
328
  """
329
  if not user_input.strip():
330
  user_input = "trending"
 
331
  current_user_input = user_input
332
  is_loading = True
 
333
  share_links = ""
334
 
335
+ html_content = generate_html([], False, 0, user_input, is_loading, "initializing", random.choice(PROGRESS_STAGES["initializing"]), 10)
336
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
 
337
 
338
  try:
339
+ html_content = generate_html([], False, 0, user_input, is_loading, "generating_ideas", random.choice(PROGRESS_STAGES["generating_ideas"]), 20)
340
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
341
+ ideas = generate_ideas(user_input)
 
 
 
 
 
 
 
 
 
 
342
 
343
+ item_generator = generate_item(user_input, ideas, generate_video=generate_video)
344
+ for progress in item_generator:
345
+ if isinstance(progress, dict) and "stage" in progress:
346
+ html_content = generate_html([], False, 0, user_input, is_loading, progress["stage"], progress["message"], progress["progress"])
347
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
348
+ else:
349
+ item = progress
350
+ feed_items = [item]
351
+ current_index = 0
352
+ share_links = generate_share_links(item['image_base64'], item['video_base64'], item['text'])
353
+ is_loading = False
354
+ html_content = generate_html(feed_items, False, current_index, user_input, is_loading)
355
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
356
+ return
357
  except Exception as e:
358
  print(f"Error in start_feed: {e}")
359
+ html_content = """
 
 
360
  <div style="
361
  display: flex;
362
  flex-direction: column;
 
374
  <p>Error generating content. Please try again!</p>
375
  </div>
376
  """
 
377
  is_loading = False
378
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
379
+ return
 
 
 
380
 
381
  def load_next(user_input, generate_video, current_index, feed_items):
382
  """
383
+ Load the next item in the feed with progress updates.
 
384
  """
385
  current_user_input = user_input if user_input.strip() else "trending"
386
  user_input = current_user_input
387
  is_loading = True
 
388
  share_links = ""
389
+
390
+ html_content = generate_html(feed_items, False, current_index, user_input, is_loading, "initializing", random.choice(PROGRESS_STAGES["initializing"]), 10)
391
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
 
392
 
393
  try:
394
  if current_index + 1 < len(feed_items):
395
  current_index += 1
396
+ is_loading = False
397
+ html_content = generate_html(feed_items, False, current_index, user_input, is_loading)
398
+ share_links = generate_share_links(
399
+ feed_items[current_index]['image_base64'],
400
+ feed_items[current_index]['video_base64'],
401
+ feed_items[current_index]['text']
402
+ )
403
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
404
+ return
405
  else:
406
+ ideas = feed_items[-1]['ideas'] if feed_items else generate_ideas(user_input)
407
+ html_content = generate_html(feed_items, False, current_index, user_input, is_loading, "generating_ideas", random.choice(PROGRESS_STAGES["generating_ideas"]), 20)
408
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
 
 
 
 
409
 
410
+ item_generator = generate_item(user_input, ideas, generate_video=generate_video)
411
+ for progress in item_generator:
412
+ if isinstance(progress, dict) and "stage" in progress:
413
+ html_content = generate_html(feed_items, False, current_index, user_input, is_loading, progress["stage"], progress["message"], progress["progress"])
414
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
415
+ else:
416
+ new_item = progress
417
+ feed_items.append(new_item)
418
+ current_index = len(feed_items) - 1
419
+ is_loading = False
420
+ html_content = generate_html(feed_items, False, current_index, user_input, is_loading)
421
+ share_links = generate_share_links(
422
+ feed_items[current_index]['image_base64'],
423
+ feed_items[current_index]['video_base64'],
424
+ feed_items[current_index]['text']
425
+ )
426
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
427
+ return
428
  except Exception as e:
429
  print(f"Error in load_next: {e}")
430
+ html_content = """
431
  <div style="
432
  display: flex;
433
  flex-direction: column;
 
445
  <p>Error generating content. Please try again!</p>
446
  </div>
447
  """
 
448
  is_loading = False
449
+ yield current_user_input, current_index, feed_items, html_content, share_links, is_loading
450
+ return
 
 
 
451
 
452
  def load_previous(user_input, generate_video, current_index, feed_items):
453
  """
 
455
  """
456
  current_user_input = user_input if user_input.strip() else "trending"
457
 
 
 
 
 
458
  if current_index > 0:
459
  current_index -= 1
460
+ html_content = generate_html(feed_items, False, current_index, user_input, False)
461
  share_links = generate_share_links(
462
+ feed_items[current_index]['image_base64'],
463
+ feed_items[current_index]['video_base64'],
464
+ feed_items[current_index]['text']
465
  )
466
+ return current_user_input, current_index, feed_items, html_content, share_links, False
467
 
468
  def generate_share_links(image_base64, video_base64, caption):
469
  """
470
  Generate share links for social media platforms with download links for image and video.
471
  """
472
+ image_data_url = f"data:image/png;base64,{image_base64}"
473
+ encoded_caption = urllib.parse.quote(caption)
474
 
475
  download_links = f"""
476
  <p style="text-align: center; margin-bottom: 10px;">Download the media to share:</p>
 
577
  justify-content: center;
578
  margin-top: 10px;
579
  ">
580
+ <a href="https://www.youtube.com/upload?description={caption}" target="_blank" style="
581
  background-color: #ff0000;
582
  color: white;
583
  padding: 8px 16px;
 
607
  </div>
608
  """.format(caption=encoded_caption)
609
 
610
+ def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_input="", is_loading=False, progress_stage="initializing", progress_message="", progress_percent=10):
611
  """
612
+ Generate an HTML string to display the current feed item or a loading screen with a progress bar.
613
  """
 
 
 
 
614
  if is_loading:
615
+ progress_percent = max(0, min(100, progress_percent))
616
+ return f"""
617
  <div id="feed-container" style="
618
  display: flex;
619
  flex-direction: column;
620
  align-items: center;
621
+ justify-content: center;
622
  max-width: 360px;
623
  margin: 0 auto;
624
  background-color: #000;
 
627
  border-radius: 10px;
628
  color: white;
629
  font-family: Arial, sans-serif;
630
+ position: relative;
631
  ">
632
+ <div id="loading-message" style="
633
+ font-size: 18px;
634
+ font-weight: bold;
635
+ text-align: center;
636
+ margin-bottom: 20px;
637
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
638
+ max-width: 90%;
639
+ ">
640
+ {progress_message}
641
+ </div>
642
+ <div style="
643
+ width: 80%;
644
+ height: 10px;
645
+ background-color: #333;
646
+ border-radius: 5px;
647
+ overflow: hidden;
648
+ ">
649
+ <div style="
650
+ width: {progress_percent}%;
651
+ height: 100%;
652
+ background: linear-gradient(to right, #ff2d55, #ff5e78);
653
+ transition: width 0.5s ease-in-out;
654
+ "></div>
655
+ </div>
656
+ <style>
657
+ @keyframes pulse {{
658
+ 0% {{ opacity: 1; }}
659
+ 50% {{ opacity: 0.7; }}
660
+ 100% {{ opacity: 1; }}
661
+ }}
662
+ #loading-message {{
663
+ animation: pulse 2s infinite;
664
+ }}
665
+ </style>
666
+ <script>
667
+ const stages = {json.dumps(PROGRESS_STAGES)};
668
+ const currentStage = "{progress_stage}";
669
+ let currentMessageIndex = 0;
670
+ const messageElement = document.getElementById('loading-message');
671
+ function rotateMessages() {{
672
+ if (stages[currentStage] && stages[currentStage].length > 0) {{
673
+ currentMessageIndex = (currentMessageIndex + 1) % stages[currentStage].length;
674
+ messageElement.textContent = stages[currentStage][currentMessageIndex];
675
+ }}
676
+ }}
677
+ setInterval(rotateMessages, 2000);
678
+ </script>
679
  </div>
680
  """
681
 
682
  if not feed_items or current_index >= len(feed_items):
683
  return """
684
+ <div style="
685
  display: flex;
686
  flex-direction: column;
687
  align-items: center;
 
700
  """
701
 
702
  item = feed_items[current_index]
703
+ media_element = f"""
704
+ <img id="feed-image" src="data:image/png;base64,{item['image_base64']}" style="
705
+ width: 100%;
706
+ height: 100%;
707
+ object-fit: cover;
708
+ position: absolute;
709
+ top: 0;
710
+ left: 0;
711
+ z-index: 1;
712
+ ">
713
+ """ if not item['video_base64'] else f"""
714
+ <video id="feed-video" controls style="
715
+ width: 100%;
716
+ height: 100%;
717
+ object-fit: cover;
718
+ position: absolute;
719
+ top: 0;
720
+ left: 0;
721
+ z-index: 1;
722
+ ">
723
+ <source src="data:video/mp4;base64,{item['video_base64']}" type="video/mp4">
724
+ Your browser does not support the video tag.
725
+ </video>
726
+ """
 
 
 
727
 
728
  html_str = f"""
729
  <div id="feed-container" style="
 
775
  }}
776
  }}
777
  </script>
 
778
  """
779
  return html_str
780
 
 
793
  ) as demo:
794
  current_user_input = gr.State(value="")
795
  current_index = gr.State(value=0)
796
+ feed_items = gr.State(value=[])
797
  is_loading = gr.State(value=False)
798
  share_links = gr.State(value="")
799
 
 
811
  )
812
  magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
813
 
 
814
  feed_html = gr.HTML()
815
  share_html = gr.HTML(label="Share this item:")
816
 
817
  user_input.submit(
818
  fn=start_feed,
819
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
820
+ outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading],
821
+ _js="() => { return { stream: true }; }"
822
  )
823
 
824
  magic_button.click(
825
  fn=load_next,
826
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
827
+ outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading],
828
+ _js="() => { return { stream: true }; }"
829
  )
830
 
831
  previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
 
832
  previous_button.click(
833
  fn=load_previous,
834
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
835
+ outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading]
836
  )
837
 
 
838
  demo.launch()