Локальная транскрипция подкаста через whisper.cpp
Что это?
Локальный инструмент для транскрипции аудиофайлов через whisper.cpp. Устанавливается на MacOS через Homebrew и добавляется как команда voice в ~/bin.
Идея простая: в терминале набираю voice и путь к одному или нескольким файлам, нажимаю Enter, и рядом с каждым исходным аудиофайлом появляется сразу несколько файлов:
*.txt— чистый текст без тайм-кодов*.md— копия того же текста в формате Markdown*.vtt— субтитры WebVTT с тайм-кодами*.srt— субтитры SRT с тайм-кодами
Это подходит не только для подкаста, но и вообще для любой речи: диктофонных записей, лекций, встреч, совещаний, голосовых заметок и других аудиофайлов. Такой набор нужен не столько для публикации субтитров, сколько для себя и «машины»: для поиска по тексту, навигации по записи, дальнейшей чистки через ИИ и сохранения в архив.
Требования
Установка
Установить FFmpeg и whisper.cpp:
1brew install ffmpeg whisper-cppПроверить, что ffmpeg доступен:
1ffmpeg -versionПроверить, что whisper CLI доступен:
1command -v whisper-cli || command -v whisper-cpp || command -v whisperМодель Whisper
whisper.cpp использует отдельные файлы моделей. Модель скачивается один раз и хранится локально.
В этом варианте по умолчанию используется medium:
1mkdir -p ~/models/whisper
2cd ~/models/whisper
3curl -L -o ggml-medium.bin https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.binПроверить, что файл на месте:
1ls -lh ~/models/whisper/ggml-medium.binПри необходимости можно скачать и другие модели, например large-v3, и запускать их вручную через переменную MODEL.
Настройка: команда voice в ~/bin
Папка ~/bin
Создай папку для личных команд, если её ещё нет:
1mkdir -p ~/binСкрипт ~/bin/voice
Создай файл:
1nano ~/bin/voiceВставь код:
1#!/usr/bin/env zsh
2set -u
3set -o pipefail
4
5MODEL="${MODEL:-$HOME/models/whisper/ggml-medium.bin}"
6LANG_ASR="${LANG_ASR:-ru}"
7
8# Находим CLI whisper.cpp
9if command -v whisper-cli >/dev/null 2>&1; then
10 WHISPER_BIN="whisper-cli"
11elif command -v whisper-cpp >/dev/null 2>&1; then
12 WHISPER_BIN="whisper-cpp"
13elif command -v whisper >/dev/null 2>&1; then
14 WHISPER_BIN="whisper"
15else
16 echo "Не найден whisper CLI. Установи: brew install whisper-cpp"
17 exit 1
18fi
19
20command -v ffmpeg >/dev/null 2>&1 || {
21 echo "Не найден ffmpeg. Установи: brew install ffmpeg"
22 exit 1
23}
24
25[[ -f "$MODEL" ]] || {
26 echo "Не найдена модель: $MODEL"
27 exit 1
28}
29
30cmd_name="$(basename "$0")"
31
32if [[ $# -lt 1 ]]; then
33 echo "Использование: ${cmd_name} <путь_к_файлу_или_url> [ещё файлы или url...]"
34 exit 1
35fi
36
37process_one() {
38 local INPUT="$1"
39 local TMPROOT filename OUTDIR OUTBASE base WAVTMP
40
41 TMPROOT="$(mktemp -d)"
42
43 cleanup_one() {
44 rm -rf "$TMPROOT"
45 }
46 trap cleanup_one RETURN
47
48 if [[ "$INPUT" == http://* || "$INPUT" == https://* ]]; then
49 filename="$(basename "${INPUT%%\?*}")"
50 [[ -n "$filename" ]] || filename="audio"
51 curl -L --fail "$INPUT" -o "$TMPROOT/$filename" || {
52 echo "Ошибка загрузки URL: $INPUT"
53 return 1
54 }
55 INPUT="$TMPROOT/$filename"
56 OUTDIR="$PWD"
57 OUTBASE="$OUTDIR/${filename%.*}"
58 else
59 INPUT="${INPUT#file://}"
60
61 if [[ ! -f "$INPUT" ]]; then
62 echo "Не файл: $INPUT"
63 return 1
64 fi
65
66 OUTDIR="$(cd "$(dirname "$INPUT")" && pwd)"
67 base="$(basename "$INPUT")"
68 OUTBASE="$OUTDIR/${base%.*}"
69 fi
70
71 echo
72 echo "[voice] Обработка: $INPUT"
73
74 WAVTMP="$TMPROOT/input.wav"
75 ffmpeg -hide_banner -loglevel error -y -i "$INPUT" -ar 16000 -ac 1 -c:a pcm_s16le "$WAVTMP" || {
76 echo "[voice] Ошибка FFmpeg: $INPUT"
77 return 1
78 }
79
80 "$WHISPER_BIN" \
81 -m "$MODEL" \
82 -f "$WAVTMP" \
83 -l "$LANG_ASR" \
84 -otxt -ovtt -osrt \
85 -of "$OUTBASE" || {
86 echo "[voice] Ошибка whisper.cpp: $INPUT"
87 return 1
88 }
89
90 if [[ -f "${OUTBASE}.txt" ]]; then
91 cp -f "${OUTBASE}.txt" "${OUTBASE}.md" || {
92 echo "[voice] Не удалось создать Markdown-копию: ${OUTBASE}.md"
93 return 1
94 }
95 else
96 echo "[voice] Не найден TXT-файл: ${OUTBASE}.txt"
97 return 1
98 fi
99
100 echo "[voice] Готово:"
101 echo " ${OUTBASE}.txt"
102 echo " ${OUTBASE}.md"
103 echo " ${OUTBASE}.vtt"
104 echo " ${OUTBASE}.srt"
105
106 return 0
107}
108
109ok_count=0
110fail_count=0
111
112for item in "$@"; do
113 if process_one "$item"; then
114 ok_count=$((ok_count + 1))
115 else
116 fail_count=$((fail_count + 1))
117 fi
118done
119
120echo
121echo "[voice] Завершено. Успешно: ${ok_count}, с ошибками: ${fail_count}"
122
123[[ "$fail_count" -eq 0 ]]Сделай файл исполняемым:
1chmod +x ~/bin/voicePATH: чтобы voice работала без полного пути
Если ~/bin ещё не добавлен в PATH, открой ~/.zshrc:
1nano ~/.zshrcДобавь в конец:
1export PATH="$HOME/bin:$PATH"Применить изменения:
1source ~/.zshrcПроверить, что команда доступна:
1type voiceИспользование
Базовый запуск
1voice /путь/к/файлу.mp3Результат: рядом с исходным файлом появятся:
имя.txtимя.mdимя.vttимя.srt
Несколько файлов сразу
1voice /путь/к/первому.mp3 /путь/к/второму.wav /путь/к/третьему.m4aКоманда обработает каждый файл по очереди и создаст отдельный набор выходных файлов рядом с каждым из них.
Поддерживаются любые форматы, которые открывает FFmpeg: mp3, wav, m4a, flac и другие.
Запуск с другой моделью
Если нужно протестировать другую модель без переписывания скрипта, можно передать её через переменную MODEL:
1MODEL="$HOME/models/whisper/ggml-large-v3.bin" voice /путь/к/файлу.wavИли явно указать medium:
1MODEL="$HOME/models/whisper/ggml-medium.bin" voice /путь/к/файлу.wavЯзык распознавания
По умолчанию используется ru. Переопределение через переменную окружения:
1LANG_ASR=en voice /путь/к/файлу.mp3URL, если нужно
Команда принимает один или несколько URL и кладёт результат в текущую папку терминала:
1voice https://example.com/audio1.mp3 https://example.com/audio2.mp3Примечания
txtсохраняется без тайм-кодов и подходит для чтения, поиска и дальнейшей обработки через ИИ.md— это копияtxtв Markdown-расширении. Удобно для заметок, Obsidian и дальнейшей ручной работы с текстом.vttиsrtпочти одинаковы по содержанию, но отличаются форматом.vttначинается со строкиWEBVTT, аsrtиспользует нумерацию блоков и запятые в тайм-кодах.- Тайм-коды в
vttиsrtсегментные, то есть фразами, а не по словам. Для навигации по записи этого обычно достаточно. - Все файлы для каждого аудио создаются за один запуск Whisper. Это почти не увеличивает время по сравнению с генерацией одного формата.
- Скрипт всегда делает временный WAV 16 kHz mono. Это повышает стабильность обработки на длинных записях.
- В этом варианте по умолчанию используется
ggml-medium.bin. На некоторых записях эта модель ведёт себя стабильнее, чемlarge-v3, и реже уходит в повторы.
Диагностика
- «Не найден whisper CLI»: проверь
brew install whisper-cppиcommand -v whisper-cli. - «Не найдена модель»: проверь путь к файлу модели в переменной
MODEL. - «Команда не находится»: проверь PATH в
~/.zshrcи выполниsource ~/.zshrc. - Если распознавание идёт не на том языке, явно задай
LANG_ASR=ru. - Если на длинной записи появляются повторы одной и той же фразы, это может быть не ошибка скрипта, а особенность самой модели Whisper на конкретном фрагменте аудио.
- Если передать несколько файлов сразу, команда должна обработать их все по очереди. Если обработался только один, проверь, не был ли старый вариант скрипта всё ещё сохранён в
~/bin/voice.