Update main.py
Browse files
main.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
| 1 |
# main.py
|
| 2 |
-
import io
|
| 3 |
import json
|
| 4 |
import torch
|
| 5 |
import torchaudio
|
| 6 |
import requests
|
| 7 |
import numpy as np
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
from fastapi import FastAPI, UploadFile, File
|
| 10 |
from transformers import AutoProcessor, AutoModelForCTC
|
| 11 |
from pydantic import BaseModel
|
| 12 |
|
|
|
|
| 13 |
with open("config.json", "r") as f:
|
| 14 |
CONFIG = json.load(f)
|
| 15 |
|
|
@@ -26,7 +28,7 @@ OVERLAP_SECONDS = 2
|
|
| 26 |
MAX_SAMPLES = SAMPLE_RATE * MAX_AUDIO_SECONDS
|
| 27 |
|
| 28 |
|
| 29 |
-
app = FastAPI(title="Universal Audio STT", version="1.
|
| 30 |
|
| 31 |
|
| 32 |
processor = AutoProcessor.from_pretrained(MODEL_NAME)
|
|
@@ -37,18 +39,28 @@ model.eval()
|
|
| 37 |
class STTResponse(BaseModel):
|
| 38 |
transcript: str
|
| 39 |
downstream_response: dict | None = None
|
|
|
|
| 40 |
|
| 41 |
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
|
|
|
| 45 |
|
| 46 |
try:
|
| 47 |
-
waveform, sr = torchaudio.load(
|
| 48 |
except Exception:
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
waveform = waveform.mean(dim=0)
|
| 52 |
|
| 53 |
if sr != SAMPLE_RATE:
|
| 54 |
waveform = torchaudio.functional.resample(
|
|
@@ -56,9 +68,9 @@ def load_audio(file_bytes: bytes) -> np.ndarray:
|
|
| 56 |
)
|
| 57 |
|
| 58 |
if waveform.numel() > MAX_SAMPLES:
|
| 59 |
-
|
| 60 |
|
| 61 |
-
return waveform.numpy()
|
| 62 |
|
| 63 |
|
| 64 |
def chunk_audio(audio: np.ndarray):
|
|
@@ -67,7 +79,7 @@ def chunk_audio(audio: np.ndarray):
|
|
| 67 |
step = chunk_size - overlap
|
| 68 |
|
| 69 |
for start in range(0, len(audio), step):
|
| 70 |
-
chunk = audio[start
|
| 71 |
if len(chunk) < SAMPLE_RATE:
|
| 72 |
break
|
| 73 |
yield chunk
|
|
@@ -82,9 +94,7 @@ def transcribe_chunk(chunk: np.ndarray) -> str:
|
|
| 82 |
)
|
| 83 |
|
| 84 |
with torch.no_grad():
|
| 85 |
-
logits = model(
|
| 86 |
-
inputs.input_values.to(DEVICE)
|
| 87 |
-
).logits
|
| 88 |
|
| 89 |
predicted_ids = torch.argmax(logits, dim=-1)
|
| 90 |
return processor.batch_decode(predicted_ids)[0].strip()
|
|
@@ -112,12 +122,18 @@ def forward_to_text_model(text: str):
|
|
| 112 |
|
| 113 |
@app.post("/stt", response_model=STTResponse)
|
| 114 |
async def stt(audio: UploadFile = File(...)):
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
downstream = None
|
| 123 |
try:
|
|
@@ -128,5 +144,7 @@ async def stt(audio: UploadFile = File(...)):
|
|
| 128 |
return STTResponse(
|
| 129 |
transcript=transcript,
|
| 130 |
downstream_response=downstream,
|
|
|
|
| 131 |
)
|
| 132 |
|
|
|
|
|
|
| 1 |
# main.py
|
|
|
|
| 2 |
import json
|
| 3 |
import torch
|
| 4 |
import torchaudio
|
| 5 |
import requests
|
| 6 |
import numpy as np
|
| 7 |
+
import tempfile
|
| 8 |
+
import os
|
| 9 |
|
| 10 |
+
from fastapi import FastAPI, UploadFile, File
|
| 11 |
from transformers import AutoProcessor, AutoModelForCTC
|
| 12 |
from pydantic import BaseModel
|
| 13 |
|
| 14 |
+
|
| 15 |
with open("config.json", "r") as f:
|
| 16 |
CONFIG = json.load(f)
|
| 17 |
|
|
|
|
| 28 |
MAX_SAMPLES = SAMPLE_RATE * MAX_AUDIO_SECONDS
|
| 29 |
|
| 30 |
|
| 31 |
+
app = FastAPI(title="Universal Audio STT", version="1.4.0")
|
| 32 |
|
| 33 |
|
| 34 |
processor = AutoProcessor.from_pretrained(MODEL_NAME)
|
|
|
|
| 39 |
class STTResponse(BaseModel):
|
| 40 |
transcript: str
|
| 41 |
downstream_response: dict | None = None
|
| 42 |
+
error: str | None = None
|
| 43 |
|
| 44 |
|
| 45 |
+
def load_audio_safe(file_bytes: bytes) -> tuple[np.ndarray | None, str | None]:
|
| 46 |
+
if not file_bytes:
|
| 47 |
+
return None, "Empty audio file"
|
| 48 |
|
| 49 |
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
| 50 |
+
tmp.write(file_bytes)
|
| 51 |
+
tmp_path = tmp.name
|
| 52 |
|
| 53 |
try:
|
| 54 |
+
waveform, sr = torchaudio.load(tmp_path)
|
| 55 |
except Exception:
|
| 56 |
+
return None, "Unsupported or corrupted audio format"
|
| 57 |
+
finally:
|
| 58 |
+
os.unlink(tmp_path)
|
| 59 |
+
|
| 60 |
+
if waveform.numel() == 0:
|
| 61 |
+
return None, "Audio contains no samples"
|
| 62 |
|
| 63 |
+
waveform = waveform.mean(dim=0)
|
| 64 |
|
| 65 |
if sr != SAMPLE_RATE:
|
| 66 |
waveform = torchaudio.functional.resample(
|
|
|
|
| 68 |
)
|
| 69 |
|
| 70 |
if waveform.numel() > MAX_SAMPLES:
|
| 71 |
+
return None, "Audio exceeds 5 minute limit"
|
| 72 |
|
| 73 |
+
return waveform.numpy(), None
|
| 74 |
|
| 75 |
|
| 76 |
def chunk_audio(audio: np.ndarray):
|
|
|
|
| 79 |
step = chunk_size - overlap
|
| 80 |
|
| 81 |
for start in range(0, len(audio), step):
|
| 82 |
+
chunk = audio[start:start + chunk_size]
|
| 83 |
if len(chunk) < SAMPLE_RATE:
|
| 84 |
break
|
| 85 |
yield chunk
|
|
|
|
| 94 |
)
|
| 95 |
|
| 96 |
with torch.no_grad():
|
| 97 |
+
logits = model(inputs.input_values.to(DEVICE)).logits
|
|
|
|
|
|
|
| 98 |
|
| 99 |
predicted_ids = torch.argmax(logits, dim=-1)
|
| 100 |
return processor.batch_decode(predicted_ids)[0].strip()
|
|
|
|
| 122 |
|
| 123 |
@app.post("/stt", response_model=STTResponse)
|
| 124 |
async def stt(audio: UploadFile = File(...)):
|
| 125 |
+
audio_bytes = await audio.read()
|
| 126 |
+
|
| 127 |
+
audio_data, error = load_audio_safe(audio_bytes)
|
| 128 |
+
|
| 129 |
+
if error:
|
| 130 |
+
return STTResponse(
|
| 131 |
+
transcript="",
|
| 132 |
+
downstream_response=None,
|
| 133 |
+
error=error,
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
transcript = transcribe_long(audio_data)
|
| 137 |
|
| 138 |
downstream = None
|
| 139 |
try:
|
|
|
|
| 144 |
return STTResponse(
|
| 145 |
transcript=transcript,
|
| 146 |
downstream_response=downstream,
|
| 147 |
+
error=None,
|
| 148 |
)
|
| 149 |
|
| 150 |
+
|