fix: complete image pipeline - rotation, URL, preview
- Add rotation_degrees parameter to ImageProcessor.process_photo() - Pass rotation through _auto_save_photo_from_extraction() to processor - Allow no-crop fallback when crop_bounds is None - Add buildPhotoUrl() helper to resolve backend URLs correctly - Update frontend components to use backend URL for image sources - Replace Use/Skip Photo buttons with checkbox in AI extraction UI - Add images/ to .gitignore to prevent accidental commits Addresses: rotation never applied, image 404s (relative to Next.js not backend), preview blank in edit form
This commit is contained in:
@@ -503,54 +503,48 @@ def _auto_save_photo_from_extraction(
|
||||
"reason": "Empty image bytes"
|
||||
}
|
||||
|
||||
# Graceful skip if crop_bounds is None
|
||||
if crop_bounds is None:
|
||||
log.info(f"Auto-save photo for item {item_id}: crop_bounds is None, skipping")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": "crop_bounds is None"
|
||||
}
|
||||
# Validate crop_bounds (if provided)
|
||||
crop_bounds_validated = None
|
||||
if crop_bounds is not None:
|
||||
if not isinstance(crop_bounds, dict):
|
||||
log.warning(f"Auto-save photo for item {item_id}: crop_bounds is not a dict, skipping")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": "crop_bounds must be a dict"
|
||||
}
|
||||
|
||||
# Validate crop_bounds
|
||||
if not isinstance(crop_bounds, dict):
|
||||
log.warning(f"Auto-save photo for item {item_id}: crop_bounds is not a dict, skipping")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": "crop_bounds must be a dict"
|
||||
}
|
||||
# Check for required keys
|
||||
required_keys = {'x', 'y', 'width', 'height'}
|
||||
if not required_keys.issubset(crop_bounds.keys()):
|
||||
missing = required_keys - set(crop_bounds.keys())
|
||||
log.warning(f"Auto-save photo for item {item_id}: Missing crop_bounds keys: {missing}")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": f"Missing crop_bounds keys: {missing}"
|
||||
}
|
||||
|
||||
# Check for required keys
|
||||
required_keys = {'x', 'y', 'width', 'height'}
|
||||
if not required_keys.issubset(crop_bounds.keys()):
|
||||
missing = required_keys - set(crop_bounds.keys())
|
||||
log.warning(f"Auto-save photo for item {item_id}: Missing crop_bounds keys: {missing}")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": f"Missing crop_bounds keys: {missing}"
|
||||
}
|
||||
# Validate all values are integers >= 0
|
||||
try:
|
||||
crop_bounds_validated = {}
|
||||
for key in required_keys:
|
||||
val = crop_bounds[key]
|
||||
# Convert to int if it's numeric
|
||||
if isinstance(val, (int, float)):
|
||||
int_val = int(val)
|
||||
else:
|
||||
raise ValueError(f"Non-numeric value for {key}: {val}")
|
||||
|
||||
# Validate all values are integers >= 0
|
||||
try:
|
||||
crop_bounds_validated = {}
|
||||
for key in required_keys:
|
||||
val = crop_bounds[key]
|
||||
# Convert to int if it's numeric
|
||||
if isinstance(val, (int, float)):
|
||||
int_val = int(val)
|
||||
else:
|
||||
raise ValueError(f"Non-numeric value for {key}: {val}")
|
||||
if int_val < 0:
|
||||
raise ValueError(f"Negative value for {key}: {int_val}")
|
||||
|
||||
if int_val < 0:
|
||||
raise ValueError(f"Negative value for {key}: {int_val}")
|
||||
crop_bounds_validated[key] = int_val
|
||||
|
||||
crop_bounds_validated[key] = int_val
|
||||
|
||||
except (ValueError, TypeError) as e:
|
||||
log.warning(f"Auto-save photo for item {item_id}: Invalid crop_bounds: {str(e)}")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": f"Invalid crop_bounds: {str(e)}"
|
||||
}
|
||||
except (ValueError, TypeError) as e:
|
||||
log.warning(f"Auto-save photo for item {item_id}: Invalid crop_bounds: {str(e)}")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": f"Invalid crop_bounds: {str(e)}"
|
||||
}
|
||||
|
||||
# Validate rotation_degrees (optional but if provided, must be valid)
|
||||
if rotation_degrees is not None:
|
||||
@@ -573,7 +567,7 @@ def _auto_save_photo_from_extraction(
|
||||
try:
|
||||
# Process image (crop + rotation + compression + thumbnail)
|
||||
processor = ImageProcessor()
|
||||
process_result = processor.process_photo(image_bytes, crop_bounds_validated)
|
||||
process_result = processor.process_photo(image_bytes, crop_bounds_validated, rotation_degrees=rotation_degrees or 0)
|
||||
|
||||
if process_result.get('status') != 'success':
|
||||
error_msg = process_result.get('error', 'Unknown error')
|
||||
|
||||
Reference in New Issue
Block a user