Синопсис

Здравствуйте-здравствуйте, играю я значит в классический World of Warcraft и понимаю, что игра тосклива из-за отсутствия более-менее озвученных квестов. Конечно, инди-студия Метелица не способна озвучить всю свою игру, да и понятно, что сюжет развивается, а люди имеют свойство менять работу или, да это печально, физической гибели, как например случилось с озвучкой Артаса Менетила в Warcraft III: Frozen Throne.

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

Введение

И вот я случайно-неслучайно натыкаюсь на аддоны (дополнения) для wow. До этого ими не пользовался, так как в первую очередь, я - игрок лора, мне не особо была интересна PvP составляющая или походы в подземелья.

Среди пачки аддонов находятся ребята - https://www.curseforge.com/wow/addons/voiceover, которые каким-то образом сделали озвучку для персонажей. В описании сказано: "вы должны скачать voice pack", то есть аддон имеет какой-то dependency.

То есть дополнение является player-ом, а вот сама озвучка - это какой-то архив. И внизу есть ссылка на GitHub. Открываем

А поддержка языка у ребят урезана, судя по Usage

Я нашел ссылку на их Discord и полез туды, чтобы прояснить в чём дело, оказалось ребята использовали 3rd party, которая позволяла генерировать text-to-speech основываясь на кусочках голосов. То есть вы должны создать голоса под каждую расу и каждый пол, а дальше, используя их нейронку нагенерить себе всё что нужно и сложить в аудиофайлы.

Чел пишет, что потратил не менее 600 баксов для английской версии, но вот тратить дальше - денег нет. Я хотел было плюнуть и попробовать получить русскую версию там же заплатив сколько нужно. Но по подсчетам тарифа там выходит за 2к баксов только чтобы что-то получить нормальное.

И тут услышать квесты на русском стало целью. Не то, чтобы я не знаю английский (и IELTS сдавал, и на Microsoft работал, и в Берлине год прожил (если надо могу написать свой взгляд на то, как выглядит непредвзятая жизнь семейного человека, а также советы при релокации, просто посигнальте в комментах)), да и в целом могу свой гайд по английскому написать как его за год освоить до разговорного.

Сорри за оффтоп, едем дальше: В какой-то момент я подумал, а что если ребята просто не шарят, что можно юзать open source?
Там как раз есть целая тьма проектов, например закрывающийся https://github.com/coqui-ai/TTS
Далее в ходе моих размышлений я выстроил целый чудовищный план по уничтожению языков в контенте, но забил на него. Как всегда. Но вот недавно я просто подумал - а может мне перевести просто их английскую версию на русскую?
Coqui-ai tts (который КМК поверх Mozilla TTS) умеет клонировать голос и травить его на нужный текст используя семпл хоть на 6 секунд.

Надо понять как устроен этот самый аддон.

Как работает существующий аддон, да и вообще аддоны в целом

В ходе исследования, стало понятно, что всё куда просто-сложно. Аддоны это всратый lua-код, который не имеет доступ ни к чему вне WoW-а. То есть вы не можете просто так постучаться во внешний REST API или обращаться к I/O. У вас есть этот доступ на момент загрузки аддона, но не более. А так хотелось подключить телеграм бота в чат в wow, но увы, в следующий раз, на уме есть пара лазеек, но лень.
Что сделали ребята - нашли открытую бд для всех квестов, весит она сколько-то там.

Значит делают они так:

На каждую расу приходится 2 голоса - мужской и женский, а рас около 15 минимум, итого порядка 30 голосов и 10кк символов текста на каждую. Мде.

Приступаем к настройке окружения

Иду клонировать репозиторий, но через GitHub Codespaces. Почему? Потому что на Mac Studio стоит ARM, запускать x86 комп мне лень, через UTM есть винда и NetBSD в качестве рабочих машин, но вот для того всратого tts нужна бубунта, да и скорее всего в x86. А ставить через Podman и засирать свою машину мне лень потом вычищать всё это добро. В общем лучшее враг хорошего. Поэтому просто в GitHub Codespaces.

В их же репозитории начинаем обогащать петухон-проект, добавляем туды TTS

python -m venv .venv
. .venv/bin/activate
echo TTS >> requirements.txt
pip install -r requirements.txt

Дальше тащим их Soundpack в папочку с translator-ом (этот пак весит 1 гб)

mkdir translator
cd translator
mkdir assets/wow-classic-en
wget https://github.com/mrthinger/wow-voiceover/releases/download/v1.3.1/AI_VoiceOverData_Vanilla-v1.0.0.zip
unzip AI_VoiceOverData_Vanilla-v1.0.0.zip

# сохраним архив, мало ли что-то накосячим
mv AI_VoiceOverData_Vanilla-v1.0.0.zip ~/downloads

Теперь тащим tts, я хотел было целиком собирать их репозиторий, но потом плюнул и увидел, что у Codespaces итак есть docker, как бы я ненавидел их, желание получить результат как можно быстрее победил.

P.S. Причем месяц назад я баловался с tts, локально он запускается, но на петухоне не выше 3.10 ЕМНИП.

То есть оно запускается в контейнере, значит можно дописать и compose

podman run --rm -it -p 5002:5002 --entrypoint /bin/bash ghcr.io/coqui-ai/tts-cpu

Получилась вот такая шляпа

  tts:
    image: ghcr.io/coqui-ai/tts-cpu
    container_name: tts-server
    ports:
      - "5002:5002"

Запускаю её через podman-compose up tts, а там контейнер на 6.3GB бугага

Теперь судя по архиву там какая-то хрень и надо генерировать под аддон свои, назову это, пре-файлы.

cp .env.example .env
# у них там уже в репе мускул
docker compose up -d
python cli-main.py init-db

python cli-main.py gen_lookup_tables --lang=ruRU

в папке tts_cli создаю tts_ai.py, для того, чтобы оно подтянуло модель

import torch
from TTS.api import TTS

# Get device
device = "cpu"

# List available ?TTS models
print(TTS().list_models())

# Init TTS
tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)

def getVoice(text, input_sound_path, output_sound_path, language):
    tts.tts_to_file(text=text, speaker_wav=input_sound_path, language=language, file_path=output_sound_path)

в самой папке вызываю

python tts_ai.py

и убеждаюсь, что оно не умеет тянуть модель сама, значит надо качать репо из https://huggingface.co/coqui/XTTS-v2/tree/main?clone=true

git lfs install
git clone https://huggingface.co/coqui/XTTS-v2

и вот закончилось место на виртуалке

но мы удалим скачанный архив sound-ов и вот + 1 гигабайтик:

rm -rf ~/downloads

запустил ncdu и освободил на 2.2GB
потом понял, что там numpy не дружит с версией какой-то у tts, короче даунгрейднул пакет

в итоге места опять не хватило, плюнул и апгрейднул тачку

пока она там конвертируется пошел читать как публиковать свой аддон, оказалось, что надо просто перейти по ссылке https://legacy.curseforge.com/project/create и заполнить соответствующую форму, отложу пока на потом, так как тачка успешно бампнулась

Заполняю под копирку с основного, везде вставляя Russian и уговаривая купить мне кофе через подписку на мой паблик по разработке ПО в телеграм https://t.me/digitable_sub_bot по цене 250р в месяц)

Проверка, что TTS-модель работает

Дальше тыкаюсь в tts локально, чтобы получить на основе текста квеста и входной звуковой дорожки выходную звуковую дорожку

tts \
--model_name "tts_models/multilingual/multi-dataset/xtts_v2" \
--speaker_wav /workspaces/wow-voiceover/translator/assets/wow-classic-en/AI_VoiceOverData_Vanilla/generated/sounds/quests/2-complete.wav \
--out_path /workspaces/wow-voiceover/translator/assets/wow-classic-ru/AI_VoiceOverData_Vanilla/generated/sounds/quests/2-complete.wav \
--text "Могучий гиппогриф Острокоготь был убит, и коготь этой свирепой твари – свидетельство вашей победы. Сенани Громосерд на заставе Расщепленного Древа несомненно пожелает увидеть этот трофей – доказательство ваших деяний." \
--language_idx 'ru' 

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

Начинаем использовать встроенный генератор

Так-с, теперь запустим

Видать где-то в передаче языка накосячил и передается английский

python cli-main.py gen_lookup_tables --lang=ruRU

Страшно, конечно, что 17к аудиофайлов бедный Codespace будет обрабатывать, с другой стороны, а зачем я тогда за подписку плачу?

Оказалось что этот всратый gen_lookup_tables создаст в output папке нужные мне тексты квестов, поэтому свапаю местами тексты квестов в input папку обычным копированием.

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

Пример - https://raw.githubusercontent.com/the-homeless-god/wow-voiceover/master/assets/video/10-complete.mp3

И вот ошибки во время генерации:

Улучшаем исходники

Оказывается, что не все квесты переведены, а значит топаем сюда https://www.hiveworkshop.com/threads/warcraft-iii-medivh-soundset.337074/ и достаем голос Медива, чтобы как в третьем варкрафте закадравый голос был для случаев, когда голоса нет

Но там все примеры короткие, нашел на YouTube https://www.youtube.com/watch?v=3svAOHX7FLI, сейчас стащим видеофайл и с ffmpeg достанем оттуда mp3.
Пока тащил файл через ssyoutube там же скачал mp4 и сохранил как mp3. Так что не в этот раз, ffmpeg.

Можно поступить круче и озвучить все квесты моим голосом, но это будет слишком

  def tts(self, text: str, inputName: str, outputName: str, output_subfolder: str, language: str, forceGen: bool = False):
        result = ""
        outpath = os.path.join(SOUND_OUTPUT_FOLDER, output_subfolder, outputName)
        inpath = os.path.join(SOUND_INPUT_FOLDER, output_subfolder, inputName)

        if os.path.isfile(outpath) and forceGen is not True:
            result = "duplicate generation, skipping"
            return

        # вот тут добавил проверку, что если input-sample квеста отсутствует, то пусть берет голос Медива
        if os.path.isfile(inpath) is False:
            inpath = DEFAULT_VOICE
            return
    
        self.convert(text, inpath, outpath, language)

        result = f"Audio file saved successfully!: {outpath}"

        return result

Оцениваем затраты

Но чтобы сгенерировать 19784 файла по 30 секунд каждый надо 164 часа вроде бы
((19784 * 30) / 60) / 60 / 24 - это примерно 7 дней фактически

А если распараллелим в 10 потоков, то получим 16 часов, поехали

А если вырезать альянс, то выйдет еще в 2 раза быстрее.

И пока эта штука генерится я подумал, ладно черт с ним запущу на своём M1 Max Pro 64 128 Ultra Hard Nano Nice. Пойду стяну репу. Надо сделать форк исходной и закоммитить в том числе английские аудиофайлы. Да на +1гб в репо, но мне лень перекачивать архивы. А потом если надо будет - ребята откатят коммит, делов-то.

Хотя не, не буду, а то потом коммит не примут еще.

Надо архив опять качать, в этой статье где-то это было)

wget https://github.com/mrthinger/wow-voiceover/releases/download/v1.3.1/AI_VoiceOverData_Vanilla-v1.0.0.zip

unzip translator/assets/wow-classic-en/AI_VoiceOverData_Vanilla-v1.0.0.zip

Пытаемся использовать M1 & Metal Accelerator в PyTorch

Пока качал понял, что в M1 можно взять Metal Accelerator, чтобы использовать GPU - https://developer.apple.com/metal/pytorch/

Оказалось что там и зависимости по-другому работают, мде

pip3 install --pre -r requirements.txt --index-url https://download.pytorch.org/whl/nightly/cpu

И определение девайса болезненное

# Get device
device = "cpu"

if torch.backends.mps.is_available():
    device = torch.device("mps")
    x = torch.ones(1, device=device)
    print (x)
else:
    print ("MPS device not found.")

Да и его поддержка, аж на баг наткнулся, заспекал и написал workaround для чела, который тоже вчера видимо с этим столкнулся https://github.com/coqui-ai/TTS/issues/3758

Ищем пути оптимизации воркеров, тредов, параллелей и любых других слов

Решил запустить локально генерацию для одной части, а для другой в Codespaces, в очередной раз апнув виртуалку на 16 ядер, у них кстати есть крутая фича по-умолчанию на все тачки тянуть ваши dotfiles, мелочь, а приятно

Мои dotfiles тут - https://github.com/the-homeless-god/dotfiles, звездочку туда поставь и дальше иди
Мои dotfiles тут - https://github.com/the-homeless-god/dotfiles, звездочку туда поставь и дальше иди

Вот теперь процессор работает (запустил 8 воркеров)

pip install bpytop, если что
pip install bpytop, если что

Но вот беда - у разрабов где-то косяк с воркерами и они падают с index of out range, то есть у него что-то там с массивами не получается в индексы, но беда не в этом, мульти-воркеры работают дольше одного воркера

Поэтому запустил в один поток. Это сами знаете что.

Что самое смешное это косяки, а с ними прям замечательно в речи. То есть когда персонаж гоблин говорит "безднА" вместо "бЕздна", то это как наречие или акцент, что добавляет живности языку. Грубо говоря как "звОнит" и "звонИт". Так что оставляем.

Примеры:

https://github.com/the-homeless-god/wow-voiceover/raw/master/assets/video/10-accept.mp3
https://github.com/the-homeless-god/wow-voiceover/raw/master/assets/video/33-accept.mp3
https://github.com/the-homeless-god/wow-voiceover/raw/master/assets/video/m-f07f5e6d40044eae745e47cfd0b2ac14.mp3

За 15 минут обработано всего лишь 114/19784 элементов.

Видимо придется разбираться где там в настройках многопотока накосячили они. Потому что выполнение обещает минимум 133 часа в один поток. Надо попробовать разобраться с Google Collab может быть, а пока просто перевернул pandas dataframe df = df.sort_index(ascending=False) чтобы на виртуалке с конца списка пойти, пока локально иду сначала. Главное успевать синкать директории.

Думаем, а может вместо CPU использовать GPU?

Прошел час - на mac вышло порядка 2%, на виртуалке 3%, разница в том что на виртуалке с конца списка пошли короткие файлы с набором фраз в 10 слов, а на маке крутятся тексты квестов. В целом обработано 1к из 19к. Сейчас попробую оживить виндовый комп на AMD RX 580 и сравнить как же быстро дело пойдёт с видеокартой. А в параллели опрашиваем знакомых

Причем мак говорит что справится за 33 часа, а виртуалка если дернуть neofetch говорит за 54 часа (который кстати сюрприз-сюрприз архивирован, вроде бы потому что разраб ушел на гусинную ферму, судя по треду в реддите)

Сижу генерю

Пока там всё ещё петухон генерирует нам из-под torch-а аудиофайлы пойду что ли описание аддону заполню

Спустя пару часов виртуалка съела все ресурсы по лимитам подписки

6,5 часов и остаётся 47,5 по её оценкам
6,5 часов и остаётся 47,5 по её оценкам

Выкачиваю всё что она сгенерила с конца списка, пакую в архив и отдаю племяннику генерировать на его Nvidia 2060 что-то там

Причём на Mac Studio скорость стабилизировалась на 16s-24s на строку, но и там пока торможу процесс, чтобы слить файлы в одну систему

7 часов и остаётся 81 час по её оценкам
7 часов и остаётся 81 час по её оценкам

После склейки 7 часов работы: 5 directories, 1937 files

На Windows в параллели запускать тоже оказалось проблемно, всратая cuda, долго побеждали зависимости, но справились, сейчас всё ещё пытаемся понять почему многопоточность не работает, там что-то для tty не отрабаывает.

Решили запускать 2 powershell-а в разные стороны

  • Прошло ещё 5 часов: 2500 + 6000 из 19784, надо как-то параллелить.

    Потеряв тонну времени, в попытках перебирать мультипроцессинг оказалось действенным:

    • на каждый тред при каждом файле генерить TTS

    • указать TTS общую шаренную память

    • разбивать датасет на чанки

    Из недостатков: КАЖДЫЙ ФАЙЛ БУДЕТ ПОДКЛЮЧАТЬСЯ К МОДЕЛИ

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

	def convert(self, text, input_sound_path, output_sound_path, language):
        print(input_sound_path)
        print(output_sound_path)
        print(text)
        device = "cpu"

        ttsai = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
        ttsai.share_memory()

        return ttsai.tts_to_file(text=text, speaker_wav=input_sound_path, language=language, file_path=output_sound_path)


    def process_rows_in_parallel(self, df, row_proccesing_fn, max_workers=STATIC_MAX_WORKERS):
        total_rows = len(df)
        bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}] {postfix}'

        # num_processes = mp.cpu_count()
        num_processes = max_workers

        chunk_size = int(df.shape[0]/num_processes)

        chunks = [df.iloc[df.index[i:i + chunk_size]] for i in range(0, df.shape[0], chunk_size)]
        # chunks = [df.iloc[i:i + chunk_size,:] for i in range(0, df.shape[0], chunk_size)]
        
        with tqdm(total=total_rows, unit='rows', ncols=100, desc='Generating Audio', ascii=False, bar_format=bar_format, dynamic_ncols=True) as pbar:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                for chunk in chunks:
                    for custom_message in zip(executor.map(row_proccesing_fn, chunk.itertuples())):
                        pbar.set_postfix_str(custom_message)
                        pbar.update(1)

Ищем сервера с GPU

Еще +5 часов, но уже в 2 потока и на 2 машинах: Mac Studio M1 Max и какая-то винта на Super 2060, суммарно имеем 6907 + 3251 из 19784, то есть за прошедшие грубо говоря 12-13 часов только подошло к половине, пойду посмотрю цены на выделенные сервера с GPU

И удивлюсь с цен

Мде
Мде

у Selectel - 330к в месяц
у Reg.ru уже по скромнее

38 700 рублей в месяц
38 700 рублей в месяц

Понравились ребята с iqhost, но у них отвалился сайт, и пришлось идти дальше

А цены-то гуманные, жалко легли в тот момент
А цены-то гуманные, жалко легли в тот момент

Судя по всему мощность должна быть кратно выше

пошел на другой с какими-то ультра настройками

кстати эти ребята понравились - https://gpudc.ru
кстати эти ребята понравились - https://gpudc.ru

Арендовалось

сколько буковок
сколько буковок

В итоге пока устанавливал репо и все зависимости - потерял 300р)

Ну и пока assets перекладывал еще 300 съел)
Ну и пока assets перекладывал еще 300 съел)

А потом видишь косяки либы этих ваших петухоновых торчей

Из не менее прикольного: при генерации можно указать ник вашего персонажа

В 179 потоках на CPU:

Причем наконец-то параллелинг

Толькот вот пользы никакой, потрачено 10 часов в попытках заставить хоть что-то работать (надеюсь комментаторы подскажут где же тут ошибка)
Толькот вот пользы никакой, потрачено 10 часов в попытках заставить хоть что-то работать (надеюсь комментаторы подскажут где же тут ошибка)
Пойду дальше руками догенерирую
Пойду дальше руками догенерирую

Генерируем на M1 Max в Mac Studio

+7 часов за ночь на macStudio, в 1 поток нагенерилось 200 файлов, причём потому что студия уходила периодически в сон и некоторые файлы грузились порядка 8 минут

Пойду поставлю в 2 потока и с постоянной загрузкой в памятью

И так система ведет себя лучше, нежели чем всегда под 100%
И так система ведет себя лучше, нежели чем всегда под 100%

Порядка 40-50 файлов в час продолжительностью аудиодорожки 10-30 сек, на данный момент нагерено 5061 из 9563 файлов.

Пока открыл PR понял, что GitHub не тянет отображение count для такого объема файлов, но я складываю mp3 файлы в репозиторий в процессе генерации, чтобы с нескольких девайсов можно было не заморачиваться с распаковкой

Когда слишком много файлов
Когда слишком много файлов

Про проблемы параллелизма писали тут - https://github.com/coqui-ai/TTS/discussions/1852, фактически нужно иметь больше 1 синтезатора, так как 1 синтезатор обрабатывает только 1 файл

Прошло еще 10 часов,
139 файлов, квест номер 4129
обработано 5685/19784
6508 файлов из 9563 файлов
29%

Конечно можно задать вопрос:

И получить ответ - я, поэксперементировал с параллелизмом и многопоточностью - как мне кажется моих знаний питона не хватило, я попробовал делить на чанки, пробовал из dataframe вычитывать до операций, но всё равно - генерация не ускоряется, может быть читатель публикации найдёт способ ускорения, в моём случае - я не сидел за компом столько времени и никуда не торопился, поэтому включил и раз в 5 часов подошел проверил

Причем что забавно, если в предложении остаётся Adventurer, то есть английское слово, то оно затормаживает генерацию длинных предложений на секунд 10-20

Проблемы Synthesizer

Что я попробовал из оптимизации, но всё равно бестолку:

Идею нашел в интернетах - поднять Synthesizer выше, сделал workaround вокруг thread safe singleton, но всё равно - там где-то ошибка на уровне торча, ему не нравится количество в весах и он игнорирует в таком случае файлы, поэтому сделал failback лист, так выглядит генератор

import os
from TTS.utils.manage import ModelManager
from TTS.utils.synthesizer import Synthesizer
from tqdm import tqdm

import threading

lock = threading.Lock()

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            with lock:
                if cls not in cls._instances:
                    cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Converter(metaclass=Singleton):
    def __init__(self):
        models_path = os.getenv('TTS_MODELS_JSON_PATH')
        assets_path = os.getenv('ASSETS_PATH')
        
        print(f'tts models path is located at {models_path}')
        print(f'tts assets path is located at {assets_path}')

        model_manager = ModelManager(models_path, output_prefix=assets_path)

        model_path, _, model_item = model_manager.download_model("tts_models/multilingual/multi-dataset/xtts_v2")

        print(f'model is {model_item}')

        self.syn = Synthesizer(
            tts_checkpoint=model_path,
            tts_config_path=os.path.join(model_path, "config.json"),
        )
        
        self.tqdm_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}] {postfix}'
        self.tqdm = None
        self.failed_inputs = []
                
    def convert(self, text, input_sound_path, output_sound_path, language):
        print(f"text: {text}")
        print(f"input: {input_sound_path}")
        print(f"output: {output_sound_path}")

        try:
            outputs = self.syn.tts(
                text=text,
                speaker_name=None,
                language_name=language,
                speaker_wav=input_sound_path, 
                reference_wav=None,
                style_wav=None,
                style_text=None,
                reference_speaker_name=None,
                split_sentences=True,
            )
            
            return self.syn.save_wav(outputs, output_sound_path)

        except:
            self.failed_inputs.append(input_sound_path)
            
        return None
    
    def process_dataframe(self, df, num_processes, executor, row_proccesing_fn):
        total_rows = len(df)
        chunk_size = int(df.shape[0]/num_processes)

        print(f'total rows: {total_rows}')
        print(f'chunk size: {chunk_size}')
        
        if (self.tqdm is None):
            self.tqdm = tqdm(total=total_rows, unit='rows', ncols=100, desc='Generating Audio', ascii=False, bar_format=self.tqdm_bar_format, dynamic_ncols=True)
            
        chunks = [df.iloc[df.index[i:i + chunk_size]] for i in range(0, df.shape[0], chunk_size)]

        for chunk in chunks:
                for custom_message in zip(executor.map(row_proccesing_fn, chunk.itertuples())):
                    self.tqdm.set_postfix_str(custom_message)
                    self.tqdm.update(1)

        print(self.failed_inputs)

А логика трединга

    def process_rows_in_parallel(self, df, row_proccesing_fn, max_workers=STATIC_MAX_WORKERS):
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            Converter().process_dataframe(
                df=df,
                num_processes=max_workers,
                executor=executor,
                row_proccesing_fn=row_proccesing_fn
            )


    def tts(self, text: str, inputName: str, outputName: str, output_subfolder: str, language: str, forceGen: bool = False):
        result = ""
        outpath = os.path.join(SOUND_OUTPUT_FOLDER, output_subfolder, outputName)
        inpath = os.path.join(SOUND_INPUT_FOLDER, output_subfolder, inputName)

        if os.path.isfile(outpath) and forceGen is not True:
            result = "duplicate generation, skipping"
            return

        if os.path.isfile(inpath) is False:
            inpath = DEFAULT_VOICE
            return

        Converter().convert(text=text, input_sound_path=inpath, language=language, output_sound_path=outpath)

        result = f"Audio file saved successfully!: {outpath}"

        return result

То есть, всё равно не удаётся сделать близкое значение для

Processing time: 75.22344183921814
Real-time factor: 2.09797635813799

Где сама сетка получает вывод за 2 секунды, а вот процессинг минимум х5 для коротких предложений и многовато для длинных

Умные мысли преследовали его - используем Tmux

Появилась гипотеза:

  • запускаю с этими изменениями на новой тачке

  • запускаю просто много терминалов на новой тачке

уже по 75 рублей в час
уже по 75 рублей в час

Самый простой вариант оказался также бесполезным:

Всё на cuda в нескольких воркерах
Всё на cuda в нескольких воркерах

Всё в перемешку в 1 воркер:

В перемешку просто вывод:

  • UPD: в перемешку скорость выросла, но не значительно, вместо 1 файла в минуту выходит 4-5

  • UPD: RPS уже 8 файлов в минуту, вариант гонять на CPU в 1 воркер куда быстрее оказался в моём случае и потраченном времени на исследовании.

Проснулся и пошел смотреть, пишет что готово

Повторный прогон сообщает о том же

Идем складывать в репозиторий - а там куча проблем с размерами

Заодно надо сделать саббранч от предыдущей, чтобы сохранить аудиофайлы в сабветке, а основной открыть PR, а все коммиты засквошить в один, обычный пак вышел в 5GB у всего архива, надо как-то переделать в mp3 сжатый, была конечно идея делать ускоренные записи через tts, а в плеере замедлять, ну да ладно

git checkout russian-translation-through-local-tts 

# делаем бекап ветку
git checkout -b russian-translation-through-local-tts-backup
git push origin russian-translation-through-local-tts-backup

git checkout russian-translation-through-local-tts 

# меняем расширение с mp3 на wav
for f in *.mp3; do mv -- "$f" "${f%.mp3}.wav"; done

# удаляем wav-ки
rm -rf *.wav


# основа наверное всего что связано с медиа, имхо лежит в любом проприетарном обработке видео, аудио и так далее
sudo apt install ffmpeg


# сжимаем до упора ru-звуки
for f in *.wav; do ffmpeg -i "$f" -vn -ar 44100 -ac 2 -b:a 192k "${f%.*}.mp3"; done

# делаем архивчик
zip -r AI_VoiceOverData_Vanilla-RU.zip wow-classic-ru/

# смотрим сколько весит
ls -sh AI_VoiceOverData_Vanilla-RU.zip

# понимаем что 2.9GB гитхаб не пропустит, поэтому делаем lfs
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
sudo apt-get install git-lfs

# и в какой-то момент оказалось, что нельзя в форках пользоваться LFS.
# cначала хотел было сделать detach
# но потом понял, что лучше создать новую репу под assets и туда всё закинуть
git clone https://github.com/the-homeless-god/wow-voiceover-assets 
cp -r translator/assets ~/projects/wow-voiceover-assets
cd ~/projects/wow-voiceover-assets

git lfs track AI_VoiceOverData_Vanilla-RU.zip
git add AI_VoiceOverData_Vanilla-RU.zip .gitattributes

git commit -m "feat(ru): add russian soundpack"
git tag v1.4.4 
git push origin --tags

# а потом оказывается что и там лимит на 2gb, значит жмем архив дальше
# сжимаем с максимальной компрессией
zip -9r ru.zip AI_VoiceOverData_Vanilla-RU.zip 

# всё равно получаем тоже самое, поэтому давайте распилим архив
# также как когда-то пилили диски и прочее
split -b 1024m AI_VoiceOverData_Vanilla-RU.zip --additional-suffix=`basename "AI_VoiceOverData_Vanilla-RU.zip"`

# для склейки например оставим простой скрипт внутри
cat xaaAI_VoiceOverData_Vanilla.zip \
	xabAI_VoiceOverData_Vanilla.zip \
    xacAI_VoiceOverData_Vanilla.zip > AI_VoiceOverData_Vanilla-RU.zip

# по идее можно выстроить логику split-а вокруг git hooks, то есть вы мокаете папку
# и при pull/push её клеите и разбиваете, да это долго, но работает
# в итоге плюнул и отказался от архивов, слишком долго и много заморочек

# ну а в оригинальной репе дропаем ассеты, чтобы PR был красивым
rm -rf translator/assets/wow-classic-ru
rm -rf translator/assets/wow-classic-en
git commit -m "refactor: drop assets" -n

# Для сквоша я храню эту команду у себя в zshrc в Dotfiles можно найти больше примеров тут
# https://github.com/the-homeless-god/dotfiles/blob/master/configs/.zshrc#L50
git reset $(git merge-base master $(git branch --show-current))
меняем формат ffmpeg-ом
меняем формат ffmpeg-ом

Публикация аддона

Идем создаем аддон опять, у них там что-то всё сломалось и надо в новом UI

Картинка разумеется из-под локального stable diffusion
Картинка разумеется из-под локального stable diffusion

У английского пака 600к скачиваний, ставь класс - посмотрим сколько нас

Буду верить тут нет ограничений на размер файла

Но есть ограничение на ваш айпишник, ибо Curse принадлежит Twitch, которая принадлежит Amazon
Но есть ограничение на ваш айпишник, ибо Curse принадлежит Twitch, которая принадлежит Amazon

Выложил, сейчас на ревью

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

Тестирование

Пойду хоть проверю, выбираю русский язык и звук

Как установить сейчас?

Самим вов заниматься мне лень, я побаловался, хватит, хотя добавить всем в квестах "Нихао, братья" думаю было бы забавно, тут другие мододелы доделают. Как модерацию плагин пройдёт допилю там мелочи, чтобы без проблем пользователи ставили, но даже сейчас вы можете просто установить оригинальный VoiceOver аддон, а к нему положить soundpack

Как это сделать пока аддон на ревью:

  1. Ставим этот аддон https://www.curseforge.com/wow/addons/voiceover

  2. Затем идем в папку где у вас установлен вов и ищем там __classic-era__/interface/AddOns/AI_VoiceOverData_Vanilla

  3. И закидываем туда всё содержимое из https://github.com/the-homeless-god/wow-voiceover/tree/master/translator/assets/wow-classic-ru/AI_VoiceOverData_Vanilla

Итого

Что получилось?

  • Аддон на CurseForge: https://legacy.curseforge.com/wow/addons/voiceover-sounds-vanilla-russian

  • Мой репозиторий: https://github.com/the-homeless-god/wow-voiceover

    В целом, результатом я неудовлетворен:

    • Бесит питоновская трабла с генерацией, ищу помощи комментаторов что там можно улучшить

    • Некоторые квесты отсутствуют в бд mangos, но догенерировать их уже не составляет труда, так как это небольшое количество

    • В некоторых квестах есть шумы, как например квест у нежити добавляет "моджо", миленько, но хотелось бы без них

    • Прикольно, что генерируется на основе коротких сэмплов

    Что еще можно сделать?

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

    • натравить с помощью нейросетей улучшения качества можно как ни странно улучшить это самое качество звуковых дорожек

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

    • генерировать текстовое описание всем персонажам с помощью LLM и создавать голоса на основе текстовых описаний, например кормим картинку/видеоряд/сорс из книг и так далее, чтобы получить более живой голос (причём по той же логике можно обогатить диалоги)

    Что хочу попробовать дальше:

    • Переозвучить какую-нибудь анимеху с помощью такого же алгоритма

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

    • Улучшить качество звуковых дорожек

    • Взять шайтан-видеокарту чтобы прогонять это дело в разы быстрее

    • Поиграться с оптимизацией, чтобы в k3c был балансировщик и хостить контейнеры с tts, а к ним дописать простейший REST либо поискать существующие.

    • Еще есть мысль сделать эту систему в режиме реального времени, то есть вы включаете видео в браузере, и на ходу вам догенерируется следующая минута, почти как Демон Лапласа, гг

    • Можно переписать все тексты на клингонский или эльфийский, можно попробовать оживить латынь, достаточно просто взять sample голоса за основу, а её как раз-таки можно сгенерировать - прикиньте Тралл говорит на клингонском. Или того хуже заставить мир говорить языками программирования, бред, но красиво же.


    Как конкретно ты мне можешь помочь:

    • Накидывать идеи в комментарии

    • Купить подписку на мой личный паблик в телеграмме (ценник от 250 рублей в месяц), там я выпускаю посты небольшого объема, которые не тянут на уровень хабра, но при этом включают полный цикл разработки ПО по всем возможным темам, например недавно я там писал про устройство декораторов в typescript или развертывание своего email-сервера с помощью классических наборов утилит, а также с помощью gui. В общем приходите все и купите мне кофе, а я и дальше буду пробовать писать что-то полезное и открытое https://t.me/digitable_sub_bot

    • Если хотите чтобы я попробовал написать вам какой-нибудь gui который всё это под капотом содержит и так далее тоже пингуйте, заинтересован в ваших донатах, чтобы больше творить всякого ненормального, так-то можем договориться об оплате через телегу или paypal)

    Подписывайся https://t.me/digitable_sub_bot :) Лок'тар огар!

    • Отдельное спасибо Ярославу Вдовину за помощь в генерации на его компе, пока я пытался вкурить как это сделать на арендованной тачке быстрее

    • Написал еще и в дискорд ребятам

      Из забавного: нашли, что часть квестов пропала, не хватает 1200-1500 файлов, оказалось, что в английских квестах нет склонения глаголов, а в русском есть, так что опять гоняем до потери пульса, но это уже другая история

      Богатый русский язык
      Богатый русский язык

Комментарии (6)