Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
4838f88
1
Parent(s):
78c30f0
Fix: Resolve Content-Length error with explicit JSONResponse serialization
Browse files- api/routes.py +70 -56
api/routes.py
CHANGED
|
@@ -7,9 +7,10 @@ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
|
|
| 7 |
from fastapi.responses import JSONResponse
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from pydantic import BaseModel
|
| 10 |
-
from typing import Optional, List
|
| 11 |
import tempfile
|
| 12 |
import os
|
|
|
|
| 13 |
from pathlib import Path
|
| 14 |
|
| 15 |
from core.scoring_engine import AdvancedVocalScoringSystem, ScoreResult
|
|
@@ -114,26 +115,29 @@ async def shutdown_event():
|
|
| 114 |
# API ENDPOINTS
|
| 115 |
# =======================================
|
| 116 |
|
| 117 |
-
@app.get("/",
|
| 118 |
async def root():
|
| 119 |
"""Root endpoint"""
|
| 120 |
-
return
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
"
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
"
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
@app.get("/health", response_model=HealthResponse)
|
| 139 |
async def health_check():
|
|
@@ -153,7 +157,7 @@ async def get_levels():
|
|
| 153 |
total_levels=len(ARTICULATION_LEVELS)
|
| 154 |
)
|
| 155 |
|
| 156 |
-
@app.post("/score",
|
| 157 |
async def score_audio(
|
| 158 |
audio: UploadFile = File(..., description="Audio file (WAV, MP3, M4A, etc.)"),
|
| 159 |
target_text: str = Form(..., description="Target text yang seharusnya diucapkan"),
|
|
@@ -204,25 +208,31 @@ async def score_audio(
|
|
| 204 |
# Clean up temp file
|
| 205 |
os.unlink(tmp_path)
|
| 206 |
|
| 207 |
-
#
|
| 208 |
-
|
| 209 |
-
success
|
| 210 |
-
overall_score
|
| 211 |
-
grade
|
| 212 |
-
clarity_score
|
| 213 |
-
energy_score
|
| 214 |
-
speech_rate_score
|
| 215 |
-
pitch_consistency_score
|
| 216 |
-
snr_score
|
| 217 |
-
articulation_score
|
| 218 |
-
transcription
|
| 219 |
-
target
|
| 220 |
-
similarity
|
| 221 |
-
wer
|
| 222 |
-
feedback
|
| 223 |
-
suggestions
|
| 224 |
-
audio_features
|
| 225 |
-
level
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
)
|
| 227 |
|
| 228 |
except Exception as e:
|
|
@@ -290,25 +300,26 @@ async def batch_score_audio(
|
|
| 290 |
# Clean up
|
| 291 |
os.unlink(tmp_path)
|
| 292 |
|
|
|
|
| 293 |
results.append({
|
| 294 |
"filename": audio.filename,
|
| 295 |
"success": True,
|
| 296 |
-
"overall_score": result.overall_score,
|
| 297 |
-
"grade": result.grade,
|
| 298 |
-
"clarity_score": result.clarity_score,
|
| 299 |
-
"energy_score": result.energy_score,
|
| 300 |
-
"speech_rate_score": result.speech_rate_score,
|
| 301 |
-
"pitch_consistency_score": result.pitch_consistency_score,
|
| 302 |
-
"snr_score": result.snr_score,
|
| 303 |
-
"articulation_score": result.articulation_score,
|
| 304 |
-
"transcription": result.transcription,
|
| 305 |
-
"target": result.target,
|
| 306 |
-
"similarity": result.similarity,
|
| 307 |
-
"wer": result.wer,
|
| 308 |
-
"feedback": result.feedback,
|
| 309 |
-
"suggestions": result.suggestions,
|
| 310 |
-
"audio_features": result.audio_features,
|
| 311 |
-
"level": result.level
|
| 312 |
})
|
| 313 |
|
| 314 |
except Exception as e:
|
|
@@ -321,7 +332,10 @@ async def batch_score_audio(
|
|
| 321 |
"error": str(e)
|
| 322 |
})
|
| 323 |
|
| 324 |
-
return
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
# =======================================
|
| 327 |
# RUN SERVER
|
|
|
|
| 7 |
from fastapi.responses import JSONResponse
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from pydantic import BaseModel
|
| 10 |
+
from typing import Optional, List, Dict, Any
|
| 11 |
import tempfile
|
| 12 |
import os
|
| 13 |
+
import json
|
| 14 |
from pathlib import Path
|
| 15 |
|
| 16 |
from core.scoring_engine import AdvancedVocalScoringSystem, ScoreResult
|
|
|
|
| 115 |
# API ENDPOINTS
|
| 116 |
# =======================================
|
| 117 |
|
| 118 |
+
@app.get("/", response_class=JSONResponse)
|
| 119 |
async def root():
|
| 120 |
"""Root endpoint"""
|
| 121 |
+
return JSONResponse(
|
| 122 |
+
content={
|
| 123 |
+
"message": "Vocal Articulation Assessment API v2",
|
| 124 |
+
"version": "2.0.0",
|
| 125 |
+
"features": [
|
| 126 |
+
"Whisper ASR-based clarity scoring",
|
| 127 |
+
"Multi-level support (Level 1-5)",
|
| 128 |
+
"6 scoring metrics",
|
| 129 |
+
"Comprehensive audio analysis"
|
| 130 |
+
],
|
| 131 |
+
"endpoints": {
|
| 132 |
+
"health": "/health",
|
| 133 |
+
"levels": "/levels",
|
| 134 |
+
"score": "/score",
|
| 135 |
+
"batch_score": "/batch_score",
|
| 136 |
+
"docs": "/docs"
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
media_type="application/json"
|
| 140 |
+
)
|
| 141 |
|
| 142 |
@app.get("/health", response_model=HealthResponse)
|
| 143 |
async def health_check():
|
|
|
|
| 157 |
total_levels=len(ARTICULATION_LEVELS)
|
| 158 |
)
|
| 159 |
|
| 160 |
+
@app.post("/score", response_class=JSONResponse)
|
| 161 |
async def score_audio(
|
| 162 |
audio: UploadFile = File(..., description="Audio file (WAV, MP3, M4A, etc.)"),
|
| 163 |
target_text: str = Form(..., description="Target text yang seharusnya diucapkan"),
|
|
|
|
| 208 |
# Clean up temp file
|
| 209 |
os.unlink(tmp_path)
|
| 210 |
|
| 211 |
+
# Convert result to dict and ensure proper serialization
|
| 212 |
+
response_data = {
|
| 213 |
+
"success": True,
|
| 214 |
+
"overall_score": float(result.overall_score),
|
| 215 |
+
"grade": str(result.grade),
|
| 216 |
+
"clarity_score": float(result.clarity_score),
|
| 217 |
+
"energy_score": float(result.energy_score),
|
| 218 |
+
"speech_rate_score": float(result.speech_rate_score),
|
| 219 |
+
"pitch_consistency_score": float(result.pitch_consistency_score),
|
| 220 |
+
"snr_score": float(result.snr_score),
|
| 221 |
+
"articulation_score": float(result.articulation_score),
|
| 222 |
+
"transcription": str(result.transcription),
|
| 223 |
+
"target": str(result.target),
|
| 224 |
+
"similarity": float(result.similarity),
|
| 225 |
+
"wer": float(result.wer),
|
| 226 |
+
"feedback": str(result.feedback),
|
| 227 |
+
"suggestions": [str(s) for s in result.suggestions],
|
| 228 |
+
"audio_features": {k: (float(v) if isinstance(v, (int, float)) else str(v)) for k, v in result.audio_features.items()},
|
| 229 |
+
"level": int(result.level)
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
# Return with explicit JSON response
|
| 233 |
+
return JSONResponse(
|
| 234 |
+
content=response_data,
|
| 235 |
+
media_type="application/json"
|
| 236 |
)
|
| 237 |
|
| 238 |
except Exception as e:
|
|
|
|
| 300 |
# Clean up
|
| 301 |
os.unlink(tmp_path)
|
| 302 |
|
| 303 |
+
# Properly serialize result
|
| 304 |
results.append({
|
| 305 |
"filename": audio.filename,
|
| 306 |
"success": True,
|
| 307 |
+
"overall_score": float(result.overall_score),
|
| 308 |
+
"grade": str(result.grade),
|
| 309 |
+
"clarity_score": float(result.clarity_score),
|
| 310 |
+
"energy_score": float(result.energy_score),
|
| 311 |
+
"speech_rate_score": float(result.speech_rate_score),
|
| 312 |
+
"pitch_consistency_score": float(result.pitch_consistency_score),
|
| 313 |
+
"snr_score": float(result.snr_score),
|
| 314 |
+
"articulation_score": float(result.articulation_score),
|
| 315 |
+
"transcription": str(result.transcription),
|
| 316 |
+
"target": str(result.target),
|
| 317 |
+
"similarity": float(result.similarity),
|
| 318 |
+
"wer": float(result.wer),
|
| 319 |
+
"feedback": str(result.feedback),
|
| 320 |
+
"suggestions": [str(s) for s in result.suggestions],
|
| 321 |
+
"audio_features": {k: (float(v) if isinstance(v, (int, float)) else str(v)) for k, v in result.audio_features.items()},
|
| 322 |
+
"level": int(result.level)
|
| 323 |
})
|
| 324 |
|
| 325 |
except Exception as e:
|
|
|
|
| 332 |
"error": str(e)
|
| 333 |
})
|
| 334 |
|
| 335 |
+
return JSONResponse(
|
| 336 |
+
content={"results": results, "total": len(results)},
|
| 337 |
+
media_type="application/json"
|
| 338 |
+
)
|
| 339 |
|
| 340 |
# =======================================
|
| 341 |
# RUN SERVER
|