oss_upload.py 4.7 KB

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