legco_ai_assistant/backend/app/routers/video.py

111 lines
3.5 KiB
Python

import logging
import uuid
from pathlib import Path
import aiofiles
from fastapi import APIRouter, UploadFile, File, HTTPException
from fastapi.responses import FileResponse
from app.models.video import VideoUploadResponse, FullTranscriptResponse
from app.services.video_service import VideoService
from app.services.asr_client import ASRClient
logger = logging.getLogger(__name__)
router = APIRouter(tags=["video"])
def _get_video_service() -> VideoService:
from app.core.config import get_settings
s = get_settings()
return VideoService(
upload_dir=s.video_upload_dir,
max_size_mb=s.max_video_size_mb,
supported_formats=s.supported_video_formats,
)
@router.post("/video/upload", response_model=VideoUploadResponse)
async def upload_video(file: UploadFile = File(...)):
service = _get_video_service()
filename = file.filename or "unknown"
ext = Path(filename).suffix.lower()
total_size = 0
video_id = uuid.uuid4().hex[:12]
dest_path = service.upload_dir / f"{video_id}{ext}"
try:
async with aiofiles.open(dest_path, "wb") as out:
while chunk := await file.read(1024 * 1024):
total_size += len(chunk)
if total_size > service.max_size_bytes:
raise HTTPException(
status_code=413,
detail=f"File exceeds {service.max_size_bytes // 1024 // 1024}MB limit",
)
await out.write(chunk)
except HTTPException:
dest_path.unlink(missing_ok=True)
raise
except Exception:
dest_path.unlink(missing_ok=True)
raise HTTPException(status_code=500, detail="Upload failed")
service.validate_video(filename, file.content_type, total_size)
logger.info("Video uploaded: id=%s filename=%s size=%d", video_id, filename, total_size)
return VideoUploadResponse(
video_id=video_id,
filename=filename,
size_bytes=total_size,
url=f"/api/v1/video/{video_id}",
)
@router.get("/video/{video_id}")
async def serve_video(video_id: str):
service = _get_video_service()
video_path = service.get_video_path(video_id)
ext = video_path.suffix.lower()
media_types = {
".mp4": "video/mp4",
".webm": "video/webm",
".mov": "video/quicktime",
".avi": "video/x-msvideo",
".mkv": "video/x-matroska",
}
return FileResponse(str(video_path), media_type=media_types.get(ext, "application/octet-stream"))
@router.post("/video/{video_id}/transcribe", response_model=FullTranscriptResponse)
async def transcribe_video(video_id: str, language: str = "yue"):
from app.core.config import get_settings
settings = get_settings()
if not settings.dashscope_api_key:
raise HTTPException(
status_code=500,
detail="DASHSCOPE_API_KEY is not configured. Set it in .env to enable transcription.",
)
service = _get_video_service()
wav_path = await service.extract_audio(video_id)
try:
audio_bytes = wav_path.read_bytes()
asr = ASRClient(settings)
text = asr.transcribe_full(audio_bytes, language=language)
except Exception as e:
logger.error("Transcription failed for video_id=%s: %s", video_id, e)
raise HTTPException(status_code=500, detail=f"Transcription failed: {str(e)}")
finally:
if wav_path.exists():
wav_path.unlink(missing_ok=True)
return FullTranscriptResponse(
text=text,
language=language,
duration_seconds=None,
)