service.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. """Media stabilization service for the formal acquisition pipeline."""
  2. from __future__ import annotations
  3. from dataclasses import dataclass
  4. import time
  5. from typing import Callable
  6. from acquisition.media.oss_client import to_oss
  7. from core.config import Settings
  8. @dataclass(frozen=True)
  9. class StabilizedMedia:
  10. media_type: str
  11. source_url: str
  12. cdn_url: str
  13. position: int
  14. status: str = "ready"
  15. error_message: str = ""
  16. Uploader = Callable[[str, str], str]
  17. def stabilize_media_urls(
  18. *,
  19. image_urls: list[str] | None = None,
  20. video_urls: list[str] | None = None,
  21. settings: Settings | None = None,
  22. uploader: Uploader | None = None,
  23. video_fallback_to_source: bool = True,
  24. video_retry_delays_seconds: tuple[float, ...] = (),
  25. sleep: Callable[[float], None] = time.sleep,
  26. ) -> list[StabilizedMedia]:
  27. """Transfer image/video URLs to OSS/CDN and return DB-ready media rows.
  28. Images keep the old soft-fallback behavior. Videos can opt into strict mode
  29. so expiring source URLs do not leak into classification/decode.
  30. """
  31. upload = uploader or (
  32. lambda url, media_type: to_oss(url, media_type, settings=settings)
  33. )
  34. rows: list[StabilizedMedia] = []
  35. position = 0
  36. for url in image_urls or []:
  37. if not url:
  38. continue
  39. position += 1
  40. try:
  41. cdn = upload(url, "image")
  42. except Exception:
  43. cdn = url
  44. rows.append(
  45. StabilizedMedia(
  46. media_type="image",
  47. source_url=url,
  48. cdn_url=cdn,
  49. position=position,
  50. status="ready" if cdn else "failed",
  51. )
  52. )
  53. for url in video_urls or []:
  54. if not url:
  55. continue
  56. position += 1
  57. cdn = ""
  58. error_message = ""
  59. attempts = len(video_retry_delays_seconds) + 1
  60. for attempt in range(attempts):
  61. try:
  62. cdn = upload(url, "video")
  63. if cdn and (video_fallback_to_source or cdn != url):
  64. error_message = ""
  65. break
  66. error_message = "oss_upload_returned_source_url"
  67. cdn = ""
  68. except Exception as exc:
  69. error_message = str(exc)
  70. if attempt < len(video_retry_delays_seconds):
  71. sleep(video_retry_delays_seconds[attempt])
  72. if not cdn and video_fallback_to_source:
  73. cdn = url
  74. rows.append(
  75. StabilizedMedia(
  76. media_type="video",
  77. source_url=url,
  78. cdn_url=cdn,
  79. position=position,
  80. status="ready" if cdn else "failed",
  81. error_message=error_message,
  82. )
  83. )
  84. return rows