"""Background Remover Service - Clipping Magic API""" import httpx import os import base64 from uuid import uuid4 from datetime import datetime from app.database import SessionLocal from app.models.job import Job from app.models.asset import Asset from app.config import settings async def remove_background(job_id: str): """Remove background from image using Clipping Magic""" db = SessionLocal() try: job = db.query(Job).filter(Job.id == job_id).first() if not job: return input_data = job.input_data input_asset_ids = job.input_asset_ids if not input_asset_ids: raise ValueError("No input asset provided") input_asset = db.query(Asset).filter(Asset.id == input_asset_ids[0]).first() if not input_asset: raise ValueError("Input asset not found") job.progress = 10 job.api_provider = "clipping_magic" db.commit() # Read input image with open(input_asset.file_path, "rb") as f: image_data = f.read() output_format = input_data.get("output_format", "png") job.progress = 20 db.commit() # Call Clipping Magic API async with httpx.AsyncClient(timeout=120) as client: # Use API ID and Secret for HTTP Basic Auth api_id = settings.clipping_magic_api_id or settings.clipping_magic_api_key api_secret = settings.clipping_magic_api_secret or "" response = await client.post( "https://clippingmagic.com/api/v1/images", auth=(api_id, api_secret), files={"image": (input_asset.original_filename, image_data, input_asset.mime_type)}, data={ "format": "clipping_path_tiff" if output_format == "tiff" else ("result" if output_format == "png" else "result") } ) response.raise_for_status() content_type = response.headers.get("content-type", "") if "application/json" in content_type: # Flow 1: API returns JSON with image ID (async processing or default) result = response.json() image_id = result.get("image", {}).get("id") job.progress = 50 db.commit() if image_id: # Download the result download_response = await client.get( f"https://clippingmagic.com/api/v1/images/{image_id}", auth=(api_id, api_secret), params={"format": "clipping_path_tiff" if output_format == "tiff" else "result"} ) download_response.raise_for_status() processed_data = download_response.content else: # Flow 2: API returns the image directly (synchronous processing requested via format='result') processed_data = response.content job.progress = 70 db.commit() job.progress = 80 db.commit() # Save output ext = "tiff" if output_format == "tiff" else ("png" if output_format == "png" else "webp") filename = f"nobg_{uuid4()}.{ext}" storage_path = os.path.join(settings.storage_path, "images") os.makedirs(storage_path, exist_ok=True) file_path = os.path.join(storage_path, filename) with open(file_path, "wb") as f: f.write(processed_data) # Create output asset output_asset = Asset( user_id=job.user_id, project_id=job.project_id, original_filename=filename, stored_filename=filename, file_path=file_path, file_type="image", mime_type=f"image/{ext}", file_size_bytes=len(processed_data), width=input_asset.width, height=input_asset.height, source_module="background_remover", source_job_id=job.id, parent_asset_id=input_asset.id, asset_metadata={"output_format": output_format} ) db.add(output_asset) db.commit() db.refresh(output_asset) job.output_asset_ids = [output_asset.id] job.output_data = {"asset_id": str(output_asset.id), "file_path": file_path} # Delete from Clipping Magic if we have an image_id (Only needed for Flow 1) if "application/json" in content_type and 'image_id' in locals() and image_id: try: await client.post( f"https://clippingmagic.com/api/v1/images/{image_id}/delete", auth=(api_id, api_secret) ) except Exception as e: pass # Ignore cleanup errors job.progress = 100 job.status = "completed" job.completed_at = datetime.utcnow() db.commit() except Exception as e: job.status = "failed" job.error_message = str(e) db.commit() finally: db.close()