keling_image2video_task.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import logging
  2. import time
  3. import jwt
  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.http_util import logged_client
  9. logger = logging.getLogger(__name__)
  10. class KelingImage2VideoTask(BaseTask):
  11. def get_type(self) -> str:
  12. return "keling_image2video"
  13. @property
  14. def execute_url(self) -> str:
  15. return settings.keling_image2video_url
  16. @property
  17. def search_url(self) -> str:
  18. return settings.keling_image2video_url
  19. def _generate_jwt(self) -> str:
  20. now = time.time()
  21. payload = {
  22. "iss": settings.keling_app_key,
  23. "exp": int(now) + 1800,
  24. "nbf": int(now) - 5,
  25. }
  26. return jwt.encode(payload, settings.keling_app_secret, algorithm="HS256")
  27. def _headers(self) -> dict[str, str]:
  28. return {
  29. "Content-Type": "application/json",
  30. "Authorization": f"Bearer {self._generate_jwt()}",
  31. }
  32. def _retry_execute(self, param: TaskParam, remaining: int = 30) -> dict | None:
  33. logger.info("[%s] Submitting task: POST %s (remaining_retries=%d)",
  34. self.get_type(), self.execute_url, remaining)
  35. try:
  36. with logged_client(timeout=60) as client:
  37. resp = client.post(self.execute_url, headers=self._headers(), json=param.inputs)
  38. logger.info("[%s] Submit response: status=%d", self.get_type(), resp.status_code)
  39. bean = resp.json()
  40. except Exception as e:
  41. if remaining > 0:
  42. logger.warning("[%s] Request error: %s, retrying in 5s ... remaining=%d",
  43. self.get_type(), e, remaining - 1)
  44. time.sleep(5)
  45. return self._retry_execute(param, remaining - 1)
  46. raise RuntimeError(f"Request failed after retries: {e}")
  47. code = bean.get("code", 0)
  48. if code in (1302, 1303) and remaining > 0:
  49. logger.warning("[%s] Rate limited (code=%d), retrying in 5s ... remaining=%d",
  50. self.get_type(), code, remaining - 1)
  51. time.sleep(5)
  52. return self._retry_execute(param, remaining - 1)
  53. return bean
  54. def execute(self, param: TaskParam) -> TaskResult:
  55. t0 = time.time()
  56. bean = self._retry_execute(param)
  57. if not bean:
  58. return TaskResult.failure("submit task fail !")
  59. if not bean.get("data"):
  60. msg = bean.get("message", "unknown error")
  61. logger.error("[%s] Submit failed: %s", self.get_type(), msg)
  62. return TaskResult.failure(msg)
  63. task_id = bean["data"].get("task_id")
  64. logger.info("[%s] Task submitted, remote_id=%s", self.get_type(), task_id)
  65. poll_count = 0
  66. poll_t0 = time.time()
  67. while True:
  68. poll_count += 1
  69. poll = self._poll(task_id)
  70. poll_data = poll.get("data", {})
  71. status = poll_data.get("task_status")
  72. logger.info("[%s] Poll #%d: remote_id=%s, status=%s, elapsed=%.1fs",
  73. self.get_type(), poll_count, task_id, status, time.time() - poll_t0)
  74. if status in ("submitted", "processing"):
  75. time.sleep(3)
  76. elif status == "succeed":
  77. task_result = poll_data.get("task_result", {})
  78. break
  79. else:
  80. logger.warning("[%s] Unexpected status '%s', treating as failure", self.get_type(), status)
  81. time.sleep(3)
  82. return TaskResult.failure(str(poll_data))
  83. videos_data = task_result.get("videos")
  84. if not videos_data:
  85. return TaskResult.failure("No video results found")
  86. videos = [v["url"] for v in videos_data if v.get("url", "").strip()]
  87. logger.info("[%s] Completed: %d videos, total_elapsed=%.2fs",
  88. self.get_type(), len(videos), time.time() - t0)
  89. return TaskResult.success({"videos": videos})
  90. def _poll(self, task_id: str) -> dict:
  91. url = f"{self.search_url}/{task_id}"
  92. with logged_client(timeout=60) as client:
  93. resp = client.get(url, headers=self._headers())
  94. return resp.json()