Перевод игры The Invincible на другой язык


Относительно недавно вышедшая игра «The Invincible» не имеет альтернативной озвучки — только чопорный английский. Возможно ли силами одного человека за относительно короткий срок (1–2 дня) перевести игру (сделать «озвучку»), например, на «великий, могучий, правдивый и свободный» язык? Попробуем.

Предисловие.


u-jnhognw1epzmnf9reefkegmyg.jpeg

«The Invincible» как игра вызывает смешанные чувства: с одной стороны прекрасные космические пейзажи, диалоги, наполненные смыслом с опорой на фантастический рассказ, с другой — вялотекущий сюжет, в котором ты только и делаешь, что куда-то идешь, и… бесконечные диалоги.
Как раз о последних и пойдет речь.

Основная сюжетная линия построена на общении главной героини по имени «Ясна» с ее коллегой по рации. И этих разговоров много, даже с избытком.
И, если основная суть разговоров вполне ясна, то рассуждения на отвлеченные темы без субтитров не переварить даже со знанием английского разговорного. Благо данные субтитры любезно появляются внизу экрана на русском языке.
И все бы ничего, если бы не их количество. Приходится бесконечно читать, забывая о том, что надо еще и что-то делать — игра все-таки.
Поэтому и родилась идея перевести на русский бесконечную болтовню Ясны и ее коллеги по космосу.

Перевод файлов игры, содержащих звук.


dgdgejtiupqrf-heo852vgnzkek.jpeg

С «The Invincible» повезло.
Как оказалось, вся озвучка упакована в один большой файл формата .bank. И все, что нужно сделать, чтобы ее оттуда достать — распаковать этот файл. Смысл манипуляций простой — распаковать озвучку, перевести ее, запаковать обратно и «подсунуть» игре.

Поэтому, взяв программу под windows «Fmod Bank Tools», в папку bank помещаем файл
«VOEN.bank»:
tohizjeeivir4lxqooaunathdqg.jpeg

Сам voen.bank можно найти в папке с игрой в The Invincible\TheInvincible\Content\Audio\FMOD\Desktop
def302dh4nw_aci_uubpniy2uoc.jpeg

Нажав на «Extract», получим множество wav файлов. А точнее 10506.
h2ivcvgom_jyiqrcfrtffuotjmw.jpeg

Перевод.


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

c597ikcuvctw0iopkwwbmx7zqhg.jpeg

Кроме того, в содержании диалогов присутствует слэнг, индивидуальные речевые особенности, охи-вздохи, кряхтение-сопение, в общем, обычная человеческая речь:
пример 1
пример 2
Это впоследствии осложнит перевод.

Первое преобразование — английские звуки в английский текст.


Здесь в бой пойдет модель vosk-model-en-us-0.22-lgraph, она занимает всего 128 Мб и должна осилить слэнг:

"""
Model	Size	Word error rate/Speed	Notes	License
English	 	 	 	 
vosk-model-small-en-us-0.15	        40M	9.85 (librispeech test-clean) 10.38 (tedlium)	Lightweight wideband model for Android and RPi	Apache 2.0
vosk-model-en-us-0.22	                1.8G	5.69 (librispeech test-clean) 6.05 (tedlium) 29.78(callcenter)	Accurate generic US English model	Apache 2.0
vosk-model-en-us-0.22-lgraph	        128M	7.82 (librispeech) 8.20 (tedlium)	Big US English model with dynamic graph	Apache 2.0
vosk-model-en-us-0.42-gigaspeech	2.3G	5.64 (librispeech test-clean) 6.24 (tedlium) 30.17 (callcenter)
"""
from datetime import datetime
from glob import glob
import wave,json
from vosk import Model, KaldiRecognizer, SetLogLevel

SetLogLevel(-1)
model = Model(model_name="vosk-model-en-us-0.22-lgraph")

def job(file):
    start = datetime.now()
    wf = wave.open(file, "rb")
    rec = KaldiRecognizer(model, wf.getframerate())
    rec.SetWords(True)
    rec.SetPartialWords(True)

    while True:
        data = wf.readframes(4000)
        if len(data) == 0:
            break
        if rec.AcceptWaveform(data):
            pass
            #print(rec.Result())        

    res = json.loads(rec.FinalResult())
    #print(res['text'])
    
    a=file.split('\\')[1].split('.')[0]
    with open (f"{a}.txt","a") as f:
        f.write(res['text'])
    print(datetime.now()- start)

import multiprocessing

def main():
    p = multiprocessing.Pool(3)
    for f in glob(r'./*.wav'):
        #print('work on:',f.split('\\')[1])
        p.apply_async(job, [f]) 

    p.close()
    p.join() 

if __name__ == '__main__':
    main()


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

По результату перевода из eng wav в eng txt можно понять, что слэнг, модель «переварила»:

ddkwhomxo_jaagcul-mpsdx_4zk.jpeg

Но не все так гладко.
Например, с именами героев и с мхатовскими паузами все сложнее:

klmoo8tvv_bj0lhiy96suurgapq.jpeg

Здесь «Merit» — имя персонажа. И, видно, что вопросительная интонация предложения отброшена. А зачем она…
Помимо прочего — часть файлов пустые.

h7jekueecz8q7_a60teaycs9z9c.jpeg

Что это за файлы?
В большинстве своем — содержащие посторонний шум:
пример
Но выкинуть их нельзя, так как в общем bank файле есть файл со списком всех файлов озвучки — VOEN.txt, в котором упомянуты все эти файлы в том числе.

Из английского в русский !


v-zijsujzfry4l7gsbygm8rbdlg.jpeg

Теперь переведем все файлы на русский. Со словарем Даля. Почти.
Здесь поможет нейросеть на базе argostranslate.

Все модели лежат по ссылке с репозитория — drive.google.com/drive/folders/11wxM3Ze7NCgOk_tdtRjwet10DmtvFu3i — models
И одну нужно будет положить рядом с исполняемым кодом — translate-en_ru-1_7.argosmodel

А сам код будет выглядеть так:

import pathlib
import argostranslate.package
import argostranslate.translate
import multiprocessing

#https://drive.google.com/drive/folders/11wxM3Ze7NCgOk_tdtRjwet10DmtvFu3i - models
package_path = pathlib.Path("translate-en_ru-1_7.argosmodel")
argostranslate.package.install_from_path(package_path)
from datetime import datetime
from glob import glob
import os
os.environ["ARGOS_DEBUG"] = "-1"
os.environ["ARGOS_DEVICE_TYPE"] ="cpu" #cpu

#print(translatedText)

def job(file):
    start = datetime.now()
    with open (file) as f:
        text=f.read()
        translatedText = argostranslate.translate.translate(text, "en", "ru")
    
    a=file.split('\\')[1].split('.')[0]
    with open (f"{a}.ru","a") as f:
        f.write(translatedText)
    print(datetime.now()- start)

def main():
    p = multiprocessing.Pool(3)
    for f in glob(r'./*.txt'):
        #print('work on:',f.split('\\')[1])
        p.apply_async(job, [f]) 

    p.close()
    p.join() 

if __name__ == '__main__':
    main()

Файлы после обработки будут иметь странное расширение .ru.
Это не обязательное требование, но так будет проще использовать их в дальнейшем — файлы не будут сливаться с другими txt.

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

zc-cjjq3nmgzzikfcybyhouby84.jpeg

Создаем звуки, русские звуки.


zdidotlzvroynhib3hs2f04ho8c.jpeg

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

ak9mht522d5cl_yz7txog3cjgpg.jpeg

пример

Но это не так страшно, как то, что в модели, которая использовалась при переводе, оказался мат.
Да, да, тот самый.

l7vaq-vxz5-ayw1gfhn0ynnueqw.jpeg

Но, надо отдать должное, он и в английской версии текста присутствует.

_vugrnhe2s2lo2b7heclarfkup4.jpeg

Так что, из песни слов не вынешь. Но, неприятно.

Осталось озвучить.


b5j30h6z0a4sgkwqd0_ntlkeu4c.jpeg

Теперь, когда есть файлы с текстом на русском, осталось их озвучить. И здесь нам поможет, не без известные модели Silero.

Моделей голосов в свободном доступе не так много и, было бы совсем хорошо, сделать клон голоса Ясны. Но, это, как говорится, совсем другая история.

Поэтому просто озвучим текст голосом 'baya' из модели silero для русского языка.

import torch
from datetime import datetime
from glob import glob
import multiprocessing
torch._C._jit_set_profiling_mode(False)

language = 'ru'
model_id = 'v4_ru'
device = torch.device('cpu') 

model, _ = torch.hub.load(repo_or_dir='snakers4/silero-models',
                                     model='silero_tts',
                                     language=language,
                                     speaker=model_id)
model.to(device)  # gpu or cpu
sample_rate = 48000
speaker = 'baya' #xenia
put_accent=True
put_yo=True
#audio_path= 't.wav'
#example_text = 'В недрах тундры выдры в г+етрах т+ырят в вёдра ядра к+едров?'

def job(file):
    start = datetime.now()
    with open (file) as f:
        text=f.read()
    print(text)
    a=file.split('\\')[1].split('.')[0]
    try:
        model.save_wav(text=text,
                   audio_path= f'{a}.wav',
                   sample_rate = sample_rate,
                   speaker = speaker )
    except:
        pass
    print(datetime.now()- start)
 
for f in glob(r'./*.ru'):
    job(f)

Модель silero v4 содержит автоматическое расставление ударений в словах. Оно не идеально работает, но все же лучше, чем без него.
Если же есть желание поработать над ударениями, то по тексту необходимо расставить »+» на ударный слог. В коде выше есть упоминание об этом.
У baya голос совсем детский, такая мини-Ясна получится.

Финальная упаковка.


gsi3yuxe4fiuqowif3hm_dmrmnk.jpeg

Полученные wav c русской речью необходимо упаковать обратно в формат bank и заменить полученный файл на одноименный в папке с игрой.
Здесь все просто.

В «Fmod Bank Tools» указываем папку, где содержатся wavы на русском и нажимаем «Rebuild».

k4yv7jggunhrk_bwn9nkq9mh_3e.jpeg

Получившийся файл и будет файлом с новой озвучкой.
Новый VOEN.bank есть по ссылке в описании к видео на youtube.

Выводы


Перевод игры не такой трудоемкий процесс, как может показаться, скорее интересный.

Программный перевод еще далек от идеала:
— теряется интонация, а иногда и суть фраз, когда переводится имя персонажа с «лошадиной фамилией»;
— каждая нейросеть «вымывает» что-то свое.
При переводе wav в txt речь на фоне помех не слышна либо ошибочна.
При языковом переводе не все файлы обрабатываются корректно, не верна не только грамматика, но и стилистика. Модель может подкинуть неожиданностей в виде матерных слов либо добавить символов, там где нет речи вообще.
При обратном переводе txt в речь также есть неудобства. Во-первых, речь переводится одним говорящим, а хотелось бы диалогов. Во-вторых, как выяснилось в дальнейшем, говорящий на русском не успевает за фразами сюжета, то есть длина фраз на английском и русском иногда явно не равны по длительности.

Таким образом, пока людям нет смысла беспокоиться о потере работы. По крайней мере, играя в такие игры, как «The Invincible».

© Habrahabr.ru