Files
rogaining_srv/rog/utils/s3_image_uploader.py
2025-09-03 07:56:40 +09:00

154 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
S3画像アップロードユーティリティ
チェックイン時の画像をS3にアップロードし、URLを生成します
"""
import boto3
import logging
import uuid
from datetime import datetime
from django.conf import settings
from botocore.exceptions import ClientError, NoCredentialsError
import base64
import io
import requests
logger = logging.getLogger(__name__)
class S3ImageUploader:
def __init__(self):
"""S3クライアントを初期化"""
try:
self.s3_client = boto3.client(
's3',
aws_access_key_id=getattr(settings, 'AWS_ACCESS_KEY', None),
aws_secret_access_key=getattr(settings, 'AWS_SECRET_ACCESS_KEY', None),
region_name=getattr(settings, 'AWS_REGION', 'us-west-2')
)
self.bucket_name = getattr(settings, 'S3_BUCKET_NAME', 'sumasenrogaining')
logger.info(f"S3 client initialized for bucket: {self.bucket_name}")
except Exception as e:
logger.error(f"Failed to initialize S3 client: {e}")
self.s3_client = None
self.bucket_name = None
def upload_checkin_image(self, image_data, event_code, zekken_number, cp_number):
"""
チェックイン画像をS3にアップロード
Args:
image_data: 画像データURLまたはBase64
event_code: イベントコード
zekken_number: ゼッケン番号
cp_number: チェックポイント番号
Returns:
str: S3のURL、失敗時は元のimage_data
"""
if not self.s3_client or not image_data:
logger.warning("S3 client not available or no image data, returning original")
return image_data
try:
# 画像データを取得
image_binary = self._get_image_binary(image_data)
if not image_binary:
logger.error("Failed to get image binary data, returning original")
return image_data
# S3キーを生成: {event_code}/{zekken_number}/{cp_number}.jpg
s3_key = f"{event_code}/{zekken_number}/{cp_number}.jpg"
# S3にアップロード
self.s3_client.put_object(
Bucket=self.bucket_name,
Key=s3_key,
Body=image_binary,
ContentType='image/jpeg',
ACL='public-read' # 公開読み取り可能
)
# S3 URLを生成
aws_region = getattr(settings, 'AWS_REGION', 'us-west-2')
s3_url = f"https://{self.bucket_name}.s3.{aws_region}.amazonaws.com/{s3_key}"
logger.info(f"Successfully uploaded image to S3: {s3_url}")
return s3_url
except ClientError as e:
logger.error(f"S3 upload failed: {e}, returning original URL")
return image_data
except Exception as e:
logger.error(f"Unexpected error during S3 upload: {e}, returning original URL")
return image_data
def _get_image_binary(self, image_data):
"""
画像データからバイナリデータを取得
Args:
image_data: 画像URLHTTPまたはBase64エンコードされた画像データ
Returns:
bytes: 画像のバイナリデータ
"""
try:
if isinstance(image_data, str):
# HTTPURLの場合
if image_data.startswith('http'):
return self._download_image_from_url(image_data)
# Base64の場合
elif self._is_base64(image_data):
return base64.b64decode(image_data)
# data:image/jpeg;base64,の形式の場合
elif image_data.startswith('data:image'):
base64_data = image_data.split(',')[1]
return base64.b64decode(base64_data)
logger.error(f"Unsupported image data format: {type(image_data)}")
return None
except Exception as e:
logger.error(f"Error processing image data: {e}")
return None
def _download_image_from_url(self, url):
"""URLから画像をダウンロード"""
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response.content
except Exception as e:
logger.error(f"Failed to download image from URL {url}: {e}")
return None
def _is_base64(self, data):
"""文字列がBase64かどうかをチェック"""
try:
if isinstance(data, str):
base64.b64decode(data, validate=True)
return True
except Exception:
return False
return False
def generate_s3_url(self, event_code, zekken_number, cp_number):
"""
S3 URLを生成アップロード済みの画像用
Args:
event_code: イベントコード
zekken_number: ゼッケン番号
cp_number: チェックポイント番号
Returns:
str: S3のURL
"""
aws_region = getattr(settings, 'AWS_REGION', 'us-west-2')
s3_key = f"{event_code}/{zekken_number}/{cp_number}.jpg"
return f"https://{self.bucket_name}.s3.{aws_region}.amazonaws.com/{s3_key}"
# グローバルインスタンス
s3_uploader = S3ImageUploader()