ГлавнаяБлог → Распознавание речи на Go: рабочий код на стандартной библиотеке

Распознавание речи на Go: рабочий код на стандартной библиотеке

Опубликовано 2026-08-20 · 5 мин чтения

В Go для отправки аудио не нужен ни SDK, ни сторонние пакеты: multipart-запрос собирается стандартной библиотекой в двадцать строк. Ниже готовая функция, обработка ошибок и параллельный разбор архива записей.

Функция отправки

package main

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

func transcribe(path, key string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	var body bytes.Buffer
	w := multipart.NewWriter(&body)
	part, _ := w.CreateFormFile("file", f.Name())
	if _, err := io.Copy(part, f); err != nil {
		return "", err
	}
	w.WriteField("model", "whisper-1")
	w.Close()

	req, _ := http.NewRequest("POST", "https://voicesscribe.com/v1/audio/transcriptions", &body)
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("Content-Type", w.FormDataContentType())

	client := &http.Client{Timeout: 3 * time.Minute}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var out struct {
		Text  string `json:"text"`
		Error struct{ Message string } `json:"error"`
	}
	json.NewDecoder(resp.Body).Decode(&out)
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("статус %d: %s", resp.StatusCode, out.Error.Message)
	}
	return out.Text, nil
}

Обратите внимание на тайм-аут клиента: значение по умолчанию отсутствует вовсе, и запрос на длинной записи может висеть бесконечно.

Разбор ошибок

Ответ об ошибке приходит в том же виде, что и у большинства подобных сервисов: объект error с полем message. Различать стоит две группы: постоянные ошибки (400, 401, 413) и временные (429, 502, сетевые обрывы). Первые повторять бессмысленно, вторые — нужно, с растущей паузой.

func withRetry(path, key string) (string, error) {
	delay := 2 * time.Second
	var last error
	for i := 0; i < 4; i++ {
		text, err := transcribe(path, key)
		if err == nil {
			return text, nil
		}
		last = err
		if !temporary(err) {
			break
		}
		time.Sleep(delay)
		delay *= 2
	}
	return "", last
}

Параллельная обработка архива

Go хорош именно здесь: пул воркеров пишется в несколько строк, а ограничение параллельности защищает от отказа по лимиту запросов.

files := make(chan string)
var wg sync.WaitGroup

for i := 0; i < 4; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		for path := range files {
			text, err := withRetry(path, key)
			if err != nil {
				log.Println(path, err)
				continue
			}
			os.WriteFile(path+".txt", []byte(text), 0o644)
		}
	}()
}

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

Что учесть в бою

Попробуйте на своих записях. Регистрация занимает минуту, бесплатных минут хватает, чтобы оценить качество.

Получить ключ бесплатно

Частые вопросы

Нужен ли SDK для Go?

Нет. Multipart-запрос собирается стандартной библиотекой, внешние зависимости не требуются.

Почему запрос обрывается на длинных записях?

У http.Client по умолчанию нет тайм-аута, а у прокси и балансировщиков он есть. Ставьте свой тайм-аут в пару-тройку минут.

Как передать язык и подсказку?

Дополнительными полями формы: language и prompt рядом с полем model.

Сколько горутин запускать?

Ровно столько, сколько разрешает лимит запросов в секунду вашего тарифа. Превышение вернёт 429 и заставит повторять работу.

Читайте также