Augšupielāde atgriež darbu, nevis transkriptu. Pievienojiet katrai izveidei Idempotency-Key, lai atkārtots mēģinājums nekad neizveidotu un neiekasētu otru transkripciju; pēc tam gaidiet darba pabeigšanu un lasiet teikumus, vārdus vai eksportu no tā paša identifikatora.
Failu transkripcija ir vienkāršākā lieta, ko VocalStack API dara, un tai ir jebkura asinhronā darba forma: augšupielāde, gaidīšana, lasīšana. Augšupielāde uzreiz atbild ar transkripciju un tās darbu, bet vēl bez vārdiem. Kamēr jūsu programma aptauja darbu, serveris failu transkribē vairākkārt ātrāk, nekā audio tiek atskaņots. Kad stāvoklis ir done, transkriptu lasāt kā teikumus ar runātājiem un laikiem vai eksportējat kā failu vienā no četriem formātiem – vai nu runātajā valodā, vai tulkotu.
Ikviena šāda forma attiecas uz jebkuru partiju API: izveide, kas atgriež identifikatoru, stāvoklis, ko aptaujājat, rezultāts, ko pēc tam iegūstat. Tikai lauku nosaukumi ir specifiski VocalStack, un šajā pamācībā tie aplūkoti ar TypeScript un Python SDK, kā arī ar HTTP izsaukumiem, kur tie palīdz.
Atveriet Developers → API keys vadības panelī un izveidojiet atslēgu ar Read un Write atļaujām. Atslēga tiek parādīta tikai vienreiz; ievietojiet to vides mainīgajā:
export VOCALSTACK_API_KEY="<your API key>"
Atslēga paredzēta serveriem un skriptiem. Viss, kas tiek piegādāts pārlūkprogrammai, piegādā arī atslēgu, tāpēc turiet to ārpus lapas koda un URL, jo URL nonāk žurnālos. Lai straumētu no pārlūkprogrammas, jūsu serveris izveido reāllaika sesiju; tas ir cits pamācības raksts.
Katrs izsaukums autentificējas ar atslēgu kā nesēja pilnvaru (bearer token):
Authorization: Bearer vs_key_...
Kad atslēga ir vides mainīgajā, nākamais solis ir klients, kas to nosūta.
Ir divi SDK. Katram ir viens klients, VocalStack, ar metodēm, kas grupētas pēc resursa: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime un vēl dažas. TypeScript SDK darbojas ar Node 20 vai jaunāku versiju un pārlūkprogrammās; Python SDK balstās uz asyncio un prasa Python 3.10 vai jaunāku versiju.
SDK nav npm vai PyPI. API piegādā versijas ikvienam kontam. Instalēšanas komandas, aizpildītas jūsu videi, atrodamas ātrajā sākšanā API atsauksmē sadaļā Install, un tās nosūta jūsu atslēgu tikai API serverim. Ja nevēlaties neko instalēt, tālāk aprakstītajiem HTTP izsaukumiem pietiek ar curl un jq.
Lai kā izvēlaties, pirmais izsaukums ir augšupielāde.
POST /v1/transcriptions pieņem failu kā multipart/form-data: daļu file un papildu daļu options ar JSON. Tas atbild ar 201 un transkripciju. Tā job.recordingId ir no faila izveidotā ieraksta identifikators, un visi turpmākie izsaukumi adresējas ar šo identifikatoru.
Pirmajai palaišanai svarīgi ir divi iestatījumi, abi options.settings iekšienē:
spokenLanguage: kods, piemēram, en. Ja to neliekat, valoda tiek noteikta automātiski. Norādot to, uzlabojas precizitāte trokšņainos ierakstos un spēcīgos akcentos.speakerLabels: auto, on vai off. Ar on katrs teikums saņem runātāja numuru; ar auto etiķetes parādās tikai pēc tam, kad dzirdēts otrs runātājs.Lielākā daļa audio un video failu tiek pieņemti: mp3, wav, flac, ogg, m4a, mp4 un citi. Video saglabā tikai audio. Formātam ir niecīga nozīme precizitātei, jo runas atpazīšana darbojas ar 16 kHz monorežīmu, un viss pēc saņemšanas tiek pārkodēts uz to. 48 kHz stereo WAV ar vienas personas runu lielāko daļu baitu tērē augstas kvalitātes klusuma aprakstam. Tas tiek transkribēts tieši tikpat labi kā tās pašas runas 64 kbps monorežīma MP3 vai Opus fails, bet tas ir apmēram divdesmit reizes lielāks, tātad augšupielāde aizņem arī divdesmit reizes ilgāk. Ja pārvēršat pirms augšupielādes, vērts mainīt divus iestatījumus: monorežīmu un mērenu bitu ātrumu.
Nosūtiet Idempotency-Key ar katru augšupielādi un saprotiet, kāpēc. Tīkla noildze neko nepastāsta par to, ko serveris izdarīja: tas varēja vispār nesaņemt pieprasījumu vai arī saglabāt failu un ieradināt darbu, pirms atbilde pazuda. Akla atkārtošana tādēļ riskē radīt otru transkripciju un otru maksu. Atslēga to novērš: kad pieprasījums tiek atkārtots ar to pašu atslēgu un tādu pašu saturu 24 stundu laikā, serveris atgriež pirmo atbildi, nevis izveido ko jaunu, un tā pārvērš “vismaz vienreiz” par “praktiski tieši vienreiz”. Tātad noteikums ir viena atslēga vienai loģiskai augšupielādei, izveidota pirms pirmā mēģinājuma un izmantota katrā atkārtojumā. Jauna atslēga katram mēģinājumam padarītu katru atkārtojumu par jaunu augšupielādi, un galvene nepasargātu nevienu.
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
Tas pats izsaukums ar 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)
201 atgriežas, pirms transkribēts ir neviens vārds; rokās ir darba identifikators, tāpēc nākamais solis ir gaidīt to.
Darbs pāriet no queued → transcribing → done vai beidzas ar failed, kam seko error ziņojums un, ja cēloni var nolasīt mašīna, errorCode. Ceturtais stāvoklis, cropping, nozīmē, ka darbs ir apturēts, līdz kāds izvēlas, kuru ieraksta daļu transkribēt, kā to dara paneļa apgriešanas solis. Parasta augšupielāde nekad neiekļūst šajā stāvoklī, tomēr jobs.wait atgriežas arī tad, kad darbs tajā nonāk. Lasiet GET /v1/recordings/{id}/job, līdz stāvoklis ir done vai failed.
Webhook nav, un tas ir apzināts kompromiss. Webhook prasa publisku HTTPS galapunktu jūsu pusē, paraksta pārbaudi, lai neviens cits to nevarētu izsaukt, un atkārtošanas politiku gadījumam, kad jūsu galapunkts nedarbojas – tas ir daudz papildu darba darbam, kas aizņem minūti. Aptauja tam nekā nevajag. Jūsu skripts jautās “vai jau gatavs?” ik pēc divām sekundēm kā bērns automašīnas aizmugurējā sēdeklī, bet atšķirībā no bērna tas saņem atpakaļ procentus.
Jau atvērta tīmekļa lapa var sekot transkripcijas notikumu ligzdai (WS /v1/transcriptions/{id}/events, job.progress kadrs, kad darbs virzās uz priekšu). Skriptam labākais rīks ir aptauja.
SDK veic aptauju: vs.jobs.wait nolasa darbu ik pēc 2 sekundēm un katru nolasījumu nodod onProgress (Python – on_progress). Darba progress satur pct (no 0 līdz 100) un processedS, transkribēto mediju sekundes līdz šim. Tālāk esošās programmas izdrukā rindu tikai tad, kad mainās stāvoklis vai procentuālā vērtība.
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}")
Ņemiet vērā, ka wait neizmet kļūdu pie failed; tas atgriež darbu un atstāj pārbaudi jums. Divas sekundes atbilst runai. Trīs stundu ierakstam vai simtiem failu partijai palieliniet intervālu (intervalMs, interval_s) vai palēniniet to, kad darbs turpinās: pirmie nolasījumi pasaka visvairāk, bet vēlākie galvenokārt apliecina, ka serveris joprojām ir aizņemts. Bez SDK šo pašu ciklu rakstāt ar rokām:
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 stāvoklis rāda done, transkripts ir gatavs.
GET /v1/recordings/{id}/transcript atbild ar visu transkriptu vienā dokumentā:
sentences: tas, ko rādīt cilvēkiem. Katram ir text, speaker, un start un end sekundēs.segments: dzinēja rindas, katra ar vārdu līmeņa laikiem words (katram vārdam ir text, start un end).speakers: viens ieraksts katram runātāja numuram ar tā name.Runātāji tiek numurēti no 0. speaker vērtība -1 nozīmē, ka ierakstam runātāju etiķetes bija izslēgtas. Pievienojiet ?lang=es, lai lasītu transkriptu, tulkotu spāņu valodā; teikumi atgriezīsies šajā valodā.
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'
Diviem laika līmeņiem ir divi uzdevumi. Teikumi ir lasīšanas vienība, tāpēc tos rāda subtitri vai transkripta lapa. Vārdi ir norādīšanas vienība, tāpēc izcelšana stilā karaoke, meklēšanas rezultāts vai klipa griešana iet cauri segments[].words. Eksporta formāti veidoti no teikumiem.
GET /v1/recordings/{id}/export atgriež īstu failu. format ir viens no srt, json, xlsx vai pdf, un fails iekļauj visas korekcijas. Pievienojiet lang= tulkotam failam: tad eksports ir šajā valodā, pa vienai rindai katram teikumam, un katrs vēl nepārtulkots teikums vispirms tiek pārtulkots.
SRT ir vērts pazīt pēc izskata, jo tas ir subtitru formāts, ko gandrīz katrs atskaņotājs pieņem, un tas ir vienkāršs teksts. Fails ir titulu saraksts, ko atdala tukšas rindas, un katrs titulu bloks ir numurs, laika intervāls un teksts. Laika zīmogi pirms milisekundēm lieto komatu – franču paradums, no kura formāts nekad neatbrīvojās:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Viens teikums vienā titulā ir pareizais graudainums: katru titulu var izlasīt laikā, kamēr tas ir ekrānā, un neviens nepārtrūkst vidū domai. Ja ierakstam bija runātāju etiķetes, katra titula teksts sākas ar runātāja vārdu un kolu.
TypeScript SDK vs.recordings.export atgriež Response; izlasiet tā baitus ar arrayBuffer(). Python tas atgriež 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 tiek saglabāta, tāpēc tā parādās vadības panelī sadaļā Transcriptions, var tikt kopīgota ar saiti un var tikt lasīta atkal jebkurā laikā vēlāk. Tas ir pamata cikls: izveidot, gaidīt, lasīt, eksportēt. Pārējā pamācība aplūko variācijas: jau tiešsaistē esošus medijus, kļūdas, ar kurām sastapsieties, un to, ko maksā fails.
Ja mediji jau ir tiešsaistē, nododiet API saiti, un tas pats faila iegūs: publisku http(s) adresi (mp3, video, podkāsta epizode) vai YouTube video. Izsaukums ir tāds pats POST /v1/transcriptions, ar JSON ķermeni { "url": "..." } multipart augšupielādes vietā, un name un settings kā iepriekš. SDK vs.transcriptions.createFromUrl(url) (TypeScript) un vs.transcriptions.create(url=...) (Python) veic to pašu pieprasījumu.
Pirms ko izveidot, GET /v1/transcriptions/probe?url= pastāsta, kas ir saite. Tās kind ir viens no trim:
file: ieraksts ar beigām; transkribējiet to šeit.live: YouTube tiešraide, bezgalīga radio straume vai HLS saraksts bez gala. Tiešraides saite nekad nebeidzas, tāpēc to nevar transkribēt kā failu; tā jāizmanto kā tiešraides transkripcijas avots.unknown: serveris nevarēja noteikt.Ja saite neatveras kā mediji vispār (piemēram, tīmekļa lapa), ok ir false, un problem izskaidro iemeslu vienā teikumā. Pārbaude ir neobligāta un ierobežota līdz 60 izsaukumiem minūtē.
Saites darbs sākas fetching, pirms pāriet uz transcribing un done. Kamēr notiek iegūšana, progress.phase saka, ko serveris dara: fetching, kamēr atnāk baiti (ar fetchedBytes un fetchTotalBytes, ja otra puse saka, cik ir), vai extracting, kamēr audio tiek izņemts no videoklipa. progress.phasePct rāda, cik tālu ir šī fāze. Garš video prasa laiku, tāpēc aptaujājiet ik pēc dažām sekundēm; vs.jobs.wait tam pieņem intervalMs (Python – interval_s).
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}%`);
},
});
Atsauces Transcribe a link ceļvedis satur pilnu programmu abās valodās un HTTP. Saišu iegūšana pievieno vēl kļūdu veidus, tāpēc šeit ir tās kļūdas, ar kurām patiešām sastapsieties.
Katrs neveiksmīgs izsaukums atbild ar vienu JSON aploksni. code ir virkne, pēc kuras atzarot, un status ir HTTP statuss; message ir cilvēkiem, requestId atbalstam. Zem data retryable saka, vai tā paša pieprasījuma nosūtīšana atkārtoti var palīdzēt, un details satur to, ko kods vajag. SDK met vienu kļūdas tipu, VocalStackError, ar tiem pašiem laukiem.
Trīs kodi ir vērts apstrādāt jau pirmajā dienā.
402 insufficient_credit: konta kredīts ir beidzies, un nekas nav iekasēts. details.balance ir atlikums dolāros, un details.topUpUrl ir vieta, kur pievienot kredītu. Kad izmaksa bija zināma iepriekš, kā tas ir failam ar zināmu garumu, details.required saka, cik tā būtu bijusi.429 rate_limited: nogaidiet Retry-After sekundes. Kad details.metric ir creates_per_hour, esat sasniedzis plāna izveides ātrumu (jaunas transkripcijas stundā), nevis uzliesmojuma limitu; limit un resetsAt saka, kāds ir ierobežojums un kad tas atbrīvojas.503 unavailable: pakalpojums uz brīdi nav pieejams. Nosūtiet pieprasījumu vēlreiz pēc Retry-After.SDK atkārto 429 un 503 pēc Retry-After, pēc noklusējuma divas reizes (maxRetries / max_retries klientā). 503 gan tiek atkārtots tikai izsaukumam, ko drošī nosūtīt divreiz: GET vai izveidei ar Idempotency-Key. Izveide bez tās paliek jums, jo SDK nevar zināt, vai pirmais mēģinājums izdevās. Tieši tāpēc ir praktiski jāliek atslēga katrai augšupielādei.
Katrā atbildē ir arī RateLimit-Limit, RateLimit-Remaining un RateLimit-Reset, lai aizņemta plūsma varētu palēnināties pirms 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…"
}
Pilna kodu tabula ir Kļūdas, atkārtojumi un limiti. Šī 402 kļūda ir vienīgā, kas saistīta ar naudu, tāpēc šeit ir, cik maksā fails.
Faili maksā $0.20 par stundu audio, norēķinot pa sekundēm ($0.0033 minūtē) ar 5 minūšu minimumu: 30 minūšu fails maksā $0.10. Tas ir desmit centi, mazāk nekā kafija, ko izdzersiet, kamēr progresa josla piepildās. Likme ir tāda pati katrā plānā un API tāpat kā vadības panelī. Jaunam kontam sākumā ir vienreizējs $5 reģistrācijas kredīts, un karte nav vajadzīga.
Trīs plāna ierobežojumi skar šo pamācību.
GET /v1/realtime/capabilities rāda kā limits.maxUploadBytes. Piemēro mazāko no abiem, un fails, kas tos pārsniedz, tiek noraidīts ar 413, pirms kas tiek transkribēts. Garam ierakstam tas ir vēl viens iemesls augšupielādēt saspiestu monofailu, nevis WAV.GET /v1/billing jebkurā laikā parāda jūsu plānu, atlikumu un limitus; pilna tabula ir cenu lapā.
No šejienes nākamie soļi ir audio straumēšana reāllaikā, ja nevarat gaidīt, kamēr fails beidzas, vai AI aģenta pievienošana tam pašam kontam caur MCP serveri, lai tas transkribētu un meklētu jūsu vietā bez jebkura no iepriekšējā koda.