Yükləmə sizə transkript yox, iş (job) qaytarır. Təkrar cəhd heç vaxt ikinci transkripsiya yaratmasın və ya ikinci dəfə ödəniş tutmasın deyə hər yaratma sorğusuna Idempotency-Key əlavə edin; sonra işi tamamlanana qədər sorğulayın və eyni id-dən cümlələri, sözləri və ya ixracı oxuyun.
Fayl transkripsiyası VocalStack API-nin ən sadə əməliyyatıdır və istənilən asinxron işin quruluşuna malikdir: yükləyin, gözləyin, oxuyun. Yükləmə dərhal transkripsiyanı və onun işini (job) cavab olaraq qaytarır, lakin hələ heç bir söz yoxdur. Proqramınız işi sorğulayarkən server faylı audionun oxunma sürətindən dəfələrlə tez transkripsiya edir. Vəziyyət done olduqda transkripti danışanlar və vaxt işarələri ilə cümlələr şəklində oxuya bilərsiniz, ya da dörd formatdan birində fayl kimi ixrac edə bilərsiniz, danışıq dilində və ya tərcümə edilmiş halda.
Bu quruluş hər toplu (batch) API-yə aiddir: id qaytaran yaratma, sorğulanan status, sonradan alınan nəticə. Yalnız sahə adları VocalStack-ə məxsusdur və bu dərslik TypeScript və Python SDK-ları ilə, lazım olan yerlərdə isə HTTP çağırışları ilə onları addım-addım göstərir.
Developers → API keys bölməsini İdarə panelində açın və Read və Write icazələri ilə açar yaradın. Açar yalnız bir dəfə göstərilir; onu mühit dəyişəninə köçürün:
export VOCALSTACK_API_KEY="<your API key>"
Açar serverlər və skriptlər üçündür. Brauzerə göndərilən hər şey açarı da özü ilə göndərir, ona görə açarı səhifə kodundan və URL-lərdən uzaq saxlayın, çünki URL-lər jurnallara düşür. Brauzerdən axın (stream) etmək üçün serveriniz onun üçün realtime sessiya yaradır; bu başqa bir dərslikdir.
Hər çağırış açarı bearer token kimi göndərir:
Authorization: Bearer vs_key_...
Açar mühitinizdə olduqda, növbəti addım onu göndərəcək klientdir.
İki SDK var. Hər birində VocalStack adlı bir klient var, metodları resurslara görə qruplaşıb: vs.transcriptions, vs.jobs, vs.recordings, vs.realtime və daha bir neçəsi. TypeScript SDK Node 20 və ya daha yeni versiyada və brauzerlərdə işləyir; Python SDK asyncio üzərində qurulub və Python 3.10 və ya daha yeni versiya tələb edir.
SDK-lar npm-də və ya PyPI-da deyil. API quraşdırma faylını hər hesaba verir. Quraşdırma əmrləri, mühitinizə uyğunlaşdırılmış halda, API arayışındakı sürətli başlanğıcın Quraşdırma bölməsindədir və açarınızı yalnız API hostuna göndərirlər. Heç nə quraşdırmaq istəmirsinizsə, aşağıdakı HTTP çağırışları yalnız curl və jq tələb edir.
Hansını seçsəniz seçin, ilk çağırış yükləmədir.
POST /v1/transcriptions faylı multipart/form-data kimi qəbul edir: file hissəsi və JSON saxlayan əlavə options hissəsi. Cavab olaraq 201 ilə transkripsiyanı qaytarır. Onun job.recordingId-si faylından yaradılmış yazının id-sidir və bundan sonrakı bütün çağırışlar bu id ilə ünvanlanır.
İlk işə salma üçün iki parametr vacibdir, hər ikisi options.settings daxilindədir:
spokenLanguage: en kimi kod. Buraxsanız dil avtomatik təyin olunur. Adını göstərmək səs-küylü yazılarda və güclü şivələrdə dəqiqliyi artırır.speakerLabels: auto, on və ya off. on ilə hər cümlə danışan nömrəsi daşıyır; auto ilə etiketlər yalnız ikinci danışan eşidildikdən sonra görünür.Əksər audio və video fayllar qəbul olunur: mp3, wav, flac, ogg, m4a, mp4 və digərləri. Video yalnız öz audiosunu saxlayır. Format dəqiqliyə az təsir edir, çünki nitq tanıma 16 kHz mono üzərində işləyir və hər şey gəldiyi anda bu tezliyə yenidən nümunələnir. Bir nəfərin danışdığı 48 kHz stereo WAV faylının baytlarının çoxu yüksək keyfiyyətdə səssizliyi təsvir edir. Eyni danışığın 64 kbps mono MP3 və ya Opus faylı ilə tam eyni dəqiqliklə transkripsiya olunur, amma təxminən iyirmi dəfə böyükdür, buna görə yüklənməsi də iyirmi dəfə uzun çəkir. Yükləməzdən əvvəl çevirirsinizsə, dəyişdirməyə dəyər olan iki parametr mono və orta bit sürətidir.
Hər yükləmə ilə Idempotency-Key göndərin və səbəbini anlayın. Şəbəkə fasiləsi serverin nə etdiyi barədə heç nə demir: sorğunu heç görməmiş ola bilər, ya da faylı saxlayıb işi növbəyə qoymuş ola bilər, cavab isə itmiş ola bilər. Kor təkrar cəhd ikinci transkripsiya və ikinci ödəniş riski daşıyır. Açar bunun qarşısını alır. Eyni açar və eyni gövdə ilə 24 saat ərzində sorğu təkrarlanarsa, server heç nə yaratmadan ilk cavabı qaytarır, beləliklə "ən azı bir dəfə" "effektiv olaraq bir dəfə"yə çevrilir. Qayda belədir: hər məntiqi yükləmə üçün bir açar, ilk cəhddən əvvəl yaradılır və hər təkrar cəhddə yenidən istifadə olunur. Hər cəhd üçün yeni açar hər təkrarı yeni yükləmə edərdi və başlıq heç nəyi qorumazdı.
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
Eyni çağırış curl ilə:
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 heç bir söz transkripsiya olunmadan gəlir; əlinizdə iş id-si var, buna görə növbəti addım onu gözləməkdir.
İş queued → transcribing → done sırası ilə gedir, ya da failed ilə bitir, error mesajı və səbəb maşın tərəfindən oxunursa errorCode ilə birlikdə. Dördüncü vəziyyət, cropping, o deməkdir ki, iş kənarda gözləyir və kimsə İdarə panelinin kəsmə addımında olduğu kimi, transkripsiya ediləcək yazının hansı hissəsini seçməlidir. Adi yükləmə heç vaxt bu vəziyyətə girmir, lakin jobs.wait də bu vəziyyətə çatdıqda qayıdır. done və ya failed olana qədər GET /v1/recordings/{id}/job oxuyun.
Webhook-lar yoxdur və bu qəsdəndir. Webhook sizdən ictimai HTTPS son nöqtə, başqalarının çağıra bilməməsi üçün imza yoxlaması və son nöqtəniz əlçatmaz olduqda təkrar siyasəti tələb edir. Bir dəqiqə çəkən iş üçün bu çox işdir. Sorğulama bunların heç birinə ehtiyac qoymur. Skriptiniz hər iki saniyədə "hələ hazırdır?" deyə soruşacaq, arxa oturacaqdakı uşaq kimi, amma uşaqdan fərqli olaraq faiz qaytarır.
Artıq açıq olan veb səhifə transkripsiyanın hadisələr socket-ini izləyə bilər (WS /v1/transcriptions/{id}/events, iş irəlilədikcə job.progress çərçivəsi). Skript üçün isə sorğulama doğru vasitədir.
SDK-lar sorğulamanı özləri edir: vs.jobs.wait işi hər 2 saniyədən bir oxuyur və hər oxunuşu onProgress-ə verir (Python-da on_progress). İşin progress-i pct (0-dan 100-ə) və processedS-i, yəni indiyə qədər transkripsiya olunmuş media saniyələrini daşıyır. Aşağıdakı proqramlar yalnız vəziyyət və ya faiz dəyişəndə sətir çap edir.
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 olduqda xəta vermir; işi qaytarır və yoxlamanı sizə buraxır. Qısa bir çıxış üçün iki saniyə uyğundur. Üç saatlıq yazı və ya yüzlərlə faylın toplusu üçün intervalı (intervalMs, interval_s) genişləndirin və ya iş getdikcə gecikmə tətbiq edin: ilk oxunuşlar ən çox məlumat verir, sonrakılar əsasən serverin hələ məşğul olduğunu təsdiqləyir. SDK olmadan eyni dövrü əllə yazırsınız:
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
Vəziyyət done oxuyanda transkript hazırdır.
GET /v1/recordings/{id}/transcript bütün transkripti bir sənəddə qaytarır:
sentences: insanlara göstəriləcək şey. Hər birində text, speaker, və saniyələrlə start və end var.segments: mühərrikin sətirləri, hər biri words-də söz səviyyəsində vaxtlarla (hər sözdə text, start və end var).speakers: hər danışan nömrəsi üçün bir giriş, onun name-i ilə.Danışanlar 0-dan başlayaraq nömrələnir. speaker dəyəri -1 olduqda, yazı üçün danışan etiketləri söndürülmüş demək idi. Transkripti İspan dilinə tərcümə olunmuş halda oxumaq üçün ?lang=es əlavə edin; cümlələr həmin dildə qayıdır.
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'
İki vaxt səviyyəsi iki işə xidmət edir. Cümlələr oxu vahididir, ona görə altyazı və ya transkript səhifəsi onları göstərir. Sözlər göstərmə vahididir, ona görə karaoke tipli vurğulama, axtarış nəticəsi və ya klip kəsimi segments[].words-də gəzir. İxrac formatları cümlələrdən qurulur.
GET /v1/recordings/{id}/export real fayl qaytarır. format srt, json, xlsx və ya pdf-dən biridir və fayl bütün düzəlişləri daxil edir. Tərcümə olunmuş fayl üçün lang= əlavə edin: ixrac həmin dildə, hər cümləyə bir sətir olaraq oxunur, və hələ tərcümə olunmamış hər cümlə əvvəlcə tərcümə olunur.
SRT-ni görünüşə görə tanımaq faydalıdır, çünki o, demək olar ki, hər pleyerin qəbul etdiyi altyazı formatıdır və sadə mətndir. Fayl boş sətirlərlə ayrılmış kadrlar siyahısıdır, hər kadr nömrə, vaxt aralığı və mətndən ibarətdir. Vaxt damğaları millisaniyədən əvvəl vergül işlədir, formatın heç vaxt atmadığı fransız adəti:
1
00:00:01,000 --> 00:00:04,200
Welcome, everyone. Let's get started.
Hər kadrda bir cümlə ən doğru ölçüdür: hər kadr ekranda olduğu müddətdə oxuna bilir və heç biri fikir ortasında kəsilmir. Yazıda danışan etiketləri olduqda, hər kadrın mətni danışanın adı və iki nöqtə ilə başlayır.
TypeScript SDK-da vs.recordings.export Response qaytarır; baytlarını arrayBuffer() ilə oxuyun. Python-da bytes qaytarır.
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
Yazı saxlanılır, buna görə İdarə panelində Transcriptions altında görünür, keçid ilə paylaşıla bilər və istənilən vaxt yenidən oxuna bilər. Əsas dövr budur: yarat, gözlə, oxu, ixrac et. Bu dərsliyin qalanı variantları əhatə edir: onlayn artıq olan media, qarşılaşacağınız xətalar və faylın qiyməti.
Media artıq onlayndırsa, API-yə keçidi verin və o faylı özü gətirər: ictimai http(s) ünvanı (mp3, video, podkast epizodu) və ya YouTube videosu. Çağırış eynidir: POST /v1/transcriptions, çoxhissəli yükləmə əvəzinə JSON gövdəsi { "url": "..." } ilə, name və settings əvvəlki kimi. SDK-larda vs.transcriptions.createFromUrl(url) (TypeScript) və vs.transcriptions.create(url=...) (Python) eyni sorğunu göndərir.
Heç nə yaratmazdan əvvəl GET /v1/transcriptions/probe?url= keçidin nə olduğunu sizə deyir. Onun kind-i üç dəyərdən biridir:
file: sonu olan yazı; burada transkripsiya edin.live: YouTube canlı yayımı, sonsuz radio axını və ya sonu olmayan HLS pleylisti. Canlı keçid heç vaxt bitmir, buna görə fayl kimi transkripsiya oluna bilməz; onu canlı transkripsiyanın mənbəyi kimi istifadə edin.unknown: server fərqləndirə bilmədi.Keçid heç media kimi açılmadıqda (məsələn, veb səhifə), ok false olur və problem bir cümlə ilə səbəbi izah edir. Probe isteğe bağlıdır və dəqiqədə 60 çağırışla məhdudlaşır.
Keçid işi əvvəlcə fetching-də başlayır, sonra transcribing və done-a keçir. Yükləyən zaman progress.phase serverin nə etdiyini deyir: bayt gələrkən fetching (digər tərəf neçə olduğunu deyəndə fetchedBytes və fetchTotalBytes ilə birlikdə) və ya videodan audio çıxarılarkən extracting. progress.phasePct işin həmin mərhələnin neçəsində olduğunu göstərir. Uzun video vaxt aparır, ona görə bir neçə saniyədən bir sorğulayın; vs.jobs.wait bunun üçün intervalMs (Python-da interval_s) qəbul edir.
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}%`);
},
});
Arayışdakı Keçidi transkripsiya edin bələdçisində hər iki dildə və HTTP-də tam proqram var. Keçidlərdən yükləmək daha çox uğursuzluq yolu əlavə edir, ona görə burada real qarşılaşacağınız xətalar var.
Hər uğursuz çağırış bir JSON zərfi ilə cavab verir. Ayırmaq üçün code sətridir, status HTTP statusudur. message insanlar üçün, requestId isə dəstək üçün. data altında retryable eyni sorğunun təkrarının kömək edə biləcəyini bildirir, details isə koda lazım olan hər şeyi saxlayır. SDK-lar bir xəta tipi, VocalStackError qaldırır, eyni sahələrlə.
Üç kodu ilk gündən idarə etməyə dəyər.
402 insufficient_credit: hesabın krediti bitib və heç nə ödənilməyib. details.balance dollarla balansdır, details.topUpUrl krediti artırmaq üçün ünvandır. Xərc əvvəlcədən məlum olduqda, məsələn məlum uzunluqlu fayl, details.required bunun nə olacağını deyir.429 rate_limited: Retry-After saniyə gözləyin. details.metric creates_per_hour olduqda, planın yaradılma sürətinə (saatda yeni transkripsiyaya) çatmısınız, partlayış limitinə yox; limit və resetsAt limitin nə olduğunu və nə vaxt boşalacağını bildirir.503 unavailable: xidmət qısa müddətə əlçatmazdır. Retry-After-dan sonra sorğunu yenidən göndərin.SDK-lar 429 və 503-ü Retry-After-dan sonra təkrar edir, defolt olaraq iki dəfə (klientdəki maxRetries / max_retries). 503 isə yalnız iki dəfə göndərmək təhlükəsiz olan çağırışda təkrar edilir: GET, və ya Idempotency-Key daşıyan yaratma. Açarı olmayan yaratma sizə buraxılır, çünki SDK ilk cəhdin keçib-keçmədiyini bilə bilməz. Hər yükləmədə açar qoymağın praktik səbəbi budur.
Hər cavab həmçinin RateLimit-Limit, RateLimit-Remaining və RateLimit-Reset başlıqlarını daşıyır, buna görə məşğul boru xətti 429-dan əvvəl yavaşlaya bilər.
{
"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…"
}
Bütün kodların cədvəli Xətalar, təkrarlar və limitlər səhifəsindədir. Bu 402 pulla bağlı yeganə xətadır, buna görə bir faylın nəyə başa gəldiyinə baxaq.
Fayllar hər audio saatı üçün $0.20 ilə, saniyəlik ödənişlə ($0.0033 dəqiqəsi) və 5 dəqiqəlik minimumla ödənilir: 30 dəqiqəlik fayl $0.10 başa gəlir. Bu on sentdir, işin irəliləməsini izləyərkən içəcəyiniz qəhvədən də ucuzdur. Qiymət bütün planlarda eynidir və API-dən də, İdarə panelindən də eynidir. Yeni hesab bir dəfəlik $5 qeydiyyat krediti ilə başlayır və kart tələb olunmur.
Bu dərsliyə üç plan limiti təsir edir.
GET /v1/realtime/capabilities limits.maxUploadBytes kimi bildirir. İkisindən kiçik olan tətbiq olunur və bundan böyük fayl transkripsiya olunmadan 413 ilə rədd edilir. Uzun yazı üçün bu, WAV əvəzinə sıxılmış mono fayl yükləməyin bir səbəbidir.GET /v1/billing planınızı, balansınızı və limitlərinizi istənilən vaxt bildirir; tam cədvəl qiymət səhifəsindədir.
Buradan sonrakı addımlar bunlardır: uzun faylın bitməsini gözləyə bilmədiyiniz zaman audionu real vaxtda axın etmək və ya AI agentini qoşmaq eyni hesaba MCP serveri vasitəsilə, belə ki, o, yuxarıdakı kodun heç birinə ehtiyac olmadan sizin üçün transkripsiya etsin və axtarsın.