""" Image File Operations Router Handles file serving, copying, and preprocessing operations """ from fastapi import APIRouter, Depends, HTTPException, Response, UploadFile, Form from pydantic import BaseModel from sqlalchemy.orm import Session from typing import List, Optional import logging import io import os import time import mimetypes from .. import crud, schemas, database, storage from ..config import settings from ..services.image_preprocessor import ImagePreprocessor logger = logging.getLogger(__name__) router = APIRouter() def get_db(): db = database.SessionLocal() try: yield db finally: db.close() class CopyImageRequest(BaseModel): source_image_id: str source: str event_type: str countries: str = "" epsg: str = "" image_type: str = "crisis_map" # Drone-specific fields (optional) center_lon: Optional[float] = None center_lat: Optional[float] = None amsl_m: Optional[float] = None agl_m: Optional[float] = None heading_deg: Optional[float] = None yaw_deg: Optional[float] = None pitch_deg: Optional[float] = None roll_deg: Optional[float] = None rtk_fix: Optional[bool] = None std_h_m: Optional[float] = None std_v_m: Optional[float] = None @router.get("/{image_id}/file") async def get_image_file(image_id: str, db: Session = Depends(get_db)): """Serve the actual image file""" logger.debug(f"Serving image file for image_id: {image_id}") img = crud.get_image(db, image_id) if not img: logger.warning(f"Image not found: {image_id}") raise HTTPException(404, "Image not found") logger.debug(f"Found image: {img.image_id}, file_key: {img.file_key}") try: if hasattr(storage, 's3') and settings.STORAGE_PROVIDER != "local": logger.debug(f"Using S3 storage - serving file content directly") try: response = storage.s3.get_object(Bucket=settings.S3_BUCKET, Key=img.file_key) content = response['Body'].read() logger.debug(f"Read {len(content)} bytes from S3") except Exception as e: logger.error(f"Failed to get S3 object: {e}") raise HTTPException(500, f"Failed to retrieve image from storage: {e}") else: logger.debug(f"Using local storage") file_path = os.path.join(settings.STORAGE_DIR, img.file_key) logger.debug(f"Reading from: {file_path}") logger.debug(f"File exists: {os.path.exists(file_path)}") if not os.path.exists(file_path): logger.error(f"File not found at: {file_path}") raise FileNotFoundError(f"Image file not found: {file_path}") with open(file_path, 'rb') as f: content = f.read() logger.debug(f"Read {len(content)} bytes from file") content_type, _ = mimetypes.guess_type(img.file_key) if not content_type: content_type = 'application/octet-stream' logger.debug(f"Serving image with content-type: {content_type}, size: {len(content)} bytes") return Response(content=content, media_type=content_type) except Exception as e: logger.error(f"Error serving image: {e}") import traceback logger.debug(f"Full traceback: {traceback.format_exc()}") raise HTTPException(500, f"Failed to serve image file: {e}") @router.post("/copy", response_model=schemas.ImageOut) async def copy_image_for_contribution( request: CopyImageRequest, db: Session = Depends(get_db) ): """Copy an existing image for contribution purposes, creating a new image_id""" logger.info(f"Copying image {request.source_image_id} for contribution") source_img = crud.get_image(db, request.source_image_id) if not source_img: logger.warning(f"Source image not found: {request.source_image_id}") raise HTTPException(404, "Source image not found") try: # Get image content from storage if hasattr(storage, 's3') and settings.STORAGE_PROVIDER != "local": response = storage.s3.get_object( Bucket=settings.S3_BUCKET, Key=source_img.file_key, ) image_content = response["Body"].read() else: file_path = os.path.join(settings.STORAGE_DIR, source_img.file_key) with open(file_path, 'rb') as f: image_content = f.read() # Create new file with unique name new_filename = f"contribution_{request.source_image_id}_{int(time.time())}.jpg" new_key = storage.upload_fileobj(io.BytesIO(image_content), new_filename) # Parse countries countries_list = [c.strip() for c in request.countries.split(',') if c.strip()] if request.countries else [] # Create new image record new_img = crud.create_image( db, request.source, request.event_type, new_key, source_img.sha256, countries_list, request.epsg, request.image_type, request.center_lon, request.center_lat, request.amsl_m, request.agl_m, request.heading_deg, request.yaw_deg, request.pitch_deg, request.roll_deg, request.rtk_fix, request.std_h_m, request.std_v_m ) # Generate URL try: url = storage.get_object_url(new_key) except Exception as e: url = f"/api/images/{new_img.image_id}/file" # Convert to response format from ..utils.image_utils import convert_image_to_dict img_dict = convert_image_to_dict(new_img, url) result = schemas.ImageOut(**img_dict) logger.info(f"Successfully copied image {request.source_image_id} -> {new_img.image_id}") return result except Exception as e: logger.error(f"Failed to copy image: {str(e)}") raise HTTPException(500, f"Failed to copy image: {str(e)}") @router.post("/preprocess") async def preprocess_image( file: UploadFile = Form(...), db: Session = Depends(get_db) ): """Preprocess an image file (convert format, optimize, etc.)""" logger.info(f"Preprocessing image: {file.filename}") try: content = await file.read() # Preprocess the image processed_content, processed_filename, mime_type = ImagePreprocessor.preprocess_image( content, file.filename, target_format='PNG', quality=95 ) logger.info(f"Image preprocessed: {file.filename} -> {processed_filename}") return { "original_filename": file.filename, "processed_filename": processed_filename, "original_size": len(content), "processed_size": len(processed_content), "mime_type": mime_type, "preprocessing_applied": processed_filename != file.filename } except Exception as e: logger.error(f"Image preprocessing failed: {str(e)}") raise HTTPException(500, f"Image preprocessing failed: {str(e)}")