Live progress for web apps
This guide shows how to display a live progress bar while a file is transcribed. The SDK reports progress through two callbacks. You combine them into a single 0–100 value, store it per job, and serve it to your frontend.
Speech Revolutions reports progress for both the file upload and the transcription of pre-recorded audio, so you can show a real progress bar instead of a spinner. AssemblyAI and Deepgram don't expose a percentage for pre-recorded audio.
The two callbacks
transcribe() accepts two progress callbacks. Each receives a progress event:
on_upload_progress/onUploadProgressfires while the file uploads (event.step === "upload").on_progress/onProgressfires while the server transcribes.
Each event has a percent field, a 0–100 value that is None/undefined until the totals are known. Write it to your database, push it over a WebSocket, or keep it in memory for an HTTP endpoint to read.
Files under about 3 MiB may report no transcription progress before they complete. Expect the bar to jump from the end of the upload straight to done.
Weight the two phases into one bar
Upload is usually quick, so give it the first slice of the bar and let transcription fill the rest. Store the latest value per job, never let it decrease (events can arrive slightly out of order), and serve { phase, percent } to your frontend.
import asyncio
import threading
from dataclasses import dataclass, field
from speechrevolutions import AsyncSpeechRevolutions, ProgressEvent
# Weight the two phases into one bar. Upload is usually quick; give it the
# first slice and let transcription fill the rest. Tune to taste.
UPLOAD_WEIGHT = 0.15 # upload spans 0–15% of the overall bar
TRANSCRIBE_WEIGHT = 0.85 # transcription spans 15–100%
@dataclass
class JobProgress:
"""The latest progress for one job — the shape you'd serve to your frontend."""
phase: str = "starting" # "upload" | "transcribe" | "done" | "failed"
percent: float = 0.0 # overall 0–100 across both phases
lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
def _set(self, phase: str, overall: float) -> None:
with self.lock:
self.phase = phase
# never let the bar go backwards (events can arrive out of order)
self.percent = max(self.percent, round(overall, 1))
def on_upload(self, event: ProgressEvent) -> None:
self._set("upload", (event.percent or 0.0) * UPLOAD_WEIGHT)
def on_transcribe(self, event: ProgressEvent) -> None:
pct = event.percent or 0.0
self._set("transcribe", UPLOAD_WEIGHT * 100 + pct * TRANSCRIBE_WEIGHT)
def snapshot(self) -> dict:
with self.lock:
return {"phase": self.phase, "percent": self.percent}
async def transcribe_with_progress(audio: str, store: JobProgress):
try:
async with AsyncSpeechRevolutions() as client:
result = await client.transcribe(
audio,
on_upload_progress=store.on_upload, # <- do anything with event.percent
on_progress=store.on_transcribe,
)
except Exception:
store._set("failed", store.percent) # tell the frontend, instead of stalling
raise
store._set("done", 100.0)
return resultServe it to the frontend
Keep a map keyed by job ID, run the transcription in the background, and return immediately. Your frontend polls a progress endpoint, or you push each snapshot over a WebSocket.
import uuid
from fastapi import FastAPI, BackgroundTasks, HTTPException
app = FastAPI()
JOBS: dict[str, JobProgress] = {}
@app.post("/transcribe")
async def start(url: str, background: BackgroundTasks):
job_id = str(uuid.uuid4())
JOBS[job_id] = JobProgress()
background.add_task(transcribe_with_progress, url, JOBS[job_id])
return {"job_id": job_id} # returns immediately; runs in background
@app.get("/progress/{job_id}")
def progress(job_id: str):
store = JOBS.get(job_id)
if store is None: # restarted, expired, or a typo
raise HTTPException(status_code=404, detail="unknown job")
return store.snapshot() # {"phase": "transcribe", "percent": 63.5}The server streams completed/total step counts over SSE. The SDK computes percent = completed / total × 100 and passes it to your callbacks. For the callback signatures, see the Python, JavaScript, Go, and C# SDK pages.