File size: 6,308 Bytes
c7e434a 1e0d7f9 c7e434a 1e0d7f9 c7e434a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
"""
API Routes
"""
from fastapi import APIRouter, UploadFile, File, Form, HTTPException
from typing import Optional, List
import uuid
import os
import json
from app.models import TaskResponse, TaskStatusResponse, TaskStatus, AnalysisRequest
from app.core.redis_client import get_queue
from app.core.storage import save_uploaded_file
from app.config import settings
from app.tasks import process_audio_task
router = APIRouter()
@router.post("/analyze", response_model=TaskResponse)
async def analyze_audio(
audio: UploadFile = File(...),
reference_text: Optional[str] = Form(None),
topic_id: Optional[str] = Form(None),
custom_topic: Optional[str] = Form(None),
custom_keywords: Optional[str] = Form(None), # JSON string dari frontend
analyze_tempo: bool = Form(True),
analyze_articulation: bool = Form(True),
analyze_structure: bool = Form(True),
analyze_keywords: bool = Form(True), # Default TRUE
analyze_profanity: bool = Form(True) # Default TRUE
):
"""
Submit audio file untuk analisis
Parameters:
- audio: File audio (.wav, .mp3, .m4a, .flac, .ogg)
- reference_text: Teks referensi untuk artikulasi (optional)
- topic_id: ID topik dari database untuk Level 1-2 (optional)
- custom_topic: Topik custom untuk Level 3 (optional)
- custom_keywords: JSON array kata kunci dari GPT, contoh: ["inovasi", "kreativitas", "perubahan"] (optional)
- analyze_tempo: Analisis tempo (default: true)
- analyze_articulation: Analisis artikulasi (default: true)
- analyze_structure: Analisis struktur (default: true)
- analyze_keywords: Analisis kata kunci (default: true) - otomatis skip jika tidak ada topic_id/custom_keywords
- analyze_profanity: Deteksi kata tidak senonoh (default: true)
Returns task_id yang bisa digunakan untuk check status
"""
# Validate file extension
file_ext = os.path.splitext(audio.filename)[1].lower()
if file_ext not in settings.ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=400,
detail=f"File type {file_ext} not allowed. Allowed: {settings.ALLOWED_EXTENSIONS}"
)
# Validate file size
content = await audio.read()
if len(content) > settings.MAX_UPLOAD_SIZE:
raise HTTPException(
status_code=400,
detail=f"File too large. Max size: {settings.MAX_UPLOAD_SIZE / 1024 / 1024}MB"
)
# Parse custom_keywords dari JSON string
parsed_custom_keywords = None
if custom_keywords:
try:
parsed_custom_keywords = json.loads(custom_keywords)
if not isinstance(parsed_custom_keywords, list):
raise ValueError("custom_keywords harus berupa array")
except json.JSONDecodeError:
raise HTTPException(
status_code=400,
detail="custom_keywords harus berupa JSON array valid, contoh: [\"kata1\", \"kata2\"]"
)
# Save file
task_id = str(uuid.uuid4())
filename = f"{task_id}{file_ext}"
file_path = save_uploaded_file(content, filename)
# Submit task to queue
queue = get_queue()
job = queue.enqueue(
process_audio_task,
audio_path=file_path,
reference_text=reference_text,
topic_id=topic_id,
custom_topic=custom_topic,
custom_keywords=parsed_custom_keywords,
analyze_tempo=analyze_tempo,
analyze_articulation=analyze_articulation,
analyze_structure=analyze_structure,
analyze_keywords=analyze_keywords,
analyze_profanity=analyze_profanity,
job_id=task_id,
job_timeout=settings.JOB_TIMEOUT,
result_ttl=settings.RESULT_TTL
)
return TaskResponse(
task_id=task_id,
status=TaskStatus.QUEUED,
message="Task submitted successfully"
)
@router.get("/status/{task_id}", response_model=TaskStatusResponse)
async def get_task_status(task_id: str):
"""
Check status dari task
Returns status dan result jika sudah selesai
"""
from rq.job import Job
from app.core.redis_client import get_redis_connection
try:
redis_conn = get_redis_connection()
job = Job.fetch(task_id, connection=redis_conn)
# Map job status to our TaskStatus
if job.is_queued:
status = TaskStatus.QUEUED
elif job.is_started:
status = TaskStatus.PROCESSING
elif job.is_finished:
status = TaskStatus.COMPLETED
elif job.is_failed:
status = TaskStatus.FAILED
else:
status = TaskStatus.QUEUED
# Get result if completed
result = None
error = None
if job.is_finished:
job_result = job.result
if isinstance(job_result, dict):
if job_result.get('status') == 'completed':
result = job_result.get('result')
elif job_result.get('status') == 'failed':
error = job_result.get('error')
status = TaskStatus.FAILED
if job.is_failed:
error = str(job.exc_info)
return TaskStatusResponse(
task_id=task_id,
status=status,
result=result,
error=error,
created_at=job.created_at.isoformat() if job.created_at else None,
updated_at=job.ended_at.isoformat() if job.ended_at else None
)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"Task not found: {str(e)}"
)
@router.get("/health")
async def health_check():
"""Health check endpoint"""
from app.core.redis_client import check_redis_connection
from app.core.device import get_device_info
is_connected, error_msg = check_redis_connection()
if is_connected:
redis_status = "healthy"
else:
redis_status = f"unhealthy: {error_msg}"
# Get device information
device_info = get_device_info()
return {
"status": "healthy" if is_connected else "degraded",
"redis": redis_status,
"version": settings.VERSION,
"device": device_info
}
|