СОЗДАТЬ_КЛЮЧ.bat в папке D:\AiYURAS_API.Звук идёт на сервер по ходу речи, текст возвращается сразу: серым — пока человек говорит, чёрным — когда фраза закончилась, уже со знаками препинания.
Русский, узбекский (латиница) и английский. Кусок на нужном языке оставляется как есть.
Так это будет у коллеги в его конференции: звук каждого участника — отдельный поток, поэтому кто говорит, известно без распознавания голосов. Здесь роль участника играет ваша камера и микрофон.
WAV, 16 бит, до 30 минут. Длиннее — частями: Cloudflare обрывает ответ, который готовится дольше 100 секунд.
Адрес: {HTTP}. Ключ — в заголовке Authorization: Bearer <ключ>.
| Команда | Что делает |
|---|---|
WS /v1/stream | живая стенограмма: звук по ходу речи → текст, по желанию перевод |
POST /v1/translate | перевод текста |
POST /v1/transcribe | стенограмма готового WAV (до 30 минут) |
GET /v1/whoami | проверка ключа |
GET /v1/health | работают ли службы (без ключа) |
Подключиться к {WS}. Первое сообщение — start (JSON), дальше звук: двоичные сообщения, PCM 16 бит, моно, little-endian, по 20–200 мс. Частота любая от 8000 до 48000 Гц — укажите её в sample_rate, сервер пересчитает сам.
{"type": "start",
"key": "aiy…", // если не передан заголовком Authorization
"sample_rate": 48000,
"punctuation": true, // знаки препинания и заглавные
"partial": true, // промежуточный текст, пока человек говорит
"translate_to": "ru", // ru | uz | en | null
"latin": "uz"} // латиница — узбекский (uz) или английский (en)
Ответы сервера (JSON):
{"type":"ready", "session":"…"} // можно слать звук
{"type":"partial", "start":3.2, "text":"…"} // пока говорит
{"type":"final", "id":1, "start":3.2, "end":6.9,
"text":"Фраза со знаками.", "raw":"фраза без знаков", "lang":"ru"}
{"type":"translation", "id":1, "to":"ru", "text":"…"} // перевод фразы id
{"type":"error", "message":"…"}
{"type":"end", "audio_sec":123.4, "phrases":17} // после {"type":"stop"}
Команды по ходу: {"type":"config","translate_to":"uz"} — сменить перевод, {"type":"stop"} — закончить (сервер дорасшифрует последнюю фразу, пришлёт end и закроет поток). Без звука дольше 2 минут поток закрывается сам.
// mediaStream — микрофон или звук участника: new MediaStream([remoteAudioTrack])
async function aiyurasStream(mediaStream, opts, onMsg) {
const ws = new WebSocket("{WS}");
ws.binaryType = "arraybuffer";
const ctx = new AudioContext();
const src = ctx.createMediaStreamSource(mediaStream);
const code = "class Tap extends AudioWorkletProcessor{process(i){const c=i[0]&&i[0][0];" +
"if(c)this.port.postMessage(c.slice(0));return true}}registerProcessor('tap',Tap)";
await ctx.audioWorklet.addModule(URL.createObjectURL(new Blob([code], {type: "application/javascript"})));
const tap = new AudioWorkletNode(ctx, "tap"), mute = ctx.createGain();
mute.gain.value = 0; src.connect(tap); tap.connect(mute); mute.connect(ctx.destination);
let parts = [], len = 0, ready = false;
tap.port.onmessage = (e) => {
if (!ready) return;
parts.push(e.data); len += e.data.length;
if (len < ctx.sampleRate / 10) return; // шлём кусками по 100 мс
const pcm = new Int16Array(len); let o = 0;
for (const p of parts) for (const s of p) pcm[o++] = Math.max(-1, Math.min(1, s)) * 32767;
parts = []; len = 0;
if (ws.readyState === 1) ws.send(pcm.buffer);
};
ws.onopen = () => ws.send(JSON.stringify({type: "start", key: opts.key,
sample_rate: ctx.sampleRate, translate_to: opts.translate_to || null}));
ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.type === "ready") ready = true; onMsg(m); };
ws.onclose = () => ctx.close();
return { stop: () => ws.send(JSON.stringify({type: "stop"})) };
}
// в конференции — на каждого участника свой поток:
pc.ontrack = (e) => {
if (e.track.kind !== "audio") return;
aiyurasStream(new MediaStream([e.track]), {key: KEY, translate_to: "ru"}, (m) => {
if (m.type === "partial") showCaption(e.streams[0].id, m.text, true);
if (m.type === "final") showCaption(e.streams[0].id, m.text, false);
if (m.type === "translation") showTranslation(e.streams[0].id, m.id, m.text);
});
};
import asyncio, json, wave
import websockets # pip install websockets
KEY = "aiy…"
URL = "{WS}"
async def main(path):
hdr = {"Authorization": "Bearer " + KEY}
async with websockets.connect(URL, additional_headers=hdr) as ws: # старые версии: extra_headers=
w = wave.open(path) # WAV, 16 бит, моно
await ws.send(json.dumps({"type": "start", "sample_rate": w.getframerate(),
"translate_to": "ru"}))
print(await ws.recv()) # ready
async def read():
async for msg in ws:
print(json.loads(msg))
reader = asyncio.create_task(read())
step = w.getframerate() // 10 # 100 мс
while chunk := w.readframes(step):
await ws.send(chunk)
await asyncio.sleep(0.1) # темп живой речи
await ws.send(json.dumps({"type": "stop"}))
await reader
asyncio.run(main("zapis.wav"))
curl -X POST "{HTTP}/v1/translate" ^
-H "Authorization: Bearer aiy…" -H "Content-Type: application/json" ^
-d "{\"text\": \"Sud majlisi ochiq deb e'lon qilinadi.\", \"to\": \"ru\"}"
→ {"text": "Судебное заседание объявляется открытым.", "to": "ru"}
to: ru, uz, en. from: "en", если латиница — английский (по умолчанию узбекский). До 5000 знаков за раз, до 120 запросов в минуту.
curl -X POST "{HTTP}/v1/transcribe?translate_to=ru" ^
-H "Authorization: Bearer aiy…" -H "Content-Type: audio/wav" ^
--data-binary @zapis.wav
→ {"duration": 95.2, "text": "…",
"segments": [{"start": 0.4, "end": 5.1, "text": "…", "translation": "…"}, …]}
Можно и сырой PCM 16 бит: тогда частоту — в адресе, ?sample_rate=16000. Параметры: punctuation=0 — без знаков, translate_to, latin=en.
401 / закрытие 4401 | неверный или отозванный ключ |
429 / закрытие 4429 | слишком много одновременных потоков или переводов |
400 / закрытие 4400 | неверный запрос (формат, частота, размер) |
503 | модель загружается или недоступна — повторить через несколько секунд |