Nalaganje vrne opravilo, ne prepisa. Pri vsakem ustvarjanju pošljite Idempotency-Key, da ponovni poskus nikoli ne ustvari ali zaračuna drugega prepisa; nato preverjajte stanje opravila, dokler ni končano, in iz istega ID-ja preberite stavke, besede ali izvoz.
Prepis datoteke je najpreprostejša stvar, ki jo počne API VocalStack, in ima obliko vsakega asinhronega opravila: naložite, počakajte, preberite. Nalaganje takoj odgovori s prepisom in njegovim opravilom, vendar še brez besed. Medtem ko vaš program preverja opravilo, strežnik datoteko prepiše mnogokrat hitreje, kot se zvok predvaja. Ko je stanje done, prepis preberete kot stavke z govorci in časovnimi oznakami ali pa ga izvozite kot datoteko v enem od štirih formatov, bodisi v jeziku govora bodisi prevedenega.
Vse v tej obliki velja za katerikoli paketni API: ustvarjanje, ki vrne ID, stanje, ki ga preverjate, in rezultat, ki ga pridobite kasneje. Samo imena polj so značilna za VocalStack, ta vadnica pa jih razloži s SDK-jema za TypeScript in Python ter, kjer pomaga, s klici HTTP.
Odprite Developers → API keys v nadzorni plošči VocalStack in ustvarite ključ z dovoljenjema Read in Write. Ključ je prikazan samo enkrat; kopirajte ga v okoljsko spremenljivko:
export VOCALSTACK_API_KEY="<your API key>"
Ključ je namenjen strežnikom in skriptam. Vse, kar se naloži v brskalnik, nosi ključ s seboj, zato ga hranite stran od kode strani in od URL-jev, ki končajo v dnevnikih. Za pretakanje iz brskalnika vaš strežnik ustvari sejo v realnem času za brskalnik; to je druga vadnica.
Vsak klic se overi s ključem kot nosilnim žetonom (bearer token):
Authorization: Bearer vs_key_...
Ko je ključ v vašem okolju, potrebujete še odjemalca, ki ga pošlje.
Na voljo sta dva SDK-ja. Vsak ima enega odjemalca, VocalStack, z metodami, razvrščenimi po virih: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime in še nekaj drugih. SDK za TypeScript teče na Node 20 ali novejšem in v brskalnikih; SDK za Python temelji na asyncio in potrebuje Python 3.10 ali novejši.
SDK-ja nista na npm ali PyPI. Strežnik API ponuja gradnje za vsak račun. Ukazi za namestitev, prilagojeni vašemu okolju, so v razdelku »Install« hitrega začetka v referenci API, in pošiljajo ključ samo na gostitelja API. Če ne želite ničesar namestiti, spodnji klici HTTP potrebujejo le curl in jq.
Ne glede na to, kaj izberete, je prvi klic nalaganje.
POST /v1/transcriptions sprejme datoteko kot multipart/form-data: del file in neobvezen del options z JSON-om. Odgovori s 201 in prepisom. Njegov job.recordingId je ID posnetka, ustvarjenega iz datoteke, in vsak naslednji klic naslovite s tem ID-jem.
Za prvi zagon sta pomembni dve nastavitvi, obe znotraj options.settings:
spokenLanguage: koda, na primer en. Če jo izpustite, se jezik zazna samodejno. Če jo navedete, izboljša natančnost pri hrupnih posnetkih in močnih naglasih.speakerLabels: auto, on ali off. Pri on vsak stavek nosi številko govorca; pri auto se oznake pojavijo šele, ko je slišan drugi govorec.Večina avdio in video datotek je sprejetih: mp3, wav, flac, ogg, m4a, mp4 in drugi. Video obdrži le svoj zvok. Format na točnost skoraj ne vpliva, ker prepoznavanje govora deluje na 16 kHz mono in se vse ob sprejemu pretvori na to. 48 kHz stereo WAV ene osebe, ki govori, porabi večino bajtov za opisovanje tišine v visoki kakovosti. Prepiše se prav tako dobro kot 64 kbps mono MP3 ali Opus datoteka istega govora, vendar je približno dvajsetkrat večji, zato bo nalaganje trajalo dvajsetkrat dlje. Če pred nalaganjem pretvorite, sta vredni spremembe mono in zmeren bitni tok.
Pošljite Idempotency-Key pri vsakem nalaganju in razumite, zakaj. Časovna omejitev omrežja vam ne pove ničesar o tem, kaj je strežnik naredil: morda zahteve sploh ni videl ali pa je datoteko shranil in opravilo postavil v čakalno vrsto, preden je odgovor izgubljen. Slepi ponovni poskus tako tvega drugi prepis in drugo zaračunavanje. Ključ to prepreči. Ko se zahteva ponovi z istim ključem in telesom v 24 urah, strežnik vrne prvi odgovor namesto da bi kaj ustvaril, s čimer »vsaj enkrat« postane »učinkovito enkrat«. Pravilo je torej en ključ za eno logično nalaganje, ustvarjen pred prvim poskusom in ponovno uporabljen pri vsakem ponovitvi. Nov ključ pri vsakem poskusu bi pomenil, da je vsak ponovni poskus novo nalaganje, in glava ne bi varovala ničesar.
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 klic s curl:
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 pride, preden je prepisana ena sama beseda; dobite ID opravila, zato je naslednji korak čakanje nanj.
Opravilo gre iz stanja queued → transcribing → done ali se konča v failed s sporočilom error in, kadar je vzrok strojno berljiv, z errorCode. Četrto stanje, cropping, pomeni, da je opravilo zadržano, dokler nekdo ne izbere dela posnetka za prepis, kot to naredi korak obrezovanja v nadzorni plošči. Običajno nalaganje tega stanja nikoli ne doseže, čeprav se jobs.wait vrne tudi takrat, ko opravilo tja pride. Berite GET /v1/recordings/{id}/job, dokler stanje ni done ali failed.
Webhookov ni, in to je namerna odločitev. Webhook potrebuje javno dostopen HTTPS končni naslov na vaši strani, preverjanje podpisa, da ga nihče drug ne more klicati, in pravilnik ponovnih poskusov za primer, ko je vaš končni naslov nedosegljiv – veliko vodovoda za opravilo, ki traja minuto. Anketiranje tega ne potrebuje. Vaša skripta bo vsaki dve sekundi vprašala »Ali je že konec?«, kot otrok na zadnjem sedežu avtomobila, toda za razliko od otroka dobi nazaj odstotek.
Že odprta spletna stran lahko namesto tega sledi dogodkovni vtičnici prepisa (WS /v1/transcriptions/{id}/events, okvir job.progress ob napredovanju opravila). Za skripto je anketiranje pravo orodje.
SDK-ji anketirajo sami: vs.jobs.wait bere opravilo vsaki 2 sekundi in vsako branje poda v onProgress (v Pythonu on_progress). progress opravila vsebuje pct (0 do 100) in processedS, sekunde medija, prepisane do zdaj. Spodnja programa izpišeta vrstico samo tedaj, ko se spremeni stanje ali odstotek.
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}")
Upoštevajte, da wait pri failed ne sproži napake; vrne opravilo in preverjanje prepusti vam. Dve sekundi ustrezata predavanju. Pri tri ure dolgem posnetku ali seriji stotin datotek interval povečajte (intervalMs, interval_s) ali ga podaljšajte, ko opravilo teče: prva branja vam povedo največ, poznejša pa večinoma potrdijo, da je strežnik še zaposlen. Brez SDK-ja isto zanko napišete sami:
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
Ko stanje kaže done, je prepis pripravljen.
GET /v1/recordings/{id}/transcript odgovori s celotnim prepisom v enem dokumentu:
sentences: kar pokažete ljudem. Vsak ima text, speaker ter start in end v sekundah.segments: vrstice motorja, vsaka z časovnimi oznakami besed v words (vsaka beseda ima text, start in end).speakers: po en vnos za vsako številko govorca, z njegovim name.Govorci so oštevilčeni od 0. speaker z vrednostjo -1 pomeni, da so bile oznake govorcev za posnetek izklopljene. Dodajte ?lang=es, če želite prepis v španščini; stavki se vrnejo v tem 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'
Obe ravni časovnih oznak služita dvema nalogama. Stavki so enota branja, zato jih prikaže podnapis ali stran s prepisom. Besede so enota kazanja, zato karaoke poudarjanje, iskalni zadetek ali rezanje posnetka hodi po segments[].words. Izvozni formati so zgrajeni iz stavkov.
GET /v1/recordings/{id}/export vrne pravo datoteko. format je eden od srt, json, xlsx ali pdf, datoteka pa vključuje morebitne popravke. Dodajte lang= za prevedeno datoteko: izvoz nato bere v tem jeziku, po ena vrstica na stavek, vsak še neprevedeni stavek pa se najprej prevede.
SRT je vredno poznati na pogled, ker je format podnapisov, ki ga skoraj vsak predvajalnik sprejme, in je navadno besedilo. Datoteka je seznam podnapisnih blokov (cues), ločenih s praznimi vrsticami, vsak blok pa je zaporedna številka, časovni razpon in besedilo. Časovne oznake uporabljajo vejico pred milisekundami, francosko navado, ki je format nikoli ni opustil:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
En stavek na podnapisni blok je prava zrnatost: vsak blok je mogoče prebrati v času, ko je na zaslonu, in nobeden se ne konča sredi misli. Če je imel posnetek oznake govorcev, se besedilu vsakega bloka pojavi ime govorca in dvopičje.
V SDK-ju za TypeScript vs.recordings.export vrne Response; njegove bajte preberete z arrayBuffer(). V Pythonu vrne 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
Prepis je shranjen, zato se prikaže pod Transcriptions v nadzorni plošči, lahko ga delite s povezavo in ga kadar koli pozneje znova preberete. To je jedro: ustvari, počakaj, preberi, izvozi. Preostanek vadnice pokriva variacije: medij, ki je že na spletu, napake, na katere boste naleteli, in koliko stane datoteka.
Če je medij že na spletu, API dobi povezavo in datoteko pridobi sam: javni naslov http(s) (mp3, video, epizoda podcasta) ali video na YouTubu. Klic je enak POST /v1/transcriptions, s telesom JSON { "url": "..." } namesto večdelnega nalaganja, name in settings pa ostaneta enaka. V SDK-jih vs.transcriptions.createFromUrl(url) (TypeScript) in vs.transcriptions.create(url=...) (Python) pošljeta isto zahtevo.
Preden kaj ustvarite, vam GET /v1/transcriptions/probe?url= pove, kaj je povezava. Njen kind je eden od treh:
file: posnetek s koncem; tukaj ga prepišite.live: pretok v živo na YouTubu, pretok radia brez konca ali seznam HLS brez konca. Povezava v živo se nikoli ne konča, zato je ni mogoče prepisati kot datoteke; namesto tega jo uporabite kot vir prepisa v živo.unknown: strežnik ni mogel ugotoviti.Če se povezava sploh ne odpre kot medij (na primer spletna stran), je ok enak false, problem pa z enim stavkom pojasni razlog. Preverjanje je neobvezno in omejeno na 60 klicev na minuto.
Povezavno opravilo se začne v fetching, preden preide v transcribing in done. Med pridobivanjem progress.phase pove, kaj strežnik počne: fetching, medtem ko prihajajo bajti (z fetchedBytes in fetchTotalBytes, če drug konec pove, koliko jih je), ali extracting, medtem ko se zvok izvleče iz videa. progress.phasePct pove, kako daleč je opravilo v tej fazi. Dolg video vzame nekaj časa, zato anketirajte vsakih nekaj sekund; za to vs.jobs.wait sprejme intervalMs (interval_s v 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}%`);
},
});
Vodnik Transcribe a link v referenci ima celoten program v obeh jezikih in v HTTP. Pridobivanje iz povezav doda še več načinov za napako, zato so tukaj napake, ki jih boste res videli.
Vsak neuspeli klic odgovori z enim JSON ovojem. code je niz, po katerem se razvejite, status pa je statusna koda HTTP; message je namenjen ljudem, requestId pa podpori. Pod data pove retryable, ali lahko pošiljanje iste zahteve znova pomaga, details pa vsebuje, kar koda potrebuje. SDK-ji vse to zavijejo v en tip napake, VocalStackError, z istimi polji.
Tri kode so vredne obravnave že prvi dan.
402 insufficient_credit: dobroimetje na računu je pošlo, nič pa ni bilo zaračunano. details.balance je stanje v dolarjih, details.topUpUrl pa je naslov, kjer dodate dobroimetje. Kadar je bil strošek znan vnaprej, kot pri datoteki znane dolžine, details.required pove, koliko bi bil.429 rate_limited: počakajte Retry-After sekund. Če je details.metric enak creates_per_hour, ste dosegli hitrost ustvarjanja paketa (novi prepisi na uro), ne omejitve sunkov; limit in resetsAt povesta, kakšna je omejitev in kdaj se sprosti.503 unavailable: storitev je za kratek čas nedosegljiva. Pošljite zahtevo znova po Retry-After.SDK-ji ponovijo 429 in 503 po Retry-After, privzeto dvakrat (maxRetries / max_retries na odjemalcu). 503 pa se ponovi samo pri klicu, ki ga je varno poslati dvakrat: GET ali ustvarjanje z Idempotency-Key. Ustvarjanje brez njega prepustijo vam, ker SDK ne more vedeti, ali je prvi poskus uspel. To je praktičen razlog, da na vsako nalaganje damo ključ.
Vsak odgovor prav tako nosi RateLimit-Limit, RateLimit-Remaining in RateLimit-Reset, da lahko zaposlen cevovod upočasni, preden pride do 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…"
}
Celotna tabela kod je v Errors, retries and limits. Ta 402 je edina napaka, povezana z denarjem, zato je tukaj, koliko stane datoteka.
Datoteke stanejo $0.20 na uro zvoka, zaračunane na sekundo ($0.0033 na minuto), z najmanj 5 minutami: 30-minutna datoteka stane $0.10. To je deset centov, manj kot kava, ki jo popijete, medtem ko se napredovalna vrstica polni. Cena je enaka na vseh paketih in prek API-ja kot v nadzorni plošči. Nov račun začne z enkratnim registracijskim dobroimetjem v višini $5, kartica ni potrebna.
Tri omejitve paketa se dotikajo te vadnice.
GET /v1/realtime/capabilities prikaže kot limits.maxUploadBytes. Velja manjša od obeh, datoteka čez njo pa je zavrnjena s 413, preden se karkoli prepiše. Pri dolgem posnetku je to še en razlog, da naložite stisnjeno mono datoteko namesto WAV.GET /v1/billing kadar koli pove vaš paket, stanje dobroimetja in omejitve; celotna tabela je na ceniku.
Naslednji koraki so od tu naprej pretakanje zvoka v realnem času, kadar ne morete čakati, da se datoteka konča, ali povezovanje AI agenta z istim računom prek strežnika MCP, da prepisuje in išče namesto vas, brez vse kode zgoraj.