Исправление русской озвучки Half-Life 2 и Эпизодов

Half-Life 2 собрала более 35 наград в номинации 'Игра года' и разошлась тиражом более 4 млн экземпляров.
Сообщение
Автор
Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#1 Сообщение 21.07.2015, 12:09

В русской озвучке Half-Life 2 и Эпизодов имеются ошибки:
- обрывающиеся или накладывающиеся друг на друга фразы
- отсутствие синхронизации озвучки и анимации
- некоторые баги



исправления озвучки:
HλLF-LIFE 2 или HλLF-LIFE 2 Update:
только исправление -------------> https://mega.nz/#!YwoyAZRZ!DRzmna3D_2bw ... dI_xFoGB8M
вся озвучка с исправлением ---> https://mega.nz/#!4pQVzDSa!AcdZTEh_eGVM ... 6e6WckT16A

HλLF-LIFE 2 Episode One:
только исправление -------------> https://mega.nz/#!U0QnmJaA!NYGLtAAUPxeB ... sHwOLRYPNI
вся озвучка с исправлением ---> https://mega.nz/#!ggZkASLS!yfVMmFhk7ifx ... aF9gaHczhA

HλLF-LIFE 2 Episode Two:
только исправление -------------> https://mega.nz/#!Vp412TDD!jfpzc1illSvP ... Z0HMye0EZs
вся озвучка с исправлением ---> https://mega.nz/#!dpwwwQ6L!Tzlje4vEenov ... iFNx0VNi-o

HλLF-LIFE 2 Lost Coast: исправление не требуется

Portal: исправление возможно тоже не требуется



формат: steampipe



установка:
- положить vpk-файл исправления в папку <moddir>\custom (где <moddir> – это hl2, episodic, ep2 и т.д.)
для пользователей Steam: в Steam, на вкладке «Язык» в свойствах игры должен быть выбран «Русский» (чтобы не получилось мешанины из русских и, например, английских фраз)
установка всей озвучки с исправлением:
- положить vpk-файл озвучки в <moddir> (где <moddir> – это hl2, episodic, ep2 и т.д.) с заменой
- удалить файл звукового кэша (https://developer.valvesoftware.com/wiki/Soundcache:ru), если он есть, для того чтобы по мере прохождения из исправленной озвучки создался, пополнялся и использовался новый
(например для HL2:Ep2 имя файла звукового кэша такое: ep2_sound_vo_russian.vpk.sound.cache).
для пользователей Steam: после установки озвучки не "проверять целостность кэша", тк хэш-суммы исправленной и неисправленной озвучки не совпадут и Steam перекачает с заменой неисправленную.


история обновлений
11.08.2015
исправлена вся озвучка Half-Life 2 Episode Two.
версия исправления - 1.0.
список изменений внутри архива.

27.08.2015
исправлена вся озвучка Half-Life 2.
версия исправления - 1.0.
список изменений внутри архива.

31.08.2015
исправлена вся озвучка Half-Life 2 Episode One.
версия исправления - 1.0.
список изменений внутри архива.
методы исправления
Исправляется всё разными методами:
1. укорачивание пауз между словами и предложениями
2. удаление вдохов
3. вырезание отсебятины локализаторов.
4. ускорение всего звука или его частей вот таким макаром:
------- звук подгоняется до нужной длины в "Sony Sound Forge" инструментом "Time Stretch"
------- самопальная программа "retime_phoneme.exe" загружает фонему из исходного звука, изменяет её интервалы времени и сохраняет в исправленный звук.
программа консольная
команды:

Код: Выделить всё

[-s]        (обязательно) - оригинальный звуковой файл с фонемой для изменения по времени
[-d]        (обязательно) - целевой звуковой файл, в который сохраняется изменённая по времени фонема
[-o, -oc]   (опционально) - копия звукового файла из [-d] (с доп. командой 'c' без лишних данных на конце), в который сохраняется изменённая по времени фонема
[-l]        (опционально) - вкл. логирование
пример: -s "C:\source.wav" -d "C:\destination.wav" -oc "C:\output.wav" -l
скачать (версии x86 и x64 + исходник): https://mega.co.nz/#!s5IgxYRY!s97sXdueR ... rryCgdjDXE

5. распределение невписывающихся слов на соседние фразы.
6. различные варианты комбинаций из вышеупомянутых методов
7. создание новых или дополнение и модификация существующих фраз из частей других фраз



дополнительно:
вспомогательная программа (уже часть программы выше) для удаления лишних данных после аудио данных (например метаданных оставляемых Sony Sound Forge)
описание работы в ReadMe в архиве с программой.
скачать (версии x86 и x64 + исходник): https://mega.co.nz/#!NwB2GAxY!p5o5OL_li ... mt3M29OjG8
по мере появления новых исправлений или улучшения уже существующих тема будет обновляться.
Последний раз редактировалось Vit_amiN 21.07.2015, 12:09, всего редактировалось 73 раза.
Причина: Прилеплено.

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#2 Сообщение 21.07.2015, 15:40

wowks
developer.valvesoftware.com писал(а):Lip synch

Lip synch data (Phonemes) are stored in the raw .WAV speech file rather than the choreography VCD for portability, reliability and localization reasons, but are still created with Faceposer.
Как пересчитать временные метки в фонеме, я уже писал. Используй тот же множитель, что и при редактировании .WAV, для нормирования значений привязок. Иными словами, подели их на него.
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#3 Сообщение 21.07.2015, 19:32

Vit_amiN
спасибо, пошёл разбираться,
Vit_amiN &raquo; Вт июл 21, 2015 1:40 pm писал(а): Как пересчитать временные метки в фонеме, я уже писал
вроде оно: viewtopic.php?f=8&t=39244#p998999

upd:

неужели придётся ставить Windows XP, чтоб что-то работало?

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#4 Сообщение 22.07.2015, 00:43

wowks » 21 июл 2015, 19:32 писал(а):неужели придётся ставить Windows XP, чтоб что-то работало?
Возьми утилиты отсюда, и будет тебе счастье под Vista и выше.
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#5 Сообщение 22.07.2015, 18:50

Vit_amiN
уже запарился, скачал архив и заменил библиотеку.
постоянно вылезает вот такая ошибка
Скрытый текст
Изображение
в настройка распознавания речи нет этого "English UK", а с другими настройками sdk вообще крашится при попытке извлечь фонему
пользуюсь "Source SDK Base 2013 Singleplayer" и в его папке bin я заменил библиотеку из твоей ссылки
дальше хуже.
поставил Windows XP Sp3 (x86) через Virtual Box там та же ошибка с "English UK"
может дело в том, что я сижу на русской версии Win7(x64) Ultimate
и винда XP тоже русская?
ты не знаешь решения проблемы или может дело в sdk?
есть ли сборка sdk в которой Phoneme Editor 100% работает? (думаю тут подойдёт и старый sdk, ведь мне он только для звуков и нужен)

upd:
вроде проблема решается так https://www.youtube.com/watch?v=1NGvKiHmiKw
качаю английскую win7

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#6 Сообщение 22.07.2015, 23:17

wowks
Если честно, мне было как-то сподручнее извлекать фонемы HEX-редактором, а зашивать обратно (в пакетном режиме) командой COPY с ключом /B. Могу лишь сказать, что когда компьютеры были большими, а Source SDK маленьким Source SDK работал ещё на основе .GCF и базировался на Source Engine 2006 (до выхода Episode Two дело было), редактор фонем работал. Но то было давно и на Windows XP.
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#7 Сообщение 24.07.2015, 01:23

Vit_amiN
в общем по порядку.
взял для старта аудио "sound\vo\outland_02\junction\gman_mono03.wav"
поставил английскую винду, настроил всё как в видео из моего последнего сообщения и все заработало для английской озвучки
с русской озвучкой проблема при распаковке фонемы, т.к. к аудио не привязан текст. Его я пытался привязать (печатал типа: "no mne prishlos ...")
также пробовал через сайт http://translit.net/ переводить русский текст в транслит и это в конечном итоге тоже ничего путного не дало.
потом я решил следуя твоим словам заглянуть в конец звука программно распечатав данные после данных звука в консоли через __debug_memprint (ниже в коде);
и увидел что оказывается фонемы записаны в виде текста (теперь я понял , что ты имел в виду говоря "сподручнее извлекать фонемы HEX-редактором" ) вот так например для вышеупомянутого звука.
сначала идёт заголовок (в Валве сидят юмористы) на манер RIFF Chunk-ов в WAVE-файле
Скрытый текст
typedef struct {
char VDAT[4]; // "VDAT"
uint32_t VDATSize; // длина текста после этого кусочка
} VDATCHUNK;
потом сам текст
Скрытый текст
VERSION 1.0
PLAINTEXT
{
[Textless]
}
WORDS
{
WORD [Textless] 0.000 9.648
{
95 <sil> 0.000 0.112 1
110 n 0.112 0.160 1
104 hh 0.160 0.400 1
652 ah 0.400 0.464 1
104 hh 0.464 0.688 1
618 ih 0.688 0.800 1
118 v 0.800 0.864 1
104 hh 0.864 0.960 1
117 uw 0.960 1.056 1
643 sh 1.056 1.216 1
601 ax 1.216 1.248 1
108 l 1.248 1.360 1
104 hh 1.360 1.568 1
95 <sil> 1.568 1.776 1
104 hh 1.776 1.904 1
601 ax 1.904 1.936 1
98 b 1.936 2.048 1
101 ey 2.048 2.176 1
658 zh 2.176 2.256 1
601 ax 2.256 2.304 1
109 m 2.304 2.416 1
104 hh 2.416 2.576 1
601 ax 2.576 2.608 1
116 t 2.608 2.688 1
115 s 2.688 2.848 1
601 ax 2.848 2.896 1
95 <sil> 2.896 2.960 1
109 m 2.960 3.024 1
117 uw 3.024 3.072 1
601 ax 3.072 3.136 1
109 m 3.136 3.248 1
618 ih 3.248 3.360 1
603 eh 3.360 3.424 1
104 hh 3.424 3.520 1
117 uw 3.520 3.616 1
95 <sil> 3.616 3.744 1
240 dh 3.744 3.872 1
95 <sil> 3.872 4.176 1
104 hh 4.176 4.432 1
230 ae 4.432 4.496 1
104 hh 4.496 4.704 1
95 <sil> 4.704 4.976 1
103 g 4.976 5.040 1
618 ih 5.040 5.088 1
100 d 5.088 5.136 1
117 uw 5.136 5.168 1
109 m 5.168 5.232 1
601 ax 5.232 5.264 1
95 <sil> 5.264 5.408 1
593 ay 5.408 5.600 1
643 sh 5.600 5.760 1
601 ax 5.760 5.824 1
109 m 5.824 5.888 1
117 uw 5.888 6.032 1
601 ax 6.032 6.080 1
658 zh 6.080 6.160 1
105 iy 6.160 6.368 1
230 ae 6.368 6.448 1
104 hh 6.448 6.800 1
601 ax 6.800 6.832 1
100 d 6.832 6.880 1
110 n 6.880 6.976 1
105 iy 6.976 7.104 1
122 z 7.104 7.280 1
117 uw 7.280 7.648 1
122 z 7.648 7.696 1
98 b 7.696 7.824 1
117 uw 7.824 7.936 1
95 <sil> 7.936 8.032 1
104 hh 8.032 8.128 1
109 m 8.128 8.192 1
95 <sil> 8.192 8.288 1
119 w 8.288 8.352 1
601 ax 8.352 8.368 1
109 m 8.368 8.464 1
601 ax 8.464 8.528 1
643 sh 8.528 8.736 1
601 ax 8.736 8.784 1
95 <sil> 8.784 9.200 1
122 z 9.200 9.328 1
95 <sil> 9.328 9.648 1
}
}
EMPHASIS
{
}
OPTIONS
{
voice_duck 1
}
структура довольно простая. Например "95 <sil> 9.328 9.648 1", где
95 - скорее всего код символа
<sil> - имя фонемы
9.328 - время начала
9.648 - время конца
1 - хз
не долго думая я решил написать код (чтоб не сидеть с калькулятором в HEX-редакторе) который будет выдёргивать этот текст, изменять время в соответствии с уже сжатым в Sony Sound Forge звуком и дописывать в изменённом виде в конец как и положено
вот ранняя версия кода программы:
Скрытый текст

Код: Выделить всё

#include <iostream>
#include <windows.h>
#include <ksmedia.h>


enum WAVEFILETYPE
{
    WF_EX  = 1,
    WF_EXT = 2
};

typedef struct
{
    char		RIFF[4];
    uint32_t 	RIFFSize;
    char	 	WAVE[4];
} WAVEFILEHEADER;

typedef struct
{
    char		ChunkName[4];
    uint32_t	ChunkSize;
} RIFFCHUNK;

typedef struct
{
    uint16_t	FormatTag;
    uint16_t	Channels;
    uint32_t	SamplesPerSec;
    uint32_t	AvgBytesPerSec;
    uint16_t	BlockAlign;
    uint16_t	BitsPerSample;
    uint16_t	Size;
    uint16_t  	Reserved;
    uint32_t	ChannelMask;
    GUID  		guidSubFormat;
} WAVEFMT;


#ifndef _WAVEFORMATEX_
    #define _WAVEFORMATEX_
typedef struct tWAVEFORMATEX
{
    uint16_t    wFormatTag;
    uint16_t    nChannels;
    uint32_t    nSamplesPerSec;
    uint32_t    nAvgBytesPerSec;
    uint16_t    nBlockAlign;
    uint16_t    wBitsPerSample;
    uint16_t    cbSize;
} WAVEFORMATEX;
#endif /* _WAVEFORMATEX_ */

#ifndef _WAVEFORMATEXTENSIBLE_
    #define _WAVEFORMATEXTENSIBLE_
typedef struct
{
    WAVEFORMATEX    Format;
    union
    {
        uint16_t    ValidBitsPerSample;   /* bits of precision  */
		uint16_t    SamplesPerBlock;      /* valid if BitsPerSample==0 */
		uint16_t    Reserved;             /* If neither applies, set to zero. */
    } Samples;
    uint32_t        ChannelMask;          /* which channels are present in stream  */
    GUID            SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
#endif // _WAVEFORMATEXTENSIBLE_

typedef struct
{
    WAVEFILETYPE	        wfType;
    WAVEFORMATEXTENSIBLE    wfEXT;		// For non-WAVEFORMATEXTENSIBLE wavefiles, the header is stored in the Format member of wfEXT
    unsigned long	        DataSize;
    FILE *                  File;
    unsigned long	        DataOffset;
} WAVEFILEINFO, * LPWAVEFILEINFO;

bool OpenWaveFile(const wchar_t * FileName, WAVEFILEINFO * WaveInfo)
{
    WAVEFILEHEADER	waveFileHeader;
    RIFFCHUNK		riffChunk;
    WAVEFMT			waveFmt;
    bool		    res = false;

    memset(WaveInfo, 0, sizeof(WAVEFILEINFO));

    // Open the wave file for reading
    WaveInfo->File = _wfopen(FileName, L"rb");
    if (WaveInfo->File != NULL) {
        // Read Wave file header
        fread(&waveFileHeader, 1, sizeof(WAVEFILEHEADER), WaveInfo->File);
        if ((strnicmp(waveFileHeader.RIFF, "RIFF", 4) == 0) && (strnicmp(waveFileHeader.WAVE, "WAVE", 4) == 0))
        {
            while (fread(&riffChunk, 1, sizeof(RIFFCHUNK), WaveInfo->File) == sizeof(RIFFCHUNK))
            {
                if (strnicmp(riffChunk.ChunkName, "fmt ", 4) == 0)
                {
                    if (riffChunk.ChunkSize <= sizeof(WAVEFMT))
                    {
                        fread(&waveFmt, 1, riffChunk.ChunkSize, WaveInfo->File);

                        // Determine if this is a WAVEFORMATEX or WAVEFORMATEXTENSIBLE wave file
                        if (waveFmt.FormatTag == WAVE_FORMAT_PCM)
                        {
                            WaveInfo->wfType = WF_EX;
                            memcpy(&WaveInfo->wfEXT.Format, &waveFmt, sizeof(PCMWAVEFORMAT));
                        }
                        else if (waveFmt.FormatTag == WAVE_FORMAT_EXTENSIBLE)
                        {
                            WaveInfo->wfType = WF_EXT;
                            memcpy(&WaveInfo->wfEXT, &waveFmt, sizeof(WAVEFORMATEXTENSIBLE));
                        }
                    }
                    else
                    {
                        fseek(WaveInfo->File, riffChunk.ChunkSize, SEEK_CUR);
                    }
                }
                else if (strnicmp(riffChunk.ChunkName, "data", 4) == 0)
                {
                    WaveInfo->DataSize = riffChunk.ChunkSize;
                    WaveInfo->DataOffset = ftell(WaveInfo->File);
                    fseek(WaveInfo->File, riffChunk.ChunkSize, SEEK_CUR);
                }
                else
                {
                    fseek(WaveInfo->File, riffChunk.ChunkSize, SEEK_CUR);
                }

                // Ensure that we are correctly aligned for next chunk
                if (riffChunk.ChunkSize & 1)
                {
                    fseek(WaveInfo->File, 1, SEEK_CUR);
                }
            }

            if (WaveInfo->DataSize &&
                WaveInfo->DataOffset &&
                ((WaveInfo->wfType == WF_EX) || (WaveInfo->wfType == WF_EXT)))
            {
                res = true;
            }
            else
            {
                fclose(WaveInfo->File);
            }
        }
    }

    return res;
}

/*
float GetWaveFileDuration(WAVEFILEINFO * WaveInfo) {
#define BITS_IN_BYTE 8
    long _samples = ((WaveInfo->DataSize * BITS_IN_BYTE) / WaveInfo->wfEXT.Format.wBitsPerSample ) / WaveInfo->wfEXT.Format.nChannels;
    return (float)_samples / (float)WaveInfo->wfEXT.Format.nSamplesPerSec;
}
*/


void CloseWaveFile(WAVEFILEINFO * WaveInfo)
{
    if ((WaveInfo != NULL) && (WaveInfo->File != NULL))
    {
        fclose(WaveInfo->File);
    }
}



void __debug_memprint(const void * data, size_t size)
{
    unsigned char * c = (unsigned char *) data;
    for (size_t i = 0; i < size; i++)
    {
        printf("%c", c[i]);
    }
}




typedef struct {
    char		VDAT[4]; // "VDAT"
    uint32_t 	VDATSize; // Text Length
} VDATCHUNK;

typedef struct {
    VDATCHUNK VDat;
    char * Text;
} PHONEME;


// search str in file
long fstr(FILE * f, const char * str)
{
    long res = EOF;

    size_t len = strlen(str);
    char buff[len];
    for (;;)
    {
        int c = fgetc(f);
        if (c == EOF) { break; }
        if (c == *str)
        {
            long tmp_pos = ftell(f);
            fseek(f, -1, SEEK_CUR);
            if (fread(&buff[0], len, 1, f))
            {
                if (strnicmp(&buff[0], str, len) == 0) {
                    res = tmp_pos - 1;
                }
                else
                {
                    fseek(f, SEEK_SET, tmp_pos);
                }
            }
            else
            {
                break;
            }
        }
    }
    return res;
}

bool LoadPhoneme(const WAVEFILEINFO * WaveInfo, PHONEME * Phoneme)
{
    bool res = false;

    memset(Phoneme, 0, sizeof(PHONEME));

    fseek(WaveInfo->File, 0L, SEEK_END);
    unsigned long FileSize = ftell(WaveInfo->File);
    unsigned long WaveSize = WaveInfo->DataOffset + WaveInfo->DataSize;
    unsigned long FileRestSize = FileSize - WaveSize;
    if (FileRestSize > 0)
    {
        fseek(WaveInfo->File, WaveSize, SEEK_SET);
        long VDATCHUNK_offset = fstr(WaveInfo->File, "VDAT");
        if (VDATCHUNK_offset != EOF)
        {
            fseek(WaveInfo->File, VDATCHUNK_offset, SEEK_SET);
            fread(&Phoneme->VDat, sizeof(VDATCHUNK), 1, WaveInfo->File);

            Phoneme->Text = (char *) malloc(Phoneme->VDat.VDATSize);
            if (Phoneme->Text != NULL)
            {
                if (fread(Phoneme->Text, Phoneme->VDat.VDATSize, 1, WaveInfo->File))
                {
                    res = true;
                    __debug_memprint(Phoneme->Text, Phoneme->VDat.VDATSize);
                }
            }
        }
    }

    return res;
}


bool SavePhoneme(const PHONEME * Phoneme, const wchar_t * FileName)
{
    bool res = false;

    FILE * f = _wfopen(FileName, L"ab");
    if (f != NULL)
    {
        if (fwrite(&Phoneme->VDat, sizeof(VDATCHUNK), 1, f))
        {
            if (fwrite(Phoneme->Text, Phoneme->VDat.VDATSize, 1, f))
            {
                res = true;
            }
        }
        fclose(f);
    }

    return res;
}


void UnloadPhoneme(PHONEME * Phoneme)
{
    if ((Phoneme != NULL) && (Phoneme->Text != NULL))
    {
        free(Phoneme->Text);
    }
}


// example: 95 <sil> 8.784 9.200 1
typedef struct {
    int         Code;       // = 95
    char        Name[8];    // = "<sil>"
    double      StartTime; // = 8.784
    double      EndTime;   // = 9.200
    int         Num;        // = 1
} PHONEME_LINE;

bool __debug_printPhonemeLine(PHONEME_LINE * PhonemeLine)
{
    printf("%d %s %.3f %.3f %d\n",
           PhonemeLine->Code,
           &PhonemeLine->Name[0],
           PhonemeLine->StartTime,
           PhonemeLine->EndTime,
           PhonemeLine->Num);
}

void StrLineToPhonemeLine(char * StrLine, PHONEME_LINE * PhonemeLine, uint32_t * Len) // returns length
{
    bool __Code = false;
    bool __Name = false;
    bool __StartTime = false;
    bool __EndTime = false;

    memset(PhonemeLine, 0, sizeof(PHONEME_LINE));

    uint32_t start = 0;
    uint32_t n = 0;
    for (;;)
    {
        if (StrLine[n] == ' ')
        {
            StrLine[n] = '\0'; // we need '\0' for conwerting

            if ( ! __Code) { PhonemeLine->Code = atoi(&StrLine[start]); __Code = true;
                StrLine[n] = ' '; // set ' ' back
                n++; // skip ' '
                start = n; // set start from pos after ' '

                continue;
            }

            if ( ! __Name) { strcpy(&PhonemeLine->Name[0], &StrLine[start]); __Name = true;
                StrLine[n] = ' ';
                n++;
                start = n;

                continue;
            }

            if ( ! __StartTime) { PhonemeLine->StartTime = atof(&StrLine[start]); __StartTime = true;
                StrLine[n] = ' ';
                n++;
                start = n;

                continue;
            }

            if ( ! __EndTime) { PhonemeLine->EndTime = atof(&StrLine[start]); __EndTime = true;
                StrLine[n] = ' ';
                n++;
                start = n;

                continue;
            }

        }
        else if (StrLine[n] == '\n')
        {
            StrLine[n] = '\0';
            PhonemeLine->Num = atoi(&StrLine[start]);
            StrLine[n] = '\n';

            break;
        }

        n++;
    }

    *Len = n;
}

void ReTimePhonemeLine(PHONEME_LINE * PhonemeLine, double OldWaveDuration, double NewWaveDuration) {
    double NewStartTime = (PhonemeLine->StartTime * NewWaveDuration) / OldWaveDuration;
    double NewEndTime = (PhonemeLine->EndTime * NewWaveDuration) / OldWaveDuration;
    PhonemeLine->StartTime = NewStartTime;
    PhonemeLine->EndTime = NewEndTime;
}

void PhonemeLineToStrLine(const PHONEME_LINE * PhonemeLine, char * StrLine, uint32_t Len) {
    snprintf(StrLine, Len, "%d %s %.3f %.3f %d",
            PhonemeLine->Code,
            &PhonemeLine->Name[0],
            PhonemeLine->StartTime,
            PhonemeLine->EndTime,
            PhonemeLine->Num);
}

bool ReTimePhoneme(PHONEME * Phoneme, double OldWaveDuration, double NewWaveDuration)
{
#define IS_NUM_CHAR(c)          ((c >= '0') && (c <= '9'))
#define IS_END_OF_LINE_CHAR(c)  (c == '\n')
    register char * Text = Phoneme->Text;
    register const uint32_t Size = Phoneme->VDat.VDATSize;

    for (register uint32_t n = 0; n < Size; n++)
    {
        // skip line
        if ( ! IS_NUM_CHAR(Text[n]))
        {
            do { n++; } while (( ! IS_END_OF_LINE_CHAR(Text[n])) && (n < Size));
            continue;
        }


        PHONEME_LINE PhonemeLine;
        uint32_t Len;
        StrLineToPhonemeLine(&Text[n], &PhonemeLine, &Len);
        ReTimePhonemeLine(&PhonemeLine, OldWaveDuration, NewWaveDuration);
        PhonemeLineToStrLine(&PhonemeLine, &Text[n], Len);
        n += Len;

        __debug_printPhonemeLine(&PhonemeLine);
    }
    __debug_memprint(Phoneme->Text, Phoneme->VDat.VDATSize);
}




int main()
{
    WAVEFILEINFO WaveInfo;
    if (OpenWaveFile(L"K:\\gman_mono03_ru.wav", &WaveInfo))
    {
        PHONEME Phoneme;
        if (LoadPhoneme(&WaveInfo, &Phoneme))
        {
            //ReTimePhoneme(&Phoneme, 9.648, 7.639);

            if (SavePhoneme(&Phoneme, L"K:\\gman_mono03_corrected.wav"))
            {
                printf("\n\nSAVED\n\n");
            }
            UnloadPhoneme(&Phoneme);
        }
        CloseWaveFile(&WaveInfo);
    }

    return 0;
}
сначала я к укороченному аудио добавлял без ретайминга текстовый блок с фонемами и результат положительный - аудио проигрывается без обрывания, звучит не быстро, но нормально и конечно губы шевелятся хоть и не в соответствии со звуком но это было ожидаемо.
теперь уже я попробовал изменить интервалы фонем по формуле
новое_время_старта_фонемы = (старое_время_старта_фонемы * новое_время_длины_звука) / старое_время_длины_звука.
для времени конца фонемы та же формула.
получил такой текст:
Скрытый текст
VERSION 1.0
PLAINTEXT
{
[Textless]
}
WORDS
{
WORD [Textless] 0.000 9.648
{
95 <sil> 0.000 0.088
110 n 0.088 0.126
104 hh 0.126 0.316
652 ah 0.316 0.366
104 hh 0.366 0.543
618 ih 0.543 0.632
118 v 0.632 0.682
104 hh 0.682 0.758
117 uw 0.758 0.834
643 sh 0.834 0.960
601 ax 0.960 0.985
108 l 0.985 1.074
104 hh 1.074 1.238
95 <sil> 1.238 1.402
104 hh 1.402 1.503
601 ax 1.503 1.528
98 b 1.528 1.617
101 ey 1.617 1.718
658 zh 1.718 1.781
601 ax 1.781 1.819
109 m 1.819 1.907
104 hh 1.907 2.033
601 ax 2.033 2.059
116 t 2.059 2.122
115 s 2.122 2.248
601 ax 2.248 2.286
95 <sil> 2.286 2.337
109 m 2.337 2.387
117 uw 2.387 2.425
601 ax 2.425 2.476
109 m 2.476 2.564
618 ih 2.564 2.652
603 eh 2.652 2.703
104 hh 2.703 2.779
117 uw 2.779 2.854
95 <sil> 2.854 2.955
240 dh 2.955 3.057
95 <sil> 3.057 3.296
104 hh 3.296 3.499
230 ae 3.499 3.549
104 hh 3.549 3.713
95 <sil> 3.713 3.928
103 g 3.928 3.979
618 ih 3.979 4.016
100 d 4.016 4.054
117 uw 4.054 4.080
109 m 4.080 4.130
601 ax 4.130 4.155
95 <sil> 4.155 4.269
593 ay 4.269 4.421
643 sh 4.421 4.547
601 ax 4.547 4.597
109 m 4.597 4.648
117 uw 4.648 4.762
601 ax 4.762 4.799
658 zh 4.799 4.863
105 iy 4.863 5.027
230 ae 5.027 5.090
104 hh 5.090 5.368
601 ax 5.368 5.393
100 d 5.393 5.431
110 n 5.431 5.507
105 iy 5.507 5.608
122 z 5.608 5.747
117 uw 5.747 6.037
122 z 6.037 6.075
98 b 6.075 6.176
117 uw 6.176 6.265
95 <sil> 6.265 6.340
104 hh 6.340 6.416
109 m 6.416 6.467
95 <sil> 6.467 6.542
119 w 6.542 6.593
601 ax 6.593 6.606
109 m 6.606 6.681
601 ax 6.681 6.732
643 sh 6.732 6.896
601 ax 6.896 6.934
95 <sil> 6.934 7.262
122 z 7.262 7.363
95 <sil> 7.363 7.616
}
}
EMPHASIS
{
}
OPTIONS
{
voice_duck 1
}
и сохранил всё это в конец звука (в коде выше пока не реализована установка длины слова "WORD [Textless] 0.000 9.648" и её я в последствии поменял через HEX редактор)
при загрузке сохранения (с тем чтобы проверить звук) игра вылетела, даже не показав в чём ошибка, хотя и так понятно, что дело в изменённом тексте фонем.
попробовал загрузить этот звук в sdk а он (sdk) звкрылся с ошибкой.
короче я не знаю в чём дело.

хочу ещё заментить: в вышеупомянутом исходном в текте фонем указана длина 9.648, но это не длина всего звука. Он длиной 9.659
может быть это время конца последней фоенмы, но как тогда их подгонять для уже испраленного звука длиной в 7.616 ?

к тому же есть ещё одна интересная деталь. Я сегодня узнал что Sony Sound Forge дописывает в конец файла инфу о себе и свою версию,
так вот звуки буки тоже содержат эту запись. Они как минимум сохранялись (а то и редактировались) 7-й версией этой программы, а уже после этой инфы идёт тестовый блок с фонемами. В звуках Валве такого нет. Там сразу идёт ткст с фонемами

у тебя есть мысли в чём тут дело и где я ошибся?

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#8 Сообщение 24.07.2015, 15:42

wowks
Единичку потерял в конце каждого элементарного звука, всегда должно быть 5 элементов. Не знаю, за что отвечает этот параметр, даже в официальной документации формата он находится под меткой TODO, но раз уж он был в исходной разметке, удалять его не стоит.

Код: Выделить всё

WORD [Textless] 0.000 9.648
Посмотри внимательно, это начало первой и конец последней. К длине .WAV отношение имеет посредственное (фактически, .WAV всегда будет немного длиннее). Соответственно, после пересчёта временных привязок берёшь первый float из первого звука и последний - из последнего, и записываешь в указанный блок. А можно просто пересчитать напрямую - будет одно и то же. Длина фонетических данных при пересчёте времени может измениться, так что не будет лишним также обновлять размер во вспомогательном VDAT-заголовке.

P.S. Запись метаданных в Sound Forge лучше отключить, или вообще использовать Audacity.
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#9 Сообщение 25.07.2015, 16:14

Vit_amiN
Vit_amiN &raquo; Пт июл 24, 2015 1:42 pm писал(а):Единичку потерял в конце каждого элементарного звука
спасибо, что нашёл ошибку
всё как обычно по невнимательности.

касательно метаданных оставляемых Sony Sound Forge
не знаю как их отключить.
думаю проще будет написать маленькую программу которая из сохранит Wave с мусором на конце в новый файл без мусора.

ps: вроде субтитры по длине подгонять не нужно судя по тестам, хотя как знать
Последний раз редактировалось wowks 25.07.2015, 16:19, всего редактировалось 2 раза.

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#10 Сообщение 25.07.2015, 16:17

wowks
Длительность отображения субтитров на экране определяется динамически по WAVE-данным для всех звуков, кроме тех, к которым субтитры прикреплены в scripts/titles.txt (преимущественно HEV-костюм).

Подкину тебе ещё идейку по пакетному поиску несовпадений длины файлов озвучки. Можно получать длины звучания для двух одноимённых WAV-файлов (с учётом иерархии папок - например, en\sound\vo\example.wav и ru\sound\vo\example.wav), и, в том случае, если локализованный файл звучит дольше, добавлять его в список "на рассмотрение". Можно ещё прикрутить временной ресэмплинг и вызывать твою утилиту с заведомо известными значениями ключа -r. Тогда получится вообще полная автоматика, и программа станет универсальной для всех Source-игр. :)
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#11 Сообщение 25.07.2015, 16:19

Vit_amiN
Vit_amiN &raquo; Сб июл 25, 2015 1:35 pm писал(а):Подкину тебе ещё идейку
спасибо за идею!
тут пока дело в необходимости. Те если звук не обрывается то уже норм. Я это к тому что сначала нужно исправить самые критичные несовпадения по длине, а дальше уже можно полировать оставшиеся звуки по времени и на слух
пока есть другие проблемы например в монологе г-мена теперь некорые фразы звучат заметно быстрее чем остальные и неплохо бы для общего казачества подогнать все
вот например фраза "Были времена, когда мисс Вэнс для них ничего не значила," идёт сразу вплотную к следующей "а человек казался агрессивным существом с монтировкой в руках. "
и тут я уже по 50мс сокращал потому что звук неумолимо ускоряется а отсутствие хотя бы маленькой паузы между этими фразами не есть хорошо на слух :(

кстати сопоставить длины в пакетной обработке я могу программно - в коде сверху закомментирована функция GetWaveFileDuration()

+ я до сих пор не знаю что делать с несовпадением длины звука. Те в текске фонемы длина звука например 10.000 а в реале он длиной 10.200. Я забил на это дело и использовал реальные длины звуков и вроде всё нормально.

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#12 Сообщение 25.07.2015, 16:39

wowks
Давай только без шаманств. Многие аудиоредакторы добавляют в начало и в конец аудиоданных некоторое количество тишины (порядка 100-150 мс). Фонема построена по исходным WAVE-данным уже с учётом этой тишины. При масштабировании ты изменяешь и её продолжительность тоже. Расчёт новое_время = старое_время * (новая_продолжительность / старая_продолжительность) является верным, если Sound Forge самопроизвольно не добавляет новые порции тишины.
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#13 Сообщение 25.07.2015, 16:46

Vit_amiN &raquo; Сб июл 25, 2015 2:39 pm писал(а):Давай только без шаманств.
не никаких шаманств, всё дело в размере данных из RIFF-Chunk-а со словом "data"
те это с учётом и тишины, другими словами всех данных звука, а сами данные начинаются сразу после этого RIFF-Chanck-а. В противном случае представь себе как проигрывался бы последний участок звука где вместо звуковых семплов записан некий текст. Он то проиграется, только это будет не звук а насилование ушей :)
Последний раз редактировалось Vit_amiN 25.07.2015, 16:46, всего редактировалось 5 раз.
Причина: Так а я о чём.

Аватара пользователя
Vit_amiN
Супермодератор
Супермодератор
Сообщения: 2509
Зарегистрирован: 01.02.2007
Откуда: Over Old Hills
Благодарил (а): 15 раз
Поблагодарили: 91 раз

#14 Сообщение 25.07.2015, 18:30

wowks
Информация об общей продолжительности из ASCII-данных не используется для пересчёте. Она привязана к сэмплам, а не сэмплы к ней. Не будет лишним проверить, что Sound Forge не добавляет новые сэмплы тишины (неучтённые) при экспорте в файл, вот о чём речь. Что-то подобное за ним я припоминаю, и именно это было в своё время причиной перехода на бесплатный Audacity.

Мне вот что непонятно, зачем тебе параметр -r? У тебя есть исходный звук (-s "..."), у тебя есть отмасштабированный звук (-d "..."). Подели число сэмплов из второго на число из первого (при условии одинаковой частоты дискретизации, разумеется, или воспользуйся GetWaveFileDuration() во избежание накладок), вот тебе и точный коэффициент для нормирования привязок. И неплохо бы, кстати, дать возможность сохранять готовый результат в другой файл (опциональный ключ -o "..." (output)), не трогая отредактированный (чистый) .WAV.
Изображение

Мои русификаторы и другие полезные файлы здесь
ЗАПОМНИТЕ, ПОИСК — БЛИЖАЙШИЙ ПУТЬ К ИСТИНЕ!

Аватара пользователя
wowks
Майор
Майор
Сообщения: 525
Зарегистрирован: 09.12.2008
Благодарил (а): 67 раз
Поблагодарили: 37 раз

#15 Сообщение 25.07.2015, 19:23

Vit_amiN
ну всё, накатал вспомогательную программу "clean_wave_file.exe" (добавил в шапку темы)
она для промежуточной очистки (после Sony Sound Forge и до добавления новой фонемы) лишних данных в конце, если они есть. Те остаются только аудио данные, а то что дописывает Sony Sound Forge или фонема воспринимается как лишнее и не сохраняется в новом файле.
и не переживай. Звук остаётся невредим. Проверено!
Vit_amiN &raquo; Сб июл 25, 2015 4:30 pm писал(а):Мне вот что непонятно, зачем тебе параметр -r
ты прав. Можно обойтись и без него. Он нужен потому что я пока точно не знаю брать ли мне длину звука из текста фонемы или реальную длину звука. Они, эти длины, отличаются и может быть так и должно быть и тут есть какая-то закономерность. Мне действительно куда проще самому высчитывать длину звука из заголовка аудио, чем лишний лезть за ней в текст фонемы или править ком строку в батнике.
Vit_amiN &raquo; Сб июл 25, 2015 4:30 pm писал(а):опциональный ключ -o
ты имеешь в виду только саму фонему с её маленьким заголовком?
потому-что при логировании она и так целиком сохраняется в текст лога

к тому же вопрос в целесообразности пакетной обработки.
тк идёт сравнение с той же английской озвучкой и тут есть нюансы
Вот взять например фразу г-мена "Я осознаю, что выбрал не лучшее время для беседы тет-а-тет, "
в англ озвучке её длина 7 с. 410 мс.
в русской от буки 10 с.
а исправленная (в шапке) 8 с. 400 мс.

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

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

Ответить