- BrainTools - https://www.braintools.ru -

Как я переводы видео автоматизировать собирался

2 года назад переводил я локальными моделями WoW на русский язык (https://habr.com/ru/articles/818513/ [1]) и тут недавно возобновил канал свой на YouTube-ах этих ваших, но выкладывая нарезки со стримов про прогу (https://www.youtube.com/@the_homeless_god [2]). И в тестовом режиме переозвучил видео от Fireship про OpenClaw. Да и на тех же стримах возник концепт про цифровые замещения и аватары.

И вот сижу я и думаю, что, например, владея английским языком смотреть могу видео в оригинале, но, тот же Veritasium смотрел в оригинале всего несколько раз, так как мне ближе адаптационная озвучка от Vert Dider. А я чем хуже? Правильно, мне лень этим заниматься на стабильной основе. Что я могу сделать? Автоматизировать за несколько часов часть процесса, который по-хорошему должен занимать 15 минут, зачем тогда десятый год я программирую?

В общем, статья написана как всегда в стиле (б|в)лога, потому заваривайте чай, мы начинаем!

Итоговый результат

Voicer – opensource-приложение http://github.com/the-homeless-god/voicer, которое поможет Вам автоматизировать Ваши недопереводы клонированными голосами

Voicer – opensource-приложение http://github.com/the-homeless-god/voicer [3], которое поможет Вам автоматизировать Ваши недопереводы клонированными голосами

Я пилил для себя, в ходе эксперимента, обернул в десктоп, чтобы те, кому нужно, могли пользоваться, не пытаясь понять устройство cli. Разумеется все работает локально об ollama или если перепишите код в вызовах и с lm studio и я думаю вы просто откроете PR и вольем вашу поддержку любых других провайдеров.

Работает просто:

  • петухон десктоп приложение

  • интегрирован с ollama

  • позволяет с помощью одной модели (в моём случае translategemma:27b) выполнить сначала очистку сырого текста и адаптацию, затем перевод текста на нужный Вам язык и его опять же очистку и адаптацию

    • разумеется с промптами под нарации текста в отдельной вкладке

  • делает с помощью другой модели (в моём случае Qwen3-TTS) переведенный текст записать голосом аудиореференса с текстореференсом

    • позволяет это делать батчем (кол-во предложений)

    • позволяет указать самому словарь ударений

      • а если у Вас руки из плеч, то тогда Вы сможете в аудиореференс сами встроить аудио “правильного” произношения, которое Вам там надо

  • ну и cli тоже предоставляет (так как есть цель со стрима засунуть это в openclaw, чтобы он сам вместо меня на созвоны ходил и рестораны бронировал)

Если:

  • вы как-то там неистово реагируете – могу допилить и вылить в те же app store-ы и прочее

    • однако доступ к репо у вас есть, контрибьютьте, я там в лицензии а-ля BSD написал “меня упомянать, не позорить, на моем сайте что-то покупать/донатить мне, на каналы мои подписываться и можете хоть в коммерческом хоть в некоммерческом использовать” так как моя цель и дать Вам инструменты и себя не забыть. Сам себя не забудешь – никто не забудет! also sprach zarathustra!

      • рофлы рофлами, но писать колонками мне показалось забавно, всяко лучше, чем любой LLM-текст-кекс

Подготовительный этап

Берем мы значит ищем 2 года спустя появился ли клонер лучше, чем тот, что я юзал для клонирования 19784 аудиозаписей ворлдофваркрафта, и сразу же за день до очередного стрима вылетела у Alibaba квенья (не Сильмариллион, а жаль) а Qwen3-TTS [4], ну а я-то сразу протестил. Прямо на стриме (запись стрима на YouTube нарезанная нарезчиком [5]) склонировав свой войс для озвучки “Хоббит”, было понятно, что работает, шустро и пригодно!

И несколько дней а-посля, решил попробовать для себя формат переводов, помог мне после стрима один мой коллега-сосломаннойногокалекаколени с настройкой микро, показывал всякие звуковые приколы, и получился звук лучше, чем был. Я решил попробовать переозвучить, записывая через OBS процесс переозвучки.

Надо было надыбать текст, выбрал видос про Openclaw разумеется, просто ибо лень было искать что-то другое и надо было понять как формат вообще выглядит.

Как достать текст из видоса с ютьюба? Там внизу помните есть всратая кнопка captions, ну так вот, их качать можно например на сайте похожем (или на нем я хз на такие ресурсы можно давать ссылки или нет, если не можна, то скажите, я зашифрую) https://downsub.com/ [6]

Кнопка включения этих ваших captions/subtitles

Кнопка включения этих ваших captions/subtitles

Скачали в txt формате, теперь можно полученный текст чистить

Всратый английской

Всратый английской

Как чистить? Можно руками, можно гпт, а можно через локальные модели, пошел гуглить лучшие модели-переводчики, и нашел сначала от тбанка их модель про-версия, про Qwen что-то пишут 3, мол неплох, да и еще видел вот таких вот

Ну а что, надо пробовать

Ну а что, надо пробовать

Пуллим ее в ollama

Как я переводы видео автоматизировать собирался - 5

Конечно, в моментах и в сердцах я думал поставить и меньшее количество ярдов параметров, но мне было лень останавливать скачивание

Промптинг в три этапа

Дискламер: эти промпты не претендуют на абсолютную истину, они работают у меня и делались на коленке, но работают, хотите тюньте, хотите нет, я же играючи, так чисто, ну, допустим, отца, отыгрываю.

Дальше херачим скрипт перевода в три этапа: очистка мусорного энглийского, от дублей и прочего, затем филигранный перевод, потом конвертируем в более понятный перевод.

Как это делать? Апи там классическое openapi chat completions, следовательно нужны промпты, оч похожие на RCTF, но можно попроще.

И делаем первый промпт (разумявки на инглише)

You are a text editor working with YouTube transcripts.

Clean the following transcript while preserving the original meaning.

Rules:
- Merge broken sentences caused by subtitle line breaks
- Remove duplicated words or fragments
- Fix punctuation
- Keep the original wording as much as possible
- Do not summarize or shorten the text
- Do not add commentary

Output only the cleaned English transcript.

Transcript:

Дальше нужно заколбасить промпт перевода, согласно RCTF, конечно, лучше промптить без пункта 2 и 9, так как негативный промптинг хуже, чем конкретизирующий. Но лень.

You are an expert translator and technical writer specializing in programming and software engineering content.

Your task is to translate the following English transcript into natural Russian suitable for a YouTube tech video narration.

Important: This is a spoken video transcript.

Guidelines:

1. Preserve the meaning and technical information.
2. Do NOT translate literally.
3. Rewrite sentences so they sound natural in Russian.
4. Use clear, natural Russian with a slightly conversational tone.
5. Prefer shorter sentences suitable for narration.
6. Keep product names, libraries, commands, companies, and technologies in English.
7. Adapt jokes if necessary so they sound natural in Russian.
8. If a direct translation sounds unnatural, rewrite the sentence while preserving the meaning.
9. Do not add commentary or explanations.

Formatting rules:

- Output only the Russian translation
- Keep paragraph structure
- Make the result suitable for voice narration

Text to translate:

А-посля финальный промпт, который оживит текст

You are editing a Russian translation of a programming YouTube video.

Rewrite the text so it sounds more natural and fluid for voice narration.

Rules:

- Do not change the meaning
- Improve readability and flow
- Prefer shorter spoken sentences
- Make it sound like a developer explaining technology in a YouTube video
- Remove awkward phrasing
- Keep technical names in English
- Do not add explanations or commentary

Output only the final Russian narration script.

Text:

Промптинги свои складываем в папощку translation_prompts (так как рано или поздно будет видео промптинг бугага спойлеры аватаров)

Делаем петухон-скрипт для перевода

Состо-ять скрипт будет из 4 функторов (жаль из computation theory, but no category), ладно не функторов, так и быть, петухон-функций.

Нам нужно ollama client сделать, надо сделать вызов ollama, надо сделать загрузку промптов и обязательно подключить mlflow, чтобы красиво было, ведь да?

Это что-то вроде Langchain (или что там с платными вашими апи и обязаловкой регаться теперь и апиключи получать) ну по сути это а-ля opentelemetry/grafana, но для ваших ollama и прочих openai вызовов. Позволяет мониторить и токеномику считать

Это что-то вроде Langchain (или что там с платными вашими апи и обязаловкой регаться теперь и апиключи получать) ну по сути это а-ля opentelemetry/grafana, но для ваших ollama и прочих openai вызовов. Позволяет мониторить и токеномику считать

Получается, что нужно учесть, что мне mlflow надо, а вам не надо, значит опционально передаем аргументы

Ожидаемый usage согласно unix-у (ну или моему восприятию [7] юникслайков)

poetry run python src/python/translate_with_gemma.py [input.txt] [-o output.txt]

Или

MLFLOW_TRACKING_URI=http://localhost:5001 poetry run python src/python/translate_with_gemma.py [input.txt] [-o output.txt]

Разумеется для mlflow в make я вам засуну стартер с гуи.

Поехали реализовывать, в импортах понадобится

from __future__ import annotations

import argparse
import os
import sys
import time
from pathlib import Path

from openai import OpenAI

Ну и плюс константы, комменты придется писать к коду и на английском (ибо мало ли аудитории нужно будет, сегодня как раз видел человека из Турции, спрашиваю его “доставка или себе воду везете?” а он мне такой “инглиш”, а я ему “ду ю деливер фор юрселф ор деливери?”, он такой “фа?”, а я ему “вер а ю фром”, а он мне “Туркия”, а я ему “Мераба!”, а он такой “о”, как буква о в кавычках (сорри, но я не могу не писать так текст, иначе мне будет тупо скучно его писать, так я хотя бы приколы-переколы пишу сам смеюс и думаюс что кто-нибудь-с посмеется, как же еще порадовать человекачитающего, когда вокруг человекиграющий, а?

# Script and prompts directory / каталог со скриптом и промптами
SCRIPT_DIR = Path(__file__).resolve().parent
PROMPTS_DIR = SCRIPT_DIR / "translation_prompts"

OLLAMA_BASE_URL = "http://localhost:11434/v1"
MODEL = "translategemma:27b"

В общем или вообщем, или вообще, или в ообщем, захардкожу себе модельку, но буду давать се равно ее переопределять, мне тестить надо, а не PRC писать (да-да production ready code так оказывается сокращают, я тоже в шоке был вчера/сегодня не помню когда когда увидел прочитал)

А дальше все просто, реализуем-копипастимсинтернета-гуглим-читаемдоку-спарыраздазавелось

Обертка mlflow:

def setup_mlflow(tracking_uri: str | None, experiment_name: str = "voicer") -> None:
    """Enable MLflow autolog for OpenAI-compatible calls (Ollama). Включить MLflow autolog для вызовов Ollama."""
    if not tracking_uri:
        return
    try:
        """Потому что нехрен импортировать когда не надо"""
        import mlflow
        mlflow.set_tracking_uri(tracking_uri)
        mlflow.set_experiment(experiment_name)
        mlflow.openai.autolog()
        print(
            f"MLflow: tracking_uri={tracking_uri}, experiment={experiment_name!r}n"
            "  Трейсы смотри во вкладке «Traces» эксперимента в UI (не в Runs).",
            file=sys.stderr,
        )
    except Exception as e:
        err = str(e).strip()
        if "403" in err:
            print(
                "MLflow setup skipped: 403 Forbidden.n"
                "  На macOS порт 5000 часто занят AirPlay — используйте 5001:n"
                "  make mlflow  (запускает на 5001), затем MLFLOW_TRACKING_URI=http://localhost:5001 make translate",
                file=sys.stderr,
            )
        else:
            print(f"MLflow setup skipped: {e}", file=sys.stderr)

Долго думал почему там с авторизацией вилы были, а оказывается airplay на ваших проприетарных ос стоят, то ли дело говорила мне другая личность доделай форк netbsd, доделай, но ты ленился, видимо придется доделать как-нибудь

Дальше делаем как в этих ваших синглотанах-адаптер-директор (за почти 3 года в MS-е все паттерны были заучены и до появления енотов гуру)

def get_ollama_client(timeout: int = 600) -> OpenAI:
    return OpenAI(
        base_url=OLLAMA_BASE_URL,
        api_key="dummy",
        timeout=timeout,
    )


def ollama_generate(client: OpenAI, prompt: str, model: str = MODEL) -> str:
    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2,
        max_tokens=16384,
    )
    return (resp.choices[0].message.content or "").strip()

Что тут интересного:

  • openai клиент, зачем он нужен? правильно, только из-за mlflow, мне было интересно метрики посмотреть, без него можно и без openai клиента

    • кол-во токенов тоже из настроек ollama, температура рандом (демоны Лапласа, дратути)

Напоминаю на коленке пилим

Загрузка промпта такая же простая

def load_prompt(name: str) -> str:
    path = PROMPTS_DIR / name
    if not path.exists():
        raise FileNotFoundError(f"Prompt file not found: {path}")
    return path.read_text(encoding="utf-8").strip()

Тут надо помнить про кодировки, change codepage помним, да? И я вспомнил, когда квадратики и вопросики поплыли, оставляем также для входных аргументов доступы для переопределения в cli

def run_translate(
    raw_text: str,
    timeout: int = 600,
    tracking_uri: str | None = None,
    log_callback: callable | None = None,
    prompts: dict[str, str] | None = None,
    model: str = MODEL,
) -> dict[str, str]:
    """
    Выполняет трёхшаговый перевод. Возвращает {"step1", "step2", "step3", "final"}.
    log_callback(text) вызывается для вывода в GUI/логи.
    prompts: опционально {"clean", "translate", "final"} — тексты промптов; если None — загрузка из translation_prompts/.
    """
    def log(msg: str) -> None:
        if log_callback:
            log_callback(msg)
        else:
            print(msg, file=sys.stderr)

    setup_mlflow(tracking_uri)
    client = get_ollama_client(timeout=timeout)
    if prompts:
        prompt_clean = (prompts.get("clean") or prompts.get("prompt_clean_sub") or "").strip()
        prompt_translate = (prompts.get("translate") or prompts.get("prompt_translate") or "").strip()
        prompt_final = (prompts.get("final") or prompts.get("prompt_final") or "").strip()
        if not prompt_clean or not prompt_translate or not prompt_final:
            raise ValueError("prompts must contain 'clean', 'translate', 'final' (or prompt_clean_sub, prompt_translate, prompt_final)")
    else:
        prompt_clean = load_prompt("prompt_clean_sub.txt")
        prompt_translate = load_prompt("prompt_translate.txt")
        prompt_final = load_prompt("prompt_final.txt")

    log("Step 1: Cleaning transcript...")
    t0 = time.perf_counter()
    cleaned_en = ollama_generate(client, f"{prompt_clean}nn{raw_text.strip()}", model=model)
    log(f"Шаг 1 готов: очищенный английский ({time.perf_counter() - t0:.1f} с)")

    log("Step 2: Translating to Russian...")
    t0 = time.perf_counter()
    translated_ru = ollama_generate(client, f"{prompt_translate}nn{cleaned_en}", model=model)
    log(f"Шаг 2 готов: черновик перевода ({time.perf_counter() - t0:.1f} с)")

    log("Step 3: Final pass for narration...")
    t0 = time.perf_counter()
    final_ru = ollama_generate(client, f"{prompt_final}nn{translated_ru}", model=model)
    log(f"Шаг 3 готов: финальный текст ({time.perf_counter() - t0:.1f} с)")

    return {
        "step1": cleaned_en,
        "step2": translated_ru,
        "step3": final_ru,
        "final": final_ru,
    }

Делаем первый коммент онглийским, а второй русским, править под язык выбранным юзером мне лень, пишем на скорую руку, главное, чтобы работало, опенсорсеры сами поправят, PR-ы буду вливать хорошие вай нот

А дальше самое скучное main вызов для cli, тут ничего особо интересного, надо просто параметров разобрать и параметров передать, как json-укладщик на backend-стройке

def main() -> None:
    parser = argparse.ArgumentParser(
        description="Трёхшаговый перевод через translategemma:27b (Ollama)"
    )
    parser.add_argument(
        "input",
        type=Path,
        nargs="?",
        default=SCRIPT_DIR / "reference_text_to_translate.txt",
        help="Файл с сырым английским транскриптом",
    )
    parser.add_argument(
        "-o", "--output",
        type=Path,
        default=None,
        help="Файл для итогового русского текста (по умолчанию — stdout)",
    )
    parser.add_argument(
        "--step1-out",
        type=Path,
        default=None,
        help="Опционально: сохранить результат шага 1 (очищенный EN)",
    )
    parser.add_argument(
        "--step2-out",
        type=Path,
        default=None,
        help="Опционально: сохранить результат шага 2 (черновик перевода)",
    )
    parser.add_argument(
        "--timeout",
        type=int,
        default=600,
        help="Таймаут одного запроса к Ollama в секундах (default: 600)",
    )
    parser.add_argument(
        "--tracking-uri",
        type=str,
        default=os.environ.get("MLFLOW_TRACKING_URI", ""),
        help="MLflow tracking URI (или MLFLOW_TRACKING_URI); если задан — включается autolog",
    )
    parser.add_argument(
        "--experiment",
        type=str,
        default=os.environ.get("MLFLOW_EXPERIMENT_NAME", "voicer"),
        help="Имя эксперимента MLflow (default: voicer)",
    )
    args = parser.parse_args()

    tracking_uri = (args.tracking_uri or os.environ.get("MLFLOW_TRACKING_URI")) or None
    setup_mlflow(tracking_uri, experiment_name=args.experiment)
    client = get_ollama_client(timeout=args.timeout)

    if not args.input.exists():
        print(f"Error: input file not found: {args.input}", file=sys.stderr)
        sys.exit(1)

    prompt_clean = load_prompt("prompt_clean_sub.txt")
    prompt_translate = load_prompt("prompt_translate.txt")
    prompt_final = load_prompt("prompt_final.txt")

    raw_text = args.input.read_text(encoding="utf-8").strip()
    if not raw_text:
        print("Error: input file is empty.", file=sys.stderr)
        sys.exit(1)

    def show_step(name: str, text: str) -> None:
        sep = "─" * 60
        print(f"n{sep}n  {name}n{sep}", file=sys.stderr)
        print(text, file=sys.stderr)
        print(sep + "n", file=sys.stderr)

    print("Step 1: Cleaning transcript...", file=sys.stderr)
    step1_prompt = f"{prompt_clean}nn{raw_text}"
    cleaned_en = ollama_generate(client, step1_prompt)
    show_step("Шаг 1 — Очищенный английский", cleaned_en)
    if args.step1_out:
        args.step1_out.write_text(cleaned_en, encoding="utf-8")
        print(f"  -> сохранено в {args.step1_out}", file=sys.stderr)

    print("Step 2: Translating to Russian...", file=sys.stderr)
    step2_prompt = f"{prompt_translate}nn{cleaned_en}"
    translated_ru = ollama_generate(client, step2_prompt)
    show_step("Шаг 2 — Черновик перевода (RU)", translated_ru)
    if args.step2_out:
        args.step2_out.write_text(translated_ru, encoding="utf-8")
        print(f"  -> сохранено в {args.step2_out}", file=sys.stderr)

    print("Step 3: Final pass for narration...", file=sys.stderr)
    step3_prompt = f"{prompt_final}nn{translated_ru}"
    final_ru = ollama_generate(client, step3_prompt)
    show_step("Шаг 3 — Финальный текст для нарации", final_ru)
    if args.output:
        args.output.write_text(final_ru, encoding="utf-8")
        print(f"Готово. Итог записан в {args.output}", file=sys.stderr)
    else:
        print(final_ru)

Грязно ли? Да!

Потому что не любим петухон и не будем стараться? Да!

Делаем ли мы ввод на одном языке, а ввод на другом? Да!

Зачем? Чтобы отличать на каком мы шаге? Да!

Кодировочку в константы? Нет, так как command + c и command + v.

И в целом-то все по переводам этим вашим.

Основная цель была сложить вывод в translated.txt (эх как вспомню свою первую поделку и попытку NLP решить, да)

Складываю все в translate_with_gemma.py, создаем папку src (как в тупескрупте) и там делаем пифон

Делаем make-команду и коллим её

ollama не включил лохнесс

ollama не включил лохнесс

первый результат

Как я переводы видео автоматизировать собирался - 8
Этот меня, конечно, напугал фразой про кто-то там выйти хочет, но в видосе был Нео из Матрицы, как забавно и моментно однако, да? Запятые ставлю куда попало

Этот меня, конечно, напугал фразой про кто-то там выйти хочет, но в видосе был Нео из Матрицы, как забавно и моментно однако, да? Запятые ставлю куда попало
Как я переводы видео автоматизировать собирался - 10

Давайте хоть посмотрим в чем разница

Как я переводы видео автоматизировать собирался - 11

По идее суть промпта была убрать повторы и некоторые кейсы с ремами (английский текст по повествованию отличается)

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

  • Марат переводил текст и он казался серьезным.

Тут “он” по сути переключатель к “реме” (погуглите/пояндексите/погптште), это некая форма артикля the, где он используется как указатель на последнее существительное, но в русском языке у нас появляется механизм, который с точки зрения [8] литературных канонов запретен, только, вот, создает двусмысленность, отсутствие идемпотентности. Он – это кто? Марат? Или текст?

В русском языке, мы чаще “он” с кастуем к живому, а текст пусть и мужского рода, но оно будет “это”, тогда как в русском языке заменяя “он” на “это”, мы вообще построим рему к глаголу “переводил” и смешно и непонятно. Так вот английская LLM в сообщении (немного коряво построю, но как пример сойдет) “A text was wrapped and sound was proceed. The object looks better now after the change”. Я специально допустил несколько ошибок (или прикидываюсь), но упущена ссылочность и теперь появилось то самое ambiguity. В русском языке, (нравится повторять [9] да-с), у нас вообще обобщение идет или даже смена полной темы в “текст был обернут и звук запроцешен. объект выглядит лучше после изменения”, тут либо объект обобщает, либо это просто полная смена темы. Вот те на.

Переозвучка

По сути это самый простой кусок кода, на стриме тогда выдернул его с документашки Qwen-а, плюс добавил переменных и чтобы юзались файлики локальные, тут специфики нет

"""Single-file clone: translated.txt + reference -> one WAV. Use env VOICER_DEVICE, VOICER_ATTN, VOICER_LANGUAGE."""
from pathlib import Path
import os
import sys

import torch
import soundfile as sf
from qwen_tts import Qwen3TTSModel

_ref_dir = Path(__file__).resolve().parent
if str(_ref_dir) not in sys.path:
    sys.path.insert(0, str(_ref_dir))
from stress_utils import apply_stress_overrides

device = os.environ.get("VOICER_DEVICE", "mps:0")
attn = os.environ.get("VOICER_ATTN", "sdpa")
language = os.environ.get("VOICER_LANGUAGE", "Russian")

model = Qwen3TTSModel.from_pretrained(
    "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
    device_map=device,
    dtype=torch.float16,
    attn_implementation=attn,
)

ref_audio = str(_ref_dir / "reference_audio.wav")
reference_text = (_ref_dir / "reference_voice.txt").read_text(encoding="utf-8").strip()
translated_text = (_ref_dir / "translated.txt").read_text(encoding="utf-8").strip()
translated_text = apply_stress_overrides(translated_text)

wavs, sr = model.generate_voice_clone(
    text=translated_text,
    language=language,
    ref_audio=ref_audio,
    ref_text=reference_text,
)
sf.write(str(_ref_dir / "output-translated-clone.wav"), wavs[0], sr)

Проверяем озвучку (разумеется сократив текст, ибо мне лень 76 чанков генерить)

2 минуты экзекьюции и готово

2 минуты экзекьюции и готово

https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/output-translated-clone.wav [10]

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

Типа при переозвучке исходной я текст под себя подгонял

Как я переводы видео автоматизировать собирался - 13
А потом уже по факту писал, так как разумеется при беглом чтении во время озвучки часть слов мной игнорировались или склеивались, чтобы избежать десинхрона с основной озвучкой от челика на ютубе

А потом уже по факту писал, так как разумеется при беглом чтении во время озвучки часть слов мной игнорировались или склеивались, чтобы избежать десинхрона с основной озвучкой от челика на ютубе

Дальше с помощью интернетов/вимолам допиливаем клиентское приложение, пайплайны, makefile-ы и прочее, чтобы красиво завернуть, добавив батчей, кучу говнокода и прочих экспериментов

смотрите как много змей в этом редакторе, код мог быть чище если бы я просто платил за codex/cursor или что там щас на хайпе? Но нет, я выбрал context engineering, а не этот ваш vibe coding, мне важно отдать часть ленивого кода на аутсорс, а не мышление, мышление вот что делает эти статьи

смотрите как много змей в этом редакторе, код мог быть чище если бы я просто платил за codex/cursor или что там щас на хайпе? Но нет, я выбрал context engineering, а не этот ваш vibe coding, мне важно отдать часть ленивого кода на аутсорс, а не мышление [11], мышление вот что делает эти статьи

Еще предварительно поделал исключения для ударений типа через судоку-судо́ку, а чтобы не морочить голову с этим символом, просто судОку, для явной озвучки. Но лучше просто закидывайте огрызком в reference audio, так надежнее.

Обмазываемся тестами и линтером, дописываем переписываем пару раз readme, шоб красивое было!!!1

И пушим в github

Дальше как и во всех прошлых (б|в)логах методом проб и ошибок чиним пайплайны пока не получим это:

Как я переводы видео автоматизировать собирался - 16

И дальше пытаемся затеггировать и прилинковать релизы, в целом получилось, кроме линуксового, там превышение размера, однако, кому надо допилят, так как под линукса там тоже сугубоиндивидуальные бинари (от deb пакетов до прочих ваших магазинов flatpack-нутых)

Как я переводы видео автоматизировать собирался - 17

MVP собрался (под винды не тестил, но потом через Whisky запущу, сейчас надо скачать и запустить для OS X)

700 метров под ваши OS X

700 метров под ваши OS X

Я же тебя сам публиковал ты чу

Как я переводы видео автоматизировать собирался - 19
Ладно так и быть разрешу

Ладно так и быть разрешу

На выходе получилась версия под винду и macos M-процессоры, можете сами себе локально собрать склонив репку

И вроде бы можно жить

И вроде бы можно жить

Причем локально запустить бинарь на маке получилось только через правую кнопку мыши открыв Package Contents и там запустить unix файл, в противном случае работали только переводы, кстати gemma:4b для перевода действительно пошустрее

Как я переводы видео автоматизировать собирался - 22

С другой стороны все работает, а работает не трожь, для себя я инструментик для переозвучки сделал удобнее, а если хотите улучшить репозиторий открыт https://github.com/the-homeless-god/voicer/tree/master [12]

Как я переводы видео автоматизировать собирался - 23

Что по десктоп фичам-то?

  • Добавил сразу опции озвучки без перевода (может кому-то надо или например мне допилить кусочек слова например)

  • Добавил опции выбора языка

  • Добавил опции настройки масштаба полей и копирование в логах

  • Добавил логи собс-на

  • Работу с батчем и кол-вом токенов

  • Выбор моделей и для перевода и для озвучки

  • Файловый выбор референсов

  • Опция перевода сразу с выводом в три окошка (копирование не работает, но мне оно было и не надо – надо будет допиши в репозиторий открыто же ну)

  • Также добавил опцию указывать папку и открывать ее сразу же

  • Промпты в отдельном окне, словарь ударений тоже

  • Плюс кнопка проверки все ли доступно для софтины

Как я переводы видео автоматизировать собирался - 24

Have a fun!

Что из экспериментов еще удалось понять

  • если возьмете какой-нибудь flash-attention, работать будет быстрее, на macos есть альтернатива – metal-attention, но собирается криво

    • есть aule-attention, но я её форкнул, поигрался, пришлось форкать qwen3, потом пришлось брать Zod язык и на нем что-то подпиливать напильником, а в конце оказалось, что с 64 битами оно работать не могёт, в общем, если надо отдельной веткой или PR могу открыть с экспериментами и git submodule-ями

  • ковырение замены SDPA для Metal противно, собирать vulkan было такое себе удовольствие, но может именно Вы сможете его заставить работать, потому в качестве переменных Вам будет доступно

    также и сами можете туда печатать

    также и сами можете туда печатать
  • аудиклонирование лучше тогда когда войс чище, иначе оно клонирует и шум, то есть почудите с настройками или очисткой аудиодорожки на входе

  • генерация зависит от того какой размер батча, сколько предложений за раз, в идеале я озвучивал Хоббит на 48 сек и текст потом тоже примерно был склонирован на 48 сек, что можно на нарезке со стрима увидеть

  • translategemma:27b работает долго, лучше наверное брать модель мельче, но мне было интересно что можно выжать

    • подтянув translategemma:4b на M1 Max Mac Studio 32GB

  • для gui юзнул customtkinter, самый быстрый способ на коленке собрать ui с ollama

  • не надо пытаться за один лоад модели в память [13] пытаться прогонять по максимуму в один инференс, то есть лучше периодически сгружать модель – проблема та же, что и в переозвучке wow, там было вообще дешевле перезапускать и загружать в память ее каждый раз, если попробуете сразу озвучить 76 предложений например, то где-то в середине будут галлюцинации

  • первый запуск из-под десктопа может чуть дольше тянуть себе в кеш qwen3-tts, это нормально

  • записывайте кусочек без ваших речевых проблем (например с моей гортанной смычкой оно достигло апофеоза и сделал максимум картавости, самому аж противно, как вы меня слушаете)

Что дальше?

  • Встроить в openclaw и думать как звонить, либо через n8n пайп видосы авто-озвучивать

    • почему так можно – потому что есть cli, который можно скормить вашему агенту или workflower-у типа n8n / alfred / openclaw

  • Переозвучить аниме (разумеется, но сначала визуальную новеллу Шайтан-калитку родными голосами Окабэ и Курису, потом и сгенерированными без сейю (как там пишется))

  • Продолжить цифровые портреты в рамках стримов

Где контент искать (а-то чет на хабр я раз в два года что ле пишу):

Если есть идеи для экспериментов дальше – кидайте в комменты, чекну

И финальные скрины как пример лежат в самом гитхабе, а с cli если найдутся рукастые го в brew и в pip допушим, че бы нет?

Репозиторий с проектом по ссылке https://github.com/the-homeless-god/voicer/tree/master [12]

Автор: the_homeless_god

Источник [18]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/27234

URLs in this post:

[1] https://habr.com/ru/articles/818513/: https://habr.com/ru/articles/818513/

[2] https://www.youtube.com/@the_homeless_god: https://www.youtube.com/@the_homeless_god

[3] http://github.com/the-homeless-god/voicer: http://github.com/the-homeless-god/voicer

[4] Qwen3-TTS: https://qwen.ai/blog?id=qwen3tts-0115

[5] запись стрима на YouTube нарезанная нарезчиком: https://www.youtube.com/watch?v=ycOb3VetcmM

[6] https://downsub.com/: https://downsub.com/

[7] восприятию: http://www.braintools.ru/article/7534

[8] зрения: http://www.braintools.ru/article/6238

[9] повторять: http://www.braintools.ru/article/4012

[10] https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/output-translated-clone.wav: https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/output-translated-clone.wav

[11] мышление: http://www.braintools.ru/thinking

[12] https://github.com/the-homeless-god/voicer/tree/master: https://github.com/the-homeless-god/voicer/tree/master

[13] память: http://www.braintools.ru/article/4140

[14] https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/001.wav: https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/001.wav

[15] https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/002.wav: https://github.com/the-homeless-god/voicer/raw/refs/heads/master/examples/002.wav

[16] https://www.twitch.tv/marat_zimnurov: https://www.twitch.tv/marat_zimnurov

[17] https://t.me/digitable_blog: https://t.me/digitable_blog

[18] Источник: https://habr.com/ru/articles/1011072/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1011072

www.BrainTools.ru

Rambler's Top100