oss_upload.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import time
  2. from io import BytesIO
  3. from pathlib import Path
  4. import oss2
  5. try:
  6. from PIL import Image
  7. except ImportError as e:
  8. raise ImportError("请先安装 Pillow: pip install Pillow") from e
  9. from commons.config import (
  10. OSS_ACCESS_KEY_ID,
  11. OSS_ACCESS_KEY_SECRET,
  12. OSS_ENDPOINT,
  13. OSS_BUCKET_NAME,
  14. )
  15. # 目标体积:约 60~100 KB(JPEG)
  16. TARGET_MIN_BYTES = 60 * 1024
  17. TARGET_MAX_BYTES = 100 * 1024
  18. try:
  19. _RESAMPLE = Image.Resampling.LANCZOS
  20. except AttributeError:
  21. _RESAMPLE = Image.LANCZOS
  22. def _jpeg_bytes(img, quality):
  23. buf = BytesIO()
  24. img.save(buf, format="JPEG", quality=int(quality), optimize=True)
  25. return buf.getvalue()
  26. def _prepare_rgb(img):
  27. if img.mode in ("RGBA", "LA"):
  28. background = Image.new("RGB", img.size, (255, 255, 255))
  29. background.paste(img, mask=img.split()[-1])
  30. return background
  31. if img.mode == "P":
  32. img = img.convert("RGBA")
  33. background = Image.new("RGB", img.size, (255, 255, 255))
  34. background.paste(img, mask=img.split()[-1])
  35. return background
  36. return img.convert("RGB")
  37. def compress_image_to_jpeg_range(
  38. image_source,
  39. min_bytes=TARGET_MIN_BYTES,
  40. max_bytes=TARGET_MAX_BYTES, ):
  41. """
  42. 将图片压成 JPEG,尽量使体积落在 [min_bytes, max_bytes]。
  43. image_source: 本地路径(str) 或 原始字节(bytes)。
  44. """
  45. if isinstance(image_source, bytes):
  46. img = Image.open(BytesIO(image_source))
  47. else:
  48. img = Image.open(image_source)
  49. base = _prepare_rgb(img)
  50. scale = 1.0
  51. while scale >= 0.05:
  52. if scale < 1.0:
  53. w, h = base.size
  54. cur = base.resize(
  55. (max(1, int(w * scale)), max(1, int(h * scale))),
  56. _RESAMPLE,
  57. )
  58. else:
  59. cur = base
  60. lo, hi = 1, 95
  61. while lo <= hi:
  62. mid = (lo + hi) // 2
  63. data = _jpeg_bytes(cur, mid)
  64. n = len(data)
  65. if min_bytes <= n <= max_bytes:
  66. return data
  67. if n > max_bytes:
  68. hi = mid - 1
  69. else:
  70. lo = mid + 1
  71. n_min_q = len(_jpeg_bytes(cur, 1))
  72. if n_min_q > max_bytes:
  73. scale *= 0.82
  74. continue
  75. n_max_q = len(_jpeg_bytes(cur, 95))
  76. if n_max_q < min_bytes:
  77. return _jpeg_bytes(cur, 95)
  78. best = None
  79. best_dist = None
  80. target = (min_bytes + max_bytes) // 2
  81. for q in range(1, 96):
  82. data = _jpeg_bytes(cur, q)
  83. n = len(data)
  84. if min_bytes <= n <= max_bytes:
  85. return data
  86. dist = abs(n - target)
  87. if best_dist is None or dist < best_dist:
  88. best_dist = dist
  89. best = data
  90. return best
  91. return _jpeg_bytes(base, 85)
  92. class AliyunOSSUploader:
  93. """阿里云OSS上传工具类"""
  94. def __init__(self):
  95. self.auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
  96. self.bucket = oss2.Bucket(self.auth, f"https://{OSS_ENDPOINT}", OSS_BUCKET_NAME)
  97. self.bucket_name = OSS_BUCKET_NAME
  98. self.endpoint = OSS_ENDPOINT
  99. def upload_image(self, local_file_path, content_type='image/jpeg'):
  100. filename = Path(local_file_path).name
  101. object_name = f"screenshots/{str(time.strftime('%Y%m%d_%H%M%S'))}_{filename}"
  102. if not object_name.lower().endswith(('.jpg', '.jpeg')):
  103. object_name = object_name.rsplit('.', 1)[0] + '.jpg'
  104. compressed = compress_image_to_jpeg_range(local_file_path)
  105. headers = {'Content-Type': 'image/jpeg'}
  106. print(object_name)
  107. self.bucket.put_object(object_name, compressed, headers=headers)
  108. return f"https://{self.bucket_name}.{self.endpoint}/{object_name}"
  109. def upload_from_bytes(self, image_data, filename, content_type='image/jpeg'):
  110. filename = f"screenshots/{time.strftime('%Y%m%d_%H%M%S')}_{filename}.jpg"
  111. compressed = compress_image_to_jpeg_range(image_data)
  112. headers = {'Content-Type': 'image/jpeg'}
  113. self.bucket.put_object(filename, compressed, headers=headers)
  114. return f"https://{self.bucket_name}.{self.endpoint}/{filename}"
  115. # 使用示例
  116. if __name__ == "__main__":
  117. uploader = AliyunOSSUploader()
  118. # url = uploader.upload_image("screenshots/68906391.jpg", content_type='image/jpeg')
  119. # print(f"上传成功:{url}")
  120. image_data = open("screenshots/68906391.jpg", "rb")
  121. url = uploader.upload_image(image_data, "68906391",content_type='image/jpeg')