நேரடி டிரான்ஸ்கிரிப்ட் இரண்டு முறை வருகிறது: முதலில் தொடர்ந்து மாறும் ஒரு டிராஃப்டாக, பின்னர் மாறாத ஓர் இறுதி வரியாக. டிராஃப்டை மாற்றக்கூடிய கடைசிப் பகுதியாகவும், இறுதி வரியை வரலாறாகவும் வரையுங்கள்; நேரடி கேப்ஷன் காட்சியின் மற்றவை எல்லாம் குழாய் வேலைதான்.
நேரலை அமர்வு (realtime session) என்பது 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 heartbeat-களுக்காகக் காத்திருக்கவும்.transcript.draft-ஐப் படிக்கவும் (கடைசிப் பகுதியை மாற்றவும்), transcript.committed-ஐயும் படிக்கவும் (இறுதி வரியைச் சேர்க்கவும்).{"type":"eof"} அனுப்பவும்; session.done-ஐப் படிக்கவும்.படி 1-க்கு மட்டுமே உங்கள் விசை தேவை; அதனால் அது உங்கள் சர்வரில் நடக்கிறது.
Developers, API keys பகுதியில் Write அனுமதியுடன் ஒரு விசையை உருவாக்குங்கள். விசை சர்வர்களுக்கும் ஸ்கிரிப்ட்களுக்கும் மட்டுமே: இணையப் பக்கத்திலிருந்தோ URL-இலோ அனுப்பப்படும் விசையை API ஏற்காது. எனவே உங்கள் சொந்த வழியின் பின்னால் அமர்வை உருவாக்குங்கள்; பக்கம் streamUrl-ஐ மட்டுமே பார்க்கும்.
பொதுவாக அறிந்திருப்பது பயனுள்ள ஓர் உலாவி வரம்பின் காரணமாக, URL தனக்கென ஒரு டோக்கனைச் சுமக்கிறது. உலாவியின் WebSocket கன்ஸ்ட்ரக்டரால் கோரிக்கை தலைப்புகளை (headers) அமைக்க முடியாது; எனவே விசையை வைக்க Authorization தலைப்பும் இல்லை. உலாவியைச் சந்திக்கும் ஒவ்வொரு பேச்சு API-யும் தன் சாக்கெட்டை URL-இல் உள்ள குறுகிய கால டோக்கனால் (அல்லது ஒரு subprotocol பெயருக்குள் மறைத்து) அங்கீகரிக்கிறது. குறுகிய காலம் என்பதுதான் நோக்கம்: 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-தான். இதுவும் ஸ்பானிஷ் கேட்கிறது, கீழே உள்ள கம்பி மாதிரிக்கு ஒத்ததாக வாக்கிய இணைப்பை (sentence assembly) அணைக்கிறது:
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 heartbeat-களை அனுப்புகிறது. பின்னர் 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;
}
});
மூல (raw) கிளையன்ட் இன்னும் இரண்டு விஷயங்களைத் தானே கையாள வேண்டும்; SDK-கள் ஏற்கெனவே செய்கின்றன. முதலாவது, மறுக்கப்பட்ட மேம்படுத்தலில் API-யின் வழக்கமான HTTP பிழை உறை வரும்: 401 invalid_token, 410 session_expired, அல்லது Retry-After தலைப்புடன் 503 unavailable; அதை மதிக்க வேண்டும்.
இரண்டாவது, நிறைவு (saturation) ஒரு பிழையாகத் தெரியாது; அமைதியாகத் தெரியும். 30 வினாடிகளுக்குள் ஒரு சட்டம் கூட வரவில்லை என்றால், அதை capacity_exceeded எனக் கருதி மீண்டும் இணையுங்கள்; புதிய அமர்வு பொதுவாக உள்ளே நுழைந்துவிடும். loading சட்டங்கள் வந்துகொண்டிருந்தும் session.ready வரவே இல்லை என்றால், அதை engine_unavailable எனக் கருதுங்கள்; அதுவும் மீண்டும் முயற்சிக்கத் தகுந்தது. SDK-களில் RealtimeSession.connect session.ready-இல் வாக்குறுதி தீர்வடைகிறது; readyDeadlineMs-க்குப் பின் சரியான code-உடன் தோல்வியடைகிறது.
session.ready வந்ததும், பேசப்படும் வேகத்திலேயே ஆடியோ வர வேண்டும் என்று சர்வர் எதிர்பார்க்கிறது.
ஆடியோ மூல PCM-இன் பைனரி சட்டங்களாக உள்ளே செல்கிறது: 32-பிட் மிதவைப் புள்ளி (float), little-endian, 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-இல், பெரும்பாலும் ஸ்டீரியோவில் பதிவு செய்கின்றன. பைட்கள் வெளியேறுவதற்கு முன் ஆடியோவை மீண்டும் மாதிரி எடுத்து (resample) சேனல்களை ஒன்றாக இணைக்க வேண்டும்: இங்கு அது 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 அந்த வரிசையின் அளவு; ஆரோக்கியமான இணைப்பில் அது பூஜ்யத்துக்கு அருகிலேயே இருக்கும். நேரடி ஆடியோவை இடைநிறுத்த முடியாது; அதனால் வளரும் வரிசைக்குப் பயனுள்ள பதில், அதைக் கவனிப்பதே, சட்டங்களை நீக்குவது அல்ல. சர்வரும் பின்னழுத்தத்தைப் (backpressure) பயன்படுத்துகிறது. நேரத்துக்கு சில வினாடிகள் முன்னதாக ஆடியோவை ஏற்கிறது (limits.audioBufferS), பின்னர் பிடித்துக்கொள்ளும் வரை படிப்பதை நிறுத்துகிறது. திரும்பி வரும் வழியில், கமிட்கள் இழப்பின்றி வரிசையில் வைக்கப்படுகின்றன; டிராஃப்ட்கள் ஒன்றிணைக்கப்படுவதால், மெதுவான வாசிப்பாளர் குறைவான, புதிய டிராஃப்ட்களையே பார்க்கிறார். கமிட்களுடனும் ஈடுகொடுக்க முடியாத கிளையன்ட் slow_consumer மற்றும் retryable: false கொண்ட error சட்டத்தைப் பெற்று துண்டிக்கப்படுகிறது; அது தென்பட்டால், உங்கள் செய்தி கையாளி அதிக வேலை செய்கிறது என்று பொருள்.
உலாவியில், @vocalstack/sdk/mic-இலிருந்து வரும் streamMicrophone மைக்ரோஃபோனைக் கேட்கிறது, 16 kHz-க்கு மாற்றி (resample) துண்டுகளை clock சட்டங்களுடன் அனுப்புகிறது; உலாவி tab-ஐ வேகம் குறைக்கும்போது சர்வர் இடைவெளிகளைக் குறிக்க அவை உதவுகின்றன. பக்கம் உங்கள் வழியிலிருந்து அமர்வைப் பெறுகிறது; விசையை ஒருபோதும் பார்ப்பதில்லை:
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, இடைவெளியின்றி), start, end, text, speaker மற்றும் ஒவ்வொரு சொல்லின் நேரங்களுடன் words கொண்ட ஒரே segment. அதைச் சேர்த்து, அது மூடும் டிராஃப்டை நீக்குங்கள்; அடுத்த டிராஃப்ட் இந்தக் கமிட் முடிந்த இடத்தில் தொடங்கும்.
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 நகலெடுக்கப்பட்ட, மின்னும் உரையைக் காட்டும். உங்கள் கேப்ஷன் காட்சியில் பேச்சாளருக்கு இல்லாத ஒரு தடுமாற்றம் தோன்றும். எல்லாக் கமிட் செய்யப்பட்ட segments-ஐயும், அதன் பின் சமீபத்திய டிராஃப்டையும் வரையுங்கள்; கமிட் வரும்போது டிராஃப்டை அழியுங்கள். ஒரு கமிட்டில் எந்த டிராஃப்டிலும் தோன்றாத சொற்கள் இருக்கலாம்.
மேலே உள்ள 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 ஒரு கமிட் செய்யப்பட்ட segment-ஐ அதே இடத்தில் திருத்துகிறது (அதே 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())
மேலே உள்ளபடி, segment-வாரியான மொழிபெயர்ப்பு எளிய வடிவம். வாசகர்கள் விரும்பும் வடிவத்துக்குத் தனிப் பகுதி உண்டு.
translate: ["es", "fr"] அமர்வு நடக்கும்போதே மொழிபெயர்ப்புகளைக் கேட்கிறது. அவை எப்படி வருகின்றன என்பது வாக்கிய இணைப்பைப் (sentences: { enabled: true }) பொறுத்தது; அது இயல்பாகவே இயக்கத்தில் இருக்கும்.
வாக்கியங்கள் அணைக்கப்பட்டிருந்தால், ஒவ்வொரு கமிட்டையும் சற்று பின்னர் ஒரு transcript.translated சட்டம் பின்தொடர்கிறது; அது அதன் seq-ஐக் குறிப்பிட்டு, மொழியால் குறியிடப்பட்ட translations-ஐச் சுமக்கிறது; அவற்றை அந்த segment-உடன் இணையுங்கள். இதுவரை எடுத்துக்காட்டுகள் பயன்படுத்தியது இதுதான். இது எளிமையானது, கமிட் பதிவு போலத் தெரியும்: ஒரு segment, ஒரு மொழிபெயர்ப்பு; பேச்சாளர் மூச்சுவிடும் இடமெல்லாம் வாக்கிய எல்லை.
வாக்கியங்கள் இயக்கத்தில் இருந்தால், சர்வர் கமிட்களை முழு வாக்கியங்களாக இணைத்து அவற்றை மொழிபெயர்க்கிறது; அது மிகச் சிறப்பாகப் படிக்கும். ஒவ்வொரு 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"] சேர்க்கவும் (அதற்கு வாக்கியங்கள் தேவை). ஒவ்வொரு மூடிய வாக்கியத்தின் ஸ்பானிஷ் மொழிபெயர்ப்பு, 24 kHz மோனோவில் base64 Ogg/Opus கிளிப்பைச் சுமந்த transcript.voice சட்டமாக வருகிறது. நீண்ட வாக்கியம் துண்டுகளாக (parts-இன் part) வருகிறது; அவற்றை அடுத்தடுத்து இயக்கலாம். தற்காலிக உரை ஒருபோதும் பேசப்படுவதில்லை. நேரடி மொழிபெயர்ப்பு வழிகாட்டி குரல்களை விரிவாக விளக்குகிறது; வினாடிகள் எங்கே போகின்றன என்பது தாமதச் சங்கிலியை விளக்குகிறது.
இலக்கு மொழிகள் உருவாக்கத்தின்போது நிலையாக அமைக்கப்படுவதில்லை.
ஒரு session.update உரைச் சட்டம் அந்தக் கட்டத்திலிருந்து spokenLanguage, translate, speakerLabels, latencyMode, voice அல்லது voiceByLang-ஐ மாற்றுகிறது; நீங்கள் விட்டுவிடும் புலங்கள் மாறாமல் இருக்கும்.
{"type":"session.update","translate":["es","fr"],"latencyMode":"fast"}
SDK-கள் இதை session.update(…) ஆக வெளிப்படுத்துகின்றன. translate முழுப் பட்டியலையும் மாற்றுகிறது, [] மொழிபெயர்ப்பை அணைக்கிறது. ஏற்கெனவே வழங்கப்பட்ட segments மீண்டும் டிரான்ஸ்கிரைப் அல்லது மொழிபெயர்க்கப்படுவதில்லை; பேச்சாளர் லேபிள்களை இயக்கினால், அந்தக் கட்டத்திலிருந்து மட்டுமே பேச்சாளர்கள் குறிக்கப்படுவார்கள். சொல்லப்பட்டது சொல்லப்பட்டதுதான்; அமைப்புகள் அடுத்து வருவதை மட்டுமே நிர்ணயிக்கின்றன.
ஆடியோ முடியும்போது {"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 குறிப்பில் உள்ள மைக்ரோஃபோனை ஸ்ட்ரீம் செய்யுங்கள் வழிகாட்டியில் உள்ளது.