165 lines
4.7 KiB
Python
165 lines
4.7 KiB
Python
"""
|
|
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_IN", "/app/media/in"))
|
|
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 input folder from environment
|
|
media_root = os.environ.get("MEDIA_IN", "/app/media/in")
|
|
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,
|
|
}
|