Upakiaji hukurudishia kazi, si nakala. Weka Idempotency-Key kwenye kila ombi la kuunda ili jaribio la pili lisiwahi kuunda au kutoza unukuzi wa pili; kisha uulize kazi hadi ikamilike, na usome sentensi, maneno au hamisho kutoka kitambulisho kile kile.
Unukuzi wa faili ndio kitu rahisi zaidi ambacho API ya VocalStack hufanya, nao una umbo la kazi yoyote isiyo ya kusawazisha: pakia, subiri, soma. Upakiaji hujibu mara moja kwa unukuzi na kazi yake, lakini bado hakuna maneno. Programu yako inapouliza hali ya kazi, seva hunukuu faili mara nyingi zaidi kuliko sauti inavyocheza. Hali ikiwa done, unasoma nakala kama sentensi zenye wazungumzaji na muda, au unahamisha faili katika moja ya miundo minne, ama kwa lugha inayozungumzwa au kwa tafsiri.
Kila kitu katika umbo hili kinatumika kwa API yoyote ya bechi: kuunda kinachorudisha kitambulisho, hali unayoiuliza, na matokeo unayoyachukua baadaye. Majina ya sehemu pekee ni mahususi kwa VocalStack, na somo hili linayapitia kwa kutumia SDK za TypeScript na Python, pamoja na miito ya HTTP pale inaposaidia.
Fungua Developers → API keys kwenye Dashibodi na uunde ufunguo wenye ruhusa za Read na Write. Ufunguo unaonyeshwa mara moja tu; Nakili kwenye kigezo cha mazingira:
export VOCALSTACK_API_KEY="<your API key>"
Ufunguo ni kwa seva na skripti. Kila kitu kinachopelekwa kwenye kivinjari kinapeleka ufunguo nacho, kwa hiyo uweke nje ya msimbo wa ukurasa na nje ya URL, ambazo huishia kwenye kumbukumbu. Ili kutiririsha kutoka kivinjari, seva yako huunda kipindi cha wakati halisi kwa ajili yake; hilo ni somo lingine.
Kila wito unathibitishwa kwa ufunguo kama bearer token:
Authorization: Bearer vs_key_...
Ufunguo ukiwa kwenye mazingira yako, hatua inayofuata ni mteja wa kuutuma.
Kuna SDK mbili. Kila moja ina mteja mmoja, VocalStack, na mbinu zake zimepangwa kwa rasilimali: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime na nyingine chache. SDK ya TypeScript inafanya kazi kwenye Node 20 au mpya zaidi na kwenye vivinjari; SDK ya Python imejengwa juu ya asyncio na inahitaji Python 3.10 au mpya zaidi.
SDK hazipo kwenye npm wala PyPI. API inatoa matoleo kwa kila akaunti. Amri za usakinishaji, zilizojazwa kwa mazingira yako, ziko kwenye sehemu ya Install ya mwongozo wa haraka katika marejeo ya API, na zinatuma ufunguo wako kwa mwenyeji wa API pekee. Kama unapendelea kutosakinisha chochote, miito ya HTTP iliyo hapa chini inahitaji curl na jq pekee.
Chochote ulichochagua, wito wa kwanza ni upakiaji.
POST /v1/transcriptions hupokea faili kama multipart/form-data: sehemu ya file, pamoja na sehemu ya hiari ya options yenye JSON. Hujibu 201 pamoja na unukuzi. job.recordingId yake ni kitambulisho cha rekodi iliyotengenezwa kutoka kwenye faili, na kila wito unaofuata unaelekezwa kwa kitambulisho hicho.
Mipangilio miwili ina umuhimu katika jaribio la kwanza, zote ndani ya options.settings:
spokenLanguage: msimbo kama en. Usipoliweka, lugha hutambuliwa yenyewe. Kulitaja kunaboresha usahihi kwenye rekodi zenye kelele na lafudhi nzito.speakerLabels: auto, on au off. Kwa on, kila sentensi hubeba namba ya mzungumzaji; kwa auto, lebo huonekana tu baada ya mzungumzaji wa pili kusikika.Faili nyingi za sauti na video zinakubaliwa: mp3, wav, flac, ogg, m4a, mp4 na nyinginezo. Video huhifadhi sauti yake pekee. Umbizo halina athari kubwa kwenye usahihi, kwa sababu utambuzi wa usemi hufanya kazi kwa mono ya 16 kHz, na kila kitu hubadilishwa kuwa hiyo mara tu kinapofika. WAV ya stereo ya 48 kHz ya mtu mmoja anayezungumza hutumia baiti zake nyingi kuelezea ukimya kwa ubora wa juu. Inanukuliwa kwa usahihi sawa na faili ya MP3 ya mono ya 64 kbps au Opus ya hotuba hiyo hiyo, lakini ni takriban mara ishirini kubwa zaidi, kwa hiyo inachukua mara ishirini zaidi kupakia. Ukibadilisha kabla ya kupakia, mono na kiwango cha biti cha wastani ndizo mipangilio miwili yenye thamani ya kubadilishwa.
Tuma Idempotency-Key kwa kila upakiaji, na elewa kwa nini. Muda wa mtandao ukiisha hakukuambii chochote kuhusu alichofanya seva: huenda haijawahi kuona ombi, au huenda ilihifadhi faili na kupanga kazi kabla jibu halijafika. Kujaribu tena bila kufikiri kwa hiyo kunahatarisha unukuzi wa pili na malipo ya pili. Ufunguo huo unarekebisha hilo. Ombi likirudiwa kwa ufunguo na mwili uliofanana ndani ya saa 24, seva hurudisha jibu la kwanza badala ya kuunda kitu chochote, jambo linalogeuza "angalau mara moja" kuwa "kwa ufanisi mara moja". Kwa hiyo kanuni ni ufunguo mmoja kwa kila upakiaji wa kimantiki, uliozalishwa kabla ya jaribio la kwanza na kutumika tena kwenye kila jaribio. Ufunguo mpya kwa kila jaribio ungefanya kila jaribio kuwa upakiaji mpya, na kichwa hicho kisingelinda chochote.
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
Wito uleule kwa 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 inarudi kabla neno moja halijanukuliwa; ulichonacho ni kitambulisho cha kazi, kwa hiyo hatua inayofuata ni kusubiri kazi hiyo.
Kazi huhama kutoka queued → transcribing → done, au huishia failed ikiwa na ujumbe wa error na, sababu inapoweza kusomwa na mashine, errorCode. Hali ya nne, cropping, inamaanisha kazi imesimamishwa hadi mtu achague sehemu ya rekodi ya kunukuu, kama hatua ya kupunguza ya Dashibodi inavyofanya. Upakiaji wa kawaida haufiki huko kamwe, ingawa jobs.wait pia hurudi kazi inapofika huko. Soma GET /v1/recordings/{id}/job hadi hali iwe done au failed.
Hakuna webhooks, na hiyo ni biashara ya makusudi. Webhook inahitaji ncha ya HTTPS ya umma upande wako, ukaguzi wa sahihi ili mtu mwingine asiweze kuiita, na sera ya kujaribu tena endapo ncha yako imezimika, ambayo ni mabomba mengi kwa kazi inayochukua dakika moja. Kuuliza hakuhitaji lolote kati ya hayo. Skripti yako itauliza "je, imekamilika?" kila sekunde mbili, kama mtoto kwenye kiti cha nyuma, na tofauti na mtoto, itapata asilimia kama jibu.
Ukurasa wa wavuti ambao tayari uko wazi unaweza kufuata badala yake soketi ya matukio ya unukuzi (WS /v1/transcriptions/{id}/events, fremu ya job.progress kazi inavyoendelea). Kwa skripti, kuuliza ndicho chombo sahihi.
SDK ndizo zinazofanya kuuliza: vs.jobs.wait husoma kazi kila sekunde 2 na kupeleka kila usomaji kwa onProgress (on_progress kwenye Python). progress ya kazi hubeba pct (0 hadi 100) na processedS, sekunde za media zilizonukuliwa hadi sasa. Programu zilizo hapa chini huchapisha mstari pale tu hali au asilimia inapobadilika.
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}")
Kumbuka kwamba wait haitupi hitilafu kwenye failed; hurudisha kazi na kukuachia ukaguzi. Sekunde mbili zinafaa kwa hotuba. Kwa rekodi ya saa tatu, au bechi ya mamia, ongeza muda kati ya maswali (intervalMs, interval_s) au ongeza muda hatua kwa hatua kadiri kazi inavyoendelea: usomaji wa kwanza ndio unaokueleza mengi, na zile za baadaye zaidi zinathibitisha kwamba seva bado ina shughuli nyingi. Bila SDK, andika kitanzi hicho hicho kwa mkono:
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
Hali ikisomeka done, nakala iko tayari.
GET /v1/recordings/{id}/transcript hujibu kwa nakala nzima katika hati moja:
sentences: kile cha kuwaonyesha watu. Kila moja ina text, speaker, na start na end kwa sekunde.segments: mistari ya injini, kila moja ikiwa na muda wa kila neno katika words (kila neno lina text, start na end).speakers: ingizo moja kwa kila namba ya mzungumzaji, pamoja na name yake.Wazungumzaji wananumbuliwa kuanzia 0. speaker ya -1 inamaanisha lebo za wazungumzaji zilikuwa zimezimwa kwa rekodi hiyo. Ongeza ?lang=es ili kusoma nakala iliyotafsiriwa kwa Kihispania; sentensi zinarudi kwa lugha hiyo.
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'
Ngazi mbili za muda zinatumika kwa kazi mbili. Sentensi ndicho kitengo cha kusoma, kwa hiyo ndicho ambacho manukuu au ukurasa wa nakala huonyesha. Maneno ndicho kitengo cha kuelekeza, kwa hiyo mwangaza wa mtindo wa karaoke, matokeo ya utafutaji au ukataji wa klipu hupitia segments[].words badala yake. Miundo ya hamisho imejengwa kutoka kwenye sentensi.
GET /v1/recordings/{id}/export hurudisha faili halisi. format ni mojawapo ya srt, json, xlsx au pdf, na faili inajumuisha masahihisho yoyote. Ongeza lang= kwa faili iliyotafsiriwa: hamisho basi husomeka kwa lugha hiyo, mstari mmoja kwa kila sentensi, na sentensi yoyote ambayo bado haijatafsiriwa hutafsiriwa kwanza.
SRT inafaa kuifahamu kwa macho, kwa sababu ni umbizo la manukuu ambalo karibu kila kicheza-media hukubali, na ni maandishi matupu. Faili ni orodha ya vipengele vilivyotenganishwa na mistari tupu, na kila kipengele ni kihesabu, kipindi cha muda na maandishi. Alama za muda hutumia koma kabla ya milisekunde, mazoea ya Kifaransa ambayo umbizo hilo halikuwahi kuyaacha:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Sentensi moja kwa kila kipengele ndiyo ukubwa sahihi: kila kipengele kinaweza kusomwa ndani ya muda wake kwenye skrini, na hakuna kinachokatika katikati ya wazo. Rekodi ikiwa na lebo za wazungumzaji, maandishi ya kila kipengele huanza kwa jina la mzungumzaji na koloni.
Kwenye SDK ya TypeScript, vs.recordings.export hurudisha Response; soma baiti zake kwa arrayBuffer(). Kwenye Python hurudisha 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
Unukuzi umehifadhiwa, kwa hiyo unaonekana chini ya Transcriptions kwenye Dashibodi, unaweza kushirikiwa kwa kiungo, na unaweza kusomwa tena wakati wowote baadaye. Huo ndio mzunguko mkuu: unda, subiri, soma, hamisha. Sehemu iliyobaki ya somo hili inashughulikia tofauti: media iliyo mtandaoni tayari, hitilafu utakazokutana nazo, na gharama ya faili.
Ikiwa media iko mtandaoni tayari, mpe API kiungo na yenyewe itachukua faili: anwani ya umma ya http(s) (mp3, video, kipindi cha podikasti) au video ya YouTube. Wito ni uleule POST /v1/transcriptions, ukiwa na mwili wa JSON { "url": "..." } badala ya upakiaji wa multipart, na name na settings kama awali. Kwenye SDK, vs.transcriptions.createFromUrl(url) (TypeScript) na vs.transcriptions.create(url=...) (Python) hutuma ombi hilo hilo.
Kabla ya kuunda chochote, GET /v1/transcriptions/probe?url= hukuambia kiungo ni nini. kind yake ni mojawapo ya aina tatu:
file: rekodi yenye mwisho; inukuliwe hapa.live: mtiririko wa moja kwa moja wa YouTube, mtiririko usio na kikomo wa redio au orodha ya kucheza ya HLS isiyo na mwisho. Kiungo cha moja kwa moja hakiishi kamwe, kwa hiyo hakiwezi kunukuliwa kama faili; kitumie badala yake kama chanzo cha unukuzi mubashara.unknown: seva haikuweza kubaini.Kiungo kisipofunguka kama media kabisa (ukurasa wa wavuti, kwa mfano), ok ni false na problem inaeleza sababu kwa sentensi moja. Probe ni hiari, na imewekewa kikomo cha miito 60 kwa dakika.
Kazi ya kiungo huanza kwa fetching kabla ya kuhamia transcribing na done. Inapopakua, progress.phase inaonyesha seva inafanya nini: fetching wakati baiti zinapokelewa (pamoja na fetchedBytes na fetchTotalBytes upande wa pili unapotaja jumla), au extracting wakati sauti inatolewa kwenye video. progress.phasePct inaonyesha kazi imefika wapi ndani ya awamu hiyo. Video ndefu huchukua muda, kwa hiyo uliza kila sekunde chache; vs.jobs.wait hupokea intervalMs (interval_s kwenye Python) kwa kazi hiyo.
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}%`);
},
});
Mwongozo wa Nukuu kiungo katika marejeo una programu kamili kwa lugha zote mbili na HTTP. Kupakua kutoka viungo kunaongeza njia zaidi za kushindwa, kwa hiyo haya ndiyo makosa utakayoyaona kweli.
Kila wito ulioshindwa hujibu kwa bahasha moja ya JSON. code ni mfuatano wa kuamulia hatua za kufuata, na status ni hali ya HTTP; message ni kwa watu na requestId kwa msaada. Chini ya data, retryable inaonyesha kama kutuma ombi lilelile tena kunaweza kusaidia, na details ina chochote msimbo unachohitaji. SDK zinatupa aina moja ya hitilafu, VocalStackError, zenye sehemu zilezile.
Misimbo mitatu inafaa kushughulikiwa tangu siku ya kwanza.
402 insufficient_credit: salio la akaunti limeisha, na hakuna kilichotozwa. details.balance ni salio kwa dola na details.topUpUrl ndipo pa kuongeza salio. Gharama ilipojulikana mapema, kama ilivyo kwa faili yenye urefu unaojulikana, details.required inaonyesha ingekuwa kiasi gani.429 rate_limited: subiri sekunde za Retry-After. details.metric ikiwa creates_per_hour, umefika kiwango cha uundaji wa mpango (unukuzi mpya kwa saa), si kikomo cha ghafla; limit na resetsAt zinaonyesha kikomo ni kipi na kinafunguka lini.503 unavailable: huduma haipatikani kwa muda mfupi. Tuma ombi tena baada ya Retry-After.SDK hujaribu tena 429 na 503 baada ya Retry-After, mara mbili kwa chaguo-msingi (maxRetries / max_retries kwenye mteja). Lakini 503 hujaribiwa tena tu kwenye wito ambao ni salama kutumwa mara mbili: GET, au uundaji wenye Idempotency-Key. Uundaji usio nayo unaachiwa wewe, kwa sababu SDK haiwezi kujua kama jaribio la kwanza lilifika. Hiyo ndiyo sababu ya kiutendaji ya kuweka ufunguo kwenye kila upakiaji.
Kila jibu pia hubeba RateLimit-Limit, RateLimit-Remaining na RateLimit-Reset, kwa hiyo mfumo wenye shughuli nyingi unaweza kupunguza kasi kabla ya 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…"
}
Jedwali kamili la misimbo liko kwenye Hitilafu, majaribio upya na vikomo. Hiyo 402 ndiyo hitilafu pekee inayohusiana na fedha, kwa hiyo haya ndiyo gharama ya faili.
Faili hugharimu $0.20 kwa saa ya sauti, zinatozwa kwa sekunde ($0.0033 kwa dakika) na kiwango cha chini cha dakika 5: faili ya dakika 30 hugharimu $0.10. Ni senti kumi, chini ya kahawa utakayokunywa wakati upau wa maendeleo unajaa. Kiwango ni kilekile kwenye kila mpango, na ni kilekile kupitia API kama kwenye Dashibodi. Akaunti mpya huanza na salio la usajili la $5 la mara moja, na hakuna kadi inayohitajika.
Vikomo vitatu vya mpango vinagusa somo hili.
GET /v1/realtime/capabilities kinaripoti kama limits.maxUploadBytes. Kile kidogo kati ya hivyo viwili ndicho kinachotumika, na faili inayozidi hukataliwa kwa 413 kabla chochote hakijanukuliwa. Kwa rekodi ndefu, hii ni sababu nyingine ya kupakia faili ya mono iliyobanwa badala ya WAV.GET /v1/billing huripoti mpango wako, salio na vikomo wakati wowote; jedwali kamili liko kwenye ukurasa wa bei.
Kuanzia hapa, hatua zinazofuata ni kutiririsha sauti kwa wakati halisi pale usipoweza kusubiri faili imalizike, au kuunganisha wakala wa AI kwenye akaunti hiyo hiyo kupitia seva ya MCP, ili inanukuu na kutafuta kwa ajili yako bila msimbo wowote hapo juu.