177 lines
5.4 KiB
Python
177 lines
5.4 KiB
Python
from fastapi import APIRouter, File, UploadFile, Form, HTTPException, Depends, Header
|
|
from fastapi.responses import JSONResponse
|
|
from typing import Optional, List
|
|
import time
|
|
import os
|
|
import hashlib
|
|
|
|
from src.config import settings
|
|
from src.services.whisper_service import transcribe_audio
|
|
from src.services.stats_service import log_usage
|
|
from src.database.db import get_db
|
|
from sqlalchemy.orm import Session
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
from src.services.stats_service import hash_api_key
|
|
from src.database.db import SessionLocal
|
|
from src.database.models import ApiKey
|
|
|
|
def verify_api_key(authorization: Optional[str] = Header(None)):
|
|
"""Verify API key from Authorization header"""
|
|
if not authorization:
|
|
raise HTTPException(status_code=401, detail="Authorization header missing")
|
|
|
|
# Extract Bearer token
|
|
if not authorization.startswith("Bearer "):
|
|
raise HTTPException(status_code=401, detail="Invalid authorization format")
|
|
|
|
api_key = authorization.replace("Bearer ", "").strip()
|
|
|
|
# Check environment variable keys first
|
|
valid_keys = settings.get_api_keys_list()
|
|
if api_key in valid_keys:
|
|
return api_key
|
|
|
|
# Check database keys
|
|
db = SessionLocal()
|
|
try:
|
|
key_hash = hash_api_key(api_key)
|
|
db_key = db.query(ApiKey).filter(
|
|
ApiKey.key_hash == key_hash,
|
|
ApiKey.is_active == True
|
|
).first()
|
|
|
|
if db_key:
|
|
return api_key
|
|
finally:
|
|
db.close()
|
|
|
|
raise HTTPException(status_code=401, detail="Invalid API key")
|
|
|
|
|
|
@router.get("/models")
|
|
async def list_models(api_key: str = Depends(verify_api_key)):
|
|
"""List available models (OpenAI compatible)"""
|
|
return {
|
|
"data": [
|
|
{
|
|
"id": "whisper-1",
|
|
"object": "model",
|
|
"created": 1677532384,
|
|
"owned_by": "openai"
|
|
},
|
|
{
|
|
"id": "large-v3",
|
|
"object": "model",
|
|
"created": 1698796800,
|
|
"owned_by": "openai"
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
@router.post("/audio/transcriptions")
|
|
async def create_transcription(
|
|
file: UploadFile = File(...),
|
|
model: str = Form("whisper-1"),
|
|
language: Optional[str] = Form(None),
|
|
prompt: Optional[str] = Form(None),
|
|
response_format: str = Form("json"),
|
|
temperature: float = Form(0.0),
|
|
timestamp_granularities: Optional[List[str]] = Form(None),
|
|
api_key: str = Depends(verify_api_key),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Transcribe audio file (OpenAI compatible endpoint)
|
|
|
|
- **file**: Audio file (mp3, mp4, mpeg, mpga, m4a, wav, webm)
|
|
- **model**: Model ID (whisper-1 or large-v3)
|
|
- **language**: Language code (e.g., 'de', 'en')
|
|
- **response_format**: json, text, srt, verbose_json, vtt
|
|
- **timestamp_granularities**: word, segment (for verbose_json)
|
|
"""
|
|
|
|
start_time = time.time()
|
|
temp_path = None
|
|
|
|
try:
|
|
# Validate file type
|
|
allowed_extensions = {'.mp3', '.mp4', '.mpeg', '.mpga', '.m4a', '.wav', '.webm', '.ogg', '.oga', '.opus', '.flac'}
|
|
file_ext = os.path.splitext(file.filename)[1].lower()
|
|
|
|
if file_ext not in allowed_extensions:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Unsupported file format: {file_ext}"
|
|
)
|
|
|
|
# Save uploaded file
|
|
temp_filename = f"{api_key[:8]}_{int(time.time())}_{file.filename}"
|
|
temp_path = os.path.join(settings.uploads_path, temp_filename)
|
|
|
|
with open(temp_path, "wb") as f:
|
|
content = await file.read()
|
|
f.write(content)
|
|
|
|
file_size = len(content)
|
|
|
|
# Transcribe
|
|
include_word_timestamps = timestamp_granularities and "word" in timestamp_granularities
|
|
|
|
result = await transcribe_audio(
|
|
audio_path=temp_path,
|
|
language=language,
|
|
include_word_timestamps=include_word_timestamps
|
|
)
|
|
|
|
processing_time = int((time.time() - start_time) * 1000)
|
|
|
|
# Log usage
|
|
await log_usage(
|
|
db=db,
|
|
api_key=api_key,
|
|
endpoint="/v1/audio/transcriptions",
|
|
file_size=file_size,
|
|
duration=result.get("duration"),
|
|
processing_time=processing_time,
|
|
model=settings.whisper_model,
|
|
status="success"
|
|
)
|
|
|
|
# Format response based on requested format
|
|
if response_format == "text":
|
|
return result["text"]
|
|
elif response_format == "verbose_json":
|
|
return result
|
|
else:
|
|
return {"text": result["text"]}
|
|
|
|
except Exception as e:
|
|
processing_time = int((time.time() - start_time) * 1000)
|
|
|
|
# Log error
|
|
await log_usage(
|
|
db=db,
|
|
api_key=api_key,
|
|
endpoint="/v1/audio/transcriptions",
|
|
file_size=file_size if 'file_size' in locals() else None,
|
|
duration=None,
|
|
processing_time=processing_time,
|
|
model=settings.whisper_model,
|
|
status="error",
|
|
error_message=str(e)
|
|
)
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
finally:
|
|
# Cleanup temp file
|
|
if temp_path and os.path.exists(temp_path):
|
|
try:
|
|
os.remove(temp_path)
|
|
except:
|
|
pass
|