""" Asset endpoints - media file registration and metadata. """ from typing import Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query from api.deps import get_asset from api.schemas import AssetCreate, AssetResponse, AssetUpdate router = APIRouter(prefix="/assets", tags=["assets"]) @router.post("/", response_model=AssetResponse, status_code=201) def create_asset(data: AssetCreate): """ Register a media file as an asset. The file must exist on disk. A probe task will be queued to extract metadata asynchronously. """ from pathlib import Path from mpr.media_assets.models import MediaAsset # Validate file exists path = Path(data.file_path) if not path.exists(): raise HTTPException(status_code=400, detail="File not found") # Store path relative to media root import os media_root = Path(os.environ.get("MEDIA_ROOT", "/app/media")) try: rel_path = str(path.relative_to(media_root)) except ValueError: rel_path = path.name # Create asset asset = MediaAsset.objects.create( filename=data.filename or path.name, file_path=rel_path, file_size=path.stat().st_size, ) # TODO: Queue probe task via gRPC/Celery return asset @router.get("/", response_model=list[AssetResponse]) def list_assets( status: Optional[str] = Query(None, description="Filter by status"), limit: int = Query(50, ge=1, le=100), offset: int = Query(0, ge=0), ): """List assets with optional filtering.""" from mpr.media_assets.models import MediaAsset qs = MediaAsset.objects.all() if status: qs = qs.filter(status=status) return list(qs[offset : offset + limit]) @router.get("/{asset_id}", response_model=AssetResponse) def get_asset_detail(asset_id: UUID, asset=Depends(get_asset)): """Get asset details.""" return asset @router.patch("/{asset_id}", response_model=AssetResponse) def update_asset(asset_id: UUID, data: AssetUpdate, asset=Depends(get_asset)): """Update asset metadata (comments, tags).""" update_fields = [] if data.comments is not None: asset.comments = data.comments update_fields.append("comments") if data.tags is not None: asset.tags = data.tags update_fields.append("tags") if update_fields: asset.save(update_fields=update_fields) return asset @router.delete("/{asset_id}", status_code=204) def delete_asset(asset_id: UUID, asset=Depends(get_asset)): """Delete an asset.""" asset.delete() @router.post("/scan", response_model=dict) def scan_media_folder(): """ Scan the media folder for new video/audio files and register them as assets. Returns a summary of files found and registered. """ import os from pathlib import Path from mpr.media_assets.models import MediaAsset # Get media root from environment media_root = os.environ.get("MEDIA_ROOT", "/app/media") media_path = Path(media_root) if not media_path.exists(): raise HTTPException( status_code=500, detail=f"Media folder not found: {media_root}" ) # Supported video/audio extensions video_exts = {".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".m4v"} audio_exts = {".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a"} supported_exts = video_exts | audio_exts # Get existing filenames to avoid duplicates existing_filenames = set(MediaAsset.objects.values_list("filename", flat=True)) # Scan for media files found_files = [] registered_files = [] skipped_files = [] for file_path in media_path.rglob("*"): if file_path.is_file() and file_path.suffix.lower() in supported_exts: found_files.append(str(file_path)) # Skip if already registered if file_path.name in existing_filenames: skipped_files.append(file_path.name) continue # Register new asset with path relative to media root rel_path = str(file_path.relative_to(media_path)) try: asset = MediaAsset.objects.create( filename=file_path.name, file_path=rel_path, file_size=file_path.stat().st_size, ) registered_files.append(file_path.name) # TODO: Queue probe task to extract metadata except Exception as e: print(f"Error registering {file_path.name}: {e}") return { "found": len(found_files), "registered": len(registered_files), "skipped": len(skipped_files), "files": registered_files, }