Spaces:
Running
Running
- image_examples.py +25 -38
image_examples.py
CHANGED
|
@@ -279,102 +279,89 @@ def generate_video_from_image(image_path):
|
|
| 279 |
with open(image_path, "rb") as img_file:
|
| 280 |
img_bytes = img_file.read()
|
| 281 |
|
| 282 |
-
# 簡單判斷 MIME type
|
| 283 |
mime_type = "image/jpeg"
|
| 284 |
if image_path.lower().endswith(".png"):
|
| 285 |
mime_type = "image/png"
|
| 286 |
elif image_path.lower().endswith(".webp"):
|
| 287 |
mime_type = "image/webp"
|
| 288 |
|
| 289 |
-
# 這是將本地圖片傳給 Veo 的關鍵步驟
|
| 290 |
-
# 官方範例的 image = imagen.generated_images[0].image 是 Imagen API 的輸出
|
| 291 |
-
# 我們需要自己建構這個 Image 物件
|
| 292 |
image_for_veo = GeminiImage(image_bytes=img_bytes, mime_type=mime_type)
|
| 293 |
app.logger.info(f"Successfully created GeminiImage object for Veo. MIME type: {mime_type}")
|
| 294 |
except Exception as e:
|
| 295 |
app.logger.error(f"Failed to read or convert image {image_path} for Veo: {e}")
|
| 296 |
-
return image_path
|
| 297 |
|
| 298 |
try:
|
| 299 |
# 2. 呼叫 Veo API 生成影片
|
| 300 |
operation = client.models.generate_videos(
|
| 301 |
-
model="veo-2.0-generate-001",
|
| 302 |
prompt=prompt,
|
| 303 |
-
image=image_for_veo,
|
| 304 |
config=types.GenerateVideosConfig(
|
| 305 |
person_generation="dont_allow",
|
| 306 |
aspect_ratio="16:9",
|
| 307 |
-
number_of_videos=1
|
| 308 |
),
|
| 309 |
)
|
| 310 |
app.logger.info(f"Video generation initiated. Operation name: {operation.name}")
|
| 311 |
|
| 312 |
# 3. 等待影片生成完成
|
| 313 |
while not operation.done:
|
| 314 |
-
app.logger.info(f"Waiting for video generation... Operation: {operation.name}
|
| 315 |
-
time.sleep(20)
|
| 316 |
-
operation = client.operations.get(operation
|
| 317 |
|
| 318 |
app.logger.info(f"Video generation operation completed. Done: {operation.done}")
|
| 319 |
|
| 320 |
# 4. 處理並儲存生成的影片
|
| 321 |
if operation.error:
|
| 322 |
-
app.logger.error(f"Video generation failed with API error: {operation.error
|
| 323 |
return image_path
|
| 324 |
|
| 325 |
if operation.response and operation.response.generated_videos:
|
| 326 |
image_dir = os.path.join(os.getcwd(), "images")
|
| 327 |
-
os.makedirs(image_dir, exist_ok=True)
|
| 328 |
|
| 329 |
for n, generated_video_part in enumerate(operation.response.generated_videos):
|
| 330 |
-
# generated_video_part.video 應該是 VideoData 物件
|
| 331 |
if hasattr(generated_video_part, 'video') and generated_video_part.video:
|
| 332 |
video_data = generated_video_part.video
|
| 333 |
|
| 334 |
base, _ = os.path.splitext(os.path.basename(image_path))
|
| 335 |
-
# 官方範例檔名 'with_image_input{n}.mp4',我們用原始檔名基礎
|
| 336 |
fname = f"{base}_veo{n}.mp4"
|
| 337 |
save_path = os.path.join(image_dir, fname)
|
| 338 |
|
| 339 |
try:
|
| 340 |
-
#
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
# # return save_path
|
| 355 |
-
else:
|
| 356 |
-
app.logger.error(f"VideoData for {fname} does not have a 'save' method or a downloadable 'uri'. Cannot save.")
|
| 357 |
-
|
| 358 |
except Exception as e:
|
| 359 |
app.logger.error(f"Error saving video {fname}: {e}")
|
| 360 |
else:
|
| 361 |
app.logger.warning(f"Generated video part {n} does not contain valid video data.")
|
| 362 |
|
| 363 |
app.logger.warning("Processed all video parts but none were successfully saved.")
|
| 364 |
-
return image_path
|
| 365 |
|
| 366 |
else:
|
| 367 |
app.logger.warning("Video generation operation completed, but no videos found in the response.")
|
| 368 |
return image_path
|
| 369 |
|
| 370 |
-
except genai_errors.GoogleAPICallError as ge:
|
| 371 |
-
app.logger.error(f"Google GenAI API Call Error during video generation: {ge}")
|
| 372 |
-
return image_path
|
| 373 |
except Exception as e:
|
| 374 |
app.logger.error(f"An unexpected error occurred during video generation: {e}")
|
| 375 |
return image_path
|
| 376 |
|
| 377 |
-
app.logger.warning(f"Reached end of generate_video_from_image for {image_path} without returning a new video path.")
|
| 378 |
return image_path
|
| 379 |
|
| 380 |
# ... (如果你的應用程式是透過 gunicorn 執行,則不需要 if __name__ == "__main__":)
|
|
|
|
| 279 |
with open(image_path, "rb") as img_file:
|
| 280 |
img_bytes = img_file.read()
|
| 281 |
|
| 282 |
+
# 簡單判斷 MIME type
|
| 283 |
mime_type = "image/jpeg"
|
| 284 |
if image_path.lower().endswith(".png"):
|
| 285 |
mime_type = "image/png"
|
| 286 |
elif image_path.lower().endswith(".webp"):
|
| 287 |
mime_type = "image/webp"
|
| 288 |
|
|
|
|
|
|
|
|
|
|
| 289 |
image_for_veo = GeminiImage(image_bytes=img_bytes, mime_type=mime_type)
|
| 290 |
app.logger.info(f"Successfully created GeminiImage object for Veo. MIME type: {mime_type}")
|
| 291 |
except Exception as e:
|
| 292 |
app.logger.error(f"Failed to read or convert image {image_path} for Veo: {e}")
|
| 293 |
+
return image_path
|
| 294 |
|
| 295 |
try:
|
| 296 |
# 2. 呼叫 Veo API 生成影片
|
| 297 |
operation = client.models.generate_videos(
|
| 298 |
+
model="veo-2.0-generate-001",
|
| 299 |
prompt=prompt,
|
| 300 |
+
image=image_for_veo,
|
| 301 |
config=types.GenerateVideosConfig(
|
| 302 |
person_generation="dont_allow",
|
| 303 |
aspect_ratio="16:9",
|
| 304 |
+
number_of_videos=1
|
| 305 |
),
|
| 306 |
)
|
| 307 |
app.logger.info(f"Video generation initiated. Operation name: {operation.name}")
|
| 308 |
|
| 309 |
# 3. 等待影片生成完成
|
| 310 |
while not operation.done:
|
| 311 |
+
app.logger.info(f"Waiting for video generation... Operation: {operation.name}")
|
| 312 |
+
time.sleep(20)
|
| 313 |
+
operation = client.operations.get(operation)
|
| 314 |
|
| 315 |
app.logger.info(f"Video generation operation completed. Done: {operation.done}")
|
| 316 |
|
| 317 |
# 4. 處理並儲存生成的影片
|
| 318 |
if operation.error:
|
| 319 |
+
app.logger.error(f"Video generation failed with API error: {operation.error}")
|
| 320 |
return image_path
|
| 321 |
|
| 322 |
if operation.response and operation.response.generated_videos:
|
| 323 |
image_dir = os.path.join(os.getcwd(), "images")
|
| 324 |
+
os.makedirs(image_dir, exist_ok=True)
|
| 325 |
|
| 326 |
for n, generated_video_part in enumerate(operation.response.generated_videos):
|
|
|
|
| 327 |
if hasattr(generated_video_part, 'video') and generated_video_part.video:
|
| 328 |
video_data = generated_video_part.video
|
| 329 |
|
| 330 |
base, _ = os.path.splitext(os.path.basename(image_path))
|
|
|
|
| 331 |
fname = f"{base}_veo{n}.mp4"
|
| 332 |
save_path = os.path.join(image_dir, fname)
|
| 333 |
|
| 334 |
try:
|
| 335 |
+
# 根據官方範例,先下載再儲存
|
| 336 |
+
client.files.download(file=video_data)
|
| 337 |
+
video_data.save(save_path)
|
| 338 |
+
|
| 339 |
+
# 生成完整的網址並 LOG 印出
|
| 340 |
+
video_filename = os.path.basename(save_path)
|
| 341 |
+
full_video_url = f"https://{base_url}/images/{video_filename}"
|
| 342 |
+
|
| 343 |
+
app.logger.info(f"✅ Video successfully saved to: {save_path}")
|
| 344 |
+
app.logger.info(f"🔗 Full video URL for testing: {full_video_url}")
|
| 345 |
+
app.logger.info(f"📁 Video filename: {video_filename}")
|
| 346 |
+
|
| 347 |
+
return save_path
|
| 348 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
except Exception as e:
|
| 350 |
app.logger.error(f"Error saving video {fname}: {e}")
|
| 351 |
else:
|
| 352 |
app.logger.warning(f"Generated video part {n} does not contain valid video data.")
|
| 353 |
|
| 354 |
app.logger.warning("Processed all video parts but none were successfully saved.")
|
| 355 |
+
return image_path
|
| 356 |
|
| 357 |
else:
|
| 358 |
app.logger.warning("Video generation operation completed, but no videos found in the response.")
|
| 359 |
return image_path
|
| 360 |
|
|
|
|
|
|
|
|
|
|
| 361 |
except Exception as e:
|
| 362 |
app.logger.error(f"An unexpected error occurred during video generation: {e}")
|
| 363 |
return image_path
|
| 364 |
|
|
|
|
| 365 |
return image_path
|
| 366 |
|
| 367 |
# ... (如果你的應用程式是透過 gunicorn 執行,則不需要 if __name__ == "__main__":)
|