रियल-टाइम ट्रान्सक्रिप्ट दुई पटक आउँछ: पहिले बारम्बार बदलिने ड्राफ्टको रूपमा, त्यसपछि नबदलिने अन्तिम पंक्तिको रूपमा। ड्राफ्टलाई बदल्न मिल्ने पुच्छरको रूपमा र अन्तिम पंक्तिलाई इतिहासको रूपमा देखाउनुहोस्; लाइभ क्याप्शन दृश्यका बाँकी भाग केवल जडान हुन्।
रियल-टाइम सत्र VocalStack API ले गर्ने सबैभन्दा सानो काम हो। तपाईंको सर्भरले एउटा HTTP कल गर्छ र हस्ताक्षरित streamUrl फिर्ता पाउँछ। तपाईंको क्लाइन्टले त्यो URL मा WebSocket खोल्छ, रेकर्ड हुँदै गरेको कच्चा अडियो पठाउँछ, र बोल्ने व्यक्तिभन्दा केही क्षण पछाडि रहेर ट्रान्सक्रिप्ट JSON टेक्स्ट फ्रेमका रूपमा पढ्छ। अडियो सकिएपछि तपाईंले eof पठाउनुहुन्छ, सर्भरले बाँकी डेटा फ्लस (पठाएर सकाउने) गर्छ, सत्रले नापेका कुरा रिपोर्ट गर्छ, र जडान बन्द गर्छ।
केही पनि भण्डारण हुँदैन। ट्रान्सक्रिप्ट केवल तारमा (wire) मात्र हुन्छ, जुन क्याप्शन दृश्य, बैठकको ओभरले वा तपाईंको एपको आवाज सुविधाले चाहन्छ। यदि पछि शब्दहरू राख्न, साझा गर्न र निर्यात गर्न चाहनुहुन्छ भने, त्यसको सट्टा लाइभ ट्रान्सक्रिप्सन र प्रसारण चाहिन्छ; अन्तिम खण्डले त्यतै संकेत गर्छ।
यो स्वरूप अधिकांश स्ट्रिमिङ भाषण API हरूले साझा गर्ने स्वरूप हो: HTTP मार्फत प्रमाणपत्र आदान-प्रदान, अडियो भित्र जाने र JSON बाहिर आउने लामो समय चल्ने सकेट, र अस्थायी पाठलाई अन्तिम पाठबाट छुट्याउने कुनै तरिका। नामहरू एउटा API बाट अर्कोमा फरक हुन्छन्; चल्ने भागहरू शायद ही फरक हुन्छन्। यहाँ क्रमअनुसार:
POST /v1/realtime/sessions। उत्तर { sessionId, streamUrl, expiresAt } हुन्छ।streamUrl मा WebSocket खोल्नुहोस्।session.ready आउँदासम्म session.loading हार्टबिट पर्खनुहोस्।transcript.draft पढ्नुहोस् (पुच्छर बदल्नुहोस्) र transcript.committed पढ्नुहोस् (अन्तिम पंक्ति थप्नुहोस्)।{"type":"eof"} पठाउनुहोस्; session.done पढ्नुहोस्।केवल चरण 1 लाई तपाईंको कुञ्जी चाहिन्छ, त्यसैले त्यो तपाईंको सर्भरमा हुन्छ।
Developers, API keys अन्तर्गत Write अनुमतिसहित कुञ्जी बनाउनुहोस्। कुञ्जी सर्भर र स्क्रिप्टका लागि हो: वेब पेजबाट वा URL मा पठाइएको कुञ्जी API ले अस्वीकार गर्छ, त्यसैले तपाईंले आफ्नै मार्गको पछाडि सत्र बनाउनुहुन्छ, र पेजले कहिल्यै streamUrl बाहेक केही देख्दैन।
URL मा आफ्नै टोकन हुन्छ, किनभने ब्राउजरको एउटा सीमा छ जुन सामान्यतया जान्नु उपयोगी हुन्छ। ब्राउजरको WebSocket कन्स्ट्रक्टरले अनुरोध हेडर सेट गर्न सक्दैन, त्यसैले कुञ्जी राख्ने Authorization हेडर हुँदैन; ब्राउजरमा प्रयोग हुने हरेक भाषण API ले आफ्नो सकेटलाई URL मा रहेको छोटो अवधिको टोकनबाट प्रमाणित गर्छ (वा सब-प्रोटोकल नाममा लुकाएर)। छोटो अवधि नै उद्देश्य हो: URL लग र इतिहासमा पुग्छ, त्यसैले त्यसमा रहेको कुराको म्याद छिट्टै सकिनुपर्छ। expiresAt अगाडि नै जडान गर्नुहोस्।
SDK हरू Developers क्षेत्रबाट डाउनलोड गरिन्छ, प्याकेज रजिस्ट्रीबाट होइन; क्विकस्टार्ट को Install खण्डले यस वातावरणका आफ्नै आदेशहरू देखाउँछ। TypeScript SDK सँग सर्भर मार्ग यसरी देखिन्छ:
// On your server, behind a route of yours (POST /api/transcribe, say):
import { VocalStack } from '@vocalstack/sdk';
const vs = new VocalStack({ baseUrl: '{{api}}', apiKey: process.env.VOCALSTACK_API_KEY });
export async function createStream(): Promise<{ sessionId: string; streamUrl: string }> {
const { sessionId, streamUrl } = await vs.realtime.createSession({ spokenLanguage: 'en' });
return { sessionId, streamUrl };
}
SDK बिना यो एउटा fetch मात्र हो। यसले स्पेनिस पनि माग्छ र वाक्य जोड्ने सुविधा बन्द गर्छ, जुन तलको तार नमूनासँग मेल खान्छ:
const res = await fetch('{{api}}/v1/realtime/sessions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.VOCALSTACK_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ spokenLanguage: 'en', translate: ['es'], sentences: { enabled: false } }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); // the error envelope: { code, message, … }
const { streamUrl } = (await res.json()) as { sessionId: string; streamUrl: string; expiresAt: string };
हरेक विकल्पको पूर्वनिर्धारित मान हुन्छ। सुरुमा जान्नुपर्ने कुरा spokenLanguage (यसलाई छाड्नुहुन्छ भने भाषा पत्ता लगाइन्छ), translate (लक्ष्य भाषाहरूको सूची), speakerLabels र latencyMode हुन्; बाँकी तल आवश्यक परेअनुसार आउँछन्।
streamUrl अडियो रेकर्ड गर्ने जुनसुकै कुरालाई दिनुहोस्; यहाँदेखि कुनै कुञ्जी संलग्न हुँदैन।
streamUrl मा जस्तो छ त्यस्तै, कुनै हेडर बिना जडान गर्नुहोस्। सत्र सुरु हुँदैगर्दा सर्भरले session.loading हार्टबिट पठाउँछ। त्यसपछि session.ready आउँछ। त्यसअघि कुनै अडियो नपठाउनुहोस्। चाँडो पठाइएको अडियो पछिको लागि राखिँदैन; सर्भरले त्यसलाई त्रुटि मान्छ, error फ्रेम पठाउँछ र सकेट बन्द गर्छ।
सकेट खुलिसकेपछि हरेक असफलता त्यही फ्रेमको रूपमा आउँछ: {"type":"error","code":"…","message":"…","retryable":true|false}। code एउटा निश्चित सूची हो (invalid_audio, invalid_options, invalid_token, capacity_exceeded, session_expired, slow_consumer, engine_unavailable, internal), message तपाईंका लगका लागि हो, र retryable ले नयाँ सत्र प्रयास गर्न लायक छ कि छैन भन्छ। पहिलो फ्रेमदेखि नै यसलाई ह्यान्डल गर्नुहोस्:
const ws = new WebSocket(streamUrl); // the token is in the URL: no header
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', (m: MessageEvent) => {
if (typeof m.data !== 'string') return;
const f = JSON.parse(m.data);
switch (f.type) {
case 'session.loading':
console.error('starting…');
break;
case 'session.ready':
startMicrophone(); // send audio only after session.ready
break;
case 'error':
console.error(`${f.code}: ${f.message}`);
if (f.retryable) scheduleReconnect(); // a new session, with backoff; see "Stop"
break;
}
});
कच्चा क्लाइन्टले आफैँले दुई कुरा ह्यान्डल गर्नुपर्छ; SDK हरूले पहिल्यै गर्छन्। पहिलो, अस्वीकार गरिएको अपग्रेडमा API को सामान्य HTTP त्रुटि लिफाफा आउँछ: 401 invalid_token, 410 session_expired, वा 503 unavailable जसमा पालना गर्नुपर्ने Retry-After हेडर हुन्छ।
दोस्रो, सन्तृप्तता (saturation) त्रुटिजस्तो होइन, मौनजस्तो देखिन्छ। यदि 30 सेकेन्डभित्र कुनै पनि फ्रेम आएन भने, त्यसलाई capacity_exceeded मानेर फेरि जडान गर्नुहोस्; नयाँ सत्रले सामान्यतया काम गर्छ। यदि लोडिङ फ्रेमहरू आइरहे तर session.ready कहिल्यै आएन भने, त्यसलाई engine_unavailable मान्नुहोस्, जुन पनि retryable हो। SDK मा RealtimeSession.connect ले session.ready मा समाधान हुन्छ र readyDeadlineMs पछि सही कोडसहित असफल हुन्छ।
session.ready आएपछि, सर्भरले बोलिएको गतिमा अडियो आउने अपेक्षा गर्छ।
अडियो कच्चा PCM को बाइनरी फ्रेमका रूपमा जान्छ: 32-बिट फ्लोट, लिटिल-एन्डियन, 16 kHz, मोनो (pcm_f32le, सत्रको पूर्वनिर्धारित audio ढाँचा)। जति बेला रेकर्ड हुन्छ त्यति बेला पठाउनुहोस्, लगभग 100 देखि 250 ms का टुक्राहरूमा, टुक्राहरूका बीचमा ट्रान्सक्रिप्ट पर्खनुपर्दैन।
बाइट हिसाब एक पटक गर्नु उपयोगी छ। प्रति सेकेन्ड 16,000 नमुना, प्रति नमुना 4 बाइट, अर्थात प्रति सेकेन्ड 64,000 बाइट; त्यसैले 100 ms मा 6,400 बाइट र 250 ms मा 16,000 बाइट। भाषण मोडेलहरू सामान्यतया 16 kHz मोनोमा तालिम दिइन्छन्, त्यसैले धेरै रियल-टाइम API ले यही माग्छन्; जबकि माइक्रोफोन र ब्राउजरहरू 44.1 वा 48 kHz मा, र प्रायः स्टेरियोमा रेकर्ड गर्छन्। बाइट पठाउनुअघि कुनै कुराले अडियोको नमुना दर फेरेर च्यानलहरूलाई एउटामा मिलाउनुपर्छ: यहाँ त्यो ffmpeg हो, र ब्राउजरमा त्यो SDK को माइक्रोफोन सहायक हो। 48 kHz अडियो पठाएर 16 kHz घोषणा गर्दा त्रुटि आउँदैन; तर ढिलो गतिको भाषण खराब रूपमा ट्रान्सक्रिप्ट हुन्छ।
Node मा ffmpeg सबैभन्दा सरल रेकर्ड गर्ने यन्त्र हो:
import { spawn } from 'node:child_process';
function startMicrophone(): void {
// Raw PCM: f32le, 16 kHz, mono, as binary frames. macOS: -f avfoundation -i :0, Linux: -f pulse -i default.
const mic = spawn('ffmpeg', ['-loglevel', 'error', '-f', 'avfoundation', '-i', ':0', '-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);
mic.stdout.on('data', (chunk: Buffer) => {
// Backpressure: bytes the socket has accepted but not yet put on the wire.
// Two seconds of audio (128,000 bytes) queued means the link, not the server, is behind.
if (ws.bufferedAmount > 128_000) console.error(`link behind by ${ws.bufferedAmount} bytes`);
ws.send(new Uint8Array(chunk));
});
mic.on('close', () => ws.send(JSON.stringify({ type: 'eof' })));
process.once('SIGINT', () => mic.kill());
}
ws.send कहिल्यै रोकिँदैन; यसले कतारमा राख्छ। bufferedAmount त्यो कतारको आकार हो, र स्वस्थ जडानमा यो लगभग शून्यमा रहन्छ। लाइभ अडियो रोक्न सकिँदैन, त्यसैले बढ्दो कतारको उपयोगी प्रतिक्रिया भनेको त्यसलाई थाहा पाउनु हो, फ्रेम खसाल्नु होइन। सर्भरले पनि ब्याकप्रेसर (दबाब) लगाउँछ। यसले वास्तविक समयभन्दा केही सेकेन्ड अगाडिसम्मको अडियो स्वीकार्छ (limits.audioBufferS), त्यसपछि बराबर नभएसम्म पढ्न रोक्छ। फिर्ता आउने बाटोमा कमिटहरू हानि नभई कतारमा बस्छन् जबकि ड्राफ्टहरू एकीकृत हुन्छन्, त्यसैले ढिलो पाठकले कम तर ताजा ड्राफ्ट देख्छ। कमिटसम्म पनि सम्हाल्न नसक्ने क्लाइन्टले slow_consumer र retryable: false भएको error फ्रेम पाउँछ, र त्यसको जडान काटिन्छ; यो देख्नुभयो भने तपाईंको सन्देश ह्यान्डलरले धेरै काम गरिरहेको छ।
ब्राउजरमा, @vocalstack/sdk/mic को streamMicrophone ले माइक्रोफोन माग्छ, 16 kHz मा नमुना दर फेर्छ, र टुक्राहरूसँगै clock फ्रेमहरू पठाउँछ; ब्राउजरले ट्याब थ्रोटल गर्दा सर्भरले खाली ठाउँ चिन्ह लगाउन यिनीहरूले मद्दत गर्छन्। पेजले तपाईंको मार्गबाट सत्र पाउँछ र कुञ्जी कहिल्यै देख्दैन:
import { RealtimeSession } from '@vocalstack/sdk';
import { streamMicrophone } from '@vocalstack/sdk/mic';
export async function transcribeMicrophone(
stream: { sessionId: string; streamUrl: string },
render: (committed: string, tail: string) => void,
): Promise<void> {
const session = await RealtimeSession.connect(stream);
const mic = await streamMicrophone(session); // asks for the microphone, sends 16 kHz PCM
document.querySelector('#stop')?.addEventListener('click', () => {
void mic.stop();
session.eof();
});
let committed = '';
for await (const ev of session) {
if (ev.type === 'transcript.draft') render(committed, ev.segments.map((s) => s.text).join(' '));
if (ev.type === 'transcript.committed') {
committed += `${ev.segment.text} `;
render(committed, '');
}
}
}
render(committed, tail) हस्ताक्षर नै सम्पूर्ण UI ढाँचा हो; अर्को खण्डले किन भनेर बुझाउँछ।
दुई फ्रेम प्रकारले ट्रान्सक्रिप्ट बनाउँछन्, र तिनलाई मिसाउनु सबैभन्दा सामान्य गल्ती हो।
पहिले, किन दुई छन् भन्ने कुरा। भाषण मोडेलले शब्द के थियो भन्ने कुरा आंशिक रूपमा त्यसपछिका शब्दहरूबाट निर्णय गर्छ: "I scream" र "ice cream" वाक्य अघि बढ्दासम्म उस्तै सुनिन्छन्। त्यसैले रियल-टाइम रिकग्नाइजरले चाँडै एउटा अनुमान दिन्छ, त्यसपछि थप अडियो आउँदा त्यसलाई संशोधन गर्छ, र पर्याप्त सुनेपछि मात्र स्थिर हुन्छ। हरेक स्ट्रिमिङ भाषण API ले यो कुनै न कुनै रूपमा देखाउँछ। कतिपयले अनुमानहरूलाई "interim" वा "partial" नतिजा भन्छन् र स्थिर भएकालाई is_final चिन्ह लगाउँछन्; शब्दावली फरक हुन्छ, अनुशासन होइन। यहाँ अनुमान ड्राफ्ट हो र स्थिर पाठ कमिट हो।
transcript.draft लाइभ पुच्छर हो: पछिल्लो कमिटपछि सुनिएका शब्दहरू, जुन अझै बदलिँदै छन्। हरेक ड्राफ्ट अघिल्लोको पूर्ण प्रतिस्थापन हो। पुच्छरलाई यसका segments सँग फेरि कोर्नुहोस्; कहिल्यै थप्नु हुँदैन। ड्राफ्टमा डिजाइनअनुसार seq हुँदैन, त्यसैले तिनलाई तिनीहरू बन्ने कमिटसँग मिलाउन खोज्नु हुँदैन।
transcript.committed अन्तिम हो: seq (0, 1, 2, बिना खाली ठाउँ) सहितको एउटा segment, start, end, text, speaker र प्रति शब्द समयसहितको words। यसलाई थप्नुहोस् र यसले ढाकेको ड्राफ्ट हटाउनुहोस्; अर्को ड्राफ्ट यो कमिट सकिएको ठाउँबाट सुरु हुन्छ।
committed: [seq 0] [seq 1]
draft: "how are" ← frame 1
draft: "how are you do" ← frame 2 REPLACES frame 1
committed: [seq 0] [seq 1] [seq 2 "How are you doing?"]
← the commit consumes the tail; drop the draft
ड्राफ्ट थप्ने UI ले दोहोरिएको, झिलमिलाउने पाठ देखाउँछ। तपाईंको क्याप्शन दृश्यमा यस्तो हकलाहट आउँछ जुन बोल्ने व्यक्तिमा कहिल्यै थिएन। सबै कमिट भएका खण्डहरू देखाउनुहोस्, त्यसपछि तिनका पछाडि पछिल्लो ड्राफ्ट देखाउनुहोस्, र कमिट आउँदा ड्राफ्ट खाली गर्नुहोस्। कमिटमा कुनै ड्राफ्टमा कहिल्यै नदेखिएका शब्दहरू पनि हुन सक्छन्।
माथिको fetch उदाहरणबाट, तारमा भाषणका केही सेकेन्डका फ्रेमहरू यहाँ छन् (translate: ["es"], वाक्य जोड्ने सुविधा बन्द):
{"type":"session.loading"}
{"type":"session.ready"}
{"type":"transcript.draft","segments":[{"start":0.0,"text":"Good","speaker":0,"tentative":true}]}
{"type":"transcript.draft","segments":[{"start":0.0,"text":"Good morning every","speaker":0,"tentative":true}]}
{"type":"transcript.committed","segment":{"seq":0,"start":0.0,"end":1.4,"text":"Good morning, everyone.","speaker":0,"words":[{"start":0.0,"end":0.3,"text":"Good"},{"start":0.3,"end":0.8,"text":"morning,"},{"start":0.8,"end":1.4,"text":"everyone."}]}}
{"type":"transcript.translated","seq":0,"translations":{"es":"Buenos días a todos."}}
{"type":"transcript.draft","segments":[{"start":1.6,"text":"Thanks for","speaker":0,"tentative":true}]}
यी बाहिर अरू दुई फ्रेम छन्। transcript.amended ले कमिट भएको खण्डलाई ठाउँमै सुधार्छ (उही seq, नयाँ text वा speaker) जब त्यसका कुनै शब्द सुधारिन्छन्; यसलाई तपाईंले पहिल्यै कोरेको पंक्तिमा लागू गर्नुहोस्। transcript.words ले कमिट हुनुअघि पुच्छरका शब्द समयहरू स्ट्रिम गर्छ। यसको लागि events.words चाहिन्छ, जुन पूर्वनिर्धारित रूपमा सक्रिय हुन्छ; कमिटले सधैँ आफ्नो words समेट्छ, घटना विकल्प जे भए पनि। अन्त्यमा, तपाईंलाई थाहा नभएका फ्रेम प्रकारहरू बेवास्ता गर्नुहोस्: नयाँहरू सूचना बिना थपिन्छन्।
यहाँ टर्मिनललाई क्याप्शन दृश्य बनाएर Python SDK को पढ्ने लूप छ। यहाँ पनि वाक्य जोड्ने सुविधा बन्द छ, त्यसैले अनुवादहरू transcript.translated का रूपमा आउँछन्, र error फ्रेम लूपबाट उठाइएको VocalStackError का रूपमा देखिन्छ:
import asyncio
import os
from vocalstack import RealtimeSession, VocalStack, VocalStackError
MIC = ["ffmpeg", "-loglevel", "error", "-f", "avfoundation", "-i", ":0",
"-f", "f32le", "-ac", "1", "-ar", "16000", "-"]
async def main() -> None:
async with VocalStack("{{api}}", api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
created = await vs.realtime.create_session(
spoken_language="en", translate=["es"], sentences={"enabled": False}
)
session = await RealtimeSession.connect(created.session_id, created.stream_url)
mic = await asyncio.create_subprocess_exec(*MIC, stdout=asyncio.subprocess.PIPE)
async def feed() -> None:
assert mic.stdout is not None
while chunk := await mic.stdout.read(8000): # 125 ms of audio
await session.send_audio(chunk)
await session.eof() # the server flushes, then sends session.done
feeder = asyncio.create_task(feed())
try:
async with session:
async for event in session:
if event.type == "transcript.draft":
# The live tail: replace it, never append it.
tail = " ".join(s.text for s in event.segments)
print(f"\r\x1b[K\x1b[2m{tail}\x1b[0m", end="", flush=True)
elif event.type == "transcript.committed":
# Final: append it (seq 0, 1, 2...), and the tail it covers is gone.
print(f"\r\x1b[K{event.segment.text}", flush=True)
elif event.type == "transcript.translated":
print(f" es: {event.translations.get('es')}")
await feeder
print(f"done ({session.done_reason})", session.stats)
except VocalStackError as e:
# An `error` frame mid-stream, or a refused connect.
print(f"\nerror {e.code}: {e.message} (retryable: {e.retryable})")
finally:
if mic.returncode is None:
mic.kill()
asyncio.run(main())
माथि जस्तै खण्ड-खण्ड अनुवाद सरल रूप हो। पाठकहरूले मन पराउने रूपलाई छुट्टै खण्ड दिइएको छ।
translate: ["es", "fr"] ले सत्र चल्दै गर्दा अनुवाद माग्छ। ती कसरी आउँछन् भन्ने कुरा वाक्य जोड्ने सुविधा (sentences: { enabled: true }) मा निर्भर गर्छ, जुन पूर्वनिर्धारित रूपमा सक्रिय हुन्छ।
वाक्य बन्द हुँदा, हरेक कमिटपछि केही क्षणमा transcript.translated फ्रेम आउँछ, जसले आफ्नो seq नाम लिन्छ र भाषा अनुसार कुञ्जी गरिएको translations बोक्छ; त्यसलाई त्यो खण्डसँग जोड्नुहोस्। अहिलेसम्मका उदाहरणहरूले यही प्रयोग गरेका छन्। यो सरल छ र कमिट लगजस्तो पढिन्छ: एउटा खण्ड, एउटा अनुवाद, वक्ताले सास फेरेको जुनसुकै ठाउँमा वाक्यको सीमा।
वाक्य सक्रिय हुँदा, सर्भरले कमिटहरूलाई पूरा वाक्यहरूमा जोड्छ र तिनको अनुवाद गर्छ, जुन धेरै राम्रो पढिन्छ। हरेक transcript.sentence फ्रेम sentenceId द्वारा कुञ्जी गरिएको एउटा वाक्यको पूर्ण प्रतिस्थापन हो। यसको status open बाट closed मा जान्छ (वा dropped, जसको अर्थ यसलाई हटाउनु हो), र यसको translations खुला रहँदा अस्थायी हुन्छन् र बन्द भएपछि अन्तिम हुन्छन्। sentenceId द्वारा upsert (छ भने बदल्ने, नभए थप्ने) गर्नुहोस्, कहिल्यै थप्नु हुँदैन। TypeScript SDK ले ठीक यही कामका लागि applySentence दिन्छ:
import { applySentence, type SentenceSegment } from '@vocalstack/sdk';
let sentences: SentenceSegment[] = [];
for await (const ev of session) {
if (ev.type !== 'transcript.sentence') continue;
sentences = applySentence(sentences, ev.sentence); // upsert by sentenceId
if (ev.sentence.status === 'closed') console.log(`${ev.sentence.text}\n es: ${ev.sentence.translations?.es}`);
}
अनुवादहरू ट्रान्सक्रिप्टभन्दा एक-दुई सेकेन्ड पछि आउँछन्। यदि तपाईं तिनलाई बोलिएको पनि चाहनुहुन्छ भने, voice: ["es"] थप्नुहोस् (यसलाई वाक्यहरू चाहिन्छ)। त्यसपछि हरेक बन्द वाक्यको स्पेनिस transcript.voice फ्रेमका रूपमा आउँछ, जसमा 24 kHz मोनोमा base64 Ogg/Opus क्लिप हुन्छ। लामो वाक्य टुक्राहरूमा (parts को part) आउँछ, जसलाई एकपछि अर्को गरी बजाउन सकिन्छ। अस्थायी पाठ कहिल्यै बोलिँदैन। लाइभ अनुवाद गाइड ले आवाजहरूबारे विस्तृत जानकारी दिन्छ, र सेकेन्डहरू कहाँ जान्छन् ले ढिलाइको शृंखला बुझाउँछ।
लक्ष्य भाषाहरू सिर्जनाको समयमा तय हुँदैनन्।
session.update पाठ फ्रेमले त्यहींबाट spokenLanguage, translate, speakerLabels, latencyMode, voice वा voiceByLang परिवर्तन गर्छ; जुन फिल्ड छाड्नुहुन्छ त्यो जस्ताको तस्तै रहन्छ।
{"type":"session.update","translate":["es","fr"],"latencyMode":"fast"}
SDK हरूले यसलाई session.update(…) का रूपमा देखाउँछन्। translate ले पूरै सूची बदल्छ, र [] ले अनुवाद बन्द गर्छ। पहिल्यै पठाइएका खण्डहरू फेरि ट्रान्सक्रिप्ट वा अनुवाद गरिँदैनन्, र वक्ता लेबल सक्रिय गर्नुभयो भने वक्ताहरू त्यही बिन्दुदेखि मात्र लेबल हुन्छन्। जे भनियो त्यो भनिएकै रहन्छ; सेटिङहरूले अर्को आउने कुरालाई मात्र नियन्त्रण गर्छन्।
अडियो सकिँदा, {"type":"eof"} पठाउनुहोस् (SDK मा session.eof())। सर्भरले अझै राखेको कुरा ट्रान्सक्रिप्ट गर्छ, अन्तिम कमिटहरू पठाउँछ, त्यसपछि session.done पठाएर बन्द गर्छ। त्यसको सट्टा cancel पठाउनुभयो भने तुरुन्तै रोकिन्छ र बाँकी हटाइन्छ।
{"type":"session.done","reason":"eof","stats":{"audioS":30.0,"processingS":30.6,"realtimeFactor":0.98}}
reason eof, max_duration (सत्र योजनाको अधिकतम लम्बाइमा पुग्यो र उही फ्लस चलायो) वा cancelled हुन सक्छ। stats सर्भरले नाप्छ: audioS तपाईंले पठाएको अडियो हो, processingS सत्र चलेको वास्तविक समय (वाल-क्लक) हो, र realtimeFactor तिनीहरूको अनुपात हो। लाइभ सत्र 1 भन्दा अलि कम आउँछ, किनभने वाल-क्लक समयमा तपाईंको eof पछिको अन्तिम फ्लस पनि पर्छ। त्यो भन्दा धेरै कम संख्याले सत्र अडियोको अवधिभन्दा उल्लेखनीय रूपमा लामो चलेको देखाउँछ; पहिले हेर्ने ठाउँ अडियो कति छिटो आयो भन्ने हो। फाइलको लागि, उही संख्याले ट्रान्सक्रिप्ट प्लेब्याकभन्दा कति गुणा छिटो फर्कियो भन्ने बताउँछ।
सत्र GET /v1/realtime/capabilities बाट limits.maxDurationS भन्दा बढी चल्दैन, र यसको अडियोले तपाईंको योजनाको लाइभ दरमा प्रति सेकेन्ड क्रेडिट खर्च गर्छ, जुन कुनै पनि ट्रान्सक्रिप्शनजस्तै 5 मिनेटको न्यूनतमसहित: अडियो प्राप्त गर्ने हरेक सत्रलाई कम्तीमा 5 मिनेट बिल हुन्छ (मूल्य)। उही सकेटले पूरै फाइल पनि लिन्छ: input: { kind: "file" } सहित सत्र सिर्जना गर्नुहोस्, फाइलको आफ्नै बाइट (mp3, wav, flac, ogg, m4a) र eof पठाउनुहोस्, र ट्रान्सक्रिप्ट अडियो बज्ने गतिभन्दा धेरै गुणा छिटो फर्कन्छ, बीचबीचमा transcript.progress फ्रेमसहित।
पुनः सुरु गर्ने सुविधा छैन। जडान टुटेमा सत्र सकिन्छ; फेरि जडान गर्नुको अर्थ नयाँ सत्र सिर्जना गर्नु हो, जसको आफ्नै न्यूनतम बिल हुन्छ, र तपाईंको लाइभ स्रोतबाट फेरि स्ट्रिम गर्नु हो, seq 0 बाट सुरु हुँदै। पुनः जडानको यो ढाँचा कुनै पनि सकेट API का लागि मानक हो, र एक पटक ठीकसँग गर्न लायक छ:
error फ्रेममा retryable: true, Retry-After सहितको 503, वा कुनै फ्रेम नआई 30 सेकेन्ड, जसले capacity_exceeded जनाउँछ। invalid_token र session_expired को अर्थ तपाईंको सर्भरबाट नयाँ streamUrl चाहिन्छ, पुरानोसँग अर्को प्रयास होइन।सत्र सिर्जना गर्ने मार्ग सस्तो रूपमा कल गर्न मिल्ने बनाउनुहोस् र बन्द हुनुलाई नयाँ सुरुवात मान्नुहोस्। पुनः सुरु गर्ने सुविधा नहुनु केही पनि भण्डारण नगर्ने मूल्य हो; यदि ट्रान्सक्रिप्ट सकेटभन्दा लामो बाँच्नुपर्छ भने, त्यसलाई राख्नुहोस्।
उही स्ट्रिम रेकर्डिङका रूपमा भण्डारण गर्न, पछि सुधार्न, निर्यात गर्न, र दर्शकहरूले आफ्नै भाषामा पढ्न चाहनुहुन्छ भने, त्यसको सट्टा त्यसलाई लाइभ ट्रान्सक्रिप्सनमा प्रकाशित गर्नुहोस्। लाइभ जाने गाइड ले उही PCM सकेटलाई ट्रान्सक्रिप्सन र प्रसारण लिङ्कमार्फत देखाउँछ, र सुन्ने गाइड ले सदस्य पक्षलाई देखाउँछ, जहाँ हरेक श्रोताले भाषा छान्छ र ढिलो सामेल हुने व्यक्ति lastSeq कर्सरबाट पछाडि परेको कुरा समात्छ। कोठाका लागि त्यो कस्तो देखिन्छ भन्ने विषय एउटा लिङ्क, हरेक भाषा को हो।
राख्न चाहनुभएको फाइलको लागि, भण्डारण गरिएको मार्ग API मार्फत फाइलको ट्रान्सक्रिप्सन गर्नुहोस् हो। हरेक फिल्डसहितको पूर्ण फ्रेम सन्दर्भ API सन्दर्भको माइक्रोफोन स्ट्रिम गर्नुहोस् गाइड हो।