Локальный возобновляемый пайплайн перевода и закадровой озвучки видео под Windows.
Оркестратор написан на C#/.NET 8 без Node.js, Python и NuGet-пакетов. Опциональный
Demucs и локальный KugelAudio используют собственные изолированные Python-окружения
только внутри tools/; системный Python не требуется. BAT-файлы служат точками
запуска и установки.
Пайплайн состоит из десяти независимо кэшируемых стадий:
input— локальный файл или загрузка URL черезyt-dlp;audio— оригинальная 48 kHz дорожка и 16 kHz mono WAV для ASR;separate— по умолчаниюvocals + backgroundчерез Demucs, либо быстрый legacy ducking;asr— NVIDIA Parakeet на CUDA с Silero VAD, таймкодами и quality gate;whisper.cppостаётся fallback;prepare— восстановление текста по эталонному SRT с таймингами только из ASR либо обычная склейка ASR-фрагментов;translate— перевод через Codex/Claude CLI крупными смысловыми пакетами с соседним контекстом;tts— локальный 4-битный KugelAudio с поддержкой сербского либо заменяемая команда другого TTS;timing— измерение, ограниченныйatempo, LLM-сокращение и перегенерация;mix— голосовая дорожка и ducking либо чистый background stem;video— MP4 с исходным видеопотоком и новой AAC-дорожкой.
Состояние лежит в work/<video-id>/process.json. Успешные стадии повторно не
запускаются, если их настройки и входные файлы не изменились. Перевод дополнительно
кэшируется пакетами по содержимому подготовленного текста и prompt-файлов, поэтому
простое обновление timestamp не сбрасывает кеш, а сетевой/LLM-сбой не уничтожает уже
готовую часть. После прерывания следующий запуск показывает translate: resuming и
обращается к выбранному движку только для отсутствующих пакетов.
Требуется Windows x64 и .NET 8 SDK или Runtime. Нативные утилиты и Whisper-модель можно положить вручную по путям из конфига либо скачать явно:
setup-tools.bat large-v3
copy config.example.yaml config.yaml
setup-tts-kugelaudio.bat
dub-video.bat doctorЕсли настроенный FFmpeg не найден, обычный интерактивный запуск спрашивает путь к
ffmpeg.exe или его bin-папке. Выбранный путь сохраняется локально в
tools/ffmpeg.path; пустой ответ скачивает только project-local FFmpeg/FFprobe.
Для неинтерактивного запуска можно задать VOICO_FFMPEG и VOICO_FFPROBE.
Если Ollama уже отвечает на стандартном http://127.0.0.1:11434,
dub-video.bat использует этот сервер и не запускает/не устанавливает portable
Ollama. Project-local сервер на 11435 остаётся fallback-вариантом, когда
системная Ollama недоступна.
setup-tools.bat скачивает yt-dlp.exe, FFmpeg, официальную self-contained CUDA-сборку
whisper.cpp и выбранную модель. CUDA runtime хранится рядом с Whisper в tools/whisper:
системный CUDA Toolkit и изменение PATH не нужны. Прежняя CPU-сборка при обновлении
сохраняется в tools/whisper-cpu. large-v3 занимает несколько гигабайт. Тот же setup
устанавливает официальный standalone Ollama и TranslateGemma 12B в папки
tools/ollama и models/ollama; архив, временные файлы, логи и process-local профиль
Ollama (USERPROFILE, HOME, LOCALAPPDATA, APPDATA) также остаются внутри
tools/ollama-data. Системная установка, изменение PATH, глобальный model store и
глобальные переменные окружения не используются.
TTS-голос setup не выбирает за пользователя.
Основной ASR — NVIDIA Parakeet TDT 0.6B v3. setup-tools.bat скачивает закреплённую
Q8-конверсию с проверкой размера и SHA-256 только в models/; native parakeet-cli
уже входит в тот же CUDA-пакет. setup-asr-parakeet.bat позволяет отдельно проверить
или переустановить модель. Whisper CUDA остаётся штатным fallback: для него задайте
asr.engine: whisper.cpp. Оба движка используют один физический Silero VAD, quality
gate и уточнение wall-clock таймкодов.
Перевод по умолчанию выполняется через авторизованный codex CLI и подписку ChatGPT.
claude-cli через подписку Claude и прежний локальный ollama остаются выбираемыми
движками. Для Ollama используется отдельный loopback endpoint 127.0.0.1:11435;
его standalone-архив, модель и контрольные суммы закреплены в
setup-translation-translategemma.bat.
Для опционального удаления исходной речи:
setup-separation.batСкрипт локально устанавливает uv, Python 3.11, CUDA PyTorch и Demucs под
tools/separation. Системный Python и PATH не изменяются. Модель Demucs загрузится
в ту же папку при первом использовании режима.
dub-video.bat "D:\video\source.mp4" --source ru --target sr-Latn
dub-video.bat "https://youtube.com/watch?v=..." ^
--config config.yaml ^
--voice "Serbian voice name"
rem Та же настройка естественным языком
dub-video.bat "D:\video\source.mp4" "Видео на русском, озвучь на сербский мужским голосом"
rem Случайный семиминутный фрагмент; Demucs включён по умолчанию
dub-video.bat "D:\video\source.mp4" --demo 7Громкость возвращаемого исходного голоса можно переопределить для отдельного запуска:
dub-video.bat "D:\video\source.mp4" --separate --original-voice-volume 0.15Готовое видео появится в output/. В MP4 по умолчанию находятся две звуковые
дорожки (дубляж и нетронутый оригинал) и две отключаемые дорожки субтитров
(оригинальный текст и перевод). Дубляж отмечен как звуковая дорожка по умолчанию,
а субтитры можно выбрать в плеере. Рядом с видео также создаются обычные UTF-8 SRT:
*.original.<язык>.srt и *.translated.<язык>.srt. Все промежуточные JSON/WAV — в work/.
Остановка через Ctrl+C безопасна: завершённые стадии останутся кэшированными.
Полезные варианты повторного запуска:
rem Пересобрать только сведение и видео
dub-video.bat "source.mp4" --from mix --force mix,video
rem Заново перевести и выполнить все зависимые стадии
dub-video.bat "source.mp4" --force translate,tts,timing,mix,video
rem Дойти только до распознанного текста
dub-video.bat "source.mp4" --to asrЕсли указывается --from, все предыдущие стадии должны уже быть успешно завершены.
Режим разделения обязан совпадать с кэшем. Demucs используется по умолчанию; для старого
duck-режима укажите --duck. Смешение demucs-фона с duck-кэшем будет отклонено до сведения.
Для стабильного имени папки можно задать --work-id car-01.
--demo N вырезает случайный фрагмент длиной N минут и прогоняет на нём весь
пайплайн в отдельной work/*-demo-Nm папке, не затрагивая полный запуск. Повторный
запуск использует тот же закэшированный фрагмент; чтобы выбрать новый, добавьте
--force input.
Для фильма без перевода и синтеза речи используйте отдельную команду:
rebalance-video.bat "D:\video\film.mkv" 220 45После пути идут два процента: сначала громкость голосов, затем громкость фона.
Оба значения принимаются в диапазоне 0..300; например, 220 45 усиливает речь
до 220% и ослабляет музыку, взрывы и прочий фон до 45%. Путь результата можно
задать через -o:
rebalance-video.bat "film.mp4" 180 60 -o "output\film-study.mp4"Перед обработкой выводится список аудиодорожек с номером, языком, названием, кодеком и раскладкой каналов. Если дорожка одна, она выбирается автоматически; если их несколько, команда попросит ввести номер. Для запуска без вопроса передайте номер явно (нумерация начинается с единицы):
rebalance-video.bat "film.mkv" 200 40 --track 2Тот же --track N поддерживается основным dubbing pipeline:
dub-video.bat "film.mkv" --track 2 --source en --target sr-LatnЕсли --track не указан и в локальном файле несколько аудиодорожек, Voico
показывает их номера, язык, название, кодек и каналы, затем предлагает выбрать.
Номер дорожки входит в имя рабочей папки и сигнатуры кэша, поэтому результаты
разных языковых дорожек не смешиваются.
Выбранная дорожка заменяется обработанной на той же позиции с сохранением языка и disposition-флагов. Остальные аудиодорожки, субтитры и главы переносятся без изменений. Для многодорожечного файла номер также входит в имя результата и в ключ кэша, поэтому можно независимо подготовить варианты для разных языков.
Тяжёлое разделение vocals/no_vocals выполняется Demucs только при первом запуске
для данного файла. Следующие варианты громкости переиспользуют stem-дорожки из
work/rebalance/ и пересводятся быстро. В новую основную аудиодорожку встроен
лимитер от цифрового клиппинга; видео не перекодируется. Исходный файл никогда
не перезаписывается.
Demucs выделяет общий vocal stem, поэтому вместе с диалогами в него иногда могут попасть песни или похожие на голос звуки, а часть речи может остаться в фоне. Это ограничение нейросетевого разделения, а не регулятора громкости.
Voico может взять оригинальный SRT из файла, по прямой HTTP(S)-ссылке либо из текстовой дорожки самого видео:
rem Отдельный файл
dub-video.bat "film.mkv" --track 2 --source en --source-srt "film.en.srt"
rem Прямая ссылка именно на SRT
dub-video.bat "film.mkv" --source en --source-srt "https://example.org/film.en.srt"
rem Встроенная текстовая дорожка; нумерация субтитров начинается с единицы
dub-video.bat "film.mkv" --track 2 --source en --source-srt embedded --subtitle-track 1Для локального видео с несколькими встроенными текстовыми дорожками номер можно
выбрать интерактивно. Для URL-видео --subtitle-track N обязателен. Графические
PGS/VobSub-дорожки отклоняются: нужен SRT, WebVTT, ASS/SSA, mov_text либо другая
текстовая дорожка. --reference-srt поддерживается как синоним --source-srt.
SRT служит эталоном только для слов и границ реплик. Все start/end, спикеры и
дальнейшая синхронизация берутся исключительно из нашего ASR. Выравниватель
находит общий сдвиг/дрейф релиза, затем локально сопоставляет реплики и собирает
разрезанные ASR-фрагменты в связные фразы. Чужой или плохо совпадающий SRT
останавливает prepare, а не подменяет распознавание вслепую.
Несопоставленные ASR-фрагменты по умолчанию сохраняются: SRT тоже может быть
неполным и, например, не содержать текст песни. Отбрасываются только лишние
ASR-фрагменты, попавшие внутрь временного покрытия уверенно сопоставленной SRT-реплики.
Строгий режим для заведомо полного SRT можно включить настройкой
source_subtitles.preserve_unmatched_asr: false; отчёт отдельно считает сохранённые
fallback_asr_segment_count и отброшенные dropped_asr_segment_count.
Нормализованный исходник сохраняется в work/<video-id>/reference.srt, подробное
сопоставление — в subtitle-alignment.json. Исходный transcript.json остаётся
неизменным, а исправленный текст с ASR-таймингами попадает в prepared.json.
Опциональный --manual-translation отключает автоматический движок только для стадии перевода:
dub-video.bat "source.mp4" --manual-translationVoico создаёт компактный manual-translation-<hash>.srt в work/<video-id>/ и
ждёт ввода в консоли. Передайте SRT любому внешнему переводчику либо отредактируйте
вручную, заменяя только текст субтитров и сохраняя номера/таймкоды. После сохранения
нажмите Enter: структура, количество сегментов, пустые строки и неожиданная письменность
будут проверены, перевод попадёт в обычный translated.json, и пайплайн продолжит TTS.
Q отменяет запуск без удаления SRT. Если процесс уже закрыт, отредактируйте тот же
файл и повторите исходную команду с --manual-translation.
В этом режиме timing не отправляет ваш текст обратно в переводчик для сокращения: формулировки
сохраняются буквально, а длительность подгоняется только безопасным atempo. Если для
реплики потребовалось бы ускорение выше timing.hard_max_tempo, пайплайн остановится и
попросит сократить соответствующую строку SRT вручную.
Без этого флага перевод выполняет выбранный translation.engine. Для повторного
импорта уже завершённого ручного перевода используйте --force translate.
Вместо локальной Ollama-модели можно запускать установленный CLI через уже
авторизованную подписку. Для Codex используется non-interactive codex exec, а для
Claude — claude -p. Voico передаёт prompt через stdin, требует структурированный JSON,
запрещает инструменты/запись в workspace и сохраняет тот же пакетный cache и проверки:
translation:
engine: codex-cli # либо claude-cli
cli_executable: # пусто = codex/claude из PATH
cli_model: # пусто = модель по умолчанию текущей подписки
cli_extra_args:
cli_timeout_minutes: 20
cli_subscription_only: true
cli_batch_size: 80 # крупный цельный участок субтитров
cli_context_segments: 20 # соседние реплики только как контекстПри cli_subscription_only: true дочерний процесс не получает OPENAI_API_KEY,
CODEX_API_KEY, ANTHROPIC_API_KEY и provider-specific API-переменные. doctor
проверяет подписочную авторизацию. Глобальные переменные среды и сохранённая
авторизация не изменяются. Установить и авторизовать CLI нужно заранее командами
codex login или claude auth login.
Для Codex Voico использует non-interactive codex --ask-for-approval never exec,
передаёт запрос через stdin (-), запрещает запись через --sandbox read-only и
забирает строго типизированный результат через --output-schema и
--output-last-message. Для Claude используется claude -p --output-format json --json-schema ... --tools "" --max-turns 1 --no-session-persistence.
CLI получает не одиночные тайминговые обрывки, а большой участок диалога с предыдущими и следующими репликами. Prompt требует восстановить предложения, разрезанные между соседними ID, перевести их как цельный разговор и затем разложить естественный перевод обратно по активным ID. Контекстные ID в ответ не включаются.
Проверить выбранный движок без обработки видео и TTS:
dub-video.bat translation-test --config config.yamlОбщие системные prompt-файлы в prompts/ написаны по-английски и не содержат
требований конкретного языка. Если в translation.target_prompt_directory существует
файл с точным тегом целевого языка, например prompts/targets/sr-Latn.txt, его текст
автоматически добавляется к инструкциям перевода, проверки и сокращения. Если файла нет,
используется только общий prompt, поэтому перевод на en не получает сербских правил.
Изменение target prompt инвалидирует и пакетный кэш перевода, и стадию timing, где текст
может дополнительно сокращаться. doctor показывает фактически выбранный файл либо
сообщает common prompt only. Сейчас отдельные правила заданы только для sr-Latn:
сербская латиница, экавица, естественный порядок слов и запрет
кириллицы/русской транслитерации.
Строка естественного языка проходит через Ollama, но результат ограничен безопасным
набором полей: исходный/целевой язык, доступный голос, duck/demucs и длительность
demo. Пути к программам, произвольные команды и модели таким способом менять нельзя.
ASR разбит на независимые 15-минутные WAV-отрезки по реальному времени исходной
дорожки. Соседние отрезки получают небольшой overlap, но каждый распознанный сегмент
принадлежит только одному временному окну. Перед Whisper отдельный Silero-процесс
находит речь в wall-clock координатах. Каждое phrase-window физически записывается в
отдельный WAV с небольшим padding, а все WAV одного пакета передаются уже загруженной
модели через несколько -f. Поэтому Whisper вообще не видит длинную паузу между
фразами и не может растянуть через неё один timestamp.
Начало результата переводится обратно из локального WAV во время исходной дорожки.
Первая реплика окна получает hard_boundary_before: prepare не вправе склеить её с
предыдущей. По умолчанию vad_min_silence_ms, vad_phrase_silence_ms и
prepare.preserve_pause_seconds согласованы на 250 мс. Legacy embedded_vad: true
оставлен только для диагностики; его timestamps после удаления тишины ненадёжны.
После каждого chunk'а проверяются циклические повторы, аномальная плотность,
несколько фраз с одинаковыми таймкодами и физически невозможное количество текста
в доступном интервале. Невозможный одиночный тайминг остаётся жёсткой ошибкой. В
physical-VAD режиме автоматически удаляются только рассеянные по ролику повторы одного
длинного шаблона, подтверждённые несколькими слишком плотными появлениями. Список таких
сегментов остаётся в asr-quality.json как discarded_segments, поэтому очистка не
скрыта от диагностики.
Остальные подозрительные результаты автоматически повторяются трёхминутными окнами
с отключённым temperature fallback. Только повторный плохой результат останавливает
стадию; сырой JSON, quality-отчёт и WAV проблемной попытки остаются в asr-chunks/.
После распознавания Voico дополнительно одним проходом измеряет тишину в asr.wav и
уточняет оставшиеся границы сегментов, попавшие внутрь неё. Исходные и исправленные
границы сохраняются в asr-alignment.json; порог, минимальная тишина и защитный
padding настраиваются через asr.timestamp_*.
При translation.verify: true каждый пакет второй раз подаётся тому же движку как
редактору. По умолчанию этот проход отключён. Независимо от него работает
детерминированная проверка алфавита, структуры, пустого/аномально длинного текста и
характерных следов русского, записанного латиницей. Кэшированные batch-файлы проходят
ту же проверку при каждом resume; версия проверки отделена от ключа model-кэша.
Иврит, кириллица или очевидная русская транслитерация в sr-Latn отклоняются.
Проблемный ID переводится отдельно, результат повторно проверяется, а после исчерпания
translation.max_retries пайплайн останавливается вместо озвучивания мусора.
По умолчанию используется separation.mode: demucs. Demucs htdemucs_ft создаёт
vocals.wav и background.wav, распознаёт очищенный vocal stem и накладывает новый
голос на background без ducking. Исходный голос подмешивается обратно с громкостью
mix.original_voice_volume (по умолчанию 0.10, то есть 10%). Значение 0 полностью
исключает vocals.wav из финального микса. --duck включает быстрый legacy-режим без
разделения stem'ов; --separate оставлен как явный совместимый флаг Demucs.
prepare.preserve_pause_seconds: 0.25 запрещает объединять соседние ASR-фрагменты
через слышимую паузу. Это сохраняет её исходное положение: текст после паузы получает
собственный timestamp, а не произносится раньше с тишиной, оставшейся в конце сегмента.
Для однословных реплик короче timing.short_phrase_seconds допускается отдельный
short_phrase_hard_max_tempo: 2.0. Обычно реплика сохраняет исходный start. Если TTS всё
равно не помещается в безопасный предел, глобальный планировщик может занять только
измеренную свободную паузу до и/или после реплики. Общая пауза делится между соседями
один раз, prepare.preserve_pause_seconds остаётся нетронутым, а постролл ограничивается
физическим концом аудиодорожки — поэтому перенос не создаёт новую слышимую накладку.
Для более естественного фона Demucs использует separation.other_method: minus: фон
строится как точный остаток original - vocals, а не как сумма независимо предсказанных
инструментальных stem'ов. Это уменьшает «дыхание» и провалы фона вокруг речи. Параметры
separation.shifts: 2 и separation.overlap: 0.50 стабилизируют результат между окнами,
но примерно удваивают время стадии separation относительно быстрого профиля 1 / 0.25.
В режиме duck приглушение покрывает объединение двух окон: исходную реплику
start..end и фактическую длительность сербской WAV. Поэтому короткий перевод больше
не возвращает русскую речь на полную громкость до конца исходной фразы. По краям
добавляется mix.source_guard_ms, а duck_volume: 0.06 опускает оригинал примерно на
24 дБ. Служебная тишина в начале и конце TTS автоматически срезается до сведения,
но timing.tail_padding_ms сохраняет тихую часть финальной фонемы и добавляет защитный постролл. Этот постролл не считается речью при расчёте atempo, поэтому не ускоряет и не съедает последнее слово.
Локальный дефолт перевода — специализированная translategemma:12b Q4_K_M. Для неё
Voico использует официальный plain-text prompt отдельно на каждую реплику: JSON нужен
только внутреннему кэшу и не подмешивается в запрос модели. Ближайшие реплики передаются
как context-only, чтобы не терять смысл фрагментов и не переносить соседний текст через
immutable ID. На RTX 4060 Ti 16 GB все 49 слоёв размещаются на CUDA без RAM-offload;
после перевода модель явно выгружается, чтобы освободить GPU для TTS. Для иной модели
Ollama сохраняется прежний batch-JSON адаптер. codex-cli и claude-cli через подписку
остаются опциональными движками.
По умолчанию используется локальный kugelaudio/kugelaudio-0-open: современная
AR + diffusion модель с явной поддержкой сербского. На RTX 4060 Ti она запускается
в 4-битном режиме и занимает около 8 ГБ VRAM. Один persistent worker загружает модель
один раз на всю TTS-стадию, поэтому каждая реплика не платит повторно за загрузку весов.
Установка:
setup-tts-kugelaudio.bat
dub-video.bat doctorИзолированный Python находится в tools/tts/kugelaudio, системный Python и PATH не
изменяются. При первом синтезе около 18 ГБ весов скачиваются в
tools/tts/kugelaudio/cache; последующие запуски полностью локальны. Ожидаемая скорость
порядка realtime, поэтому многочасовой дубляж остаётся долгой офлайн-задачей.
tts.voice_mode: single — дефолт. Если tts.voice пуст, Voico один раз создаёт спокойный
нейтральный голосовой профиль с фиксированным tts.seed, а затем использует один и тот
же профиль для всех реплик. Это важно для KugelAudio: даже при жадном декодировании её
акустическая diffusion-часть использует шум и без закреплённого профиля может менять
тембр и подачу между независимыми сегментами.
В tts.voice можно явно указать WAV длительностью 5–30 секунд с голосом, на использование
которого есть согласие. Для опционального многоголосия передайте --multi-voice либо
задайте tts.voice_mode: speakers
и положите нейтральные референсы SPEAKER_01.wav, SPEAKER_02.wav и так далее в
tts.speaker_voice_directory (по умолчанию voices). Если файла нет, Voico создаст
отдельный стабильный профиль для этого спикера. Меняются голоса, но каждая реплика всё
равно синтезируется независимо от эмоций исходной аудиодорожки.
Старый Piper сохранён как лёгкий fallback и ставится через setup-tts-piper.bat, но
sr_RS-serbski_institut-medium больше не является дефолтом.
Для VibeVoice, XTTS, Fish Speech, CosyVoice или другого локального движка предусмотрен универсальный адаптер команды:
tts:
engine: command
executable: tools/tts/my-tts.exe
voice: voices/serbian-male.wav
arguments: '--text-file "{text_file}" --output "{output}" --language "{language}" --voice "{voice}"'
timeout_minutes: 10Команда обязана создать WAV по пути {output}. Текст передаётся UTF-8-файлом, что
избегает проблем BAT/cmd с кириллицей и длиной командной строки. Таким образом,
конкретный TTS не зашит в пайплайн и меняется без повторного ASR/перевода.
Обычная модель Whisper не выполняет полноценную diarization. Если используется
совместимая модель whisper.cpp с tinydiarize, включите asr.tiny_diarize: true;
маркер [SPEAKER_TURN] преобразуется в SPEAKER_01, SPEAKER_02 и далее.
Для полноценной идентификации нескольких голосов можно заменить IAsrEngine, не
меняя формат transcript.json и остальные стадии.
build.batКаждая публикация создаётся side-by-side в dist/build-*, а dist/current.txt
указывает BAT на новую версию. Поэтому сборку можно обновлять, пока предыдущий
многочасовой запуск ещё работает. До первой публикации BAT запускает проект через
dotnet run.
Для диагностики подробного стека ошибки установите VOICO_TRACE=1.