seedance_video_task.py 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import logging
  2. import time
  3. from app.config import settings
  4. from app.service.task.base_task import BaseTask
  5. from app.service.task.task_param import TaskParam
  6. from app.service.task.task_result import TaskResult
  7. from app.util.http_util import logged_client
  8. from app.util.retry_util import retry_on_failure
  9. logger = logging.getLogger(__name__)
  10. class SeedanceVideoTask(BaseTask):
  11. def get_type(self) -> str:
  12. return "seedance_video"
  13. def _headers(self) -> dict[str, str]:
  14. return {
  15. "Content-Type": "application/json",
  16. "Authorization": f"Bearer {settings.seed_api_key}",
  17. }
  18. def _submit(self, url: str, inputs: dict) -> dict:
  19. with logged_client(timeout=60) as client:
  20. resp = client.post(url, headers=self._headers(), json=inputs)
  21. logger.info("[seedance_video] Submit response: status=%d", resp.status_code)
  22. if not resp.is_success:
  23. raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
  24. data = resp.json()
  25. task_id = data.get("id")
  26. if not task_id:
  27. raise RuntimeError(f"No task ID in response: {data}")
  28. return data
  29. def execute(self, param: TaskParam) -> TaskResult:
  30. execute_url = settings.seedance_video_url
  31. t0 = time.time()
  32. logger.info("[seedance_video] Submitting task: POST %s", execute_url)
  33. try:
  34. data = retry_on_failure(
  35. lambda: self._submit(execute_url, param.inputs),
  36. task_name="seedance_video",
  37. )
  38. task_id = data.get("id")
  39. logger.info("[seedance_video] Task submitted, remote_id=%s", task_id)
  40. except Exception as e:
  41. logger.error("[seedance_video] Submit failed: %s", e)
  42. return TaskResult.failure(f"Failed to submit task: {e}")
  43. result = self._poll(task_id)
  44. if result is None:
  45. return TaskResult.failure("Failed to poll task status")
  46. if result.get("status") != "succeeded":
  47. return TaskResult.failure(f"Task failed with status: {result.get('status')}")
  48. videos: list[str] = []
  49. content = result.get("content", {})
  50. video_url = content.get("video_url")
  51. if video_url:
  52. videos.append(video_url)
  53. logger.info("[seedance_video] Completed: %d videos, total_elapsed=%.2fs",
  54. len(videos), time.time() - t0)
  55. return TaskResult.success({"videos": videos})
  56. def _poll(self, task_id: str) -> dict | None:
  57. search_url = f"{settings.seedance_video_url}/{task_id}"
  58. max_attempts = 30
  59. poll_t0 = time.time()
  60. for attempt in range(1, max_attempts + 1):
  61. try:
  62. with logged_client(timeout=60) as client:
  63. resp = client.get(search_url, headers=self._headers())
  64. data = resp.json()
  65. status = data.get("status")
  66. logger.info("[seedance_video] Poll #%d/%d: remote_id=%s, status=%s, elapsed=%.1fs",
  67. attempt, max_attempts, task_id, status, time.time() - poll_t0)
  68. if status == "succeeded":
  69. return data
  70. if status in ("failed", "cancelled"):
  71. logger.error("[seedance_video] Task %s terminated with status=%s", task_id, status)
  72. return None
  73. if attempt < max_attempts:
  74. time.sleep(2)
  75. except Exception as e:
  76. logger.error("[seedance_video] Poll #%d error for %s: %s", attempt, task_id, e)
  77. if attempt < max_attempts:
  78. time.sleep(2)
  79. logger.warning("[seedance_video] Max polling attempts (%d) reached for %s", max_attempts, task_id)
  80. return None