Главная → Блог → Как расшифровать запись, которая не влезает в один запрос
Как расшифровать запись, которая не влезает в один запрос
Запись конференции на четыре часа, суточный дамп с диктофона, вебинар с записью экрана — всё это не помещается в один запрос. Задача решается в три шага: сжать, разрезать по паузам, собрать текст обратно. Ниже готовая схема с кодом.
Сначала сжатие, потом нарезка
Прежде чем резать, попробуйте сжать: часто этого достаточно. Час речи в opus весит около 11 МБ, то есть двухчасовая запись укладывается в лимит целиком:
ffmpeg -i conference.wav -vn -ac 1 -ar 16000 -c:a libopus -b:a 24k conference.ogg
Резать имеет смысл, только когда после сжатия файл всё ещё велик или когда хочется обрабатывать куски параллельно ради скорости.
Где резать, чтобы не разорвать фразу
Нарезка равными интервалами проста, но рано или поздно разрежет слово пополам, и на стыке появится мусор. Надёжнее резать по тишине. Сначала находим паузы:
ffmpeg -i long.ogg -af silencedetect=noise=-35dB:d=1 -f null - 2>&1 | grep silence_end
Затем выбираем ближайшую к желаемой длине паузу и режем по ней:
ffmpeg -i long.ogg -ss 0 -to 912.4 -c copy part001.ogg
Практичный компромисс — куски по 10–15 минут: их удобно отправлять параллельно, и потеря на стыке при неудачном разрезе минимальна.
Параллельная обработка
Куски не зависят друг от друга, поэтому их отправляют одновременно. Порядок сохраняем по имени файла:
import concurrent.futures as cf, pathlib
from openai import OpenAI
client = OpenAI(base_url="https://voicesscribe.com/v1", api_key="ваш ключ")
def one(path):
with path.open("rb") as f:
return path.name, client.audio.transcriptions.create(model="whisper-1", file=f).text
parts = sorted(pathlib.Path(".").glob("part*.ogg"))
with cf.ThreadPoolExecutor(max_workers=4) as pool:
result = dict(pool.map(one, parts))
full = "\n".join(result[p.name] for p in parts)
pathlib.Path("conference.txt").write_text(full, encoding="utf-8")
Число потоков подбирайте под лимит запросов в секунду вашего тарифа: если упрётесь, придёт отказ 429, и часть кусков придётся повторить.
Сквозные тайм-коды
Если нужен не просто текст, а навигация по записи, запрашивайте формат с сегментами и прибавляйте к времени смещение куска:
offset = 0.0
for path in parts:
with path.open("rb") as f:
r = client.audio.transcriptions.create(
model="whisper-1", file=f, response_format="verbose_json")
for seg in r.segments:
print(round(seg["start"] + offset, 1), seg["text"].strip())
offset += r.duration
Так тайм-коды остаются привязанными к исходной записи, и по ним можно перематывать оригинальное видео.
Попробуйте на своих записях. Регистрация занимает минуту, бесплатных минут хватает, чтобы оценить качество.
Получить ключ бесплатноЧастые вопросы
Какой предельный размер файла?
25 МБ на один запрос. В opus это примерно два часа речи, поэтому чаще всего достаточно сжатия без всякой нарезки.
Что означает ошибка 413?
Файл больше допустимого размера. Сожмите его в opus или mp3 с низким битрейтом либо разрежьте на части.
Теряется ли смысл на стыке кусков?
Если резать по паузам — нет. При нарезке равными интервалами на границе может пропасть слово, поэтому лучше искать тишину.
Можно ли обрабатывать куски одновременно?
Да, они независимы. Ограничение только одно — количество запросов в секунду по вашему тарифу.
Читайте также
- Подготовка аудио к распознаванию: рецепты ffmpeg — Готовые команды ffmpeg для распознавания речи: извлечь дорожку из видео, свести в моно, убрать тишину, разрезать по паузам и разложить каналы.
- Как построить пакетную транскрибацию архива записей — Как расшифровать тысячи записей и ничего не потерять: очередь работ, параллельность, повторные попытки, продолжение после сбоя и контроль расходов до запуска.
- Ошибки API распознавания речи и что они означают — Разбор кодов ответа API транскрибации: почему приходит 401, 400, 413, 429 и 502, как это чинить и как правильно повторять неудачные запросы.