codelion commited on
Commit
b5e0ef9
·
verified ·
1 Parent(s): a214565

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -957
app.py CHANGED
@@ -9,55 +9,31 @@ import json
9
  import random
10
  import urllib.parse
11
  import time
12
- import gradio
13
 
14
  # Check Gradio version
15
  required_version = "4.44.0"
16
- current_version = gradio.__version__
17
  if current_version < required_version:
18
  raise ValueError(f"Gradio version {current_version} is outdated. Please upgrade to {required_version} or later using 'pip install gradio=={required_version}'.")
19
 
20
- # Initialize the Google Generative AI client with the API key from environment variables
21
  try:
22
  api_key = os.environ['GEMINI_API_KEY']
23
  except KeyError:
24
  raise ValueError("Please set the GEMINI_API_KEY environment variable.")
25
  client = genai.Client(api_key=api_key)
26
 
27
- # Define safety settings to disable all filters for content generation
28
  SAFETY_SETTINGS = [
29
- types.SafetySetting(
30
- category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
31
- threshold=types.HarmBlockThreshold.BLOCK_NONE,
32
- ),
33
- types.SafetySetting(
34
- category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
35
- threshold=types.HarmBlockThreshold.BLOCK_NONE,
36
- ),
37
- types.SafetySetting(
38
- category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
39
- threshold=types.HarmBlockThreshold.BLOCK_NONE,
40
- ),
41
- types.SafetySetting(
42
- category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
43
- threshold=types.HarmBlockThreshold.BLOCK_NONE,
44
- ),
45
- types.SafetySetting(
46
- category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
47
- threshold=types.HarmBlockThreshold.BLOCK_NONE,
48
- ),
49
  ]
50
 
51
  def clean_response_text(response_text):
52
- """
53
- Clean the API response by removing Markdown code block markers.
54
-
55
- Args:
56
- response_text (str): The raw response text from the API.
57
-
58
- Returns:
59
- str: The cleaned response text.
60
- """
61
  cleaned_text = response_text.strip()
62
  if cleaned_text.startswith("```json"):
63
  cleaned_text = cleaned_text[len("```json"):].strip()
@@ -66,25 +42,13 @@ def clean_response_text(response_text):
66
  return cleaned_text
67
 
68
  def generate_ideas(user_input):
69
- """
70
- Generate a diverse set of ideas based on the user's input concept using the LLM.
71
- Yields progress updates for the loading UI.
72
-
73
- Args:
74
- user_input (str): The user's input concept or idea.
75
-
76
- Yields:
77
- tuple: (progress_percentage, message) for progress_html updates.
78
- list: Final list of ideas as strings.
79
- """
80
  yield (10, f"Brainstorming epic ideas for {user_input}... 🌟")
81
 
82
  prompt = f"""
83
- 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}".
84
- Each idea must clearly incorporate and focus on the core theme of "{user_input}" without deviating into unrelated topics.
85
  Each idea should be a short sentence describing a specific scene or concept.
86
- Return the response as a JSON object with a single key 'ideas' containing a list of 5 ideas.
87
- Ensure the response is strictly in JSON format.
88
  Example for "blindfolded Rubik's Cube challenge":
89
  {{"ideas": [
90
  "A blindfolded speedcubing competition with dramatic music",
@@ -98,23 +62,15 @@ def generate_ideas(user_input):
98
  response = client.models.generate_content(
99
  model='gemini-2.0-flash-lite',
100
  contents=[prompt],
101
- config=types.GenerateContentConfig(
102
- temperature=1.2,
103
- safety_settings=SAFETY_SETTINGS
104
- )
105
  )
106
- print(f"Raw response for ideas: {response.text}") # Debugging
107
- if not response.text or response.text.isspace():
108
- raise ValueError("Empty response from API")
109
  cleaned_text = clean_response_text(response.text)
110
  response_json = json.loads(cleaned_text)
111
- if 'ideas' not in response_json or not isinstance(response_json['ideas'], list) or len(response_json['ideas']) != 5:
112
- raise ValueError("Invalid JSON format: 'ideas' key missing, not a list, or incorrect length")
113
-
114
  ideas = response_json['ideas']
115
  yield (20, f"Ideas locked in for {user_input}! 🚀")
116
  return ideas
117
-
118
  except Exception as e:
119
  print(f"Error generating ideas: {e}")
120
  yield (20, f"Oops, tweaking the plan for {user_input}... 🔧")
@@ -126,999 +82,254 @@ def generate_ideas(user_input):
126
  f"An action-packed {user_input} challenge with dynamic angles"
127
  ]
128
 
129
- def generate_item(user_input, ideas, generate_video=False, max_retries=3):
130
- """
131
- Generate a single feed item (image and optionally one video) using one of the ideas.
132
- Yields progress updates for the loading UI.
133
-
134
- Args:
135
- user_input (str): The user's input concept or idea.
136
- ideas (list): List of ideas to choose from.
137
- generate_video (bool): Whether to generate a video from the image.
138
- max_retries (int): Maximum number of retries for image generation per cycle.
139
-
140
- Yields:
141
- tuple: (progress_percentage, message) for progress_html updates.
142
- dict: Final dictionary with 'text', 'image_base64', 'video_base64', and 'ideas'.
143
- """
144
  video_base64 = None
145
- max_total_attempts = 3 # Maximum total attempts for combined image and video generation cycles
146
 
147
  total_attempts = 0
148
- while total_attempts < max_total_attempts:
149
  total_attempts += 1
150
  yield (20 + total_attempts * 10, f"Attempt {total_attempts} to craft your {user_input} masterpiece... 🎨")
151
 
152
- # Step 1: Generate an image (retry up to max_retries times)
153
- generated_image = None
154
- text = None
155
- img_str = None
156
- image_prompt = None
157
-
158
- for image_attempt in range(max_retries):
159
- yield (20 + total_attempts * 10 + image_attempt * 5, f"Crafting a stunning image for {user_input}... 📸")
160
- selected_idea = random.choice(ideas)
161
- prompt = f"""
162
- 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
- Return a JSON object with two keys:
164
- - 'caption': A short, viral TikTok-style caption with hashtags that reflects "{user_input}".
165
- - 'image_prompt': A detailed image prompt for generating a high-quality visual scene, ensuring the theme of "{user_input}" is central.
166
- The image prompt should describe the scene vividly, specify a perspective and style, and ensure no text or letters are included.
167
- Ensure the response is strictly in JSON format.
168
- Example: {{"caption": "Blindfolded Rubik's Cube MAGIC! 🤯 #rubiks", "image_prompt": "A close-up view of a person solving a Rubik's Cube blindfolded, in a dramatic style, no text or letters"}}
169
- """
170
- try:
171
- response = client.models.generate_content(
172
- model='gemini-2.0-flash-lite',
173
- contents=[prompt],
174
- config=types.GenerateContentConfig(
175
- temperature=1.2,
176
- safety_settings=SAFETY_SETTINGS
177
- )
178
- )
179
- print(f"Raw response for item (image attempt {image_attempt + 1}, total attempt {total_attempts}): {response.text}") # Debugging
180
- if not response.text or response.text.isspace():
181
- raise ValueError("Empty response from API")
182
- cleaned_text = clean_response_text(response.text)
183
- response_json = json.loads(cleaned_text)
184
- if 'caption' not in response_json or 'image_prompt' not in response_json:
185
- raise ValueError("Invalid JSON format: 'caption' or 'image_prompt' key missing")
186
- text = response_json['caption']
187
- image_prompt = response_json['image_prompt']
188
- except Exception as e:
189
- print(f"Error generating item (image attempt {image_attempt + 1}, total attempt {total_attempts}): {e}")
190
- text = f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}"
191
- image_prompt = f"A vivid scene of {selected_idea} related to {user_input}, in a vibrant pop art style, no text or letters"
192
-
193
- # Attempt to generate the image
194
- try:
195
- yield (40 + image_attempt * 5, f"Rendering your {user_input} vision... ✨")
196
- imagen = client.models.generate_images(
197
- model='imagen-3.0-generate-002',
198
- prompt=image_prompt,
199
- config=types.GenerateImagesConfig(
200
- aspect_ratio="9:16",
201
- number_of_images=1
202
- )
203
- )
204
- if imagen.generated_images and len(imagen.generated_images) > 0:
205
- generated_image = imagen.generated_images[0]
206
- image = Image.open(BytesIO(generated_image.image.image_bytes))
207
- # Ensure the image matches the desired aspect ratio (9:16 = 0.5625)
208
- target_width = 360
209
- target_height = int(target_width / 9 * 16) # 9:16 aspect ratio
210
- image = image.resize((target_width, target_height), Image.LANCZOS)
211
- # Convert image to base64
212
- buffered = BytesIO()
213
- image.save(buffered, format="PNG")
214
- img_str = base64.b64encode(buffered.getvalue()).decode()
215
- yield (50, f"Image for {user_input} is ready! 🎉")
216
- break # Successfully generated image, exit image retry loop
217
- else:
218
- print(f"Image generation failed (image attempt {image_attempt + 1}, total attempt {total_attempts}): No images returned")
219
- if image_attempt == max_retries - 1:
220
- yield (50, f"Tweaking the image for {user_input}... 🔄")
221
- if total_attempts == max_total_attempts:
222
- # Max total attempts reached, use a gray placeholder
223
- image = Image.new('RGB', (360, 640), color='gray')
224
- buffered = BytesIO()
225
- image.save(buffered, format="PNG")
226
- img_str = base64.b64encode(buffered.getvalue()).decode()
227
- yield (60, f"Using a placeholder for {user_input}... 🖼️")
228
- return {
229
- 'text': text,
230
- 'image_base64': img_str,
231
- 'video_base64': None,
232
- 'ideas': ideas
233
- }
234
- break # Exit inner loop to retry with new idea
235
- except Exception as e:
236
- print(f"Error generating image (image attempt {image_attempt + 1}, total attempt {total_attempts}): {e}")
237
- if image_attempt == max_retries - 1:
238
- yield (50, f"Retrying image for {user_input}... 🔄")
239
- if total_attempts == max_total_attempts:
240
- # Max total attempts reached, use a gray placeholder
241
- image = Image.new('RGB', (360, 640), color='gray')
242
- buffered = BytesIO()
243
- image.save(buffered, format="PNG")
244
- img_str = base64.b64encode(buffered.getvalue()).decode()
245
- yield (60, f"Using a placeholder for {user_input}... 🖼️")
246
- return {
247
- 'text': text,
248
- 'image_base64': img_str,
249
- 'video_base64': None,
250
- 'ideas': ideas
251
- }
252
- break # Exit inner loop to retry with new idea
253
-
254
- # Step 2: Generate video if enabled (with fallback to text-to-video if image-to-video fails)
255
- if generate_video and generated_image is not None:
256
- max_video_retries_per_image = 2 # Try text-to-video generation twice if needed
257
- video_generated = False
258
-
259
- # First, try image-to-video generation (only once)
260
  try:
261
  yield (60, f"Filming a viral video for {user_input}... 🎥")
262
- video_prompt = f"""
263
- The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
264
- Use a close-up shot with a slow dolly shot circling around the subject,
265
- using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
266
- """
267
- print(f"Attempting image-to-video generation (total attempt {total_attempts}): {video_prompt}")
268
  operation = client.models.generate_videos(
269
  model="veo-2.0-generate-001",
270
- prompt=video_prompt,
271
- image=generated_image.image,
272
- config=types.GenerateVideosConfig(
273
- aspect_ratio="9:16",
274
- number_of_videos=1,
275
- duration_seconds=8,
276
- negative_prompt="blurry, low quality, text, letters"
277
- )
278
  )
279
- # Wait for video to generate
280
  while not operation.done:
281
  time.sleep(20)
282
  operation = client.operations.get(operation)
283
-
284
- # Log detailed information about the operation
285
- print(f"Video generation operation completed: {operation}")
286
- print(f"Operation done: {operation.done}")
287
- print(f"Operation error: {operation.error}")
288
- if operation.error:
289
- print(f"Operation error message: {operation.error.message}")
290
- if hasattr(operation.error, 'code'):
291
- print(f"Operation error code: {operation.error.code}")
292
- if hasattr(operation.error, 'details'):
293
- print(f"Operation error details: {operation.error.details}")
294
- print(f"Operation response: {operation.response}")
295
- if operation.response:
296
- print(f"Operation response has generated_videos: {hasattr(operation.response, 'generated_videos')}")
297
- if hasattr(operation.response, 'generated_videos'):
298
- print(f"Generated videos: {operation.response.generated_videos}")
299
- else:
300
- print("No generated_videos attribute in response")
301
-
302
- # Enhanced error handling for video generation response
303
- if operation.error:
304
- raise ValueError(f"Video generation operation failed with error: {operation.error.message}")
305
- if operation.response is None:
306
- raise ValueError("Video generation operation failed: No response")
307
- if not hasattr(operation.response, 'generated_videos') or operation.response.generated_videos is None:
308
- raise ValueError("Video generation operation failed: No generated_videos in response")
309
-
310
- # Process the single generated video
311
- if len(operation.response.generated_videos) > 0:
312
- video = operation.response.generated_videos[0]
313
- if video is None or not hasattr(video, 'video'):
314
- raise ValueError("Video is invalid or missing video data")
315
- fname = 'with_image_input.mp4'
316
- print(f"Generated video: {fname}")
317
- # Download the video and get the raw bytes
318
- video_data = client.files.download(file=video.video)
319
- # Ensure video_data is in bytes
320
- if isinstance(video_data, bytes):
321
- video_bytes = video_data
322
- else:
323
- # If video_data is a file-like object, read the bytes
324
- video_buffer = BytesIO()
325
- for chunk in video_data:
326
- video_buffer.write(chunk)
327
- video_bytes = video_buffer.getvalue()
328
- # Encode the video bytes as base64
329
  video_base64 = base64.b64encode(video_bytes).decode()
330
- video_generated = True
331
  yield (90, f"Video for {user_input} is a wrap! 🎬")
332
- # Successfully generated video, return the result
333
- return {
334
- 'text': text,
335
- 'image_base64': img_str,
336
- 'video_base64': video_base64,
337
- 'ideas': ideas
338
- }
339
- else:
340
- raise ValueError("No video was generated")
341
  except Exception as e:
342
- print(f"Error generating video (image-to-video, total attempt {total_attempts}): {e}")
343
- yield (70, f"Switching to a new video approach for {user_input}... 🎞️")
344
- print("Image-to-video generation failed. Falling back to text-to-video generation.")
345
-
346
- # If image-to-video generation failed, try text-to-video generation
347
- if not video_generated:
348
- for video_attempt in range(max_video_retries_per_image):
349
- try:
350
- yield (75 + video_attempt * 5, f"Trying a fresh video take for {user_input}... 📹")
351
- # Use the same video prompt but without the image
352
- video_prompt_base = f"""
353
- The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
354
- Use a close-up shot with a slow dolly shot circling around the subject,
355
- using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
356
- """
357
- if video_attempt == 0:
358
- video_prompt = video_prompt_base
359
- else:
360
- video_prompt = f"""
361
- The user concept is "{user_input}". Based on this and a simplified scene: {image_prompt}, create a video.
362
- Use a static close-up shot of the subject in a realistic style.
363
- """
364
-
365
- print(f"Attempting text-to-video generation (video attempt {video_attempt + 1}, total attempt {total_attempts}): {video_prompt}")
366
- operation = client.models.generate_videos(
367
- model="veo-2.0-generate-001",
368
- prompt=video_prompt,
369
- config=types.GenerateVideosConfig(
370
- aspect_ratio="9:16",
371
- number_of_videos=1,
372
- duration_seconds=8,
373
- negative_prompt="blurry, low quality, text, letters"
374
- )
375
- )
376
- # Wait for video to generate
377
- while not operation.done:
378
- time.sleep(20)
379
- operation = client.operations.get(operation)
380
-
381
- # Log detailed information about the operation
382
- print(f"Video generation operation completed: {operation}")
383
- print(f"Operation done: {operation.done}")
384
- print(f"Operation error: {operation.error}")
385
- if operation.error:
386
- print(f"Operation error message: {operation.error.message}")
387
- if hasattr(operation.error, 'code'):
388
- print(f"Operation error code: {operation.error.code}")
389
- if hasattr(operation.error, 'details'):
390
- print(f"Operation error details: {operation.error.details}")
391
- print(f"Operation response: {operation.response}")
392
- if operation.response:
393
- print(f"Operation response has generated_videos: {hasattr(operation.response, 'generated_videos')}")
394
- if hasattr(operation.response, 'generated_videos'):
395
- print(f"Generated videos: {operation.response.generated_videos}")
396
- else:
397
- print("No generated_videos attribute in response")
398
-
399
- # Enhanced error handling for video generation response
400
- if operation.error:
401
- raise ValueError(f"Video generation operation failed with error: {operation.error.message}")
402
- if operation.response is None:
403
- raise ValueError("Video generation operation failed: No response")
404
- if not hasattr(operation.response, 'generated_videos') or operation.response.generated_videos is None:
405
- raise ValueError("Video generation operation failed: No generated_videos in response")
406
-
407
- # Process the single generated video
408
- if len(operation.response.generated_videos) > 0:
409
- video = operation.response.generated_videos[0]
410
- if video is None or not hasattr(video, 'video'):
411
- raise ValueError("Video is invalid or missing video data")
412
- fname = 'text_to_video.mp4'
413
- print(f"Generated video: {fname}")
414
- # Download the video and get the raw bytes
415
- video_data = client.files.download(file=video.video)
416
- # Ensure video_data is in bytes
417
- if isinstance(video_data, bytes):
418
- video_bytes = video_data
419
- else:
420
- # If video_data is a file-like object, read the bytes
421
- video_buffer = BytesIO()
422
- for chunk in video_data:
423
- video_buffer.write(chunk)
424
- video_bytes = video_buffer.getvalue()
425
- # Encode the video bytes as base64
426
- video_base64 = base64.b64encode(video_bytes).decode()
427
- video_generated = True
428
- yield (90, f"Video for {user_input} is a wrap! 🎬")
429
- # Successfully generated video, return the result
430
- return {
431
- 'text': text,
432
- 'image_base64': img_str,
433
- 'video_base64': video_base64,
434
- 'ideas': ideas
435
- }
436
- else:
437
- raise ValueError("No video was generated")
438
- except Exception as e:
439
- print(f"Error generating video (text-to-video attempt {video_attempt + 1}, total attempt {total_attempts}): {e}")
440
- if video_attempt == max_video_retries_per_image - 1:
441
- yield (85, f"Finalizing without video for {user_input}... 📌")
442
- if total_attempts == max_total_attempts:
443
- print("Max total attempts reached. Proceeding without video.")
444
- video_base64 = None
445
- yield (95, f"Polishing your {user_input} masterpiece... ✨")
446
- return {
447
- 'text': text,
448
- 'image_base64': img_str,
449
- 'video_base64': video_base64,
450
- 'ideas': ideas
451
- }
452
- # Text-to-video failed, break to outer loop to try a new image
453
- print(f"Text-to-video generation failed after {max_video_retries_per_image} attempts. Selecting a new idea and generating a new image.")
454
- break
455
- continue # Retry text-to-video generation with a modified prompt
456
 
457
- # If video generation is not enabled or image generation failed, return the result
458
- if img_str is not None:
459
- yield (95, f"Polishing your {user_input} masterpiece... ✨")
460
- return {
461
- 'text': text,
462
- 'image_base64': img_str,
463
- 'video_base64': video_base64,
464
- 'ideas': ideas
465
- }
466
- # If img_str is None, continue to next cycle or fall back if max attempts reached
467
-
468
- # If max total attempts reached without success, use a gray placeholder image
469
- print("Max total attempts reached without successful image generation. Using placeholder.")
470
- yield (95, f"Falling back to a placeholder for {user_input}... 🖼️")
471
- image = Image.new('RGB', (360, 640), color='gray')
472
- buffered = BytesIO()
473
- image.save(buffered, format="PNG")
474
- img_str = base64.b64encode(buffered.getvalue()).decode()
475
- yield (100, f"Ready to roll with {user_input}! 🚀")
476
- return {
477
- 'text': f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}",
478
- 'image_base64': img_str,
479
- 'video_base64': None,
480
- 'ideas': ideas
481
- }
482
 
483
  def generate_progress_html(progress, message, user_input):
484
- """
485
- Generate HTML for the progress bar and witty text.
486
-
487
- Args:
488
- progress (float): Current progress percentage (0–100).
489
- message (str): Current loading message to display.
490
- user_input (str): The user's input concept or idea for context.
491
-
492
- Returns:
493
- str: HTML string for the progress bar.
494
- """
495
  return f"""
496
  <div id="progress-container" style="
497
- display: flex;
498
- flex-direction: column;
499
- align-items: center;
500
- justify-content: center;
501
- max-width: 360px;
502
- margin: 0 auto;
503
- background-color: #000;
504
- height: 200px;
505
- border: 1px solid #333;
506
- border-radius: 10px;
507
- color: white;
508
- font-family: Arial, sans-serif;
509
- position: relative;
510
- ">
511
- <div id="loading-message" style="
512
- font-size: 18px;
513
- font-weight: bold;
514
- text-align: center;
515
- margin-bottom: 20px;
516
- text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
517
- ">
518
  {message}
519
  </div>
520
- <div style="
521
- width: 80%;
522
- height: 10px;
523
- background-color: #333;
524
- border-radius: 5px;
525
- overflow: hidden;
526
- ">
527
- <div id="progress-bar" style="
528
- width: {progress}%;
529
- height: 100%;
530
- background: linear-gradient(to right, #ff2d55, #ff5e78);
531
- transition: width 0.5s ease-in-out;
532
- "></div>
533
- </div>
534
- <div style="
535
- margin-top: 10px;
536
- font-size: 14px;
537
- color: #ccc;
538
- ">
539
- {int(progress)}% Complete
540
  </div>
 
541
  <style>
542
- @keyframes pulse {{
543
- 0% {{ opacity: 1; }}
544
- 50% {{ opacity: 0.5; }}
545
- 100% {{ opacity: 1; }}
546
- }}
547
- #loading-message {{
548
- animation: pulse 2s infinite;
549
- }}
550
  </style>
551
  </div>
552
  """
553
 
554
- def start_feed(user_input, generate_video, current_index, feed_items):
555
- """
556
- Start or update the feed based on the user input.
557
- Yields progress updates and final feed content.
558
-
559
- Args:
560
- user_input (str): The user's input concept or idea.
561
- generate_video (bool): Whether to generate a video.
562
- current_index (int): The current item index.
563
- feed_items (list): The current list of feed items.
564
-
565
- Yields:
566
- tuple: (progress_html) for progress updates.
567
- tuple: (current_user_input, current_index, feed_items, feed_html, share_html, is_loading) for final result.
568
  """
569
- if not user_input.strip():
570
- user_input = "trending"
571
-
572
- # Update current_user_input with the new user_input
 
 
 
 
 
573
  current_user_input = user_input
574
-
575
- # Set initial loading state
576
  is_loading = True
577
  progress_html = generate_progress_html(0, f"Getting started with {user_input}... 🚀", user_input)
578
- yield (progress_html,)
579
- share_links = ""
580
 
581
  try:
582
- # Generate ideas with progress updates
583
  ideas_gen = generate_ideas(user_input)
584
  ideas = None
585
  for update in ideas_gen:
586
  if isinstance(update, tuple):
587
  progress, message = update
588
  progress_html = generate_progress_html(progress, message, user_input)
589
- yield (progress_html,)
590
  else:
591
  ideas = update
592
 
593
- # Generate item with progress updates
594
- item_gen = generate_item(user_input, ideas, generate_video=generate_video)
595
  item = None
596
  for update in item_gen:
597
  if isinstance(update, tuple):
598
  progress, message = update
599
  progress_html = generate_progress_html(progress, message, user_input)
600
- yield (progress_html,)
601
  else:
602
  item = update
603
 
604
  feed_items = [item]
605
  current_index = 0
606
  feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
607
- share_links = generate_share_links(
608
- item['image_base64'],
609
- item['video_base64'],
610
- item['text']
611
- )
612
  except Exception as e:
613
  print(f"Error in start_feed: {e}")
614
- feed_items = []
615
- current_index = 0
616
- feed_html = """
617
- <div 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;
625
- height: 640px;
626
- border: 1px solid #333;
627
- border-radius: 10px;
628
- color: white;
629
- font-family: Arial, sans-serif;
630
- ">
631
- <p>Error generating content. Please try again!</p>
632
- </div>
633
- """
634
- progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
635
- yield (progress_html,)
636
- is_loading = False
637
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading
638
-
639
- # Set loading state to False and update UI
640
- is_loading = False
641
- progress_html = ""
642
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading
643
 
644
  def load_next(user_input, generate_video, current_index, feed_items):
645
- """
646
- Load the next item in the feed.
647
- Yields progress updates and final feed content.
648
-
649
- Args:
650
- user_input (str): The user's input concept or idea (updated from the textbox).
651
- generate_video (bool): Whether to generate a video.
652
- current_index (int): The current item index.
653
- feed_items (list): The current list of feed_items.
654
-
655
- Yields:
656
- tuple: (progress_html) for progress updates.
657
- tuple: (current_user_input, current_index, feed_items, feed_html, share_html, is_loading) for final result.
658
- """
659
- # Update current_user_input with the latest user_input from the textbox
660
- current_user_input = user_input if user_input.strip() else "trending"
661
-
662
- # Use current_user_input for generating the next item
663
- user_input = current_user_input
664
-
665
  is_loading = True
666
- progress_html = generate_progress_html(0, f"Loading next {user_input} vibe... 🚀", user_input)
667
- yield (progress_html,)
668
- share_links = ""
669
 
670
  try:
671
  if current_index + 1 < len(feed_items):
672
  current_index += 1
673
- progress_html = generate_progress_html(50, f"Switching to the next {user_input} moment... 🔄", user_input)
674
- yield (progress_html,)
675
  else:
676
  ideas = feed_items[-1]['ideas'] if feed_items else None
677
  if not ideas:
678
- # Generate new ideas if none exist
679
- ideas_gen = generate_ideas(user_input)
680
  for update in ideas_gen:
681
  if isinstance(update, tuple):
682
  progress, message = update
683
- progress_html = generate_progress_html(progress, message, user_input)
684
- yield (progress_html,)
685
  else:
686
  ideas = update
687
 
688
- # Generate new item
689
- new_item_gen = generate_item(user_input, ideas, generate_video=generate_video)
690
- new_item = None
691
- for update in new_item_gen:
692
  if isinstance(update, tuple):
693
  progress, message = update
694
- progress_html = generate_progress_html(progress, message, user_input)
695
- yield (progress_html,)
696
  else:
697
- new_item = update
698
-
699
- feed_items.append(new_item)
700
- current_index = len(feed_items) - 1
701
 
702
- feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
703
- share_links = generate_share_links(
704
- feed_items[current_index]['image_base64'],
705
- feed_items[current_index]['video_base64'],
706
- feed_items[current_index]['text']
707
- )
708
  except Exception as e:
709
  print(f"Error in load_next: {e}")
710
- feed_html = """
711
- <div style="
712
- display: flex;
713
- flex-direction: column;
714
- align-items: center;
715
- justify-content: center;
716
- max-width: 360px;
717
- margin: 0 auto;
718
- background-color: #000;
719
- height: 640px;
720
- border: 1px solid #333;
721
- border-radius: 10px;
722
- color: white;
723
- font-family: Arial, sans-serif;
724
- ">
725
- <p>Error generating content. Please try again!</p>
726
- </div>
727
- """
728
- progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
729
- yield (progress_html,)
730
- is_loading = False
731
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading
732
-
733
- is_loading = False
734
- progress_html = ""
735
- return current_user_input, current_index, feed_items, feed_html, share_links, is_loading
736
 
737
  def load_previous(user_input, generate_video, current_index, feed_items):
738
- """
739
- Load the previous item in the feed.
740
-
741
- Args:
742
- user_input (str): The user's input concept or idea.
743
- generate_video (bool): Whether to generate a video (not used here).
744
- current_index (int): The current item index.
745
- feed_items (list): The current list of feed items.
746
-
747
- Returns:
748
- tuple: (current_user_input, current_index, feed_items, feed_html, share_html, is_loading)
749
- """
750
- # Update current_user_input with the latest user_input
751
- current_user_input = user_input if user_input.strip() else "trending"
752
-
753
  if current_index > 0:
754
  current_index -= 1
755
- feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
756
- share_links = generate_share_links(
757
- feed_items[current_index]['image_base64'],
758
- feed_items[current_index]['video_base64'],
759
- feed_items[current_index]['text']
760
- )
761
- return current_user_input, current_index, feed_items, feed_html, share_links, False
762
-
763
- def generate_share_links(image_base64, video_base64, caption):
764
- """
765
- Generate share links for social media platforms with download links for image and video.
766
- Share links only include the caption, and users are instructed to manually upload the media.
767
-
768
- Args:
769
- image_base64 (str): The base64-encoded image data.
770
- video_base64 (str or None): The base64-enoded video data (single video).
771
- caption (str): The caption to share.
772
-
773
- Returns:
774
- str: HTML string with share links and download instructions.
775
- """
776
- image_data_url = f"data:image/png;base64,{image_base64}"
777
- encoded_caption = urllib.parse.quote(caption)
778
-
779
- # Generate download links for image and video (if available)
780
- download_links = f"""
781
- <p style="text-align: center; margin-bottom: 10px;">Download the media to share:</p>
782
- <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 15px;">
783
- <a href="{image_data_url}" download="feed_item.png" style="
784
- background-color: #4CAF50;
785
- color: white;
786
- padding: 8px 16px;
787
- border-radius: 5px;
788
- text-decoration: none;
789
- font-size: 14px;
790
- font-weight: bold;
791
- transition: background-color 0.3s;
792
- " onmouseover="this.style.backgroundColor='#45a049'" onmouseout="this.style.backgroundColor='#4CAF50'">Download Image</a>
793
- """
794
- if video_base64: # Only include video download link if a video exists
795
- video_data_url = f"data:video/mp4;base64,{video_base64}"
796
- download_links += f"""
797
- <a href="{video_data_url}" download="feed_video.mp4" style="
798
- background-color: #4CAF50;
799
- color: white;
800
- padding: 8px 16px;
801
- border-radius: 5px;
802
- text-decoration: none;
803
- font-size: 14px;
804
- font-weight: bold;
805
- transition: background-color 0.3s;
806
- " onmouseover="this.style.backgroundColor='#45a049'" onmouseout="this.style.backgroundColor='#4CAF50'">Download Video</a>
807
- """
808
- download_links += "</div>"
809
-
810
- # Instruction for users
811
- instruction = """
812
- <p style="text-align: center; margin-bottom: 10px;">
813
- Click a share button below to start a post with the caption, then manually upload the downloaded image or video.
814
- </p>
815
- """
816
-
817
- # Generate share links for social media platforms (only passing the caption)
818
- share_links = """
819
- <div style="
820
- display: flex;
821
- flex-wrap: wrap;
822
- justify-content: center;
823
- gap: 8px;
824
- margin-bottom: 15px;
825
- ">
826
- <a href="https://www.tiktok.com/upload?caption={caption}" target="_blank" style="
827
- background-color: #00f2ea;
828
- color: #000;
829
- padding: 8px 16px;
830
- border-radius: 5px;
831
- text-decoration: none;
832
- font-size: 14px;
833
- font-weight: bold;
834
- transition: background-color 0.3s;
835
- " onmouseover="this.style.backgroundColor='#00d9d1'" onmouseout="this.style.backgroundColor='#00f2ea'">Share on TikTok</a>
836
- <a href="https://www.instagram.com/?caption={caption}" target="_blank" style="
837
- background-color: #e1306c;
838
- color: white;
839
- padding: 8px 16px;
840
- border-radius: 5px;
841
- text-decoration: none;
842
- font-size: 14px;
843
- font-weight: bold;
844
- transition: background-color 0.3s;
845
- " onmouseover="this.style.backgroundColor='#c72b5e'" onmouseout="this.style.backgroundColor='#e1306c'">Share on Instagram</a>
846
- <a href="https://www.facebook.com/sharer/sharer.php?quote={caption}" target="_blank" style="
847
- background-color: #4267b2;
848
- color: white;
849
- padding: 8px 16px;
850
- border-radius: 5px;
851
- text-decoration: none;
852
- font-size: 14px;
853
- font-weight: bold;
854
- transition: background-color 0.3s;
855
- " onmouseover="this.style.backgroundColor='#395a9d'" onmouseout="this.style.backgroundColor='#4267b2'">Share on Facebook</a>
856
- <a href="https://twitter.com/intent/tweet?text={caption}" target="_blank" style="
857
- background-color: #1da1f2;
858
- color: white;
859
- padding: 8px 16px;
860
- border-radius: 5px;
861
- text-decoration: none;
862
- font-size: 14px;
863
- font-weight: bold;
864
- transition: background-color 0.3s;
865
- " onmouseover="this.style.backgroundColor='#1a91da'" onmouseout="this.style.backgroundColor='#1da1f2'">Share on X</a>
866
- <a href="https://pinterest.com/pin/create/button/?description={caption}" target="_blank" style="
867
- background-color: #bd081c;
868
- color: white;
869
- padding: 8px 16px;
870
- border-radius: 5px;
871
- text-decoration: none;
872
- font-size: 14px;
873
- font-weight: bold;
874
- transition: background-color 0.3s;
875
- " onmouseover="this.style.backgroundColor='#a30718'" onmouseout="this.style.backgroundColor='#bd081c'">Share on Pinterest</a>
876
- </div>
877
- """
878
-
879
- # Add YouTube Shorts share button if a video is available
880
- youtube_share = ""
881
- if video_base64: # Only show YouTube Shorts share button if a video is generated
882
- youtube_share = f"""
883
- <div style="
884
- display: flex;
885
- justify-content: center;
886
- margin-top: 10px;
887
- ">
888
- <a href="https://studio.youtube.com/channel/UC/videos/upload?description={caption}" target="_blank" style="
889
- background-color: #ff0000;
890
- color: white;
891
- padding: 8px 16px;
892
- border-radius: 5px;
893
- text-decoration: none;
894
- font-size: 14px;
895
- font-weight: bold;
896
- transition: background-color 0.3s;
897
- " onmouseover="this.style.backgroundColor='#e60000'" onmouseout="this.style.backgroundColor='#ff0000'">Share to YouTube as a Short</a>
898
- </div>
899
- """
900
-
901
- return f"""
902
- <div style="
903
- display: flex;
904
- flex-direction: column;
905
- align-items: center;
906
- gap: 10px;
907
- margin-top: 10px;
908
- color: white;
909
- font-family: Arial, sans-serif;
910
- ">
911
- {download_links}
912
- {instruction}
913
- {share_links}
914
- {youtube_share}
915
- </div>
916
- """.format(caption=encoded_caption)
917
-
918
- def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_input="", is_loading=False, progress=0, message=""):
919
- """
920
- Generate an HTML string to display the current feed item or loading state.
921
-
922
- Args:
923
- feed_items (list): List of dictionaries containing 'text', 'image_base64', and 'video_base64'.
924
- scroll_to_latest (bool): Whether to auto-scroll to the latest item (not used here).
925
- current_index (int): The index of the item to display.
926
- user_input (str): The user's input concept or idea for context.
927
- is_loading (bool): Whether the feed is currently loading.
928
- progress (float): Current progress percentage (0–100).
929
- message (str): Current loading message to display.
930
-
931
- Returns:
932
- str: HTML string representing the feed or loading state.
933
- """
934
- if is_loading:
935
- # When loading, return an empty feed container (progress is handled by progress_html)
936
- return """
937
- <div id="feed-container" style="
938
- display: flex;
939
- flex-direction: column;
940
- align-items: center;
941
- max-width: 360px;
942
- margin: 0 auto;
943
- background-color: #000;
944
- height: 640px;
945
- border: 1px solid #333;
946
- border-radius: 10px;
947
- color: white;
948
- font-family: Arial, sans-serif;
949
- ">
950
- </div>
951
- """
952
-
953
- if not feed_items or current_index >= len(feed_items):
954
- return """
955
- <div id="feed-container" style="
956
- display: flex;
957
- flex-direction: column;
958
- align-items: center;
959
- justify-content: center;
960
- max-width: 360px;
961
- margin: 0 auto;
962
- background-color: #000;
963
- height: 640px;
964
- border: 1px solid #333;
965
- border-radius: 10px;
966
- color: white;
967
- font-family: Arial, sans-serif;
968
- ">
969
- <p>Enter a concept or idea to start your feed!</p>
970
- </div>
971
- """
972
-
973
- item = feed_items[current_index]
974
- # Check if there is a video to display
975
- if item['video_base64']:
976
- media_element = f"""
977
- <video id="feed-video" controls style="
978
- width: 100%;
979
- height: 100%;
980
- object-fit: cover;
981
- position: absolute;
982
- top: 0;
983
- left: 0;
984
- z-index: 1;
985
- ">
986
- <source src="data:video/mp4;base64,{item['video_base64']}" type="video/mp4">
987
- Your browser does not support the video tag.
988
- </video>
989
- """
990
- else:
991
- # Fallback to image if no video is available
992
- media_element = f"""
993
- <img id="feed-image" src="data:image/png;base64,{item['image_base64']}" style="
994
- width: 100%;
995
- height: 100%;
996
- object-fit: cover;
997
- position: absolute;
998
- top: 0;
999
- left: 0;
1000
- z-index: 1;
1001
- ">
1002
- """
1003
-
1004
- html_str = f"""
1005
- <div id="feed-container" style="
1006
- display: flex;
1007
- flex-direction: column;
1008
- align-items: center;
1009
- max-width: 360px;
1010
- margin: 0 auto;
1011
- background-color: #000;
1012
- height: 640px;
1013
- border: 1px solid #333;
1014
- border-radius: 10px;
1015
- position: relative;
1016
- ">
1017
- <div class="feed-item" style="
1018
- width: 100%;
1019
- height: 100%;
1020
- position: relative;
1021
- display: flex;
1022
- flex-direction: column;
1023
- justify-content: flex-end;
1024
- overflow: hidden;
1025
- cursor: pointer;
1026
- " onclick="handleClick(event)">
1027
- {media_element}
1028
- <div style="
1029
- position: relative;
1030
- z-index: 2;
1031
- background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
1032
- padding: 20px;
1033
- color: white;
1034
- font-family: Arial, sans-serif;
1035
- font-size: 18px;
1036
- font-weight: bold;
1037
- text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
1038
- ">
1039
- {item['text']}
1040
- </div>
1041
- </div>
1042
- </div>
1043
- <script>
1044
- function handleClick(event) {{
1045
- const media = document.getElementById('feed-video') || document.getElementById('feed-image');
1046
- const rect = media.getBoundingClientRect();
1047
- const clickX = event.clientX - rect.left;
1048
- const width = rect.width;
1049
- if (clickX > width * 0.75) {{
1050
- document.getElementById('previous-button').click();
1051
- }}
1052
- }}
1053
- </script>
1054
- <button id="previous-button" style="display: none;" onclick="document.getElementById('previous-button').click()"></button>
1055
- """
1056
- return html_str
1057
-
1058
- # Define the Gradio interface
1059
- with gr.Blocks(
1060
- css="""
1061
- body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
1062
- .gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
1063
- input, select, button, .gr-checkbox { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
1064
- button { background-color: #ff2d55; border: none; }
1065
- button:hover { background-color: #e0264b; }
1066
- .gr-button { width: 100%; margin-top: 10px; }
1067
- .gr-form { background-color: #111; padding: 15px; border-radius: 10px; }
1068
- """,
1069
- title="Create Your Feed"
1070
- ) as demo:
1071
- # State variables
1072
  current_user_input = gr.State(value="")
1073
  current_index = gr.State(value=0)
1074
  feed_items = gr.State(value=[])
1075
  is_loading = gr.State(value=False)
1076
- share_links = gr.State(value="")
1077
 
1078
- # Input section
1079
  with gr.Column(elem_classes="gr-form"):
1080
  gr.Markdown("### Create Your Feed")
1081
- user_input = gr.Textbox(
1082
- label="Enter Concept or Ideas",
1083
- value="",
1084
- placeholder="e.g., sushi adventure, neon tech",
1085
- submit_btn=False
1086
- )
1087
- generate_video_checkbox = gr.Checkbox(
1088
- label="Generate Video (may take longer)",
1089
- value=False
1090
- )
1091
  magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
1092
 
1093
- # Output display
1094
- progress_html = gr.HTML(label="Progress", visible=True)
1095
  feed_html = gr.HTML()
1096
  share_html = gr.HTML(label="Share this item:")
1097
 
1098
- # Event handlers
1099
- # Handle Enter keypress in the concept input
1100
- user_input.submit(
1101
- fn=start_feed,
1102
- inputs=[user_input, generate_video_checkbox, current_index, feed_items],
1103
- outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
1104
- )
1105
-
1106
- # Handle magic button click to generate next item
1107
- magic_button.click(
1108
- fn=load_next,
1109
- inputs=[user_input, generate_video_checkbox, current_index, feed_items],
1110
- outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
1111
- )
1112
-
1113
- # Hidden button for previous item navigation
1114
- previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
1115
-
1116
- # Handle click to go to previous item
1117
- previous_button.click(
1118
- fn=load_previous,
1119
- inputs=[user_input, generate_video_checkbox, current_index, feed_items],
1120
- outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
1121
- )
1122
 
1123
- # Launch the app
1124
  demo.launch()
 
9
  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
20
  try:
21
  api_key = os.environ['GEMINI_API_KEY']
22
  except KeyError:
23
  raise ValueError("Please set the GEMINI_API_KEY environment variable.")
24
  client = genai.Client(api_key=api_key)
25
 
26
+ # Safety settings to disable all filters
27
  SAFETY_SETTINGS = [
28
+ types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
29
+ types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold=types.HarmBlockThreshold.BLOCK_NONE),
30
+ types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
31
+ types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
32
+ types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY, threshold=types.HarmBlockThreshold.BLOCK_NONE),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  ]
34
 
35
  def clean_response_text(response_text):
36
+ """Clean API response by removing Markdown code block markers."""
 
 
 
 
 
 
 
 
37
  cleaned_text = response_text.strip()
38
  if cleaned_text.startswith("```json"):
39
  cleaned_text = cleaned_text[len("```json"):].strip()
 
42
  return cleaned_text
43
 
44
  def generate_ideas(user_input):
45
+ """Generate 5 creative TikTok video ideas based on user input."""
 
 
 
 
 
 
 
 
 
 
46
  yield (10, f"Brainstorming epic ideas for {user_input}... 🌟")
47
 
48
  prompt = f"""
49
+ The user has provided the concept: "{user_input}". Generate 5 diverse and creative ideas for a TikTok video explicitly related to "{user_input}".
 
50
  Each idea should be a short sentence describing a specific scene or concept.
51
+ Return the response as a JSON object with a key 'ideas' containing a list of 5 ideas.
 
52
  Example for "blindfolded Rubik's Cube challenge":
53
  {{"ideas": [
54
  "A blindfolded speedcubing competition with dramatic music",
 
62
  response = client.models.generate_content(
63
  model='gemini-2.0-flash-lite',
64
  contents=[prompt],
65
+ config=types.GenerateContentConfig(temperature=1.2, safety_settings=SAFETY_SETTINGS)
 
 
 
66
  )
 
 
 
67
  cleaned_text = clean_response_text(response.text)
68
  response_json = json.loads(cleaned_text)
69
+ if 'ideas' not in response_json or len(response_json['ideas']) != 5:
70
+ raise ValueError("Invalid JSON format or incorrect number of ideas")
 
71
  ideas = response_json['ideas']
72
  yield (20, f"Ideas locked in for {user_input}! 🚀")
73
  return ideas
 
74
  except Exception as e:
75
  print(f"Error generating ideas: {e}")
76
  yield (20, f"Oops, tweaking the plan for {user_input}... 🔧")
 
82
  f"An action-packed {user_input} challenge with dynamic angles"
83
  ]
84
 
85
+ def generate_item(user_input, ideas, generate_video=False):
86
+ """Generate a feed item (image and optionally video) with progress updates."""
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  video_base64 = None
88
+ max_attempts = 3
89
 
90
  total_attempts = 0
91
+ while total_attempts < max_attempts:
92
  total_attempts += 1
93
  yield (20 + total_attempts * 10, f"Attempt {total_attempts} to craft your {user_input} masterpiece... 🎨")
94
 
95
+ selected_idea = random.choice(ideas)
96
+ prompt = f"""
97
+ The user has provided the concept: "{user_input}". Based on this and the idea "{selected_idea}", create content for a TikTok video.
98
+ Return a JSON object with:
99
+ - 'caption': A short, viral TikTok-style caption with hashtags reflecting "{user_input}".
100
+ - 'image_prompt': A detailed image prompt for a high-quality visual scene, no text or letters.
101
+ Example: {{"caption": "Blindfolded Rubik's Cube MAGIC! 🤯 #rubiks", "image_prompt": "A close-up view of a person solving a Rubik's Cube blindfolded, dramatic style"}}
102
+ """
103
+ try:
104
+ response = client.models.generate_content(
105
+ model='gemini-2.0-flash-lite',
106
+ contents=[prompt],
107
+ config=types.GenerateContentConfig(temperature=1.2, safety_settings=SAFETY_SETTINGS)
108
+ )
109
+ cleaned_text = clean_response_text(response.text)
110
+ response_json = json.loads(cleaned_text)
111
+ text = response_json['caption']
112
+ image_prompt = response_json['image_prompt']
113
+ except Exception as e:
114
+ print(f"Error generating item: {e}")
115
+ text = f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}"
116
+ image_prompt = f"A vivid scene of {selected_idea} related to {user_input}, vibrant pop art style, no text"
117
+
118
+ # Generate image
119
+ try:
120
+ yield (40, f"Rendering your {user_input} vision... ✨")
121
+ imagen = client.models.generate_images(
122
+ model='imagen-3.0-generate-002',
123
+ prompt=image_prompt,
124
+ config=types.GenerateImagesConfig(aspect_ratio="9:16", number_of_images=1)
125
+ )
126
+ if imagen.generated_images:
127
+ image = Image.open(BytesIO(imagen.generated_images[0].image.image_bytes))
128
+ image = image.resize((360, 640), Image.LANCZOS)
129
+ buffered = BytesIO()
130
+ image.save(buffered, format="PNG")
131
+ img_str = base64.b64encode(buffered.getvalue()).decode()
132
+ yield (50, f"Image for {user_input} is ready! 🎉")
133
+ break
134
+ except Exception as e:
135
+ print(f"Error generating image: {e}")
136
+ if total_attempts == max_attempts:
137
+ image = Image.new('RGB', (360, 640), color='gray')
138
+ buffered = BytesIO()
139
+ image.save(buffered, format="PNG")
140
+ img_str = base64.b64encode(buffered.getvalue()).decode()
141
+ yield (60, f"Using a placeholder for {user_input}... 🖼️")
142
+ return {'text': text, 'image_base64': img_str, 'video_base64': None, 'ideas': ideas}
143
+ continue
144
+
145
+ # Generate video if requested
146
+ if generate_video:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  try:
148
  yield (60, f"Filming a viral video for {user_input}... 🎥")
 
 
 
 
 
 
149
  operation = client.models.generate_videos(
150
  model="veo-2.0-generate-001",
151
+ prompt=f"A close-up slow dolly shot of {image_prompt}, realistic style",
152
+ image=imagen.generated_images[0].image,
153
+ config=types.GenerateVideosConfig(aspect_ratio="9:16", duration_seconds=8)
 
 
 
 
 
154
  )
 
155
  while not operation.done:
156
  time.sleep(20)
157
  operation = client.operations.get(operation)
158
+ if operation.response and operation.response.generated_videos:
159
+ video_data = client.files.download(file=operation.response.generated_videos[0].video)
160
+ video_bytes = video_data if isinstance(video_data, bytes) else BytesIO(video_data).getvalue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  video_base64 = base64.b64encode(video_bytes).decode()
 
162
  yield (90, f"Video for {user_input} is a wrap! 🎬")
163
+ return {'text': text, 'image_base64': img_str, 'video_base64': video_base64, 'ideas': ideas}
 
 
 
 
 
 
 
 
164
  except Exception as e:
165
+ print(f"Error generating video: {e}")
166
+ yield (70, f"Skipping video for {user_input}... 📌")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
+ yield (95, f"Polishing your {user_input} masterpiece... ✨")
169
+ return {'text': text, 'image_base64': img_str, 'video_base64': video_base64, 'ideas': ideas}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
  def generate_progress_html(progress, message, user_input):
172
+ """Generate HTML for the progress bar."""
 
 
 
 
 
 
 
 
 
 
173
  return f"""
174
  <div id="progress-container" style="
175
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
176
+ max-width: 360px; margin: 0 auto; background-color: #000; height: 200px;
177
+ border: 1px solid #333; border-radius: 10px; color: white; font-family: Arial, sans-serif;">
178
+ <div id="loading-message" style="font-size: 18px; font-weight: bold; text-align: center; margin-bottom: 20px;">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  {message}
180
  </div>
181
+ <div style="width: 80%; height: 10px; background-color: #333; border-radius: 5px; overflow: hidden;">
182
+ <div id="progress-bar" style="width: {progress}%; height: 100%; background: linear-gradient(to right, #ff2d55, #ff5e78);"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  </div>
184
+ <div style="margin-top: 10px; font-size: 14px; color: #ccc;">{int(progress)}% Complete</div>
185
  <style>
186
+ @keyframes pulse {{ 0% {{ opacity: 1; }} 50% {{ opacity: 0.5; }} 100% {{ opacity: 1; }} }}
187
+ #loading-message {{ animation: pulse 2s infinite; }}
 
 
 
 
 
 
188
  </style>
189
  </div>
190
  """
191
 
192
+ def generate_html(feed_items, manual_upload, current_index, user_input, is_loading):
193
+ """Generate HTML for the feed display."""
194
+ if not feed_items or current_index >= len(feed_items):
195
+ return "<div style='color: white; text-align: center;'>No content yet!</div>"
196
+ item = feed_items[current_index]
197
+ media_html = f'<img src="data:image/png;base64,{item["image_base64"]}" style="width: 360px; height: 640px; object-fit: cover;" />'
198
+ if item['video_base64']:
199
+ media_html += f'<video controls src="data:video/mp4;base64,{item["video_base64"]}" style="width: 360px; height: 640px;"></video>'
200
+ return f"""
201
+ <div style="max-width: 360px; margin: 0 auto; background-color: #000; border: 1px solid #333; border-radius: 10px; color: white;">
202
+ {media_html}
203
+ <p style="padding: 10px;">{item['text']}</p>
204
+ </div>
 
205
  """
206
+
207
+ def generate_share_links(image_base64, video_base64, text):
208
+ """Generate YouTube share links."""
209
+ share_text = urllib.parse.quote(f"{text} Check out this cool content!")
210
+ return f'<a href="https://www.youtube.com/upload?description={share_text}" target="_blank">Share on YouTube</a>'
211
+
212
+ def start_feed(user_input, generate_video, current_index, feed_items):
213
+ """Start or reset the feed with a new item."""
214
+ user_input = user_input.strip() or "trending"
215
  current_user_input = user_input
 
 
216
  is_loading = True
217
  progress_html = generate_progress_html(0, f"Getting started with {user_input}... 🚀", user_input)
218
+ yield (current_user_input, current_index, feed_items, gr.update(), gr.update(), is_loading, progress_html)
 
219
 
220
  try:
 
221
  ideas_gen = generate_ideas(user_input)
222
  ideas = None
223
  for update in ideas_gen:
224
  if isinstance(update, tuple):
225
  progress, message = update
226
  progress_html = generate_progress_html(progress, message, user_input)
227
+ yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
228
  else:
229
  ideas = update
230
 
231
+ item_gen = generate_item(user_input, ideas, generate_video)
 
232
  item = None
233
  for update in item_gen:
234
  if isinstance(update, tuple):
235
  progress, message = update
236
  progress_html = generate_progress_html(progress, message, user_input)
237
+ yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
238
  else:
239
  item = update
240
 
241
  feed_items = [item]
242
  current_index = 0
243
  feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
244
+ share_html = generate_share_links(item['image_base64'], item['video_base64'], item['text'])
245
+ yield (current_user_input, current_index, feed_items, feed_html, share_html, False, "")
 
 
 
246
  except Exception as e:
247
  print(f"Error in start_feed: {e}")
248
+ feed_html = "<div style='color: white; text-align: center;'>Error generating content. Try again!</div>"
249
+ yield (current_user_input, current_index, feed_items, feed_html, "", False, generate_progress_html(100, "Oops, something went wrong! 😅", user_input))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  def load_next(user_input, generate_video, current_index, feed_items):
252
+ """Load the next item in the feed."""
253
+ current_user_input = user_input.strip() or "trending"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  is_loading = True
255
+ progress_html = generate_progress_html(0, f"Loading next {current_user_input} vibe... 🚀", current_user_input)
256
+ yield (current_user_input, current_index, feed_items, gr.update(), gr.update(), is_loading, progress_html)
 
257
 
258
  try:
259
  if current_index + 1 < len(feed_items):
260
  current_index += 1
261
+ progress_html = generate_progress_html(50, f"Switching to the next {current_user_input} moment... 🔄", current_user_input)
262
+ yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
263
  else:
264
  ideas = feed_items[-1]['ideas'] if feed_items else None
265
  if not ideas:
266
+ ideas_gen = generate_ideas(current_user_input)
 
267
  for update in ideas_gen:
268
  if isinstance(update, tuple):
269
  progress, message = update
270
+ progress_html = generate_progress_html(progress, message, current_user_input)
271
+ yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
272
  else:
273
  ideas = update
274
 
275
+ item_gen = generate_item(current_user_input, ideas, generate_video)
276
+ for update in item_gen:
 
 
277
  if isinstance(update, tuple):
278
  progress, message = update
279
+ progress_html = generate_progress_html(progress, message, current_user_input)
280
+ yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
281
  else:
282
+ feed_items.append(update)
283
+ current_index = len(feed_items) - 1
 
 
284
 
285
+ feed_html = generate_html(feed_items, False, current_index, current_user_input, is_loading=False)
286
+ share_html = generate_share_links(feed_items[current_index]['image_base64'], feed_items[current_index]['video_base64'], feed_items[current_index]['text'])
287
+ yield (current_user_input, current_index, feed_items, feed_html, share_html, False, "")
 
 
 
288
  except Exception as e:
289
  print(f"Error in load_next: {e}")
290
+ feed_html = "<div style='color: white; text-align: center;'>Error generating content. Try again!</div>"
291
+ yield (current_user_input, current_index, feed_items, feed_html, "", False, generate_progress_html(100, "Oops, something went wrong! 😅", current_user_input))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  def load_previous(user_input, generate_video, current_index, feed_items):
294
+ """Load the previous item in the feed."""
295
+ current_user_input = user_input.strip() or "trending"
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  if current_index > 0:
297
  current_index -= 1
298
+ feed_html = generate_html(feed_items, False, current_index, current_user_input, is_loading=False)
299
+ share_html = generate_share_links(feed_items[current_index]['image_base64'], feed_items[current_index]['video_base64'], feed_items[current_index]['text'])
300
+ return current_user_input, current_index, feed_items, feed_html, share_html, False, ""
301
+
302
+ # Gradio Interface
303
+ with gr.Blocks(css="""
304
+ body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
305
+ .gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
306
+ input, button { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
307
+ button { background-color: #ff2d55; }
308
+ button:hover { background-color: #e0264b; }
309
+ .gr-button { width: 100%; margin-top: 10px; }
310
+ .gr-form { background-color: #111; padding: 15px; border-radius: 10px; }
311
+ """, title="Create Your Feed") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  current_user_input = gr.State(value="")
313
  current_index = gr.State(value=0)
314
  feed_items = gr.State(value=[])
315
  is_loading = gr.State(value=False)
 
316
 
 
317
  with gr.Column(elem_classes="gr-form"):
318
  gr.Markdown("### Create Your Feed")
319
+ user_input = gr.Textbox(label="Enter Concept or Ideas", placeholder="e.g., jogging in gardens by the bay")
320
+ generate_video_checkbox = gr.Checkbox(label="Generate Video (may take longer)", value=False)
 
 
 
 
 
 
 
 
321
  magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
322
 
323
+ progress_html = gr.HTML(label="Progress")
 
324
  feed_html = gr.HTML()
325
  share_html = gr.HTML(label="Share this item:")
326
 
327
+ user_input.submit(start_feed, [user_input, generate_video_checkbox, current_index, feed_items],
328
+ [current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html])
329
+ magic_button.click(load_next, [user_input, generate_video_checkbox, current_index, feed_items],
330
+ [current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html])
331
+ previous_button = gr.Button("Previous", visible=False).click(load_previous,
332
+ [user_input, generate_video_checkbox, current_index, feed_items],
333
+ [current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
 
335
  demo.launch()