codelion commited on
Commit
90bcd10
·
verified ·
1 Parent(s): 83b50de

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +768 -229
app.py CHANGED
@@ -1,7 +1,11 @@
1
  import gradio as gr
 
 
2
  from PIL import Image
3
  from io import BytesIO
4
  import base64
 
 
5
  import random
6
  import urllib.parse
7
  import time
@@ -12,8 +16,41 @@ current_version = gr.__version__
12
  if current_version < required_version:
13
  raise ValueError(f"Gradio version {current_version} is outdated. Please upgrade to {required_version} or later using 'pip install gradio=={required_version}'.")
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def clean_response_text(response_text):
16
- """Clean API response by removing Markdown code block markers."""
 
 
17
  cleaned_text = response_text.strip()
18
  if cleaned_text.startswith("```json"):
19
  cleaned_text = cleaned_text[len("```json"):].strip()
@@ -22,290 +59,795 @@ def clean_response_text(response_text):
22
  return cleaned_text
23
 
24
  def generate_ideas(user_input):
25
- """Generate 5 creative TikTok video ideas based on user input."""
 
 
 
26
  yield (10, f"Brainstorming epic ideas for {user_input}... 🌟")
 
 
 
 
 
 
 
 
27
  try:
28
- # Placeholder for actual API call (e.g., Google Generative AI)
29
- ideas = [
30
- f"A vibrant {user_input} scene with dynamic lighting",
31
- f"A close-up of {user_input} in a futuristic city",
32
- f"A high-energy {user_input} moment with bold colors",
33
- f"A serene {user_input} scene with soft focus",
34
- f"An action-packed {user_input} challenge"
35
- ]
 
 
 
 
 
 
 
 
36
  yield (20, f"Ideas locked in for {user_input}! 🚀")
37
- yield ideas # Yield the final list
 
38
  except Exception as e:
39
  print(f"Error generating ideas: {e}")
40
  yield (20, f"Oops, tweaking the plan for {user_input}... 🔧")
41
- default_ideas = [
42
  f"A dramatic {user_input} scene with cinematic lighting",
43
  f"A close-up of {user_input} in a futuristic setting",
44
- f"A high-energy {user_input} moment with vibrant colors"
 
 
45
  ]
46
- yield default_ideas # Yield default ideas on failure
47
 
48
- def generate_item(user_input, ideas, generate_video=False):
49
- """Generate a feed item (image and optionally video) with progress updates."""
50
- yield (20, f"Crafting your {user_input} masterpiece... 🎨")
51
- try:
52
- selected_idea = random.choice(ideas) # Requires ideas to be a list
53
- # Simulate image generation
54
- image = Image.new('RGB', (360, 640), color='gray') # Placeholder
55
- buffered = BytesIO()
56
- image.save(buffered, format="PNG")
57
- img_str = base64.b64encode(buffered.getvalue()).decode()
58
- video_base64 = None
59
- if generate_video:
60
- yield (60, f"Filming a viral video for {user_input}... 🎥")
61
- time.sleep(2) # Simulate video generation
62
- # Placeholder for video generation
63
- video_base64 = base64.b64encode(b"fake_video_data").decode()
64
- yield (95, f"Polishing your {user_input} masterpiece... ✨")
65
- yield {
66
- 'text': f"{selected_idea}! 🔥 #{user_input.replace(' ', '')}",
67
- 'image_base64': img_str,
68
- 'video_base64': video_base64,
69
- 'ideas': ideas
70
- }
71
- except Exception as e:
72
- print(f"Error generating item: {e}")
73
- yield (95, f"Falling back to placeholder for {user_input}... 🖼️")
74
- image = Image.new('RGB', (360, 640), color='gray')
75
- buffered = BytesIO()
76
- image.save(buffered, format="PNG")
77
- img_str = base64.b64encode(buffered.getvalue()).decode()
78
- yield {
79
- 'text': f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}",
80
- 'image_base64': img_str,
81
- 'video_base64': None,
82
- 'ideas': ideas
83
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  def generate_progress_html(progress, message, user_input):
86
- """Generate HTML for the progress bar and witty text."""
 
 
87
  return f"""
88
  <div id="progress-container" style="
89
- display: flex; flex-direction: column; align-items: center; justify-content: center;
90
- max-width: 360px; margin: 0 auto; background-color: #000; height: 200px;
91
- border: 1px solid #333; border-radius: 10px; color: white; font-family: Arial, sans-serif;">
92
- <div id="loading-message" style="font-size: 18px; font-weight: bold; text-align: center; margin-bottom: 20px;">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  {message}
94
  </div>
95
- <div style="width: 80%; height: 10px; background-color: #333; border-radius: 5px; overflow: hidden;">
96
- <div id="progress-bar" style="width: {progress}%; height: 100%; background: linear-gradient(to right, #ff2d55, #ff5e78);"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  </div>
98
- <div style="margin-top: 10px; font-size: 14px; color: #ccc;">{int(progress)}% Complete</div>
99
  <style>
100
- @keyframes pulse {{ 0% {{ opacity: 1; }} 50% {{ opacity: 0.5; }} 100% {{ opacity: 1; }} }}
101
- #loading-message {{ animation: pulse 2s infinite; }}
 
 
 
 
 
 
102
  </style>
103
  </div>
104
  """
105
 
106
- def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_input="", is_loading=False):
107
- """Generate HTML for the feed display."""
108
- if is_loading:
109
- return """
110
- <div id="feed-container" style="
111
- display: flex; flex-direction: column; align-items: center;
112
- max-width: 360px; margin: 0 auto; background-color: #000; height: 640px;
113
- border: 1px solid #333; border-radius: 10px; color: white;">
114
- </div>
115
- """
116
- if not feed_items or current_index >= len(feed_items):
117
- return """
118
- <div style="color: white; text-align: center; max-width: 360px; margin: 0 auto;">
119
- Enter a concept or idea to start your feed!
120
- </div>
121
- """
122
- item = feed_items[current_index]
123
- media_element = f"""
124
- <img id="feed-image" src="data:image/png;base64,{item['image_base64']}" style="
125
- width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; z-index: 1;">
126
  """
127
- if item['video_base64']:
128
- media_element = f"""
129
- <video id="feed-video" controls style="
130
- width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; z-index: 1;">
131
- <source src="data:video/mp4;base64,{item['video_base64']}" type="video/mp4">
132
- Your browser does not support the video tag.
133
- </video>
134
- """
135
- return f"""
136
- <div id="feed-container" style="
137
- display: flex; flex-direction: column; align-items: center;
138
- max-width: 360px; margin: 0 auto; background-color: #000; height: 640px;
139
- border: 1px solid #333; border-radius: 10px; position: relative;">
140
- <div class="feed-item" style="
141
- width: 100%; height: 100%; position: relative; display: flex;
142
- flex-direction: column; justify-content: flex-end; overflow: hidden; cursor: pointer;"
143
- onclick="handleClick(event)">
144
- {media_element}
145
- <div style="
146
- position: relative; z-index: 2; background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
147
- padding: 20px; color: white; font-family: Arial, sans-serif; font-size: 18px; font-weight: bold;">
148
- {item['text']}
149
- </div>
150
- </div>
151
- </div>
152
- <script>
153
- function handleClick(event) {{
154
- const media = document.getElementById('feed-video') || document.getElementById('feed-image');
155
- const rect = media.getBoundingClientRect();
156
- const clickX = event.clientX - rect.left;
157
- const width = rect.width;
158
- if (clickX > width * 0.75) {{
159
- document.getElementById('previous-button').click();
160
- }}
161
- }}
162
- </script>
163
  """
 
 
 
 
 
 
 
 
164
 
165
- def generate_share_links(image_base64, video_base64, caption):
166
- """Generate share links for social media platforms."""
167
- image_data_url = f"data:image/png;base64,{image_base64}"
168
- encoded_caption = urllib.parse.quote(caption)
169
- download_links = f"""
170
- <p style="text-align: center; margin-bottom: 10px;">Download the media to share:</p>
171
- <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 15px;">
172
- <a href="{image_data_url}" download="feed_item.png" style="
173
- background-color: #4CAF50; color: white; padding: 8px 16px; border-radius: 5px;
174
- text-decoration: none; font-size: 14px; font-weight: bold;">Download Image</a>
175
- """
176
- if video_base64:
177
- video_data_url = f"data:video/mp4;base64,{video_base64}"
178
- download_links += f"""
179
- <a href="{video_data_url}" download="feed_video.mp4" style="
180
- background-color: #4CAF50; color: white; padding: 8px 16px; border-radius: 5px;
181
- text-decoration: none; font-size: 14px; font-weight: bold;">Download Video</a>
182
- """
183
- download_links += "</div>"
184
- instruction = """
185
- <p style="text-align: center; margin-bottom: 10px;">
186
- Click a share button below to start a post with the caption, then manually upload the downloaded media.
187
- </p>
188
  """
189
- share_links = f"""
190
- <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 15px;">
191
- <a href="https://studio.youtube.com/channel/UC/videos/upload?description={encoded_caption}" target="_blank" style="
192
- background-color: #ff0000; color: white; padding: 8px 16px; border-radius: 5px;
193
- text-decoration: none; font-size: 14px; font-weight: bold;">Share to YouTube as a Short</a>
194
- </div>
195
  """
196
- return f"""
197
- <div style="display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 10px; color: white;">
198
- {download_links}
199
- {instruction}
200
- {share_links}
201
- </div>
202
- """
203
-
204
- def start_feed(user_input, generate_video, current_index, feed_items):
205
- """Start or update the feed based on user input."""
206
- user_input = user_input.strip() or "trending"
207
  current_user_input = user_input
208
  is_loading = True
209
  progress_html = generate_progress_html(0, f"Getting started with {user_input}... 🚀", user_input)
210
- yield (current_user_input, current_index, feed_items, gr.update(), gr.update(), is_loading, progress_html)
211
 
212
  try:
 
213
  ideas_gen = generate_ideas(user_input)
214
- ideas = None
215
- for update in ideas_gen:
216
- if isinstance(update, tuple):
217
- progress, message = update
218
- progress_html = generate_progress_html(progress, message, user_input)
219
- yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
220
- else:
221
- ideas = update # Final list of ideas
222
-
223
- if ideas is None or not isinstance(ideas, list):
224
- raise ValueError("Failed to generate ideas")
225
-
226
- item_gen = generate_item(user_input, ideas, generate_video)
227
- item = None
228
- for update in item_gen:
229
- if isinstance(update, tuple):
230
- progress, message = update
231
- progress_html = generate_progress_html(progress, message, user_input)
232
- yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
233
- else:
234
- item = update # Final item dictionary
235
-
236
- if item is None or not isinstance(item, dict):
237
- raise ValueError("Failed to generate item")
238
-
239
  feed_items = [item]
240
  current_index = 0
241
  feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
242
- share_html = generate_share_links(item['image_base64'], item['video_base64'], item['text'])
243
- yield (current_user_input, current_index, feed_items, feed_html, share_html, False, "")
 
 
 
244
  except Exception as e:
245
  print(f"Error in start_feed: {e}")
246
- feed_html = "<div style='color: white; text-align: center;'>Error generating content. Try again!</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
248
- yield (current_user_input, current_index, feed_items, feed_html, "", False, progress_html)
 
 
 
 
 
249
 
250
  def load_next(user_input, generate_video, current_index, feed_items):
251
- """Load the next item in the feed."""
252
- current_user_input = user_input.strip() or "trending"
 
 
 
253
  user_input = current_user_input
254
  is_loading = True
255
  progress_html = generate_progress_html(0, f"Loading next {user_input} vibe... 🚀", 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 {user_input} moment... 🔄", 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(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, 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
- if ideas is None or not isinstance(ideas, list):
276
- raise ValueError("Failed to generate ideas")
277
-
278
- item_gen = generate_item(user_input, ideas, generate_video)
279
- for update in item_gen:
280
- if isinstance(update, tuple):
281
- progress, message = update
282
  progress_html = generate_progress_html(progress, message, user_input)
283
- yield (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), is_loading, progress_html)
284
- else:
285
- item = update
286
- feed_items.append(item)
287
- current_index = len(feed_items) - 1
288
-
289
- if item is None or not isinstance(item, dict):
290
- raise ValueError("Failed to generate item")
291
-
 
 
292
  feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
293
- share_html = generate_share_links(feed_items[current_index]['image_base64'], feed_items[current_index]['video_base64'], feed_items[current_index]['text'])
294
- yield (current_user_input, current_index, feed_items, feed_html, share_html, False, "")
 
 
 
295
  except Exception as e:
296
  print(f"Error in load_next: {e}")
297
- feed_html = "<div style='color: white; text-align: center;'>Error generating content. Try again!</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
299
- yield (current_user_input, current_index, feed_items, feed_html, "", False, progress_html)
 
 
 
 
 
300
 
301
  def load_previous(user_input, generate_video, current_index, feed_items):
302
- """Load the previous item in the feed."""
303
- current_user_input = user_input.strip() or "trending"
 
 
 
304
  if current_index > 0:
305
  current_index -= 1
306
- feed_html = generate_html(feed_items, False, current_index, current_user_input, is_loading=False)
307
- share_html = generate_share_links(feed_items[current_index]['image_base64'], feed_items[current_index]['video_base64'], feed_items[current_index]['text'])
308
- return current_user_input, current_index, feed_items, feed_html, share_html, False, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  # Define the Gradio interface
311
  with gr.Blocks(
@@ -320,14 +862,12 @@ with gr.Blocks(
320
  """,
321
  title="Create Your Feed"
322
  ) as demo:
323
- # State variables
324
  current_user_input = gr.State(value="")
325
  current_index = gr.State(value=0)
326
  feed_items = gr.State(value=[])
327
  is_loading = gr.State(value=False)
328
  share_links = gr.State(value="")
329
 
330
- # Input section
331
  with gr.Column(elem_classes="gr-form"):
332
  gr.Markdown("### Create Your Feed")
333
  user_input = gr.Textbox(
@@ -342,12 +882,10 @@ with gr.Blocks(
342
  )
343
  magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
344
 
345
- # Output display
346
  progress_html = gr.HTML(label="Progress", visible=True)
347
  feed_html = gr.HTML()
348
  share_html = gr.HTML(label="Share this item:")
349
 
350
- # Event handlers
351
  user_input.submit(
352
  fn=start_feed,
353
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
@@ -361,11 +899,12 @@ with gr.Blocks(
361
  )
362
 
363
  previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
 
364
  previous_button.click(
365
  fn=load_previous,
366
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
367
  outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
368
  )
369
 
370
- # Launch the app with share=True for public link
371
- demo.launch(share=True) # Note: share=True requires a Gradio account and may have usage limits
 
1
  import gradio as gr
2
+ from google import genai
3
+ from google.genai import types
4
  from PIL import Image
5
  from io import BytesIO
6
  import base64
7
+ import os
8
+ import json
9
  import random
10
  import urllib.parse
11
  import time
 
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']
22
+ except KeyError:
23
+ raise ValueError("Please set the GEMINI_API_KEY environment variable.")
24
+ client = genai.Client(api_key=api_key)
25
+
26
+ # Define safety settings to disable all filters for content generation
27
+ SAFETY_SETTINGS = [
28
+ types.SafetySetting(
29
+ category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
30
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
31
+ ),
32
+ types.SafetySetting(
33
+ category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
34
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
35
+ ),
36
+ types.SafetySetting(
37
+ category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
38
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
39
+ ),
40
+ types.SafetySetting(
41
+ category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
42
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
43
+ ),
44
+ types.SafetySetting(
45
+ category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
46
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
47
+ ),
48
+ ]
49
+
50
  def clean_response_text(response_text):
51
+ """
52
+ Clean the API response by removing Markdown code block markers.
53
+ """
54
  cleaned_text = response_text.strip()
55
  if cleaned_text.startswith("```json"):
56
  cleaned_text = cleaned_text[len("```json"):].strip()
 
59
  return cleaned_text
60
 
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.
71
+ Each idea should be a short sentence describing a specific scene or concept.
72
+ Return the response as a JSON object with a single key 'ideas' containing a list of 5 ideas.
73
+ Ensure the response is strictly in JSON format.
74
+ """
75
  try:
76
+ response = client.models.generate_content(
77
+ model='gemini-2.0-flash-lite',
78
+ contents=[prompt],
79
+ config=types.GenerateContentConfig(
80
+ temperature=1.2,
81
+ safety_settings=SAFETY_SETTINGS
82
+ )
83
+ )
84
+ if not response.text or response.text.isspace():
85
+ raise ValueError("Empty response from API")
86
+ cleaned_text = clean_response_text(response.text)
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",
101
+ f"A high-energy {user_input} moment with vibrant colors",
102
+ f"A serene {user_input} scene with soft focus",
103
+ f"An action-packed {user_input} challenge with dynamic angles"
104
  ]
 
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
113
+
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
121
+ img_str = None
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.
129
+ Return a JSON object with two keys:
130
+ - 'caption': A short, viral TikTok-style caption with hashtags that reflects "{user_input}".
131
+ - 'image_prompt': A detailed image prompt for generating a high-quality visual scene, ensuring the theme of "{user_input}" is central.
132
+ The image prompt should describe the scene vividly, specify a perspective and style, and ensure no text or letters are included.
133
+ Ensure the response is strictly in JSON format.
134
+ """
135
+ try:
136
+ response = client.models.generate_content(
137
+ model='gemini-2.0-flash-lite',
138
+ contents=[prompt],
139
+ config=types.GenerateContentConfig(
140
+ temperature=1.2,
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,
162
+ config=types.GenerateImagesConfig(
163
+ aspect_ratio="9:16",
164
+ number_of_images=1
165
+ )
166
+ )
167
+ if imagen.generated_images and len(imagen.generated_images) > 0:
168
+ generated_image = imagen.generated_images[0]
169
+ image = Image.open(BytesIO(generated_image.image.image_bytes))
170
+ target_width = 360
171
+ target_height = int(target_width / 9 * 16)
172
+ image = image.resize((target_width, target_height), Image.LANCZOS)
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,
190
+ 'video_base64': None,
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,
207
+ 'video_base64': None,
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
+ try:
217
+ yield (60, f"Filming a viral video for {user_input}... 🎥")
218
+ video_prompt = f"""
219
+ The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
220
+ Use a close-up shot with a slow dolly shot circling around the subject,
221
+ using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
222
+ """
223
+ operation = client.models.generate_videos(
224
+ model="veo-2.0-generate-001",
225
+ prompt=video_prompt,
226
+ image=generated_image.image,
227
+ config=types.GenerateVideosConfig(
228
+ aspect_ratio="9:16",
229
+ number_of_videos=1,
230
+ duration_seconds=8,
231
+ negative_prompt="blurry, low quality, text, letters"
232
+ )
233
+ )
234
+ while not operation.done:
235
+ time.sleep(20)
236
+ operation = client.operations.get(operation)
237
+
238
+ if operation.error:
239
+ raise ValueError(f"Video generation failed: {operation.error.message}")
240
+ if operation.response is None or not hasattr(operation.response, 'generated_videos'):
241
+ raise ValueError("Video generation failed: No generated_videos in response")
242
+
243
+ if len(operation.response.generated_videos) > 0:
244
+ video = operation.response.generated_videos[0]
245
+ if video is None or not hasattr(video, 'video'):
246
+ raise ValueError("Video is invalid or missing video data")
247
+ video_data = client.files.download(file=video.video)
248
+ if isinstance(video_data, bytes):
249
+ video_bytes = video_data
250
+ else:
251
+ video_buffer = BytesIO()
252
+ for chunk in video_data:
253
+ video_buffer.write(chunk)
254
+ video_bytes = video_buffer.getvalue()
255
+ video_base64 = base64.b64encode(video_bytes).decode()
256
+ video_generated = True
257
+ yield (90, f"Video for {user_input} is a wrap! 🎬")
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 (image-to-video): {e}")
266
+ yield (70, f"Switching to a new video approach for {user_input}... 🎞️")
267
+
268
+ if not video_generated:
269
+ for video_attempt in range(max_video_retries_per_image):
270
+ try:
271
+ yield (75 + video_attempt * 5, f"Trying a fresh video take for {user_input}... 📹")
272
+ video_prompt_base = f"""
273
+ The user concept is "{user_input}". Based on this and the scene: {image_prompt}, create a video.
274
+ Use a close-up shot with a slow dolly shot circling around the subject,
275
+ using shallow focus on the main subject to emphasize details, in a realistic style with cinematic lighting.
276
+ """
277
+ video_prompt = video_prompt_base if video_attempt == 0 else f"""
278
+ The user concept is "{user_input}". Based on this and a simplified scene: {image_prompt}, create a video.
279
+ Use a static close-up shot of the subject in a realistic style.
280
+ """
281
+ operation = client.models.generate_videos(
282
+ model="veo-2.0-generate-001",
283
+ prompt=video_prompt,
284
+ config=types.GenerateVideosConfig(
285
+ aspect_ratio="9:16",
286
+ number_of_videos=1,
287
+ duration_seconds=8,
288
+ negative_prompt="blurry, low quality, text, letters"
289
+ )
290
+ )
291
+ while not operation.done:
292
+ time.sleep(20)
293
+ operation = client.operations.get(operation)
294
+
295
+ if operation.error:
296
+ raise ValueError(f"Video generation failed: {operation.error.message}")
297
+ if operation.response is None or not hasattr(operation.response, 'generated_videos'):
298
+ raise ValueError("Video generation failed: No generated_videos in response")
299
+
300
+ if len(operation.response.generated_videos) > 0:
301
+ video = operation.response.generated_videos[0]
302
+ if video is None or not hasattr(video, 'video'):
303
+ raise ValueError("Video is invalid or missing video data")
304
+ video_data = client.files.download(file=video.video)
305
+ if isinstance(video_data, bytes):
306
+ video_bytes = video_data
307
+ else:
308
+ video_buffer = BytesIO()
309
+ for chunk in video_data:
310
+ video_buffer.write(chunk)
311
+ video_bytes = video_buffer.getvalue()
312
+ video_base64 = base64.b64encode(video_bytes).decode()
313
+ video_generated = True
314
+ yield (90, f"Video for {user_input} is a wrap! 🎬")
315
+ return {
316
+ 'text': text,
317
+ 'image_base64': img_str,
318
+ 'video_base64': video_base64,
319
+ 'ideas': ideas
320
+ }
321
+ except Exception as e:
322
+ print(f"Error generating video (text-to-video attempt {video_attempt + 1}): {e}")
323
+ if video_attempt == max_video_retries_per_image - 1:
324
+ yield (85, f"Finalizing without video for {user_input}... 📌")
325
+ if total_attempts == max_total_attempts:
326
+ yield (95, f"Polishing your {user_input} masterpiece... ✨")
327
+ return {
328
+ 'text': text,
329
+ 'image_base64': img_str,
330
+ 'video_base64': video_base64,
331
+ 'ideas': ideas
332
+ }
333
+ break
334
+
335
+ if img_str is not None:
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
+
344
+ print("Max total attempts reached without successful image generation. Using placeholder.")
345
+ yield (95, f"Falling back to a placeholder for {user_input}... 🖼️")
346
+ image = Image.new('RGB', (360, 640), color='gray')
347
+ buffered = BytesIO()
348
+ image.save(buffered, format="PNG")
349
+ img_str = base64.b64encode(buffered.getvalue()).decode()
350
+ yield (100, f"Ready to roll with {user_input}! 🚀")
351
+ return {
352
+ 'text': f"Amazing {user_input}! 🔥 #{user_input.replace(' ', '')}",
353
+ 'image_base64': img_str,
354
+ 'video_base64': None,
355
+ 'ideas': ideas
356
+ }
357
 
358
  def generate_progress_html(progress, message, user_input):
359
+ """
360
+ Generate HTML for the progress bar and witty text.
361
+ """
362
  return f"""
363
  <div id="progress-container" style="
364
+ display: flex;
365
+ flex-direction: column;
366
+ align-items: center;
367
+ justify-content: center;
368
+ max-width: 360px;
369
+ margin: 0 auto;
370
+ background-color: #000;
371
+ height: 200px;
372
+ border: 1px solid #333;
373
+ border-radius: 10px;
374
+ color: white;
375
+ font-family: Arial, sans-serif;
376
+ position: relative;
377
+ ">
378
+ <div id="loading-message" style="
379
+ font-size: 18px;
380
+ font-weight: bold;
381
+ text-align: center;
382
+ margin-bottom: 20px;
383
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
384
+ ">
385
  {message}
386
  </div>
387
+ <div style="
388
+ width: 80%;
389
+ height: 10px;
390
+ background-color: #333;
391
+ border-radius: 5px;
392
+ overflow: hidden;
393
+ ">
394
+ <div id="progress-bar" style="
395
+ width: {progress}%;
396
+ height: 100%;
397
+ background: linear-gradient(to right, #ff2d55, #ff5e78);
398
+ transition: width 0.5s ease-in-out;
399
+ "></div>
400
+ </div>
401
+ <div style="
402
+ margin-top: 10px;
403
+ font-size: 14px;
404
+ color: #ccc;
405
+ ">
406
+ {int(progress)}% Complete
407
  </div>
 
408
  <style>
409
+ @keyframes pulse {{
410
+ 0% {{ opacity: 1; }}
411
+ 50% {{ opacity: 0.5; }}
412
+ 100% {{ opacity: 1; }}
413
+ }}
414
+ #loading-message {{
415
+ animation: pulse 2s infinite;
416
+ }}
417
  </style>
418
  </div>
419
  """
420
 
421
+ def process_generator(generator):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  """
423
+ Process a generator to collect progress updates and the final result.
424
+ Returns a tuple of (progress_updates, final_result).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  """
426
+ progress_updates = []
427
+ final_result = None
428
+ for item in generator:
429
+ if isinstance(item, tuple):
430
+ progress_updates.append(item)
431
+ else:
432
+ final_result = item
433
+ return progress_updates, final_result
434
 
435
+ def start_feed(user_input, generate_video, current_index, feed_items):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  """
437
+ Start or update the feed based on the user input.
438
+ Returns all outputs expected by Gradio.
 
 
 
 
439
  """
440
+ if not user_input.strip():
441
+ user_input = "trending"
442
+
 
 
 
 
 
 
 
 
443
  current_user_input = user_input
444
  is_loading = True
445
  progress_html = generate_progress_html(0, f"Getting started with {user_input}... 🚀", user_input)
446
+ share_links = ""
447
 
448
  try:
449
+ # Generate ideas
450
  ideas_gen = generate_ideas(user_input)
451
+ progress_updates, ideas = process_generator(ideas_gen)
452
+ for progress, message in progress_updates:
453
+ progress_html = generate_progress_html(progress, message, user_input)
454
+ yield (progress_html, None, None, None, None, is_loading, progress_html)
455
+
456
+ # Generate item
457
+ item_gen = generate_item(user_input, ideas, generate_video=generate_video)
458
+ progress_updates, item = process_generator(item_gen)
459
+ for progress, message in progress_updates:
460
+ progress_html = generate_progress_html(progress, message, user_input)
461
+ yield (progress_html, None, None, None, None, is_loading, progress_html)
462
+
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  feed_items = [item]
464
  current_index = 0
465
  feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
466
+ share_links = generate_share_links(
467
+ item['image_base64'],
468
+ item['video_base64'],
469
+ item['text']
470
+ )
471
  except Exception as e:
472
  print(f"Error in start_feed: {e}")
473
+ feed_items = []
474
+ current_index = 0
475
+ feed_html = """
476
+ <div style="
477
+ display: flex;
478
+ flex-direction: column;
479
+ align-items: center;
480
+ justify-content: center;
481
+ max-width: 360px;
482
+ margin: 0 auto;
483
+ background-color: #000;
484
+ height: 640px;
485
+ border: 1px solid #333;
486
+ border-radius: 10px;
487
+ color: white;
488
+ font-family: Arial, sans-serif;
489
+ ">
490
+ <p>Error generating content. Please try again!</p>
491
+ </div>
492
+ """
493
  progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
494
+ is_loading = False
495
+ return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
496
+
497
+ is_loading = False
498
+ progress_html = ""
499
+ return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
500
 
501
  def load_next(user_input, generate_video, current_index, feed_items):
502
+ """
503
+ Load the next item in the feed.
504
+ Returns all outputs expected by Gradio.
505
+ """
506
+ current_user_input = user_input if user_input.strip() else "trending"
507
  user_input = current_user_input
508
  is_loading = True
509
  progress_html = generate_progress_html(0, f"Loading next {user_input} vibe... 🚀", user_input)
510
+ share_links = ""
511
 
512
  try:
513
  if current_index + 1 < len(feed_items):
514
  current_index += 1
515
  progress_html = generate_progress_html(50, f"Switching to the next {user_input} moment... 🔄", user_input)
516
+ yield (progress_html, None, None, None, None, is_loading, progress_html)
517
  else:
518
  ideas = feed_items[-1]['ideas'] if feed_items else None
519
  if not ideas:
520
  ideas_gen = generate_ideas(user_input)
521
+ progress_updates, ideas = process_generator(ideas_gen)
522
+ for progress, message in progress_updates:
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  progress_html = generate_progress_html(progress, message, user_input)
524
+ yield (progress_html, None, None, None, None, is_loading, progress_html)
525
+
526
+ new_item_gen = generate_item(user_input, ideas, generate_video=generate_video)
527
+ progress_updates, new_item = process_generator(new_item_gen)
528
+ for progress, message in progress_updates:
529
+ progress_html = generate_progress_html(progress, message, user_input)
530
+ yield (progress_html, None, None, None, None, is_loading, progress_html)
531
+
532
+ feed_items.append(new_item)
533
+ current_index = len(feed_items) - 1
534
+
535
  feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
536
+ share_links = generate_share_links(
537
+ feed_items[current_index]['image_base64'],
538
+ feed_items[current_index]['video_base64'],
539
+ feed_items[current_index]['text']
540
+ )
541
  except Exception as e:
542
  print(f"Error in load_next: {e}")
543
+ feed_html = """
544
+ <div style="
545
+ display: flex;
546
+ flex-direction: column;
547
+ align-items: center;
548
+ justify-content: center;
549
+ max-width: 360px;
550
+ margin: 0 auto;
551
+ background-color: #000;
552
+ height: 640px;
553
+ border: 1px solid #333;
554
+ border-radius: 10px;
555
+ color: white;
556
+ font-family: Arial, sans-serif;
557
+ ">
558
+ <p>Error generating content. Please try again!</p>
559
+ </div>
560
+ """
561
  progress_html = generate_progress_html(100, "Oops, something went wrong! 😅", user_input)
562
+ is_loading = False
563
+ return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
564
+
565
+ is_loading = False
566
+ progress_html = ""
567
+ return current_user_input, current_index, feed_items, feed_html, share_links, is_loading, progress_html
568
 
569
  def load_previous(user_input, generate_video, current_index, feed_items):
570
+ """
571
+ Load the previous item in the feed.
572
+ """
573
+ current_user_input = user_input if user_input.strip() else "trending"
574
+
575
  if current_index > 0:
576
  current_index -= 1
577
+ feed_html = generate_html(feed_items, False, current_index, user_input, is_loading=False)
578
+ share_links = generate_share_links(
579
+ feed_items[current_index]['image_base64'],
580
+ feed_items[current_index]['video_base64'],
581
+ feed_items[current_index]['text']
582
+ )
583
+ return current_user_input, current_index, feed_items, feed_html, share_links, False, ""
584
+
585
+ def generate_share_links(image_base64, video_base64, caption):
586
+ """
587
+ Generate share links for social media platforms with download links for image and video.
588
+ """
589
+ image_data_url = f"data:image/png;base64,{image_base64}"
590
+ encoded_caption = urllib.parse.quote(caption)
591
+
592
+ download_links = f"""
593
+ <p style="text-align: center; margin-bottom: 10px;">Download the media to share:</p>
594
+ <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-bottom: 15px;">
595
+ <a href="{image_data_url}" download="feed_item.png" style="
596
+ background-color: #4CAF50;
597
+ color: white;
598
+ padding: 8px 16px;
599
+ border-radius: 5px;
600
+ text-decoration: none;
601
+ font-size: 14px;
602
+ font-weight: bold;
603
+ transition: background-color 0.3s;
604
+ " onmouseover="this.style.backgroundColor='#45a049'" onmouseout="this.style.backgroundColor='#4CAF50'">Download Image</a>
605
+ """
606
+ if video_base64:
607
+ video_data_url = f"data:video/mp4;base64,{video_base64}"
608
+ download_links += f"""
609
+ <a href="{video_data_url}" download="feed_video.mp4" style="
610
+ background-color: #4CAF50;
611
+ color: white;
612
+ padding: 8px 16px;
613
+ border-radius: 5px;
614
+ text-decoration: none;
615
+ font-size: 14px;
616
+ font-weight: bold;
617
+ transition: background-color 0.3s;
618
+ " onmouseover="this.style.backgroundColor='#45a049'" onmouseout="this.style.backgroundColor='#4CAF50'">Download Video</a>
619
+ """
620
+ download_links += "</div>"
621
+
622
+ instruction = """
623
+ <p style="text-align: center; margin-bottom: 10px;">
624
+ Click a share button below to start a post with the caption, then manually upload the downloaded image or video.
625
+ </p>
626
+ """
627
+
628
+ share_links = """
629
+ <div style="
630
+ display: flex;
631
+ flex-wrap: wrap;
632
+ justify-content: center;
633
+ gap: 8px;
634
+ margin-bottom: 15px;
635
+ ">
636
+ <a href="https://www.tiktok.com/upload?caption={caption}" target="_blank" style="
637
+ background-color: #00f2ea;
638
+ color: #000;
639
+ padding: 8px 16px;
640
+ border-radius: 5px;
641
+ text-decoration: none;
642
+ font-size: 14px;
643
+ font-weight: bold;
644
+ transition: background-color 0.3s;
645
+ " onmouseover="this.style.backgroundColor='#00d9d1'" onmouseout="this.style.backgroundColor='#00f2ea'">Share on TikTok</a>
646
+ <a href="https://www.instagram.com/?caption={caption}" target="_blank" style="
647
+ background-color: #e1306c;
648
+ color: white;
649
+ padding: 8px 16px;
650
+ border-radius: 5px;
651
+ text-decoration: none;
652
+ font-size: 14px;
653
+ font-weight: bold;
654
+ transition: background-color 0.3s;
655
+ " onmouseover="this.style.backgroundColor='#c72b5e'" onmouseout="this.style.backgroundColor='#e1306c'">Share on Instagram</a>
656
+ <a href="https://www.facebook.com/sharer/sharer.php?quote={caption}" target="_blank" style="
657
+ background-color: #4267b2;
658
+ color: white;
659
+ padding: 8px 16px;
660
+ border-radius: 5px;
661
+ text-decoration: none;
662
+ font-size: 14px;
663
+ font-weight: bold;
664
+ transition: background-color 0.3s;
665
+ " onmouseover="this.style.backgroundColor='#395a9d'" onmouseout="this.style.backgroundColor='#4267b2'">Share on Facebook</a>
666
+ <a href="https://twitter.com/intent/tweet?text={caption}" target="_blank" style="
667
+ background-color: #1da1f2;
668
+ color: white;
669
+ padding: 8px 16px;
670
+ border-radius: 5px;
671
+ text-decoration: none;
672
+ font-size: 14px;
673
+ font-weight: bold;
674
+ transition: background-color 0.3s;
675
+ " onmouseover="this.style.backgroundColor='#1a91da'" onmouseout="this.style.backgroundColor='#1da1f2'">Share on X</a>
676
+ <a href="https://pinterest.com/pin/create/button/?description={caption}" target="_blank" style="
677
+ background-color: #bd081c;
678
+ color: white;
679
+ padding: 8px 16px;
680
+ border-radius: 5px;
681
+ text-decoration: none;
682
+ font-size: 14px;
683
+ font-weight: bold;
684
+ transition: background-color 0.3s;
685
+ " onmouseover="this.style.backgroundColor='#a30718'" onmouseout="this.style.backgroundColor='#bd081c'">Share on Pinterest</a>
686
+ </div>
687
+ """
688
+
689
+ youtube_share = ""
690
+ if video_base64:
691
+ youtube_share = f"""
692
+ <div style="
693
+ display: flex;
694
+ justify-content: center;
695
+ margin-top: 10px;
696
+ ">
697
+ <a href="https://studio.youtube.com/channel/UC/videos/upload?description={caption}" target="_blank" style="
698
+ background-color: #ff0000;
699
+ color: white;
700
+ padding: 8px 16px;
701
+ border-radius: 5px;
702
+ text-decoration: none;
703
+ font-size: 14px;
704
+ font-weight: bold;
705
+ transition: background-color 0.3s;
706
+ " onmouseover="this.style.backgroundColor='#e60000'" onmouseout="this.style.backgroundColor='#ff0000'">Share to YouTube as a Short</a>
707
+ </div>
708
+ """
709
+
710
+ return f"""
711
+ <div style="
712
+ display: flex;
713
+ flex-direction: column;
714
+ align-items: center;
715
+ gap: 10px;
716
+ margin-top: 10px;
717
+ color: white;
718
+ font-family: Arial, sans-serif;
719
+ ">
720
+ {download_links}
721
+ {instruction}
722
+ {share_links}
723
+ {youtube_share}
724
+ </div>
725
+ """.format(caption=encoded_caption)
726
+
727
+ def generate_html(feed_items, scroll_to_latest=False, current_index=0, user_input="", is_loading=False, progress=0, message=""):
728
+ """
729
+ Generate an HTML string to display the current feed item or loading state.
730
+ """
731
+ if is_loading:
732
+ return """
733
+ <div id="feed-container" style="
734
+ display: flex;
735
+ flex-direction: column;
736
+ align-items: center;
737
+ max-width: 360px;
738
+ margin: 0 auto;
739
+ background-color: #000;
740
+ height: 640px;
741
+ border: 1px solid #333;
742
+ border-radius: 10px;
743
+ color: white;
744
+ font-family: Arial, sans-serif;
745
+ ">
746
+ </div>
747
+ """
748
+
749
+ if not feed_items or current_index >= len(feed_items):
750
+ return """
751
+ <div id="feed-container" style="
752
+ display: flex;
753
+ flex-direction: column;
754
+ align-items: center;
755
+ justify-content: center;
756
+ max-width: 360px;
757
+ margin: 0 auto;
758
+ background-color: #000;
759
+ height: 640px;
760
+ border: 1px solid #333;
761
+ border-radius: 10px;
762
+ color: white;
763
+ font-family: Arial, sans-serif;
764
+ ">
765
+ <p>Enter a concept or idea to start your feed!</p>
766
+ </div>
767
+ """
768
+
769
+ item = feed_items[current_index]
770
+ if item['video_base64']:
771
+ media_element = f"""
772
+ <video id="feed-video" controls style="
773
+ width: 100%;
774
+ height: 100%;
775
+ object-fit: cover;
776
+ position: absolute;
777
+ top: 0;
778
+ left: 0;
779
+ z-index: 1;
780
+ ">
781
+ <source src="data:video/mp4;base64,{item['video_base64']}" type="video/mp4">
782
+ Your browser does not support the video tag.
783
+ </video>
784
+ """
785
+ else:
786
+ media_element = f"""
787
+ <img id="feed-image" src="data:image/png;base64,{item['image_base64']}" style="
788
+ width: 100%;
789
+ height: 100%;
790
+ object-fit: cover;
791
+ position: absolute;
792
+ top: 0;
793
+ left: 0;
794
+ z-index: 1;
795
+ ">
796
+ """
797
+
798
+ html_str = f"""
799
+ <div id="feed-container" style="
800
+ display: flex;
801
+ flex-direction: column;
802
+ align-items: center;
803
+ max-width: 360px;
804
+ margin: 0 auto;
805
+ background-color: #000;
806
+ height: 640px;
807
+ border: 1px solid #333;
808
+ border-radius: 10px;
809
+ position: relative;
810
+ ">
811
+ <div class="feed-item" style="
812
+ width: 100%;
813
+ height: 100%;
814
+ position: relative;
815
+ display: flex;
816
+ flex-direction: column;
817
+ justify-content: flex-end;
818
+ overflow: hidden;
819
+ cursor: pointer;
820
+ " onclick="handleClick(event)">
821
+ {media_element}
822
+ <div style="
823
+ position: relative;
824
+ z-index: 2;
825
+ background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
826
+ padding: 20px;
827
+ color: white;
828
+ font-family: Arial, sans-serif;
829
+ font-size: 18px;
830
+ font-weight: bold;
831
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
832
+ ">
833
+ {item['text']}
834
+ </div>
835
+ </div>
836
+ </div>
837
+ <script>
838
+ function handleClick(event) {{
839
+ const media = document.getElementById('feed-video') || document.getElementById('feed-image');
840
+ const rect = media.getBoundingClientRect();
841
+ const clickX = event.clientX - rect.left;
842
+ const width = rect.width;
843
+ if (clickX > width * 0.75) {{
844
+ document.getElementById('previous-button').click();
845
+ }}
846
+ }}
847
+ </script>
848
+ <button id="previous-button" style="display: none;" onclick="document.getElementById('previous-button').click()"></button>
849
+ """
850
+ return html_str
851
 
852
  # Define the Gradio interface
853
  with gr.Blocks(
 
862
  """,
863
  title="Create Your Feed"
864
  ) as demo:
 
865
  current_user_input = gr.State(value="")
866
  current_index = gr.State(value=0)
867
  feed_items = gr.State(value=[])
868
  is_loading = gr.State(value=False)
869
  share_links = gr.State(value="")
870
 
 
871
  with gr.Column(elem_classes="gr-form"):
872
  gr.Markdown("### Create Your Feed")
873
  user_input = gr.Textbox(
 
882
  )
883
  magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
884
 
 
885
  progress_html = gr.HTML(label="Progress", visible=True)
886
  feed_html = gr.HTML()
887
  share_html = gr.HTML(label="Share this item:")
888
 
 
889
  user_input.submit(
890
  fn=start_feed,
891
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
 
899
  )
900
 
901
  previous_button = gr.Button("Previous", elem_id="previous-button", visible=False)
902
+
903
  previous_button.click(
904
  fn=load_previous,
905
  inputs=[user_input, generate_video_checkbox, current_index, feed_items],
906
  outputs=[current_user_input, current_index, feed_items, feed_html, share_html, is_loading, progress_html]
907
  )
908
 
909
+ # Launch the app
910
+ demo.launch()