Spaces:
Sleeping
Sleeping
File size: 2,755 Bytes
ae77e3b 9afa571 033e455 ae77e3b 5b717de 033e455 54358d8 033e455 829baf1 9afa571 033e455 ae77e3b 033e455 ae77e3b 033e455 829baf1 033e455 9afa571 829baf1 54358d8 9afa571 54358d8 9afa571 033e455 ae77e3b 033e455 54358d8 ae77e3b 9afa571 033e455 9afa571 033e455 9afa571 ae77e3b 033e455 ae77e3b 54358d8 033e455 9afa571 54358d8 033e455 9afa571 033e455 9afa571 033e455 9afa571 54358d8 033e455 54358d8 9afa571 |
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 |
import os
import tempfile
import subprocess
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse, PlainTextResponse
from pydantic import BaseModel
from faster_whisper import WhisperModel
# Force writable cache path
CACHE_ROOT = os.environ.get("HF_HOME", "/data/hf")
os.environ["HF_HOME"] = CACHE_ROOT
os.environ["HUGGINGFACE_HUB_CACHE"] = CACHE_ROOT
os.environ["TRANSFORMERS_CACHE"] = CACHE_ROOT
os.makedirs(CACHE_ROOT, exist_ok=True)
app = FastAPI(title="Nuvia Free Transcriber", version="1.4.0")
# Root route so "/" is never a 404
@app.get("/", response_class=PlainTextResponse)
def root():
return "Nuvia Free Transcriber · try POST /transcribe or GET /health"
class HealthOut(BaseModel):
ok: bool
@app.get("/health", response_model=HealthOut)
def health():
return {"ok": True}
MODEL_NAME = os.environ.get("WHISPER_REPO", "Systran/faster-whisper-tiny.en")
COMPUTE_TYPE = os.environ.get("WHISPER_COMPUTE", "int8")
model = WhisperModel(
MODEL_NAME,
device="cpu",
compute_type=COMPUTE_TYPE,
download_root=CACHE_ROOT,
)
def ffprobe_duration(path: str):
try:
out = subprocess.check_output(
["ffprobe","-v","error","-show_entries","format=duration",
"-of","default=noprint_wrappers=1:nokey=1", path],
stderr=subprocess.STDOUT,
)
return float(out.decode().strip())
except Exception:
return None
def estimate_wpm(text: str, duration_sec: float | None):
if not text or not duration_sec or duration_sec <= 0:
return None
words = len(text.strip().split())
mins = duration_sec / 60.0
if mins <= 0:
return None
return words / mins
class TranscribeOut(BaseModel):
text: str
duration_sec: float | None = None
wpm: float | None = None
@app.post("/transcribe", response_model=TranscribeOut)
async def transcribe(file: UploadFile = File(...)):
if not file.filename:
raise HTTPException(400, "Missing file name")
suffix = os.path.splitext(file.filename)[1].lower() or ".mp3"
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
tmp.write(await file.read())
tmp_path = tmp.name
duration = ffprobe_duration(tmp_path)
segments, info = model.transcribe(
tmp_path,
language="en",
beam_size=1,
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=600),
)
parts = [seg.text.strip() for seg in segments if seg.text and seg.text.strip()]
text = " ".join(parts)
wpm = estimate_wpm(text, duration)
try:
os.unlink(tmp_path)
except Exception:
pass
return TranscribeOut(text=text, duration_sec=duration, wpm=wpm) |