oss_upload.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. from __future__ import annotations
  2. import os
  3. from typing import Any, Callable, Mapping
  4. from urllib.parse import urlparse
  5. import httpx
  6. from content_agent.integrations import timeout_config
  7. DEFAULT_OSS_UPLOAD_URL = "http://crawler-upload-v2.aiddit.com/crawler/oss/upload_stream"
  8. DEFAULT_OSS_TIMEOUT_SECONDS = 300.0 # 5min(原 3600);read 相与单次尝试同为 300s
  9. def upload_video_from_url(
  10. src_url: str,
  11. *,
  12. project: str | None = None,
  13. referer: Mapping[str, str] | None = None,
  14. use_proxy: bool = True,
  15. endpoint: str = DEFAULT_OSS_UPLOAD_URL,
  16. timeout_seconds: float = DEFAULT_OSS_TIMEOUT_SECONDS,
  17. http_post: Callable[..., Any] = httpx.post,
  18. ) -> dict[str, Any]:
  19. if not src_url:
  20. return _failure("missing_src_url")
  21. payload: dict[str, Any] = {
  22. "src_url": src_url,
  23. "src_type": "video",
  24. "use_proxy": use_proxy,
  25. }
  26. payload_mode = "no_referer"
  27. if project:
  28. payload["project"] = project
  29. timeout = timeout_config.as_httpx_timeout(timeout_seconds, read=timeout_config.read_timeout("oss"))
  30. try:
  31. response = http_post(
  32. endpoint,
  33. json=payload,
  34. timeout=timeout,
  35. )
  36. response.raise_for_status()
  37. body = response.json()
  38. except httpx.HTTPError as exc:
  39. response_absent = getattr(exc, "response", None) is None
  40. return _failure(
  41. "oss_upload_http_error",
  42. exception_type=type(exc).__name__,
  43. error_message=str(exc),
  44. http_status_code=_http_status(exc),
  45. oss_payload_mode=payload_mode,
  46. endpoint_host=urlparse(endpoint).netloc,
  47. read_timeout_seconds=timeout_config.read_timeout("oss"),
  48. attempt_timeout_seconds=timeout_seconds,
  49. response_absent=response_absent,
  50. )
  51. except Exception as exc:
  52. return _failure(
  53. "oss_upload_failed",
  54. exception_type=type(exc).__name__,
  55. error_message=str(exc),
  56. oss_payload_mode=payload_mode,
  57. endpoint_host=urlparse(endpoint).netloc,
  58. read_timeout_seconds=timeout_config.read_timeout("oss"),
  59. attempt_timeout_seconds=timeout_seconds,
  60. response_absent=True,
  61. )
  62. oss_object = body.get("oss_object") if isinstance(body, dict) else None
  63. if not isinstance(oss_object, dict) or not oss_object.get("cdn_url"):
  64. return _failure(
  65. "oss_upload_response_invalid",
  66. oss_payload_mode=payload_mode,
  67. oss_response_summary=_response_summary(body),
  68. )
  69. return {
  70. "status": "ok",
  71. "oss_url": oss_object.get("cdn_url"),
  72. "oss_object_key": oss_object.get("oss_object_key"),
  73. "save_oss_timestamp": oss_object.get("save_oss_timestamp"),
  74. "oss_payload_mode": payload_mode,
  75. "raw_payload": body,
  76. }
  77. def upload_video_from_env(
  78. src_url: str,
  79. *,
  80. referer: Mapping[str, str] | None = None,
  81. timeout_seconds: float | None = None,
  82. http_post: Callable[..., Any] = httpx.post,
  83. ) -> dict[str, Any]:
  84. return upload_video_from_url(
  85. src_url,
  86. project=os.environ.get("CONTENT_AGENT_OSS_PROJECT") or None,
  87. referer=referer,
  88. use_proxy=_env_bool("CONTENT_AGENT_OSS_USE_PROXY", default=True),
  89. endpoint=os.environ.get("CONTENT_AGENT_OSS_UPLOAD_URL") or DEFAULT_OSS_UPLOAD_URL,
  90. timeout_seconds=(
  91. timeout_seconds
  92. if timeout_seconds is not None
  93. else float(os.environ.get("CONTENT_AGENT_OSS_TIMEOUT_SECONDS") or DEFAULT_OSS_TIMEOUT_SECONDS)
  94. ),
  95. http_post=http_post,
  96. )
  97. def _failure(
  98. failure_type: str,
  99. *,
  100. exception_type: str | None = None,
  101. error_message: str | None = None,
  102. http_status_code: int | None = None,
  103. oss_payload_mode: str | None = None,
  104. oss_response_summary: dict[str, Any] | None = None,
  105. endpoint_host: str | None = None,
  106. read_timeout_seconds: float | None = None,
  107. attempt_timeout_seconds: float | None = None,
  108. response_absent: bool | None = None,
  109. ) -> dict[str, Any]:
  110. result: dict[str, Any] = {"status": "failed", "failure_type": failure_type}
  111. if exception_type:
  112. result["exception_type"] = exception_type
  113. if error_message:
  114. result["error_message"] = error_message
  115. if http_status_code is not None:
  116. result["http_status_code"] = http_status_code
  117. if oss_payload_mode:
  118. result["oss_payload_mode"] = oss_payload_mode
  119. if oss_response_summary:
  120. result["oss_response_summary"] = oss_response_summary
  121. if endpoint_host:
  122. result["endpoint_host"] = endpoint_host
  123. if read_timeout_seconds is not None:
  124. result["read_timeout_seconds"] = read_timeout_seconds
  125. if attempt_timeout_seconds is not None:
  126. result["attempt_timeout_seconds"] = attempt_timeout_seconds
  127. if response_absent is not None:
  128. result["response_absent"] = response_absent
  129. return result
  130. def _response_summary(body: Any) -> dict[str, Any]:
  131. if not isinstance(body, dict):
  132. return {"body_type": type(body).__name__}
  133. oss_object = body.get("oss_object")
  134. return {
  135. "status": body.get("status"),
  136. "msg": body.get("msg"),
  137. "oss_object_present": isinstance(oss_object, dict),
  138. "oss_object_has_cdn_url": isinstance(oss_object, dict) and bool(oss_object.get("cdn_url")),
  139. }
  140. def _http_status(exc: httpx.HTTPError) -> int | None:
  141. response = getattr(exc, "response", None)
  142. status_code = getattr(response, "status_code", None)
  143. return int(status_code) if isinstance(status_code, int) else None
  144. def _env_bool(key: str, *, default: bool) -> bool:
  145. value = os.environ.get(key)
  146. if value is None or value == "":
  147. return default
  148. return value.strip().lower() not in {"0", "false", "no", "off"}