import os import uuid import asyncio from fastapi import APIRouter, UploadFile, File, HTTPException, Query from fastapi.responses import FileResponse from pydantic import BaseModel from typing import Optional from app.config import settings router = APIRouter() class EditRequest(BaseModel): file_id: str cut_start_ms: Optional[float] = None cut_end_ms: Optional[float] = None zero_crossing_align: bool = True loop_count: int = 1 fade_in_ms: float = 0.0 fade_out_ms: float = 0.0 volume_change_db: float = 0.0 class ExportRequest(BaseModel): file_id: str format: str = "wav" sample_rate: int = 44100 bit_depth: int = 16 class AIAnalysisRequest(BaseModel): file_id: str api_base_url: Optional[str] = None model: str = "deepseek-chat" @router.post("/upload") async def upload_audio(file: UploadFile = File(...)): ext = os.path.splitext(file.filename)[1] if not ext: ext = ".wav" file_id = f"{uuid.uuid4()}{ext}" file_path = os.path.join(settings.UPLOADS_DIR, file_id) with open(file_path, "wb") as f: content = await file.read() f.write(content) # Trigger celery task from app.tasks.worker import analyze_audio_task task = analyze_audio_task.delay(file_id) return { "file_id": file_id, "filename": file.filename, "analysis_task_id": task.id } @router.post("/edit") async def edit_audio(req: EditRequest): upload_path = os.path.join(settings.UPLOADS_DIR, req.file_id) processed_path = os.path.join(settings.PROCESSED_DIR, req.file_id) # Use uploaded file if it exists, or look in processed if it was already edited if not os.path.exists(upload_path) and not os.path.exists(processed_path): raise HTTPException(status_code=404, detail="File not found") from app.tasks.worker import edit_audio_task task = edit_audio_task.delay(req.dict()) return { "task_id": task.id } @router.get("/download/{file_id}") async def download_audio(file_id: str): processed_path = os.path.join(settings.PROCESSED_DIR, file_id) upload_path = os.path.join(settings.UPLOADS_DIR, file_id) if os.path.exists(processed_path): return FileResponse(processed_path, media_type="audio/wav", filename=file_id) elif os.path.exists(upload_path): return FileResponse(upload_path, media_type="audio/wav", filename=file_id) raise HTTPException(status_code=404, detail="File not found") @router.get("/waveform/{file_id}") async def get_waveform(file_id: str, num_peaks: int = Query(default=800, ge=50, le=4000)): """ API endpoint vẽ Peak Waveform đồng bộ (Week 2). Trả về dữ liệu peak waveform cho hiển thị đồ thị sóng âm trên Frontend. """ upload_path = os.path.join(settings.UPLOADS_DIR, file_id) processed_path = os.path.join(settings.PROCESSED_DIR, file_id) if os.path.exists(processed_path): file_path = processed_path elif os.path.exists(upload_path): file_path = upload_path else: raise HTTPException(status_code=404, detail="File not found") from app.core.dsp_utils import generate_peak_waveform return await asyncio.to_thread(generate_peak_waveform, file_path, num_peaks) @router.get("/waveform-rms/{file_id}") async def get_waveform_rms(file_id: str, num_points: int = Query(default=800, ge=50, le=4000)): """ API endpoint vẽ RMS Waveform (mượt hơn peak). """ upload_path = os.path.join(settings.UPLOADS_DIR, file_id) processed_path = os.path.join(settings.PROCESSED_DIR, file_id) if os.path.exists(processed_path): file_path = processed_path elif os.path.exists(upload_path): file_path = upload_path else: raise HTTPException(status_code=404, detail="File not found") from app.core.dsp_utils import generate_rms_waveform return await asyncio.to_thread(generate_rms_waveform, file_path, num_points) @router.post("/analyze-ai") async def analyze_audio_with_ai(req: AIAnalysisRequest): """ API endpoint phân tích cấu trúc khuôn nhạc bằng AI (Week 4). Gọi OpenAI Compatible API (DeepSeek/Ollama) để phân đoạn bố cục. """ upload_path = os.path.join(settings.UPLOADS_DIR, req.file_id) processed_path = os.path.join(settings.PROCESSED_DIR, req.file_id) if os.path.exists(processed_path): file_path = processed_path elif os.path.exists(upload_path): file_path = upload_path else: raise HTTPException(status_code=404, detail="File not found") from app.tasks.worker import analyze_ai_task task = analyze_ai_task.delay( file_id=req.file_id, api_base_url=req.api_base_url, model=req.model ) return { "task_id": task.id, "file_id": req.file_id } @router.post("/export") async def export_audio(req: ExportRequest): """ API endpoint xuất tệp âm thanh sang nhiều định dạng (WAV/MP3/OGG). """ upload_path = os.path.join(settings.UPLOADS_DIR, req.file_id) processed_path = os.path.join(settings.PROCESSED_DIR, req.file_id) if os.path.exists(processed_path): source_path = processed_path elif os.path.exists(upload_path): source_path = upload_path else: raise HTTPException(status_code=404, detail="File not found") from app.tasks.worker import export_audio_task task = export_audio_task.delay( file_id=req.file_id, format=req.format, sample_rate=req.sample_rate, bit_depth=req.bit_depth ) return { "task_id": task.id, "file_id": req.file_id }