205 lines
7.0 KiB
Python
205 lines
7.0 KiB
Python
"""
|
|
Asset Audit Service
|
|
Analyzes uploaded assets and recommends enhancement operations.
|
|
"""
|
|
|
|
from typing import Dict, Any, List, Optional
|
|
from loguru import logger
|
|
import base64
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
|
|
|
|
class AssetAuditService:
|
|
"""Service to audit assets and recommend enhancements."""
|
|
|
|
def __init__(self):
|
|
"""Initialize Asset Audit Service."""
|
|
self.logger = logger
|
|
logger.info("[Asset Audit] Service initialized")
|
|
|
|
def audit_asset(
|
|
self,
|
|
image_base64: str,
|
|
asset_metadata: Optional[Dict[str, Any]] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Audit an uploaded asset and recommend enhancement operations.
|
|
|
|
Args:
|
|
image_base64: Base64 encoded image
|
|
asset_metadata: Optional metadata about the asset
|
|
|
|
Returns:
|
|
Audit results with recommendations
|
|
"""
|
|
try:
|
|
# Decode image
|
|
image_bytes = self._decode_base64(image_base64)
|
|
if not image_bytes:
|
|
raise ValueError("Invalid image data")
|
|
|
|
# Analyze image
|
|
image = Image.open(BytesIO(image_bytes))
|
|
width, height = image.size
|
|
format_type = image.format or "PNG"
|
|
mode = image.mode
|
|
|
|
# Basic quality checks
|
|
quality_score = self._assess_quality(image, width, height)
|
|
|
|
# Generate recommendations
|
|
recommendations = []
|
|
|
|
# Resolution recommendations
|
|
if width < 1080 or height < 1080:
|
|
recommendations.append({
|
|
"operation": "upscale",
|
|
"priority": "high",
|
|
"reason": f"Image resolution ({width}x{height}) is below recommended 1080p for social media",
|
|
"suggested_mode": "fast" if width < 512 else "conservative",
|
|
})
|
|
|
|
# Background recommendations
|
|
if mode == "RGBA" and self._has_transparency(image):
|
|
recommendations.append({
|
|
"operation": "remove_background",
|
|
"priority": "low",
|
|
"reason": "Image already has transparency, background removal may not be needed",
|
|
})
|
|
else:
|
|
recommendations.append({
|
|
"operation": "remove_background",
|
|
"priority": "medium",
|
|
"reason": "Background removal can create versatile product images",
|
|
})
|
|
|
|
# Enhancement recommendations based on quality
|
|
if quality_score < 0.7:
|
|
recommendations.append({
|
|
"operation": "enhance",
|
|
"priority": "high",
|
|
"reason": f"Image quality score ({quality_score:.2f}) suggests enhancement needed",
|
|
"suggested_operations": ["upscale", "general_edit"],
|
|
})
|
|
|
|
# Format recommendations
|
|
if format_type not in ["PNG", "JPEG"]:
|
|
recommendations.append({
|
|
"operation": "convert",
|
|
"priority": "low",
|
|
"reason": f"Format {format_type} may not be optimal for web/social media",
|
|
"suggested_format": "PNG" if mode == "RGBA" else "JPEG",
|
|
})
|
|
|
|
audit_result = {
|
|
"asset_info": {
|
|
"width": width,
|
|
"height": height,
|
|
"format": format_type,
|
|
"mode": mode,
|
|
"quality_score": quality_score,
|
|
},
|
|
"recommendations": recommendations,
|
|
"status": "usable" if quality_score > 0.6 else "needs_enhancement",
|
|
}
|
|
|
|
logger.info(f"[Asset Audit] Audited asset: {width}x{height}, quality: {quality_score:.2f}")
|
|
return audit_result
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Asset Audit] Error auditing asset: {str(e)}")
|
|
return {
|
|
"asset_info": {},
|
|
"recommendations": [],
|
|
"status": "error",
|
|
"error": str(e),
|
|
}
|
|
|
|
def _decode_base64(self, image_base64: str) -> Optional[bytes]:
|
|
"""Decode base64 image data."""
|
|
try:
|
|
if image_base64.startswith("data:"):
|
|
_, b64data = image_base64.split(",", 1)
|
|
else:
|
|
b64data = image_base64
|
|
return base64.b64decode(b64data)
|
|
except Exception as e:
|
|
logger.error(f"[Asset Audit] Error decoding base64: {str(e)}")
|
|
return None
|
|
|
|
def _has_transparency(self, image: Image.Image) -> bool:
|
|
"""Check if image has transparency."""
|
|
if image.mode in ("RGBA", "LA"):
|
|
alpha = image.split()[-1]
|
|
return any(pixel < 255 for pixel in alpha.getdata())
|
|
return False
|
|
|
|
def _assess_quality(self, image: Image.Image, width: int, height: int) -> float:
|
|
"""
|
|
Assess image quality score (0.0 to 1.0).
|
|
|
|
Simple heuristic based on resolution and format.
|
|
"""
|
|
score = 0.5 # Base score
|
|
|
|
# Resolution scoring
|
|
min_dimension = min(width, height)
|
|
if min_dimension >= 1080:
|
|
score += 0.3
|
|
elif min_dimension >= 512:
|
|
score += 0.2
|
|
elif min_dimension >= 256:
|
|
score += 0.1
|
|
|
|
# Format scoring
|
|
if image.format in ["PNG", "JPEG"]:
|
|
score += 0.1
|
|
|
|
# Mode scoring
|
|
if image.mode in ["RGB", "RGBA"]:
|
|
score += 0.1
|
|
|
|
return min(score, 1.0)
|
|
|
|
def batch_audit_assets(
|
|
self,
|
|
assets: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Audit multiple assets in batch.
|
|
|
|
Args:
|
|
assets: List of asset dictionaries with 'image_base64' and optional 'metadata'
|
|
|
|
Returns:
|
|
Batch audit results
|
|
"""
|
|
results = []
|
|
for asset in assets:
|
|
audit_result = self.audit_asset(
|
|
asset.get('image_base64'),
|
|
asset.get('metadata')
|
|
)
|
|
results.append({
|
|
"asset_id": asset.get('id'),
|
|
"audit": audit_result,
|
|
})
|
|
|
|
# Summary statistics
|
|
total_assets = len(results)
|
|
usable_count = sum(1 for r in results if r["audit"]["status"] == "usable")
|
|
needs_enhancement_count = sum(
|
|
1 for r in results if r["audit"]["status"] == "needs_enhancement"
|
|
)
|
|
|
|
return {
|
|
"results": results,
|
|
"summary": {
|
|
"total_assets": total_assets,
|
|
"usable": usable_count,
|
|
"needs_enhancement": needs_enhancement_count,
|
|
"error": total_assets - usable_count - needs_enhancement_count,
|
|
},
|
|
}
|