speech-recognition
Войти
← К документации API

Примеры интеграции API на популярных языках

Готовые примеры работы с API распознавания речи: полный цикл расшифровки файла — создание задачи, загрузка, запуск и получение результата — на Python, Node.js, PHP, Go и C#, а для Python, Node.js и Go — ещё и живые субтитры по WebSocket. Каждый пример самодостаточен: подставьте свой API-ключ и путь к файлу — и запускайте.

Полное описание методов, параметров и событий — в документации API. Ключ создаётся в разделе «API-ключи» личного кабинета.

Перед началом


Python

Зависимости: pip install requests, для живых субтитров — ещё websockets и sounddevice (микрофон).

Расшифровка файла

import os
import time

import requests

API = 'https://speech-recognition.ru/api/v1'
HEADERS = {'Authorization': 'Bearer sk_live_ваш_ключ'}
PATH = 'meeting.mp3'

# 1. Создать задачу — в ответ придёт временная ссылка для загрузки
resp = requests.post(f'{API}/jobs', headers=HEADERS, json={
    'fileName': os.path.basename(PATH),
    'fileSize': os.path.getsize(PATH),
    'mode': 'DIARIZED_SUMMARY',
    'summaryTemplate': 'meeting',
})
resp.raise_for_status()
job = resp.json()

# 2. Загрузить файл (без заголовка Authorization — подпись уже в ссылке)
with open(PATH, 'rb') as f:
    requests.put(job['uploadUrl'], data=f).raise_for_status()

# 3. Запустить обработку
requests.post(f"{API}/jobs/{job['jobId']}/start", headers=HEADERS).raise_for_status()

# 4. Дождаться результата (либо укажите callbackUrl при создании задачи)
while True:
    time.sleep(10)
    status = requests.get(f"{API}/jobs/{job['jobId']}", headers=HEADERS).json()['job']
    if status['status'] in ('DONE', 'FAILED'):
        break

if status['status'] == 'DONE':
    print(status['result']['text'])
    print(status['result']['summary'])
else:
    print('Ошибка:', status['error'])

Живые субтитры

Пример пишет минуту с микрофона, печатает субтитры в консоль и штатно завершает сессию — полная расшифровка записи придёт обычной задачей.

# pip install requests websockets sounddevice
import asyncio
import json

import requests
import sounddevice as sd
import websockets

API = 'https://speech-recognition.ru/api/v1'
HEADERS = {'Authorization': 'Bearer sk_live_ваш_ключ'}

async def main():
    resp = requests.post(f'{API}/live/token', headers=HEADERS)
    resp.raise_for_status()
    grant = resp.json()

    async with websockets.connect(grant['wsUrl']) as ws:
        await ws.send(json.dumps({'type': 'start', 'token': grant['token'], 'lang': 'ru-RU'}))

        session_open = asyncio.Event()
        frames: asyncio.Queue = asyncio.Queue()
        loop = asyncio.get_running_loop()

        def on_audio(indata, *_):
            loop.call_soon_threadsafe(frames.put_nowait, bytes(indata))

        async def send_audio():
            await session_open.wait()  # аудио до события session сервер отбрасывает
            # 640 сэмплов int16 с микрофона = фрейм 40 мс (1280 байт)
            with sd.RawInputStream(samplerate=16000, channels=1, dtype='int16',
                                   blocksize=640, callback=on_audio):
                while True:
                    await ws.send(await frames.get())

        async def keepalive():
            while True:
                await asyncio.sleep(25)
                await ws.send(json.dumps({'type': 'ping'}))

        async def stop_after(seconds):
            await asyncio.sleep(seconds)  # демо: минута записи, затем штатный stop
            await ws.send(json.dumps({'type': 'stop'}))

        tasks = [asyncio.create_task(t) for t in (send_audio(), keepalive(), stop_after(60))]
        try:
            async for raw in ws:
                msg = json.loads(raw)
                ev = msg.get('ev')
                if ev == 'session':
                    session_open.set()
                elif ev == 'partial':
                    print('\r' + msg['text'], end='', flush=True)
                elif ev == 'final':
                    print('\r' + msg['text'])
                elif ev == 'session_end':
                    print('Готово, запись ушла в обработку:', msg.get('jobId'))
                    break
                elif ev == 'error':
                    print('Ошибка:', msg.get('code'), msg.get('message'))
                    break
        finally:
            for t in tasks:
                t.cancel()

asyncio.run(main())

Node.js

Node 18+ — fetch встроен. Для живых субтитров — npm install ws и установленный ffmpeg.

Расшифровка файла

// Node 18+ (встроенный fetch); в package.json: { "type": "module" }
import { basename } from 'node:path';
import { readFile } from 'node:fs/promises';

const API = 'https://speech-recognition.ru/api/v1';
const auth = { Authorization: 'Bearer sk_live_ваш_ключ' };
const path = 'meeting.mp3';

// fetch не бросает на 4xx/5xx — без проверки статуса ошибка API потерялась бы
async function api(url, options) {
  const r = await fetch(url, options);
  if (!r.ok) throw new Error('HTTP ' + r.status + ': ' + (await r.text()));
  return r;
}

const file = await readFile(path);

// 1. Создать задачу — в ответ придёт временная ссылка для загрузки
const { jobId, uploadUrl } = await api(API + '/jobs', {
  method: 'POST',
  headers: { ...auth, 'Content-Type': 'application/json' },
  body: JSON.stringify({ fileName: basename(path), fileSize: file.byteLength }),
}).then((r) => r.json());

// 2. Загрузить файл (без заголовка Authorization — подпись уже в ссылке)
await api(uploadUrl, { method: 'PUT', body: file });

// 3. Запустить обработку
await api(API + '/jobs/' + jobId + '/start', { method: 'POST', headers: auth });

// 4. Дождаться результата (либо укажите callbackUrl при создании задачи)
let job;
do {
  await new Promise((r) => setTimeout(r, 10_000));
  ({ job } = await api(API + '/jobs/' + jobId, { headers: auth }).then((r) => r.json()));
} while (job.status !== 'DONE' && job.status !== 'FAILED');

console.log(job.status === 'DONE' ? job.result.text : 'Ошибка: ' + job.error);

Живые субтитры

Пример стримит готовую запись в реальном темпе (ffmpeg -re) — удобно для отладки без микрофона; источник легко заменить на любой поток PCM.

// npm install ws; нужен установленный ffmpeg; в package.json: { "type": "module" }
import { spawn } from 'node:child_process';
import WebSocket from 'ws';

const API = 'https://speech-recognition.ru/api/v1';
const auth = { Authorization: 'Bearer sk_live_ваш_ключ' };

const grant = await fetch(API + '/live/token', { method: 'POST', headers: auth });
if (!grant.ok) throw new Error(await grant.text());
const { token, wsUrl } = await grant.json();

const ws = new WebSocket(wsUrl);
ws.on('open', () => ws.send(JSON.stringify({ type: 'start', token, lang: 'ru-RU' })));
const ping = setInterval(() => ws.send(JSON.stringify({ type: 'ping' })), 25_000);

function streamFile() {
  // Декодируем запись в PCM s16le 16 кГц mono; -re отдаёт поток в реальном темпе.
  // stderr глушим: не вычитанный пайп заполнил бы OS-буфер и подвесил ffmpeg
  const ffmpeg = spawn(
    'ffmpeg',
    ['-re', '-i', 'meeting.mp3', '-f', 's16le', '-ar', '16000', '-ac', '1', '-'],
    { stdio: ['ignore', 'pipe', 'ignore'] },
  );
  let buf = Buffer.alloc(0);
  ffmpeg.stdout.on('data', (chunk) => {
    buf = Buffer.concat([buf, chunk]);
    while (buf.length >= 1280) { // фреймы по 40 мс
      ws.send(buf.subarray(0, 1280));
      buf = buf.subarray(1280);
    }
  });
  ffmpeg.on('close', () => ws.send(JSON.stringify({ type: 'stop' })));
}

ws.on('message', (raw, isBinary) => {
  if (isBinary) return;
  const msg = JSON.parse(String(raw));
  if (msg.ev === 'session') streamFile(); // аудио до этого события сервер отбрасывает
  if (msg.ev === 'partial') process.stdout.write('\r' + msg.text);
  if (msg.ev === 'final') console.log('\r' + msg.text);
  if (msg.ev === 'session_end') {
    console.log('Полная расшифровка будет в задаче:', msg.jobId);
    clearInterval(ping);
    ws.close();
  }
  if (msg.ev === 'error') {
    console.error('Ошибка:', msg.code, msg.message);
    clearInterval(ping);
    ws.close();
  }
});

PHP

Достаточно стандартного расширения curl. Для живых субтитров в PHP понадобится WebSocket-клиент (например, Pawl) — протокол описан в документации.

<?php
$api = 'https://speech-recognition.ru/api/v1';
$headers = ['Authorization: Bearer sk_live_ваш_ключ', 'Content-Type: application/json'];
$path = 'meeting.mp3';

function http(string $method, string $url, array $headers = [], ?string $body = null): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
    ]);
    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }
    $response = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($code >= 400) {
        throw new RuntimeException("HTTP $code: $response");
    }
    return $response ? (json_decode($response, true) ?? []) : [];
}

// 1. Создать задачу — в ответ придёт временная ссылка для загрузки
$job = http('POST', "$api/jobs", $headers, json_encode([
    'fileName' => basename($path),
    'fileSize' => filesize($path),
]));

// 2. Загрузить файл (без заголовка Authorization — подпись уже в ссылке)
http('PUT', $job['uploadUrl'], [], file_get_contents($path));

// 3. Запустить обработку
http('POST', "$api/jobs/{$job['jobId']}/start", $headers);

// 4. Дождаться результата (либо укажите callbackUrl при создании задачи)
do {
    sleep(10);
    $status = http('GET', "$api/jobs/{$job['jobId']}", $headers)['job'];
} while (!in_array($status['status'], ['DONE', 'FAILED'], true));

echo $status['status'] === 'DONE'
    ? $status['result']['text']
    : "Ошибка: {$status['error']}";

Go

Стандартной библиотеки хватает для расшифровки файла; для живых субтитров — go get github.com/gorilla/websocket и установленный ffmpeg.

Расшифровка файла

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

const api = "https://speech-recognition.ru/api/v1"
const key = "Bearer sk_live_ваш_ключ"

func call(method, url string, body []byte, out any) error {
	req, err := http.NewRequest(method, url, bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", key)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		return fmt.Errorf("HTTP %d", resp.StatusCode)
	}
	if out != nil {
		return json.NewDecoder(resp.Body).Decode(out)
	}
	return nil
}

func main() {
	data, err := os.ReadFile("meeting.mp3")
	if err != nil {
		panic(err)
	}

	// 1. Создать задачу — в ответ придёт временная ссылка для загрузки
	var created struct {
		JobID     string `json:"jobId"`
		UploadURL string `json:"uploadUrl"`
	}
	body, _ := json.Marshal(map[string]any{"fileName": "meeting.mp3", "fileSize": len(data)})
	if err := call("POST", api+"/jobs", body, &created); err != nil {
		panic(err)
	}

	// 2. Загрузить файл (без заголовка Authorization — подпись уже в ссылке)
	put, _ := http.NewRequest("PUT", created.UploadURL, bytes.NewReader(data))
	upload, err := http.DefaultClient.Do(put)
	if err != nil {
		panic(err)
	}
	upload.Body.Close()
	if upload.StatusCode >= 400 {
		panic(fmt.Sprintf("загрузка не удалась: HTTP %d", upload.StatusCode))
	}

	// 3. Запустить обработку
	if err := call("POST", api+"/jobs/"+created.JobID+"/start", nil, nil); err != nil {
		panic(err)
	}

	// 4. Дождаться результата (либо укажите callbackUrl при создании задачи)
	var st struct {
		Job struct {
			Status string `json:"status"`
			Error  string `json:"error"`
			Result struct {
				Text    string `json:"text"`
				Summary string `json:"summary"`
			} `json:"result"`
		} `json:"job"`
	}
	for st.Job.Status != "DONE" && st.Job.Status != "FAILED" {
		time.Sleep(10 * time.Second)
		if err := call("GET", api+"/jobs/"+created.JobID, nil, &st); err != nil {
			panic(err)
		}
	}
	if st.Job.Status == "DONE" {
		fmt.Println(st.Job.Result.Text)
	} else {
		fmt.Println("Ошибка:", st.Job.Error)
	}
}

Живые субтитры

// go get github.com/gorilla/websocket; нужен установленный ffmpeg
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os/exec"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

const api = "https://speech-recognition.ru/api/v1"

func main() {
	// 1. Одноразовый токен
	req, _ := http.NewRequest("POST", api+"/live/token", nil)
	req.Header.Set("Authorization", "Bearer sk_live_ваш_ключ")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	var grant struct {
		Token string `json:"token"`
		WsURL string `json:"wsUrl"`
	}
	json.NewDecoder(resp.Body).Decode(&grant)
	resp.Body.Close()

	// 2. Подключение и start
	ws, _, err := websocket.DefaultDialer.Dial(grant.WsURL, nil)
	if err != nil {
		panic(err)
	}
	// gorilla/websocket не разрешает конкурентную запись — все Write под мьютексом
	var mu sync.Mutex
	writeJSON := func(v any) {
		mu.Lock()
		defer mu.Unlock()
		ws.WriteJSON(v)
	}
	writeJSON(map[string]any{"type": "start", "token": grant.Token, "lang": "ru-RU"})

	go func() { // ping каждые 25 c — иначе NAT может закрыть тихое соединение
		for range time.Tick(25 * time.Second) {
			writeJSON(map[string]string{"type": "ping"})
		}
	}()

	for {
		_, raw, err := ws.ReadMessage()
		if err != nil {
			return
		}
		var msg map[string]any
		json.Unmarshal(raw, &msg)
		switch msg["ev"] {
		case "session": // сессия открыта — аудио до этого события сервер отбрасывает
			go stream(&mu, ws, writeJSON)
		case "partial":
			fmt.Print("\r", msg["text"])
		case "final":
			fmt.Println("\r", msg["text"])
		case "session_end":
			fmt.Println("Полная расшифровка будет в задаче:", msg["jobId"])
			return
		case "error":
			fmt.Println("Ошибка:", msg["code"], msg["message"])
			return
		}
	}
}

// Декодирует запись в PCM s16le 16 кГц mono и шлёт фреймами по 40 мс
// в реальном темпе (-re); хвост короче фрейма отбрасывается
func stream(mu *sync.Mutex, ws *websocket.Conn, writeJSON func(any)) {
	cmd := exec.Command("ffmpeg", "-re", "-i", "meeting.mp3",
		"-f", "s16le", "-ar", "16000", "-ac", "1", "-")
	out, _ := cmd.StdoutPipe()
	cmd.Start()
	frame := make([]byte, 1280)
	for {
		if _, err := io.ReadFull(out, frame); err != nil {
			break
		}
		mu.Lock()
		ws.WriteMessage(websocket.BinaryMessage, frame)
		mu.Unlock()
	}
	writeJSON(map[string]string{"type": "stop"})
}

C#

.NET 8 без внешних пакетов. Для живых субтитров используйте штатный ClientWebSocket — протокол описан в документации.

// .NET 8, top-level statements
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

var api = "https://speech-recognition.ru/api/v1";
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "sk_live_ваш_ключ");

var path = "meeting.mp3";
var bytes = await File.ReadAllBytesAsync(path);

// 1. Создать задачу — в ответ придёт временная ссылка для загрузки
var created = await http.PostAsJsonAsync(api + "/jobs",
    new { fileName = Path.GetFileName(path), fileSize = bytes.Length });
created.EnsureSuccessStatusCode();
var job = await created.Content.ReadFromJsonAsync<JsonElement>();
var jobId = job.GetProperty("jobId").GetString();

// 2. Загрузить файл отдельным клиентом БЕЗ Authorization —
// подпись уже в ссылке, S3 отклоняет запрос с двумя авторизациями
using (var s3 = new HttpClient())
{
    var put = await s3.PutAsync(job.GetProperty("uploadUrl").GetString(),
        new ByteArrayContent(bytes));
    put.EnsureSuccessStatusCode();
}

// 3. Запустить обработку
(await http.PostAsync(api + "/jobs/" + jobId + "/start", null)).EnsureSuccessStatusCode();

// 4. Дождаться результата (либо укажите callbackUrl при создании задачи)
JsonElement st;
do
{
    await Task.Delay(TimeSpan.FromSeconds(10));
    st = (await http.GetFromJsonAsync<JsonElement>(api + "/jobs/" + jobId))
        .GetProperty("job");
} while (st.GetProperty("status").GetString() is not ("DONE" or "FAILED"));

Console.WriteLine(st.GetProperty("status").GetString() == "DONE"
    ? st.GetProperty("result").GetProperty("text").GetString()
    : "Ошибка: " + st.GetProperty("error").GetString());