Update app.py
Browse files
app.py
CHANGED
@@ -8,6 +8,7 @@ import os
|
|
8 |
import json
|
9 |
import random
|
10 |
import urllib.parse
|
|
|
11 |
|
12 |
# Initialize the Google Generative AI client with the API key from environment variables
|
13 |
try:
|
@@ -74,18 +75,21 @@ def generate_ideas(tag):
|
|
74 |
f"An action-packed {tag} scene with dynamic colors"
|
75 |
]
|
76 |
|
77 |
-
def generate_item(tag, ideas, max_retries=3):
|
78 |
"""
|
79 |
-
Generate a single feed item using one of the ideas
|
80 |
|
81 |
Args:
|
82 |
tag (str): The tag to base the content on.
|
83 |
ideas (list): List of ideas to choose from.
|
|
|
84 |
max_retries (int): Maximum number of retries if image generation fails.
|
85 |
|
86 |
Returns:
|
87 |
-
dict: A dictionary with 'text' (str), 'image_base64' (str), and 'ideas' (list).
|
88 |
"""
|
|
|
|
|
89 |
for attempt in range(max_retries):
|
90 |
selected_idea = random.choice(ideas)
|
91 |
prompt = f"""
|
@@ -119,26 +123,61 @@ def generate_item(tag, ideas, max_retries=3):
|
|
119 |
|
120 |
# Attempt to generate the image
|
121 |
try:
|
122 |
-
|
123 |
-
image_response = client.models.generate_images(
|
124 |
model='imagen-3.0-generate-002',
|
125 |
prompt=image_prompt,
|
126 |
-
|
127 |
-
|
128 |
-
|
|
|
129 |
)
|
130 |
-
if
|
131 |
-
generated_image =
|
132 |
image = Image.open(BytesIO(generated_image.image.image_bytes))
|
133 |
# Ensure the image matches the desired aspect ratio (9:16 = 0.5625)
|
134 |
target_width = 360
|
135 |
target_height = int(target_width / 9 * 16) # 9:16 aspect ratio
|
136 |
image = image.resize((target_width, target_height), Image.LANCZOS)
|
137 |
-
#
|
138 |
buffered = BytesIO()
|
139 |
image.save(buffered, format="PNG")
|
140 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
else:
|
143 |
print(f"Image generation failed (attempt {attempt + 1}): No images returned")
|
144 |
if attempt == max_retries - 1:
|
@@ -147,7 +186,12 @@ def generate_item(tag, ideas, max_retries=3):
|
|
147 |
buffered = BytesIO()
|
148 |
image.save(buffered, format="PNG")
|
149 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
150 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
151 |
# Retry with new ideas
|
152 |
ideas = generate_ideas(tag)
|
153 |
continue
|
@@ -159,17 +203,23 @@ def generate_item(tag, ideas, max_retries=3):
|
|
159 |
buffered = BytesIO()
|
160 |
image.save(buffered, format="PNG")
|
161 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
162 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
163 |
# Retry with new ideas
|
164 |
ideas = generate_ideas(tag)
|
165 |
continue
|
166 |
|
167 |
-
def start_feed(tag, current_index, feed_items):
|
168 |
"""
|
169 |
Start or update the feed based on the tag.
|
170 |
|
171 |
Args:
|
172 |
tag (str): The tag to generate content for.
|
|
|
173 |
current_index (int): The current item index.
|
174 |
feed_items (list): The current list of feed items.
|
175 |
|
@@ -186,10 +236,14 @@ def start_feed(tag, current_index, feed_items):
|
|
186 |
|
187 |
try:
|
188 |
ideas = generate_ideas(tag)
|
189 |
-
item = generate_item(tag, ideas)
|
190 |
feed_items = [item]
|
191 |
current_index = 0
|
192 |
-
share_links = generate_share_links(
|
|
|
|
|
|
|
|
|
193 |
except Exception as e:
|
194 |
print(f"Error in start_feed: {e}")
|
195 |
feed_items = []
|
@@ -220,12 +274,13 @@ def start_feed(tag, current_index, feed_items):
|
|
220 |
html_content = generate_html(feed_items, False, current_index, tag, is_loading)
|
221 |
return tag, current_index, feed_items, html_content, share_links, is_loading
|
222 |
|
223 |
-
def load_next(tag, current_index, feed_items):
|
224 |
"""
|
225 |
Load the next item in the feed.
|
226 |
|
227 |
Args:
|
228 |
tag (str): The tag to generate content for.
|
|
|
229 |
current_index (int): The current item index.
|
230 |
feed_items (list): The current list of feed items.
|
231 |
|
@@ -241,10 +296,14 @@ def load_next(tag, current_index, feed_items):
|
|
241 |
current_index += 1
|
242 |
else:
|
243 |
ideas = feed_items[-1]['ideas'] if feed_items else generate_ideas(tag)
|
244 |
-
new_item = generate_item(tag, ideas)
|
245 |
feed_items.append(new_item)
|
246 |
current_index = len(feed_items) - 1
|
247 |
-
share_links = generate_share_links(
|
|
|
|
|
|
|
|
|
248 |
except Exception as e:
|
249 |
print(f"Error in load_next: {e}")
|
250 |
html_content = """
|
@@ -272,12 +331,13 @@ def load_next(tag, current_index, feed_items):
|
|
272 |
html_content = generate_html(feed_items, False, current_index, tag, is_loading)
|
273 |
return tag, current_index, feed_items, html_content, share_links, is_loading
|
274 |
|
275 |
-
def load_previous(tag, current_index, feed_items):
|
276 |
"""
|
277 |
Load the previous item in the feed.
|
278 |
|
279 |
Args:
|
280 |
tag (str): The tag to generate content for.
|
|
|
281 |
current_index (int): The current item index.
|
282 |
feed_items (list): The current list of feed items.
|
283 |
|
@@ -287,16 +347,21 @@ def load_previous(tag, current_index, feed_items):
|
|
287 |
if current_index > 0:
|
288 |
current_index -= 1
|
289 |
html_content = generate_html(feed_items, False, current_index, tag, False)
|
290 |
-
share_links = generate_share_links(
|
|
|
|
|
|
|
|
|
291 |
return tag, current_index, feed_items, html_content, share_links, False
|
292 |
|
293 |
-
def generate_share_links(image_base64, caption):
|
294 |
"""
|
295 |
-
Generate share links for social media platforms with
|
296 |
|
297 |
Args:
|
298 |
image_base64 (str): The base64-encoded image data.
|
299 |
-
|
|
|
300 |
|
301 |
Returns:
|
302 |
str: HTML string with share links and download instructions.
|
@@ -304,8 +369,33 @@ def generate_share_links(image_base64, caption):
|
|
304 |
image_data_url = f"data:image/png;base64,{image_base64}"
|
305 |
encoded_caption = urllib.parse.quote(caption)
|
306 |
|
307 |
-
# Generate
|
308 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
309 |
<div style="
|
310 |
display: flex;
|
311 |
flex-direction: column;
|
@@ -315,14 +405,7 @@ def generate_share_links(image_base64, caption):
|
|
315 |
color: white;
|
316 |
font-family: Arial, sans-serif;
|
317 |
">
|
318 |
-
|
319 |
-
<a href="{image_url}" download="feed_item.png" style="
|
320 |
-
background-color: #4CAF50;
|
321 |
-
color: white;
|
322 |
-
padding: 5px 10px;
|
323 |
-
border-radius: 5px;
|
324 |
-
text-decoration: none;
|
325 |
-
">Download Image</a>
|
326 |
<div style="
|
327 |
display: flex;
|
328 |
flex-wrap: wrap;
|
@@ -367,8 +450,7 @@ def generate_share_links(image_base64, caption):
|
|
367 |
">Share on Pinterest</a>
|
368 |
</div>
|
369 |
</div>
|
370 |
-
"""
|
371 |
-
|
372 |
return share_links
|
373 |
|
374 |
def generate_html(feed_items, scroll_to_latest=False, current_index=0, tag="", is_loading=False):
|
@@ -542,7 +624,7 @@ with gr.Blocks(
|
|
542 |
css="""
|
543 |
body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
|
544 |
.gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
|
545 |
-
input, select, button { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
|
546 |
button { background-color: #ff2d55; border: none; }
|
547 |
button:hover { background-color: #e0264b; }
|
548 |
.gr-button { width: 100%; margin-top: 10px; }
|
@@ -566,6 +648,10 @@ with gr.Blocks(
|
|
566 |
placeholder="e.g., sushi adventure, neon tech",
|
567 |
submit_btn=False
|
568 |
)
|
|
|
|
|
|
|
|
|
569 |
magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
|
570 |
|
571 |
# Output display
|
@@ -576,14 +662,14 @@ with gr.Blocks(
|
|
576 |
# Handle Enter keypress in the concept input
|
577 |
tag_input.submit(
|
578 |
fn=start_feed,
|
579 |
-
inputs=[tag_input, current_index, feed_items],
|
580 |
outputs=[current_tag, current_index, feed_items, feed_html, share_html, is_loading]
|
581 |
)
|
582 |
|
583 |
# Handle magic button click to generate next item
|
584 |
magic_button.click(
|
585 |
fn=load_next,
|
586 |
-
inputs=[current_tag, current_index, feed_items],
|
587 |
outputs=[current_tag, current_index, feed_items, feed_html, share_html, is_loading]
|
588 |
)
|
589 |
|
@@ -593,7 +679,7 @@ with gr.Blocks(
|
|
593 |
# Handle click to go to previous item
|
594 |
previous_button.click(
|
595 |
fn=load_previous,
|
596 |
-
inputs=[current_tag, current_index, feed_items],
|
597 |
outputs=[current_tag, current_index, feed_items, feed_html, share_html, is_loading]
|
598 |
)
|
599 |
|
|
|
8 |
import json
|
9 |
import random
|
10 |
import urllib.parse
|
11 |
+
import time
|
12 |
|
13 |
# Initialize the Google Generative AI client with the API key from environment variables
|
14 |
try:
|
|
|
75 |
f"An action-packed {tag} scene with dynamic colors"
|
76 |
]
|
77 |
|
78 |
+
def generate_item(tag, ideas, generate_video=False, max_retries=3):
|
79 |
"""
|
80 |
+
Generate a single feed item (image and optionally video) using one of the ideas.
|
81 |
|
82 |
Args:
|
83 |
tag (str): The tag to base the content on.
|
84 |
ideas (list): List of ideas to choose from.
|
85 |
+
generate_video (bool): Whether to generate a video from the image.
|
86 |
max_retries (int): Maximum number of retries if image generation fails.
|
87 |
|
88 |
Returns:
|
89 |
+
dict: A dictionary with 'text' (str), 'image_base64' (str), 'video_base64_list' (list of str), and 'ideas' (list).
|
90 |
"""
|
91 |
+
video_base64_list = []
|
92 |
+
|
93 |
for attempt in range(max_retries):
|
94 |
selected_idea = random.choice(ideas)
|
95 |
prompt = f"""
|
|
|
123 |
|
124 |
# Attempt to generate the image
|
125 |
try:
|
126 |
+
imagen = client.models.generate_images(
|
|
|
127 |
model='imagen-3.0-generate-002',
|
128 |
prompt=image_prompt,
|
129 |
+
config=types.GenerateImagesConfig(
|
130 |
+
aspect_ratio="9:16",
|
131 |
+
number_of_images=1
|
132 |
+
)
|
133 |
)
|
134 |
+
if imagen.generated_images and len(imagen.generated_images) > 0:
|
135 |
+
generated_image = imagen.generated_images[0]
|
136 |
image = Image.open(BytesIO(generated_image.image.image_bytes))
|
137 |
# Ensure the image matches the desired aspect ratio (9:16 = 0.5625)
|
138 |
target_width = 360
|
139 |
target_height = int(target_width / 9 * 16) # 9:16 aspect ratio
|
140 |
image = image.resize((target_width, target_height), Image.LANCZOS)
|
141 |
+
# Convert image to base64
|
142 |
buffered = BytesIO()
|
143 |
image.save(buffered, format="PNG")
|
144 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
145 |
+
|
146 |
+
# Generate video if enabled
|
147 |
+
if generate_video:
|
148 |
+
try:
|
149 |
+
operation = client.models.generate_videos(
|
150 |
+
model="veo-2.0-generate-001",
|
151 |
+
prompt=image_prompt,
|
152 |
+
image=generated_image.image,
|
153 |
+
config=types.GenerateVideosConfig(
|
154 |
+
aspect_ratio="9:16",
|
155 |
+
number_of_videos=2
|
156 |
+
)
|
157 |
+
)
|
158 |
+
# Wait for videos to generate
|
159 |
+
while not operation.done:
|
160 |
+
time.sleep(20)
|
161 |
+
operation = client.operations.get(operation)
|
162 |
+
|
163 |
+
for n, video in enumerate(operation.response.generated_videos):
|
164 |
+
fname = f'with_image_input{n}.mp4'
|
165 |
+
print(f"Generated video: {fname}")
|
166 |
+
client.files.download(file=video.video)
|
167 |
+
video_buffer = BytesIO()
|
168 |
+
video.video.save(video_buffer)
|
169 |
+
video_base64 = base64.b64encode(video_buffer.getvalue()).decode()
|
170 |
+
video_base64_list.append(video_base64)
|
171 |
+
except Exception as e:
|
172 |
+
print(f"Error generating video: {e}")
|
173 |
+
video_base64_list = [] # Proceed without video if generation fails
|
174 |
+
|
175 |
+
return {
|
176 |
+
'text': text,
|
177 |
+
'image_base64': img_str,
|
178 |
+
'video_base64_list': video_base64_list,
|
179 |
+
'ideas': ideas
|
180 |
+
}
|
181 |
else:
|
182 |
print(f"Image generation failed (attempt {attempt + 1}): No images returned")
|
183 |
if attempt == max_retries - 1:
|
|
|
186 |
buffered = BytesIO()
|
187 |
image.save(buffered, format="PNG")
|
188 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
189 |
+
return {
|
190 |
+
'text': text,
|
191 |
+
'image_base64': img_str,
|
192 |
+
'video_base64_list': [],
|
193 |
+
'ideas': ideas
|
194 |
+
}
|
195 |
# Retry with new ideas
|
196 |
ideas = generate_ideas(tag)
|
197 |
continue
|
|
|
203 |
buffered = BytesIO()
|
204 |
image.save(buffered, format="PNG")
|
205 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
206 |
+
return {
|
207 |
+
'text': text,
|
208 |
+
'image_base64': img_str,
|
209 |
+
'video_base64_list': [],
|
210 |
+
'ideas': ideas
|
211 |
+
}
|
212 |
# Retry with new ideas
|
213 |
ideas = generate_ideas(tag)
|
214 |
continue
|
215 |
|
216 |
+
def start_feed(tag, generate_video, current_index, feed_items):
|
217 |
"""
|
218 |
Start or update the feed based on the tag.
|
219 |
|
220 |
Args:
|
221 |
tag (str): The tag to generate content for.
|
222 |
+
generate_video (bool): Whether to generate a video.
|
223 |
current_index (int): The current item index.
|
224 |
feed_items (list): The current list of feed items.
|
225 |
|
|
|
236 |
|
237 |
try:
|
238 |
ideas = generate_ideas(tag)
|
239 |
+
item = generate_item(tag, ideas, generate_video=generate_video)
|
240 |
feed_items = [item]
|
241 |
current_index = 0
|
242 |
+
share_links = generate_share_links(
|
243 |
+
item['image_base64'],
|
244 |
+
item['video_base64_list'],
|
245 |
+
item['text']
|
246 |
+
)
|
247 |
except Exception as e:
|
248 |
print(f"Error in start_feed: {e}")
|
249 |
feed_items = []
|
|
|
274 |
html_content = generate_html(feed_items, False, current_index, tag, is_loading)
|
275 |
return tag, current_index, feed_items, html_content, share_links, is_loading
|
276 |
|
277 |
+
def load_next(tag, generate_video, current_index, feed_items):
|
278 |
"""
|
279 |
Load the next item in the feed.
|
280 |
|
281 |
Args:
|
282 |
tag (str): The tag to generate content for.
|
283 |
+
generate_video (bool): Whether to generate a video.
|
284 |
current_index (int): The current item index.
|
285 |
feed_items (list): The current list of feed items.
|
286 |
|
|
|
296 |
current_index += 1
|
297 |
else:
|
298 |
ideas = feed_items[-1]['ideas'] if feed_items else generate_ideas(tag)
|
299 |
+
new_item = generate_item(tag, ideas, generate_video=generate_video)
|
300 |
feed_items.append(new_item)
|
301 |
current_index = len(feed_items) - 1
|
302 |
+
share_links = generate_share_links(
|
303 |
+
feed_items[current_index]['image_base64'],
|
304 |
+
IRfeed_items[current_index]['video_base64_list'],
|
305 |
+
feed_items[current_index]['text']
|
306 |
+
)
|
307 |
except Exception as e:
|
308 |
print(f"Error in load_next: {e}")
|
309 |
html_content = """
|
|
|
331 |
html_content = generate_html(feed_items, False, current_index, tag, is_loading)
|
332 |
return tag, current_index, feed_items, html_content, share_links, is_loading
|
333 |
|
334 |
+
def load_previous(tag, generate_video, current_index, feed_items):
|
335 |
"""
|
336 |
Load the previous item in the feed.
|
337 |
|
338 |
Args:
|
339 |
tag (str): The tag to generate content for.
|
340 |
+
generate_video (bool): Whether to generate a video (not used here).
|
341 |
current_index (int): The current item index.
|
342 |
feed_items (list): The current list of feed items.
|
343 |
|
|
|
347 |
if current_index > 0:
|
348 |
current_index -= 1
|
349 |
html_content = generate_html(feed_items, False, current_index, tag, False)
|
350 |
+
share_links = generate_share_links(
|
351 |
+
feed_items[current_index]['image_base64'],
|
352 |
+
feed_items[current_index]['video_base64_list'],
|
353 |
+
feed_items[current_index]['text']
|
354 |
+
)
|
355 |
return tag, current_index, feed_items, html_content, share_links, False
|
356 |
|
357 |
+
def generate_share_links(image_base64, video_base64_list, caption):
|
358 |
"""
|
359 |
+
Generate share links for social media platforms with download links for image and video.
|
360 |
|
361 |
Args:
|
362 |
image_base64 (str): The base64-encoded image data.
|
363 |
+
video_base64_list (list): List of base64-encoded video data.
|
364 |
+
caption (str): The caption to share.
|
365 |
|
366 |
Returns:
|
367 |
str: HTML string with share links and download instructions.
|
|
|
369 |
image_data_url = f"data:image/png;base64,{image_base64}"
|
370 |
encoded_caption = urllib.parse.quote(caption)
|
371 |
|
372 |
+
# Generate download links for image and videos
|
373 |
+
download_links = f"""
|
374 |
+
<p style="text-align: center;">Download and attach the image/video to share:</p>
|
375 |
+
<a href="{image_data_url}" download="feed_item.png" style="
|
376 |
+
background-color: #4CAF50;
|
377 |
+
color: white;
|
378 |
+
padding: 5px 10px;
|
379 |
+
border-radius: 5px;
|
380 |
+
text-decoration: none;
|
381 |
+
margin: 5px;
|
382 |
+
">Download Image</a>
|
383 |
+
"""
|
384 |
+
for i, video_base64 in enumerate(video_base64_list):
|
385 |
+
video_data_url = f"data:video/mp4;base64,{video_base64}"
|
386 |
+
download_links += f"""
|
387 |
+
<a href="{video_data_url}" download="feed_video_{i}.mp4" style="
|
388 |
+
background-color: #4CAF50;
|
389 |
+
color: white;
|
390 |
+
padding: 5px 10px;
|
391 |
+
border-radius: 5px;
|
392 |
+
text-decoration: none;
|
393 |
+
margin: 5px;
|
394 |
+
">Download Video {i+1}</a>
|
395 |
+
"""
|
396 |
+
|
397 |
+
# Generate share links using only the caption
|
398 |
+
share_links = f"""
|
399 |
<div style="
|
400 |
display: flex;
|
401 |
flex-direction: column;
|
|
|
405 |
color: white;
|
406 |
font-family: Arial, sans-serif;
|
407 |
">
|
408 |
+
{download_links}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
409 |
<div style="
|
410 |
display: flex;
|
411 |
flex-wrap: wrap;
|
|
|
450 |
">Share on Pinterest</a>
|
451 |
</div>
|
452 |
</div>
|
453 |
+
"""
|
|
|
454 |
return share_links
|
455 |
|
456 |
def generate_html(feed_items, scroll_to_latest=False, current_index=0, tag="", is_loading=False):
|
|
|
624 |
css="""
|
625 |
body { background-color: #000; color: #fff; font-family: Arial, sans-serif; }
|
626 |
.gradio-container { max-width: 400px; margin: 0 auto; padding: 10px; }
|
627 |
+
input, select, button, .gr-checkbox { border-radius: 5px; background-color: #222; color: #fff; border: 1px solid #444; }
|
628 |
button { background-color: #ff2d55; border: none; }
|
629 |
button:hover { background-color: #e0264b; }
|
630 |
.gr-button { width: 100%; margin-top: 10px; }
|
|
|
648 |
placeholder="e.g., sushi adventure, neon tech",
|
649 |
submit_btn=False
|
650 |
)
|
651 |
+
generate_video_checkbox = gr.Checkbox(
|
652 |
+
label="Generate Video (may take longer)",
|
653 |
+
value=False
|
654 |
+
)
|
655 |
magic_button = gr.Button("✨ Generate Next Item", elem_classes="gr-button")
|
656 |
|
657 |
# Output display
|
|
|
662 |
# Handle Enter keypress in the concept input
|
663 |
tag_input.submit(
|
664 |
fn=start_feed,
|
665 |
+
inputs=[tag_input, generate_video_checkbox, current_index, feed_items],
|
666 |
outputs=[current_tag, current_index, feed_items, feed_html, share_html, is_loading]
|
667 |
)
|
668 |
|
669 |
# Handle magic button click to generate next item
|
670 |
magic_button.click(
|
671 |
fn=load_next,
|
672 |
+
inputs=[current_tag, generate_video_checkbox, current_index, feed_items],
|
673 |
outputs=[current_tag, current_index, feed_items, feed_html, share_html, is_loading]
|
674 |
)
|
675 |
|
|
|
679 |
# Handle click to go to previous item
|
680 |
previous_button.click(
|
681 |
fn=load_previous,
|
682 |
+
inputs=[current_tag, generate_video_checkbox, current_index, feed_items],
|
683 |
outputs=[current_tag, current_index, feed_items, feed_html, share_html, is_loading]
|
684 |
)
|
685 |
|