Whisper.cpp: локальный редактор субтитров с ручным выравниванием 2025 | AiManual
AiManual Logo Ai / Manual.
23 Янв 2026 Гайд

Whisper.cpp в продакшене: как собрать локальный редактор субтитров с ручным выравниванием

Пошаговый гайд по сборке продакшен-редактора субтитров на Whisper.cpp с ручным выравниванием временных меток. Работает без интернета на CPU/GPU.

Почему Whisper.cpp в 2026 году - это уже не игрушка

Помните 2023 год? Whisper.cpp тогда был скорее техническим демо - "смотрите, Whisper от OpenAI можно запустить на Raspberry Pi!". В 2026 ситуация кардинально изменилась. Проект созрел, оброс инструментами, и самое главное - появились реальные кейсы для продакшена.

Основная проблема, которую я вижу у всех облачных решений для субтитров: они не умеют в тонкую ручную правку. Автоматическая транскрипция дает точность 85-95%, но эти последние 5-15% убивают. Слова сдвинуты на полсекунды, паузы не там, где надо, и ты тратишь часы, пытаясь подогнать временные метки в каком-нибудь онлайн-редакторе.

Ключевое преимущество локального решения: ты контролируешь весь пайплайн. Нет лимитов на длительность аудио, нет ограничений по количеству запросов, и самое главное - можно встроить ручное выравнивание прямо в процесс.

Архитектура: что строим и зачем

Наша цель - не просто транскрибатор, а полноценный рабочий инструмент. Три основных компонента:

  • Бэкенд на Whisper.cpp (последняя версия на январь 2026 - 1.5.2 с поддержкой Whisper-large-v3-turbo)
  • Веб-интерфейс для редактирования с визуальной шкалой времени
  • Система ручного выравнивания с "привязкой" к аудиоволне

Почему именно такая архитектура? Потому что она решает главную боль - разрыв между автоматической транскрипцией и финальной полировкой. В TranscriptionSuite похожий подход, но там акцент на дизаризацию, а нам нужно именно выравнивание.

1 Собираем Whisper.cpp с нужными флагами

Первая ошибка, которую все совершают - собирают Whisper.cpp "как есть". Для нашего кейса нужны специфические флаги:

git clone https://github.com/ggerganov/whisper.cpp
cd whisper.cpp
# Включаем поддержку Metal для Mac и CUDA для NVIDIA
make clean
WHISPER_CUBLAS=1 WHISPER_METAL=1 make -j

Зачем WHISPER_CUBLAS=1? Даже если у тебя слабая видеокарта, CUDA ускоряет inference в 3-5 раз по сравнению с чистым CPU. Для больших файлов это разница между 10 минутами и 2 часами.

💡
На январь 2026 Whisper.cpp поддерживает модели до large-v3-turbo. Для большинства языков достаточно medium.en или medium. Русский лучше всего работает на large-v3, но требует 8+ ГБ RAM.

2 Подготавливаем модели и контекст

Скачиваем модель (актуально на 23.01.2026):

# Для английского с акцентом на точность временных меток
./models/download-ggml-model.sh medium.en

# Для мультиязычного контента
./models/download-ggml-model.sh large-v3

Конвертируем в ggml формат (если нужно):

# Новые модели уже в формате ggml, но проверяем
./quantize ./models/ggml-medium.en.bin ./models/ggml-medium.en-q5_0.bin q5_0

Ключевой момент для выравнивания: нам нужны не только слова, но и точные временные метки для каждого токена. Whisper.cpp дает это через флаг --output-json-full или --output-srt с сегментами.

Бэкенд: не просто транскрипция, а API для редактирования

Вот где большинство туториалов останавливаются. "Запустил whisper.cpp, получил SRT файл, молодец". Но нам нужно больше - API, который позволяет:

  1. Загружать аудио
  2. Запускать транскрипцию с разными параметрами
  3. Возвращать сегменты с возможностью редактирования
  4. Принимать правки и пересчитывать временные метки

Пишем простой Python-бэкенд (Flask или FastAPI):

from flask import Flask, request, jsonify
import subprocess
import json
import os

app = Flask(__name__)

@app.route('/transcribe', methods=['POST'])
def transcribe():
    audio_file = request.files['audio']
    model_size = request.form.get('model', 'medium')
    language = request.form.get('language', 'ru')
    
    # Сохраняем файл
    audio_path = f"/tmp/{audio_file.filename}"
    audio_file.save(audio_path)
    
    # Запускаем whisper.cpp
    # Ключевой флаг: --output-json-full для детальных временных меток
    cmd = [
        './main',
        '-m', f'./models/ggml-{model_size}.bin',
        '-f', audio_path,
        '-l', language,
        '--output-json-full',
        '--output-file', '/tmp/transcript'
    ]
    
    result = subprocess.run(cmd, capture_output=True, text=True)
    
    # Читаем результат
    with open('/tmp/transcript.json', 'r') as f:
        data = json.load(f)
    
    # Преобразуем в удобный формат для фронтенда
    segments = []
    for segment in data['transcription']:
        segments.append({
            'text': segment['text'],
            'start': segment['offsets']['from'],
            'end': segment['offsets']['to'],
            'tokens': segment.get('tokens', [])  # Для точного выравнивания
        })
    
    return jsonify({
        'segments': segments,
        'audio_duration': data['duration']
    })

Это упрощенный пример. В реальности нужно добавить обработку ошибок, кеширование, поддержку разных аудиоформатов через ffmpeg.

Важный нюанс: whisper.cpp по умолчанию использует 30-секундные сегменты. Для точного выравнивания лучше уменьшить до 5-10 секунд через --step и --length, но это увеличивает время обработки.

Фронтенд: редактор, который не бесит

Здесь можно пойти разными путями. Я видел "решения" на jQuery 2010 года, которые тормозят на 100 сегментах. Мы сделаем современно - Vue 3 или React с Canvas для визуализации волны.

Ключевые компоненты интерфейса:

Компонент Зачем нужен Технология
Визуализатор аудиоволны Видеть паузы, вздохи, моменты речи Web Audio API + Canvas
Таблица субтитров Редактировать текст и временные метки Virtual Scrolling (для больших файлов)
Плеер с синхронизацией Слушать и сразу править HTML5 Audio + requestAnimationFrame

Самая важная фича - drag-and-drop для границ субтитров. Пользователь берет правую или левую границу сегмента на временной шкале и тянет. Интерфейс должен показывать точное время с точностью до миллисекунды.

Для визуализации волны используем wavesurfer.js - он умеет работать с большими файлами и дает API для регионов:

import WaveSurfer from 'wavesurfer.js'
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js'

// Инициализация
const wavesurfer = WaveSurfer.create({
  container: '#waveform',
  waveColor: '#4F46E5',
  progressColor: '#7C3AED',
  cursorColor: '#000',
  height: 100,
  backend: 'WebAudio',
})

// Загружаем аудио
wavesurfer.load('/api/audio/123')

// Когда загрузились сегменты из бэкенда
segments.forEach(segment => {
  wavesurfer.addRegion({
    start: segment.start,
    end: segment.end,
    color: 'rgba(79, 70, 229, 0.3)',
    drag: true,  // Разрешаем перетаскивание
    resize: true // И изменение размера
  })
})

// Слушаем изменения регионов
wavesurfer.on('region-updated', (region) => {
  // Отправляем на бэкенд новые временные метки
  updateSegmentTime(region.id, region.start, region.end)
})

Механика ручного выравнивания: где магия

Вот что отличает наш редактор от EasyWhisperUI или других решений. Мы не просто редактируем текст - мы пересчитываем временные метки на основе правок.

Алгоритм работы:

  1. Пользователь слушает аудио, замечает, что субтитр начался на 0.3 секунды раньше
  2. Перетаскивает левую границу региона, выравнивая по аудиоволне
  3. Система автоматически сдвигает ВСЕ последующие субтитры на эту же дельту
  4. Если нужно изменить только один сегмент - специальный режим "изоляции"

Реализация на бэкенде:

@app.route('/align', methods=['POST'])
def align_segments():
    data = request.json
    segments = data['segments']
    anchor_index = data['anchor_index']  # Индекс сегмента, который выравнивали
    new_start = data['new_start']
    
    # Вычисляем дельту
    original_start = segments[anchor_index]['start']
    delta = new_start - original_start
    
    # Применяем ко всем последующим сегментам
    for i in range(anchor_index, len(segments)):
        segments[i]['start'] += delta
        segments[i]['end'] += delta
    
    # Сохраняем в файл (SRT или VTT)
    save_to_srt(segments)
    
    return jsonify({'success': True, 'segments': segments})
💡
Для профессионального использования добавьте "привязку" к фонемам или словам. Whisper.cpp в режиме --output-json-full возвращает токены с временными метками. Можно позволить пользователю выравнивать не только сегменты, но и отдельные слова внутри них.

Оптимизация для продакшена

Когда все работает на локалхосте с одним файлом - это одно. Когда нужно обрабатывать десятки часов аудио в день - другое.

Проблемы, с которыми столкнешься:

  • Whisper.cpp жрет память. Large-v3 требует 8+ ГБ RAM
  • GPU-ускорение работает, но не на всех картах
  • Транскрипция длинных файлов (2+ часа) может падать
  • Нужно кешировать результаты

Мое решение для продакшена:

# Docker-образ с оптимизациями
FROM ubuntu:22.04

# Устанавливаем только нужные зависимости
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    python3-pip \
    ffmpeg \
    && rm -rf /var/lib/apt/lists/*

# Собираем whisper.cpp с AVX2 поддержкой
RUN git clone https://github.com/ggerganov/whisper.cpp && \
    cd whisper.cpp && \
    make -j WHISPER_AVX2=1 WHISPER_AVX=1 WHISPER_FMA=1

# Оптимизация для CPU: используем все ядра
ENV OMP_NUM_THREADS=all
ENV GGML_NUM_THREADS=all

# Лимиты памяти
ENV GGML_MEMORY_LIMIT=8192

Интеграция с существующим стеком

Скорее всего, у тебя уже есть какой-то workflow для работы с видео. Наш редактор должен в него встраиваться.

Экспорт во все форматы:

  • SRT - стандарт де-факто
  • VTT - для веба с CSS-стилями
  • ASS/SSA - для сложных субтитров с анимацией
  • Текстовый файл с временными метками - для дальнейшей обработки

API-интеграции:

# Пример: автоматическая обработка загруженных видео
import requests

def process_video_subtitles(video_path):
    # 1. Извлекаем аудио через ffmpeg
    # 2. Отправляем на нашу систему транскрипции
    # 3. Получаем сегменты
    # 4. Открываем в редакторе для правки
    # 5. Экспортируем в нужный формат
    
    # Интеграция с медиа-серверами типа Plex/Jellyfin
    # можно через webhooks

Чего не хватает в Whisper.cpp (и как это исправить)

При всей моей любви к проекту, есть дыры. На январь 2026:

  1. Нет встроенной дизаризации (кто говорит). Решение: использовать отдельную модель типа PyAnnote или интеграцию с Speakr
  2. Слабые возможности пост-обработки текста. Решение: добавить LLM-очистку через Ollama локально
  3. Ограниченная поддержка реального времени. Но для редактора субтитров это не критично

Мой стек для профессиональной обработки:

# Паттерн обработки
аудио → whisper.cpp (транскрипция) → наш редактор (выравнивание) →
→ pyannote.audio (дизаризация) → GPT-4 через локальную LLM (очистка текста) →
→ финальная сборка субтитров

Производительность: какие цифры ждать

На оборудовании 2026 года:

Конфигурация Модель Скорость (реальное время) Точность временных меток
Intel i5 + CPU tiny.en 0.3x (медленнее реального времени) ±0.5 сек
RTX 4060 + CUDA medium 2.5x (быстрее реального времени) ±0.2 сек
M3 Max + Metal large-v3 1.8x ±0.1 сек

Для редактора субтитров точность временных меток важнее скорости. Лучше подождать 10 минут, но получить отклонение в 0.1 секунды, чем 2 минуты с погрешностью в полсекунды.

Что будет дальше с локальной транскрипцией

Глядя на развитие Whisper vs Wav2Vec2 и появление новых моделей, прогнозирую:

  • К концу 2026 появятся модели размером с Whisper-large, но в 10 раз быстрее
  • Встроенная дизаризация станет стандартом
  • Редакторы субтитров будут использовать AI не только для транскрипции, но и для предложений по улучшению текста
  • Появится стандартный API-протокол для локальных STT-движков (как у Ollama для LLM)

Наш редактор на Whisper.cpp - это шаг в этом направлении. Не идеальное решение, но работающее здесь и сейчас. Без облаков, без подписок, с полным контролем над данными.

Самый неочевидный совет в конце: настрой hotkeys в редакторе так, чтобы можно было править субтитры, не отрывая рук от клавиатуры. Ctrl+Стрелка_Влево - сдвинуть начало на -0.1 сек, Ctrl+Стрелка_Вправо - на +0.1 сек. Когда правишь 2000 сегментов, эти горячие клавиши экономят часы.