Жүктеу транскрипцияны емес, тапсырманы қайтарады. Әр create сұрауына Idempotency-Key қойыңыз, сонда қайталау ешқашан екінші транскрипцияны жасамайды немесе төлетпейді; содан кейін тапсырма аяқталғанша тексеріп тұрыңыз және сол id бойынша сөйлемдерді, сөздерді немесе экспортты оқыңыз.
Файлды транскрипциялау VocalStack API-дің ең қарапайым операциясы, және ол кез келген асинхронды тапсырманың құрылымына ие: жүктеу, күту, оқу. Жүктеу бірден транскрипциямен және оның тапсырмасымен жауап береді, бірақ сөз әлі жоқ. Бағдарламаңыз тапсырманы сұрап тұрғанда, сервер файлды аудионың ойнау жылдамдығынан бірнеше есе жылдам транскрипциялайды. Күйі done болған кезде транскриптті спикерлері мен уақыт белгілері бар сөйлемдер түрінде оқисыз, немесе төрт форматтың бірінде файл ретінде экспорттайсыз, сөйлеу тілінде немесе аударылған түрде.
Осы құрылымдағы барлық нәрсе кез келген пакеттік API-ге көшеді: id қайтаратын create, сұрап отыратын күй, кейін алатын нәтиже. Тек өріс атаулары VocalStack-ке тән, және бұл оқулық TypeScript және Python SDK-мен, қажет жерлерде HTTP сұрауларымен жүреді.
Басқару тақтасында Developers → API keys бөліміне кіріп, Read және Write рұқсаттары бар кілт жасаңыз. Кілт бір рет көрсетіледі; оны орта айнымалысына көшіріңіз:
export VOCALSTACK_API_KEY="<your API key>"
Кілт серверлер мен скрипттерге арналған. Браузерге жіберілетін кез келген нәрсе кілтті де бірге жібереді, сондықтан оны бет кодынан және URL-дан шығарып тастаңыз, себебі URL-дар журналдарға түседі. Браузерден ағынмен жіберу үшін серверіңіз оған нақты уақыттағы сессия жасайды; бұл басқа оқулық.
Әр сұрау кілтті bearer токені ретінде растайды:
Authorization: Bearer vs_key_...
Кілт орта айнымалысында болғаннан кейін, оны жіберетін клиент керек.
Екі SDK бар. Әрқайсысында бір клиент, VocalStack, оның әдістері ресурс бойынша топтастырылған: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime және тағы бірнеше. TypeScript SDK Node 20 немесе одан жаңа нұсқасында және браузерлерде жұмыс істейді; Python SDK asyncio негізінде құрылған және Python 3.10 немесе одан жаңа нұсқасын қажет етеді.
SDK-лар npm немесе PyPI-да жоқ. API құрастырылған нұсқаларды әр аккаунтқа береді. Орнату командалары, сіздің ортаңызға толтырылған, API анықтамасындағы жылдам бастаудың Install бөлімінде; олар кілтіңізді тек API хосттына жібереді. Ешнәрсе орнатқыңыз келмесе, төмендегі HTTP сұрауларына тек curl және jq керек.
Қандай жолды таңдасаңыз да, алғашқы сұрау — жүктеу.
POST /v1/transcriptions файлды multipart/form-data ретінде қабылдайды: file бөлігі және JSON ішіндегі міндетті емес options бөлігі. Ол транскрипциямен 201 жауабын қайтарады. Оның job.recordingId мәні файлдан жасалған жазбаның id-і, және содан кейінгі барлық сұраулар осы id бойынша жүреді.
Алғашқы іске қосу үшін екі параметр маңызды, екеуі де options.settings ішінде:
spokenLanguage: en сияқты код. Оны алып тастасаңыз, тіл анықталады. Оны атау шулы жазбаларда және қатты акцентте дәлдікті арттырады.speakerLabels: auto, on немесе off. on болса, әр сөйлемде спикер нөмірі болады; auto болса, белгілер тек екінші спикер естілгеннен кейін пайда болады.Көптеген аудио және бейне файлдар қабылданады: mp3, wav, flac, ogg, m4a, mp4 және басқалары. Бейнеден тек аудио сақталады. Формат дәлдікке аз әсер етеді, себебі сөйлеуді тану 16 кГц моно форматында жұмыс істейді, ал барлығы келгенде осы жиілікке қайта дискретизацияланады. Бір адам сөйлеген 48 кГц стерео WAV файлдың байттарының көбін тыныштықты жоғары сапада сипаттауға жұмсайды. Ол дәл сол сөйлеудің 64 кбит/с моно MP3 немесе Opus файлы сияқты дәл транскрипцияланады, бірақ шамамен жиырма есе үлкен, сондықтан жүктеуге де жиырма есе көп уақыт кетеді. Жүктемей тұрып түрлендірсеңіз, өзгертуге тұратын екі параметр — моно және орташа битрейт.
Әр жүктеуде Idempotency-Key жіберіңіз, және неге екенін түсініңіз. Желі таймауты серверде не болғанын айтпайды: ол сұрауды мүлде көрмеген де болуы мүмкін, немесе файлды сақтап, тапсырманы кезекке қойған, бірақ жауап жоғалған да болуы мүмкін. Соқыр қайталау екінші транскрипцияға және екінші төлемге қауіп төндіреді. Кілт бұл мәселені шешеді. Бір кілтпен және бірдей денемен 24 сағат ішінде қайталанған сұраудың жауабы ретінде сервер бірінші жауапты қайтарады, ештеңе жасамай, сол арқылы «кем дегенде бір рет» «нақты бір рет» болады. Демек ереже: бір логикалық жүктеуге бір кілт, оны бірінші әрекет алдында жасап, әр қайталауда қайта пайдаланыңыз. Әр әрекетке жаңа кілт берсеңіз, әр қайталау жаңа жүктеу болып шығады, ал тақырып ештеңеден қорғамайды.
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
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 жауабы ешбір сөз транскрипцияланбай тұрып келеді; сізде тапсырма id-і бар, сондықтан келесі қадам — оны күту.
Тапсырма queued → transcribing → done күйлерімен өтеді, немесе failed күйінде аяқталады, оған error хабары және машинамен оқылатын себеп болса, errorCode қосылады. Төртінші күй, cropping, тапсырма файлдың қай бөлігін транскрипциялау керек екенін біреу таңдағанша тұрып қалғанын білдіреді, басқару тақтасындағы trim қадамы сияқты. Қарапайым жүктеу бұл күйге ешқашан кірмейді, дегенмен jobs.wait тапсырма осы күйге жеткенде де қайтады. Күй done немесе failed болғанша GET /v1/recordings/{id}/job сұраңыз.
Вебхуктар жоқ, және бұл әдейі таңдау. Вебхукке сіз жақта ашық HTTPS соңғы нүкте, басқа адамның оны шақыра алмауы үшін қолтаңбаны тексеру және соңғы нүкте қолжетімсіз болған кездегі қайталау саясаты қажет. Бір минутқа созылатын тапсырма үшін бұл тым көп инфрақұрылым. Сұрап отыру мұның бәрін қажет етпейді. Скриптіңіз әр екі секунд сайын «дайын ба?» деп сұрайды, артқы орындықтағы бала сияқты, бірақ баладан айырмашылығы, ол пайызды алады.
Ашық тұрған веб-бетте транскрипцияның оқиға сокетін тыңдауға болады (WS /v1/transcriptions/{id}/events, тапсырма ілгерілегенде job.progress кадры). Скрипт үшін сұрап отыру — дұрыс құрал.
SDK-лар сұрауды өздері жасайды: vs.jobs.wait тапсырманы әр 2 секунд сайын оқиды және әр оқуды onProgress арқылы береді (Python-да on_progress). Тапсырманың progress өрісінде pct (0-ден 100-ге дейін) және processedS — осы уақытқа дейін транскрипцияланған медиа секундтары бар. Төмендегі бағдарламалар күй немесе пайыз өзгерген кезде ғана жол шығарады.
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}")
Ескеріңіз, wait failed кезінде қате шығармайды; ол тапсырманы қайтарады, тексеруді өзіңізге қалдырады. Екі секунд сөйлеуге жарайды. Үш сағаттық жазба немесе жүздеген файлдар тобы үшін интервалды кеңейтіңіз (intervalMs, interval_s) немесе тапсырма жүріп жатқанда кідіртуді арттырыңыз: алғашқы оқулар ең көп мәлімет береді, кейінгілері көбіне сервердің әлі жұмыс істеп жатқанын растайды. SDK-сыз, дәл осы циклді қолмен жазасыз:
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
Күй done болғанда, транскрипт дайын.
GET /v1/recordings/{id}/transcript транскриптті бір құжат ретінде толық қайтарады:
sentences: адамдарға көрсетуге арналғандар. Әрқайсысында text, speaker, секундпен start және end бар.segments: қозғалтқыштың жолдары, әр біреуінде words ішінде сөз деңгейіндегі уақыттар бар (әр сөзде text, start және end бар).speakers: спикер нөмірі бойынша бір жазба, оның name-імен.Спикерлер 0-ден нөмірленеді. speaker мәні -1 болса, жазба үшін спикер белгілері өшірулі болған. ?lang=es қосыңыз, сонда транскрипт испан тіліне аударылып оқылады; сөйлемдер сол тілде қайтады.
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'
Екі уақыт деңгейі екі жұмысқа қызмет етеді. Сөйлемдер — оқу бірлігі, сондықтан субтитр немесе транскрипт беті дәл солар көрсетіледі. Сөздер — нұсқау бірлігі: караоке стиліндегі бөлектеу, іздеу нәтижесі немесе клип кесу segments[].words бойымен жүреді. Экспорт форматтары сөйлемдерден жасалады.
GET /v1/recordings/{id}/export нақты файлды қайтарады. format мәні srt, json, xlsx немесе pdf болуы мүмкін, және файлға барлық түзетулер кіреді. Аударылған файл үшін lang= қосыңыз: сонда экспорт сол тілде, әр сөйлем бір жолда оқылады, ал әлі аударылмаған сөйлем алдымен аударылады.
SRT-ны көру арқылы таныған пайдалы, себебі ол дерлік барлық плеерлер қабылдайтын субтитр форматы және мәтіндік. Файл бос жолдармен бөлінген кюлдердің тізімі, әр кюл нөмірден, уақыт диапазонынан және мәтіннен тұрады. Уақыт белгілері миллисекундтан бұрын үтір қояды, бұл формат ешқашан жоймаған француз әдеті:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Бір кюлге бір сөйлем — дұрыс өлшем: әр кюлді экранда тұрған уақытта оқуға болады, ешқайсысы ойдың ортасында үзілмейді. Жазбада спикер белгілері болса, әр кюлдің мәтіні спикердің атымен және қос нүктемен басталады.
TypeScript SDK-да vs.recordings.export Response қайтарады; оның байттарын arrayBuffer() арқылы оқыңыз. Python-да ол 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
Транскрипция сақталады, сондықтан ол басқару тақтасында Transcriptions астында көрінеді, сілтеме арқылы бөлісуге болады және кейін кез келген уақытта қайта оқуға болады. Міне негізгі цикл: жасау, күту, оқу, экспорттау. Бұл оқулықтың қалған бөлімі осының вариацияларын қамтиды: желіде тұрған медиа, кездесетін қателер және файлдың құны.
Медиа желіде тұрған болса, API-ға сілтемені беріңіз, ол файлды өзі алады: жалпы ашық http(s) мекенжай (mp3, бейне, подкаст эпизоды) немесе YouTube бейнесі. Сұрау бірдей POST /v1/transcriptions, тек multipart жүктеудің орнына { "url": "..." } JSON денесімен, ал name және settings бұрынғыдай. SDK-ларда vs.transcriptions.createFromUrl(url) (TypeScript) және vs.transcriptions.create(url=...) (Python) дәл осы сұрауды жасайды.
Ештеңе жасамас бұрын, GET /v1/transcriptions/probe?url= сілтеменің не екенін айтады. Оның kind мәні үш түрдің бірі:
file: соңы бар жазба; мұнда транскрипциялаңыз.live: YouTube тікелей эфирі, соңы жоқ радиоағын немесе HLS плейлисті. Тікелей сілтеме ешқашан аяқталмайды, сондықтан оны файл ретінде транскрипциялау мүмкін емес; оны тікелей транскрипцияның көзі ретінде пайдаланыңыз.unknown: сервер анықтай алмады.Сілтеме мүлде медиа ретінде ашылмаса (мысалы, веб-бет), ok — false, ал problem себебін бір сөйлеммен түсіндіреді. Probe міндетті емес және минутына 60 шақырумен шектелген.
Сілтеме тапсырмасы fetching күйінен басталады, кейін transcribing және done күйлеріне өтеді. Ол алып жатқанда progress.phase серверде не істеп жатқанын көрсетеді: байттар келе жатқанда fetching (қашықтағы жақ қанша екенін айтса, fetchedBytes және fetchTotalBytes бірге), немесе бейнеден аудио шығарылып жатқанда extracting. progress.phasePct тапсырма сол фазада қаншалықты алға жылжығанын көрсетеді. Ұзын бейне біраз уақыт алады, сондықтан әр бірнеше секунд сайын сұраңыз; ол үшін vs.jobs.wait 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}%`);
},
});
Сілтемені транскрипциялау нұсқаулығында толық бағдарлама екі тілде және HTTP-де бар. Сілтемелерден алу қосымша қате жолдарын ашады, сондықтан нақты кездесетін қателер мына жерде.
Әр сәтсіз сұрау бір JSON конвертімен жауап береді. code — тексеретін жол, status — HTTP күйі; message адамдарға арналған, requestId — қолдау қызметіне арналған. data ішінде retryable бірдей сұрауды қайта жіберу көмектесе ала ма, соны айтады, ал details кодқа қажетті мәліметтерді ұстайды. SDK-лар бір қате түрін, VocalStackError, бірдей өрістермен көрсетеді.
Алғашқы күннен өңдеуге тұратын үш код бар.
402 insufficient_credit: аккаунттың кредиті таусылған, және ештеңе есептелмеген. details.balance — долларымен балансы, details.topUpUrl — кредит қосатын жер. Құны алдын ала белгілі болғанда, мысалы ұзындығы белгілі файл үшін, details.required сонда қанша болатынын айтады.429 rate_limited: Retry-After секунд күтіңіз. details.metric creates_per_hour болса, сіз тарифтің жасау жиілігіне (сағатына жаңа транскрипциялар) жеттіңіз, жарылыс (burst) шегіне емес; limit және resetsAt шек қандай екенін және ол қашан босайтынын айтады.503 unavailable: қызмет қысқа уақытқа қолжетімсіз. Retry-After кейін сұрауды қайта жіберіңіз.SDK-лар 429 және 503 кодтарын Retry-After кейін қайталайды, әдепкі бойынша екі рет (клиентте maxRetries / max_retries). Бірақ 503 тек екі рет жіберуге қауіпсіз сұрауда қайталанады: GET, немесе Idempotency-Key бар жасау сұрауы. Кілтсіз жасау сұрауы сізге қалдырылады, себебі SDK бірінші әрекет өтті ме, жоқ па, білмейді. Жүктеудің әрбірінде кілт қоюдың практикалық себебі осы.
Әр жауапта RateLimit-Limit, RateLimit-Remaining және RateLimit-Reset тақырыптары бар, сондықтан жүктемелі құбыр 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…"
}
Кодтардың толық кестесі Қателер, қайталаулар және шектеулер бөлімінде. Осы 402 — ақшамен байланысты жалғыз қате, сондықтан файл қанша тұратынын қарайық.
Файлдар аудионың әр сағатына $0.20 тұрады, секундына есептеледі ($0.0033 минутына), 5 минуттық минимуммен: 30 минуттық файл $0.10 тұрады. Бұл он цент, тіпті прогресс жолағы толғанша ішетін кофеден де арзан. Бағасы барлық тарифтерде бірдей, API арқылы да басқару тақтасындағыдай. Жаңа аккаунт бір реттік $5 тіркелу кредитімен басталады, карта қажет емес.
Бұл оқулыққа әсер ететін үш тариф шегі бар.
GET /v1/realtime/capabilities limits.maxUploadBytes ретінде көрсетеді. Екеуінің кішісі қолданылады, және одан үлкен файл транскрипцияланбай тұрып 413 қатесімен қабылданбайды. Ұзын жазба үшін бұл WAV емес, сығылған моно файлды жүктеудің тағы бір себебі.GET /v1/billing тарифіңізді, балансыңызды және шектеулеріңізді кез келген уақытта көрсетеді; толық кесте бағалар бетінде.
Одан әрі келесі қадамдар — файл аяқталғанша күтпеу керек болса, аудионы нақты уақытта ағынмен беру, немесе осы аккаунтқа MCP сервері арқылы AI агентін қосу, сонда ол жоғарыдағы кодсыз транскрипциялап, іздеп береді.