google_veo3_task.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import logging
  2. import os
  3. import time
  4. from app.config import settings
  5. from app.service.task.base_task import BaseTask
  6. from app.service.task.task_param import TaskParam
  7. from app.service.task.task_result import TaskResult
  8. from app.util.file_util import generate_unique_filename, write_bytes
  9. from app.util.http_util import logged_client
  10. logger = logging.getLogger(__name__)
  11. class GoogleVeo3Task(BaseTask):
  12. def get_type(self) -> str:
  13. return "google_veo3"
  14. def _headers(self) -> dict[str, str]:
  15. return {
  16. "x-goog-api-key": settings.gemini_api_key,
  17. "Content-Type": "application/json",
  18. }
  19. def execute(self, param: TaskParam) -> TaskResult:
  20. t0 = time.time()
  21. try:
  22. op_name = self._create_request(param.inputs)
  23. logger.info("[google_veo3] Operation created: %s", op_name)
  24. video_uri = self._poll_until_done(op_name)
  25. if not video_uri:
  26. return TaskResult.failure("Failed to get video download link")
  27. logger.info("[google_veo3] Downloading video: %s", video_uri)
  28. video_bytes = self._download_video(video_uri)
  29. video_name = generate_unique_filename(".mp4")
  30. video_rel = os.path.join(param.step_id, video_name)
  31. write_bytes(os.path.join(param.workspace, video_rel), video_bytes)
  32. logger.info("[google_veo3] Completed: video=%s, size=%d bytes, total_elapsed=%.2fs",
  33. video_rel, len(video_bytes), time.time() - t0)
  34. return TaskResult.success({"videos": [video_rel]})
  35. except Exception as e:
  36. logger.error("[google_veo3] Execution failed in %.2fs: %s", time.time() - t0, e, exc_info=True)
  37. return TaskResult.failure(f"GoogleVeo3Task execution failed: {e}")
  38. def _create_request(self, inputs: dict) -> str:
  39. endpoint = f"{settings.gemini_base_url}/models/{settings.gemini_veo3_model}:predictLongRunning"
  40. logger.info("[google_veo3] Submitting generation request: POST %s", endpoint)
  41. with logged_client(timeout=settings.http_timeout) as client:
  42. resp = client.post(endpoint, headers=self._headers(), json=inputs)
  43. logger.info("[google_veo3] Submit response: status=%d", resp.status_code)
  44. resp.raise_for_status()
  45. data = resp.json()
  46. op_name = data.get("name")
  47. if not op_name:
  48. raise RuntimeError(f"Operation name not found: {data}")
  49. return op_name
  50. def _poll_until_done(self, operation_name: str) -> str | None:
  51. url = f"{settings.gemini_base_url}/{operation_name}"
  52. max_retries = 60
  53. poll_t0 = time.time()
  54. for i in range(max_retries):
  55. try:
  56. with logged_client(timeout=settings.http_timeout) as client:
  57. resp = client.get(url, headers=self._headers())
  58. if not resp.is_success:
  59. logger.warning("[google_veo3] Poll #%d: HTTP %d, elapsed=%.1fs",
  60. i + 1, resp.status_code, time.time() - poll_t0)
  61. time.sleep(10)
  62. continue
  63. data = resp.json()
  64. except Exception as e:
  65. logger.warning("[google_veo3] Poll #%d: exception=%s, elapsed=%.1fs",
  66. i + 1, e, time.time() - poll_t0)
  67. time.sleep(10)
  68. continue
  69. done = data.get("done")
  70. logger.info("[google_veo3] Poll #%d: done=%s, elapsed=%.1fs",
  71. i + 1, done, time.time() - poll_t0)
  72. if done is None:
  73. time.sleep(10)
  74. continue
  75. if done:
  76. try:
  77. samples = data["response"]["generateVideoResponse"]["generatedSamples"]
  78. return samples[0]["video"]["uri"]
  79. except (KeyError, IndexError):
  80. raise RuntimeError("Task completed but video URI not found")
  81. time.sleep(10)
  82. raise RuntimeError("Polling timeout after %d attempts" % max_retries)
  83. def _download_video(self, video_uri: str) -> bytes:
  84. t0 = time.time()
  85. with logged_client(timeout=settings.http_timeout, follow_redirects=True) as client:
  86. resp = client.get(video_uri, headers=self._headers())
  87. resp.raise_for_status()
  88. logger.info("[google_veo3] Video downloaded: %d bytes in %.2fs",
  89. len(resp.content), time.time() - t0)
  90. return resp.content