Učitavanje vraća posao, a ne transkript. Stavite Idempotency-Key na svako stvaranje kako ponovni pokušaj nikad ne bi stvorio ili naplatio drugu transkripciju; zatim anketirajte posao dok nije gotov i čitajte rečenice, riječi ili izvoz s istog identifikatora.
Transkripcija datoteke najjednostavnija je stvar koju VocalStack API radi, i ima oblik svakog asinkronog posla: učitavanje, čekanje, čitanje. Učitavanje odmah vraća transkripciju i njezin posao, ali još bez ijedne riječi. Dok vaš program provjerava posao, poslužitelj transkribira datoteku mnogo puta brže nego što se zvuk reproducira. Kad je stanje done, transkript čitate kao rečenice s govornicima i vremenima ili ga izvozite kao datoteku u jednom od četiri formata, bilo na jeziku govora ili preveden.
Sve u ovom obliku vrijedi za bilo koji batch API: stvaranje koje vraća identifikator, status koji provjeravate i rezultat koji naknadno dohvaćate. Samo su nazivi polja specifični za VocalStack, a ovaj vodič prolazi kroz njih s TypeScript i Python SDK-ovima te, gdje pomažu, kroz HTTP pozive.
Otvorite Developers → API keys na nadzornoj ploči i izradite ključ s dopuštenjima Read i Write. Ključ se prikazuje samo jednom; kopirajte ga u varijablu okoline:
export VOCALSTACK_API_KEY="<your API key>"
Ključ je namijenjen poslužiteljima i skriptama. Sve što se isporučuje pregledniku isporučuje i ključ, pa ga držite izvan koda stranice i izvan URL-ova, koji završe u dnevnicima. Za streaming iz preglednika vaš poslužitelj stvara sesiju u stvarnom vremenu za nju; to je zaseban vodič.
Svaki poziv autentificira se ključem kao bearer tokenom:
Authorization: Bearer vs_key_...
S ključem u okolini, sljedeće što treba je klijent koji će ga poslati.
Postoje dva SDK-a. Svaki ima jedan klijent, VocalStack, a njegove metode grupirane su po resursu: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime i još nekoliko. TypeScript SDK radi na Node 20 ili novijem te u preglednicima; Python SDK izgrađen je na asyncio i treba Python 3.10 ili noviji.
SDK-ovi nisu na npm-u ni PyPI-ju. API poslužuje verzije svakom računu. Naredbe za instalaciju, prilagođene vašem okruženju, nalaze se u odjeljku Install brzog početka u referenci API-ja, a šalju vaš ključ samo API hostu. Ako ne želite ništa instalirati, HTTP pozivi u nastavku trebaju samo curl i jq.
Što god odaberete, prvi poziv je učitavanje.
POST /v1/transcriptions prima datoteku kao multipart/form-data: dio file i izborni dio options s JSON-om. Odgovara s 201 i transkripcijom. Njezin job.recordingId identifikator je snimke napravljene iz datoteke, a svaki sljedeći poziv adresira se tim identifikatorom.
Za prvo pokretanje važne su dvije postavke unutar options.settings:
spokenLanguage: kod poput en. Ako ga izostavite, jezik se detektira. Ako ga navedete, poboljšava se točnost na bučnim snimkama i jakim naglascima.speakerLabels: auto, on ili off. S on svaka rečenica nosi broj govornika; s auto se oznake pojavljuju tek kad se čuje drugi govornik.Većina audio i video datoteka je prihvaćena: mp3, wav, flac, ogg, m4a, mp4 i druge. Video zadržava samo svoj zvuk. Format gotovo i ne utječe na točnost, jer prepoznavanje govora radi na 16 kHz mono signalu i sve se pri prijemu na njega preuzorkuje. Stereo WAV od 48 kHz s jednom osobom koja govori većinu svojih bajtova troši na opisivanje tišine u visokoj vjernosti. Transkribira se jednako dobro kao MP3 ili Opus datoteka od 64 kbps mono istog govora, ali je otprilike dvadeset puta veći, pa se i učitavanje traje dvadeset puta dulje. Ako pretvarate prije učitavanja, vrijedi promijeniti dvije postavke: mono i umjerenu brzinu bitova.
Pošaljite Idempotency-Key uz svako učitavanje i razumijte zašto. Mrežni timeout ne govori vam ništa o tome što je poslužitelj napravio: možda nikad nije vidio zahtjev ili je spremio datoteku i stavio posao u red prije nego što je odgovor izgubljen. Slijepi ponovni pokušaj zato riskira drugu transkripciju i drugu naplatu. Ključ to sprječava. Kad se zahtjev ponovi s istim ključem i tijelom unutar 24 sata, poslužitelj vraća prvi odgovor umjesto da išta stvori, pa "najmanje jednom" postaje "praktički jednom". Pravilo je zato jedan ključ po logičkom učitavanju, generiran prije prvog pokušaja i ponovno korišten pri svakom ponavljanju. Novi ključ po pokušaju učinio bi svaki ponovni pokušaj novim učitavanjem, a zaglavlje ne bi štitilo ništa.
import { VocalStack } from '@vocalstack/sdk';
const vs = new VocalStack({ baseUrl: '{{api}}', apiKey: process.env.VOCALSTACK_API_KEY });
const uploadKey = crypto.randomUUID(); // one key for this upload, however many attempts it takes
const tx = await vs.transcriptions.create(
{ file: './talk.mp3', name: 'Talk', settings: { spokenLanguage: 'en', speakerLabels: 'on' } },
{ idempotencyKey: uploadKey },
);
const recordingId = tx.job!.recordingId;
import asyncio
import os
import uuid
from vocalstack import VocalStack
async def main() -> None:
upload_key = str(uuid.uuid4()) # one key for this upload, however many attempts it takes
async with VocalStack("{{api}}", api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
tx = await vs.transcriptions.create(
file="./talk.mp3",
name="Talk",
settings={"spokenLanguage": "en", "speakerLabels": "on"},
idempotency_key=upload_key,
)
assert tx.job is not None
recording_id = tx.job.recording_id
Isti poziv s curlom:
API={{api}}
AUTH="Authorization: Bearer $VOCALSTACK_API_KEY"
UPLOAD_KEY=$(uuidgen)
REC=$(curl -sSf "$API/v1/transcriptions" -H "$AUTH" -H "Idempotency-Key: $UPLOAD_KEY" \
-F file=@talk.mp3 \
-F 'options={"name":"Talk","settings":{"spokenLanguage":"en","speakerLabels":"on"}}' \
| jq -r .job.recordingId)
Odgovor 201 stiže prije nego što je i jedna riječ transkribirana; ono što držite je identifikator posla, pa je sljedeći korak čekanje na njega.
Posao prelazi iz queued u transcribing pa u done, ili završava u failed s porukom error i, kad je uzrok strojno čitljiv, s errorCode. Četvrto stanje, cropping, znači da je posao zadržan dok netko ne odabere dio snimke za transkripciju, kao što to radi korak obrezivanja na nadzornoj ploči. Obično učitavanje u njega nikad ne ulazi, iako se jobs.wait vraća i kad posao dođe do njega. Čitajte GET /v1/recordings/{id}/job dok stanje ne bude done ili failed.
Nema webhookova, i to je namjerna odluka. Webhook treba javni HTTPS krajnji čvor na vašoj strani, provjeru potpisa da ga nitko drugi ne bi mogao pozvati i pravila ponavljanja za slučaj da je vaš krajnji čvor nedostupan, što je mnogo infrastrukture za posao koji traje minutu. Anketiranje ne treba ništa od toga. Vaša skripta će svake dvije sekunde pitati "jesi li gotov?" kao dijete na stražnjem sjedalu, samo što za razliku od djeteta dobiva natrag postotak.
Web stranica koja je već otvorena može umjesto toga pratiti događaje transkripcije preko socketa (WS /v1/transcriptions/{id}/events, okvir job.progress dok posao napreduje). Za skriptu je anketiranje pravi alat.
SDK-ovi rade anketiranje: vs.jobs.wait čita posao svake 2 sekunde i svako čitanje predaje u onProgress (on_progress u Pythonu). progress posla sadrži pct (0 do 100) i processedS, sekunde medija transkribirane do sada. Programi u nastavku ispisuju redak samo kad se promijene stanje ili postotak.
let last = '';
const job = await vs.jobs.wait(recordingId, {
onProgress: (j) => {
const line = `${j.state} ${j.progress.pct}%`;
if (line !== last) console.log((last = line));
},
});
if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error ?? ''}`);
last = ""
def on_progress(j) -> None:
nonlocal last
line = f"{j.state} {j.progress.pct}%"
if line != last:
last = line
print(line)
job = await vs.jobs.wait(recording_id, on_progress=on_progress)
if job.state != "done":
raise RuntimeError(f"transcription {job.state}: {job.error}")
Imajte na umu da wait ne baca grešku na failed; vraća posao i prepušta provjeru vama. Dvije sekunde odgovaraju razgovoru. Za trosatnu snimku ili seriju od stotinu, povećajte interval (intervalMs, interval_s) ili ga postupno produljujte dok posao radi: prva čitanja najviše vam govore, a kasnija uglavnom potvrđuju da je poslužitelj i dalje zauzet. Bez SDK-a istu petlju pišete ručno:
while :; do
JOB=$(curl -sSf "$API/v1/recordings/$REC/job" -H "$AUTH")
STATE=$(jq -r .state <<<"$JOB")
echo "$STATE $(jq -r .progress.pct <<<"$JOB")%"
case $STATE in
done) break ;;
failed) jq -r .error <<<"$JOB" >&2; exit 1 ;;
esac
sleep 2
done
Kad stanje glasi done, transkript je spreman.
GET /v1/recordings/{id}/transcript odgovara cijelim transkriptom u jednom dokumentu:
sentences: ono što se prikazuje ljudima. Svaka ima text, speaker, te start i end u sekundama.segments: linije motora, svaka s vremenima po riječima u words (svaka riječ ima text, start i end).speakers: jedan unos po broju govornika, s njegovim name.Govornici su numerirani od 0. Vrijednost speaker od -1 znači da su oznake govornika bile isključene za snimku. Dodajte ?lang=es da biste pročitali transkript preveden na španjolski; rečenice se vraćaju na tom jeziku.
const { sentences } = await vs.recordings.transcript(recordingId);
for (const s of sentences) console.log(s.speaker >= 0 ? `[speaker ${s.speaker}] ${s.text}` : s.text);
snapshot = await vs.recordings.transcript(recording_id)
for s in snapshot.sentences:
print(f"[speaker {s.speaker}] {s.text}" if s.speaker >= 0 else s.text)
curl -sSf "$API/v1/recordings/$REC/transcript" -H "$AUTH" \
| jq -r '.sentences[] | if .speaker >= 0 then "[speaker \(.speaker)] \(.text)" else .text end'
Dvije razine vremena služe dvama zadacima. Rečenice su jedinica čitanja, pa ih prikazuje titl ili stranica transkripta. Riječi su jedinica pokazivanja, pa istaknuto praćenje u stilu karaoke, pretraga ili izrezivanje isječka prolaze kroz segments[].words. Formati izvoza grade se od rečenica.
GET /v1/recordings/{id}/export vraća pravu datoteku. format je jedan od srt, json, xlsx ili pdf, a datoteka uključuje sve ispravke. Dodajte lang= za prevedenu datoteku: izvoz se tada čita na tom jeziku, jedan redak po rečenici, a svaka rečenica koja još nije prevedena prvo se prevodi.
SRT vrijedi prepoznati na prvi pogled, jer ga gotovo svaki player prihvaća i riječ je o običnom tekstu. Datoteka je popis titlova odvojenih praznim recima, a svaki titl je redni broj, raspon vremena i tekst. Vremenske oznake koriste zarez prije milisekundi, francusku naviku koju format nikad nije odbacio:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Jedna rečenica po titlu je prava granulacija: svaki se titl može pročitati za vrijeme koliko je na ekranu, a nijedan ne prekida misao usred rečenice. Kad je snimka imala oznake govornika, tekst svakog titla počinje imenom govornika i dvotočkom.
U TypeScript SDK-u vs.recordings.export vraća Response; njegove bajtove čitate s arrayBuffer(). U Pythonu vraća bytes.
import { writeFile } from 'node:fs/promises';
const srt = await vs.recordings.export(recordingId, 'srt');
await writeFile('talk.srt', Buffer.from(await srt.arrayBuffer()));
const pdf = await vs.recordings.export(recordingId, 'pdf', { lang: 'es' });
await writeFile('talk.es.pdf', Buffer.from(await pdf.arrayBuffer()));
srt = await vs.recordings.export(recording_id, "srt")
with open("talk.srt", "wb") as f:
f.write(srt)
pdf = await vs.recordings.export(recording_id, "pdf", lang="es")
with open("talk.es.pdf", "wb") as f:
f.write(pdf)
asyncio.run(main())
curl -sSf "$API/v1/recordings/$REC/export?format=srt" -H "$AUTH" -o talk.srt
curl -sSf "$API/v1/recordings/$REC/export?format=pdf&lang=es" -H "$AUTH" -o talk.es.pdf
Transkripcija je pohranjena, pa se pojavljuje pod Transcriptions na nadzornoj ploči, može se dijeliti poveznicom i može se ponovno čitati kad god poželite. To je osnovna petlja: stvori, čekaj, čitaj, izvezi. Ostatak ovog vodiča obrađuje varijacije: medij koji je već na internetu, pogreške na koje ćete naići i koliko datoteka košta.
Ako je medij već na internetu, API-ju dajete poveznicu i on sam dohvaća datoteku: javnu http(s) adresu (mp3, video, epizodu podcasta) ili YouTube video. Poziv je isti POST /v1/transcriptions, s JSON tijelom { "url": "..." } umjesto učitavanja multipart datoteke, a name i settings ostaju kakvi su bili. U SDK-ovima vs.transcriptions.createFromUrl(url) (TypeScript) i vs.transcriptions.create(url=...) (Python) šalju isti zahtjev.
Prije stvaranja bilo čega, GET /v1/transcriptions/probe?url= kaže vam što je poveznica. Njezin kind je jedan od tri:
file: snimka s krajem; transkribirajte je ovdje.live: YouTube live stream, beskonačan radijski stream ili HLS playlista bez kraja. Live poveznica nikad ne završava, pa je ne možete transkribirati kao datoteku; umjesto toga koristite je kao izvor za transkripciju uživo.unknown: poslužitelj nije mogao utvrditi što je.Kad se poveznica uopće ne otvara kao medij (na primjer web stranica), ok je false, a problem objašnjava razlog u jednoj rečenici. Probe je izborna i ograničena na 60 poziva u minuti.
Posao s poveznicom počinje u fetching prije nego što pređe u transcribing i done. Dok dohvaća, progress.phase kaže što poslužitelj radi: fetching dok stižu bajtovi (s fetchedBytes i fetchTotalBytes kad drugi kraj kaže koliko ih ima), ili extracting dok se zvuk vadi iz videa. progress.phasePct pokazuje koliko je posao odmakao u toj fazi. Dugi video traje neko vrijeme, pa anketirajte svakih nekoliko sekundi; za to vs.jobs.wait prima intervalMs (interval_s u Pythonu).
const probe = await vs.transcriptions.probe(url);
if (!probe.ok) throw new Error(`that link did not open: ${probe.problem}`);
if (probe.kind === 'live') throw new Error('a live link: record it with a live transcription instead');
const linkKey = crypto.randomUUID();
const tx = await vs.transcriptions.createFromUrl(url, { name: 'From a link' }, { idempotencyKey: linkKey });
const job = await vs.jobs.wait(tx.job!.recordingId, {
intervalMs: 3000,
onProgress: (j) => {
const p = j.progress;
if (j.state === 'fetching') console.log(`${p.phase ?? 'fetching'} ${p.phasePct ?? 0}%`);
else console.log(`${j.state} ${p.pct}%`);
},
});
Vodič Transkripcija poveznice u referenci sadrži cijeli program na oba jezika i u HTTP-u. Dohvaćanje s poveznica dodaje više načina da nešto pođe po zlu, pa evo pogrešaka koje ćete zaista vidjeti.
Svaki neuspjeli poziv vraća jedan JSON omotač. code je string po kojem se grana, a status HTTP status; message je za ljude, a requestId za podršku. Unutar data, retryable kaže može li ponovno slanje istog zahtjeva pomoći, a details sadrži sve što kod treba. SDK-ovi bacaju jedan tip greške, VocalStackError, s istim poljima.
Tri koda vrijedi obraditi već prvog dana.
402 insufficient_credit: kredit računa je potrošen, a ništa nije naplaćeno. details.balance je stanje u dolarima, a details.topUpUrl je mjesto gdje dodajete kredit. Kad je trošak bio poznat unaprijed, kao za datoteku poznate duljine, details.required kaže koliko bi iznosio.429 rate_limited: pričekajte Retry-After sekundi. Kad je details.metric creates_per_hour, dosegli ste dopuštenu stopu pokretanja (novih transkripcija po satu), a ne ograničenje rafala; limit i resetsAt kažu koliko je ograničenje i kada se oslobađa.503 unavailable: usluga je nakratko nedostupna. Pošaljite zahtjev ponovno nakon Retry-After.SDK-ovi ponavljaju 429 i 503 nakon Retry-After, dva puta po zadanom (maxRetries / max_retries na klijentu). 503 se, međutim, ponavlja samo za poziv koji je dvaput siguran za slanje: GET ili stvaranje s Idempotency-Key. Stvaranje bez njega prepušteno je vama, jer SDK ne može znati je li prvi pokušaj prošao. To je praktičan razlog zašto na svako učitavanje stavljate ključ.
Svaki odgovor također nosi RateLimit-Limit, RateLimit-Remaining i RateLimit-Reset, pa zauzeti pipeline može usporiti prije 429.
{
"code": "insufficient_credit",
"status": 402,
"message": "this account has no credit left; add credit to continue",
"data": {
"retryable": false,
"details": { "balance": -0.42, "required": 0.075, "currency": "usd", "plan": "payg", "topUpUrl": "https://…" }
},
"requestId": "01J…"
}
Cijela tablica kodova nalazi se u Pogreške, ponovni pokušaji i ograničenja. Taj 402 je jedina pogreška vezana uz novac, pa evo koliko datoteka košta.
Datoteke koštaju 0,20 $ po satu audia, naplaćeno po sekundi (0,0033 $ po minuti) uz minimum od 5 minuta: datoteka od 30 minuta košta 0,10 $. To je deset centi, manje od kave koju ćete popiti dok se traka napretka puni. Cijena je ista na svakom paketu i ista kroz API kao na nadzornoj ploči. Novi račun dobiva jednokratni kredit za registraciju od 5 $, a kartica nije potrebna.
Tri ograničenja paketa tiču se ovog vodiča.
GET /v1/realtime/capabilities prijavljuje kao limits.maxUploadBytes. Primjenjuje se manja od te dvije, a datoteka veća od nje odbija se s 413 prije bilo kakve transkripcije. Za dugu snimku ovo je još jedan razlog da učitavate komprimiranu mono datoteku umjesto WAV-a.GET /v1/billing u svakom trenutku prikazuje vaš paket, stanje i ograničenja; cijela tablica je na stranici s cijenama.
Dalje su na redu prijenos zvuka u stvarnom vremenu kad ne možete čekati da se datoteka dovrši ili povezivanje AI agenta na isti račun preko MCP servera, pa on transkribira i pretražuje umjesto vas, bez ijednog gore navedenog retka koda.