You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3638 lines
88 KiB
3638 lines
88 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: localization_check.cpp : Defines the entry point for the console application. |
|
// |
|
// |
|
// What the tool does: |
|
// |
|
// Load g_pSoundEmitterSystem, vgui::localize, soundcombiner system, vcd system |
|
// |
|
// Catalog all files in closecaption/ folder |
|
// Iterate all known vcds |
|
// |
|
// 1) For each sound emitted by a vcd not marked as CC_DISABLED, verify that there's an entry in the localization table |
|
// 2) For each combined sound in a vcd, make sure there's a valid entry in the localization table |
|
// 3) For each combined sound, verify that the english version combined .wav file has the proper checksum |
|
// 4) Note any files in the closecaption folder which are orphaned after parsing the above |
|
// 5) If hl2_french directories etc. exist, then compare combined .wav files with localized versions and |
|
// see if localized checksum tag differs from US one, or warn if tag missing, but complain if .wav duration is different. |
|
// |
|
// UNDONE:re-create combined .wav files in english to the extent that the checksums mismatch? |
|
// |
|
//===========================================================================// |
|
#include "cbase.h" |
|
#include <stdio.h> |
|
#include <conio.h> |
|
#include <windows.h> |
|
#include <mmreg.h> |
|
#include <direct.h> |
|
#include "tier0/dbg.h" |
|
#include "utldict.h" |
|
#include "filesystem.h" |
|
#include "KeyValues.h" |
|
#include "cmdlib.h" |
|
#include "scriplib.h" |
|
#include "appframework/tier3app.h" |
|
#include "vstdlib/random.h" |
|
#include "SoundEmitterSystem/isoundemittersystembase.h" |
|
#include "choreoscene.h" |
|
#include "choreoevent.h" |
|
#include "choreochannel.h" |
|
#include "choreoactor.h" |
|
#include "iscenetokenprocessor.h" |
|
#include "ifaceposersound.h" |
|
#include "snd_audio_source.h" |
|
#include "snd_wave_source.h" |
|
#include "AudioWaveOutput.h" |
|
#include "isoundcombiner.h" |
|
#include "tier0/icommandline.h" |
|
#include <vgui/ILocalize.h> |
|
#include "vgui/ivgui.h" |
|
#include "soundchars.h" |
|
#include "sentence.h" |
|
#include "tier2/riff.h" |
|
#include "utlbuffer.h" |
|
#include "FileSystem_Helpers.h" |
|
#include "pacifier.h" |
|
#include "phonemeextractor/PhonemeExtractor.h" |
|
#include "UnicodeFileHelpers.h" |
|
|
|
using namespace vgui; |
|
|
|
bool uselogfile = false; |
|
bool regenerate = false; |
|
bool regenerate_quiet = false; |
|
bool regenerate_all = false; // user hit a to y/n/all prompt |
|
bool generate_usage = false; |
|
bool nuke = false; |
|
bool checkscriptsounds = false; |
|
bool build_cc = false; |
|
bool build_script = false; |
|
bool wavcheck = false; |
|
bool extractphonemes = false; |
|
bool checkforloops = false; |
|
bool importcaptions = false; |
|
bool checkfordups = false; |
|
bool makecopybatch = false; |
|
bool syncducking = false; |
|
|
|
char sounddir[ 512 ]; |
|
char importfile[ 512 ]; // for -i processing |
|
char fromdir[ 512 ]; |
|
char todir[ 512 ]; |
|
|
|
struct AnalysisData |
|
{ |
|
CUtlSymbolTable symbols; |
|
}; |
|
|
|
IFileSystem *filesystem = NULL; |
|
|
|
static AnalysisData g_Analysis; |
|
|
|
static CUniformRandomStream g_Random; |
|
IUniformRandomStream *random = &g_Random; |
|
|
|
static bool spewed = false; |
|
static bool forceextract = false; |
|
|
|
#define SOUND_DURATION_TOLERANCE 0.1f // 100 msec of slop |
|
|
|
|
|
void vprint( int depth, const char *fmt, ... ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void printusage( void ) |
|
{ |
|
vprint( 0, "usage: localization_check <opts> languagename\n\ |
|
\t-v = verbose output\n\ |
|
\t-l = log to file log.txt\n\ |
|
\t-u = generate usage data for .vcds based on -makereslists maplist.txt files\n\ |
|
\t-b = generate nuke.bat which will nuke all of the unreferenced .vcds from your tree\n\ |
|
\t-s = generate list of unused sounds.txt entries\n\ |
|
\t-c = build cc fixed/fixed2.txt files\n\ |
|
\t-r = regenerate missing/mismatched combined wav files\n\ |
|
\t\t-q = quiet mode during regenerate\n\ |
|
languagename = check combined language files for existence and duration, 'english' for no extra checks\n\ |
|
\t-x = build script of dialog from .vcds\n\ |
|
\t-w sounddir = spew csv of wave files in directory, including sound and cc info\n\ |
|
\t-e sounddir = do textless phoneme processing on files in dir and subdirs\n\ |
|
\t-f with above, forces extraction even if wav already has phonemes (danger!)\n\ |
|
\t-i = import unicode wavename/caption into a new closecaption_test.txt file\n\ |
|
\t-d = check for duplicated unicode strings\n\ |
|
\t-p = pull raw english txt out of closecaption document, for spellchecking\n\ |
|
\t-m = given a directory of .wav files finds the full directory path they should live in based on english\n\ |
|
\t-a english_sound_dir localized_sound_dir = sets voice duck for sounds in localized_sound_dir to match the values in the english dir\n\ |
|
\t-loop = warn on any sound files in specified directory having loop markers in the .wav\n\ |
|
\ne.g.: localization_check -l -w npc/metropolice/vo -r french\n" ); |
|
|
|
// Exit app |
|
exit( 1 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper for parsing scene data file |
|
//----------------------------------------------------------------------------- |
|
class CSceneTokenProcessor : public ISceneTokenProcessor |
|
{ |
|
public: |
|
const char *CurrentToken( void ); |
|
bool GetToken( bool crossline ); |
|
bool TokenAvailable( void ); |
|
void Error( const char *fmt, ... ); |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *CSceneTokenProcessor::CurrentToken( void ) |
|
{ |
|
return token; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : crossline - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CSceneTokenProcessor::GetToken( bool crossline ) |
|
{ |
|
return ::GetToken( crossline ) ? true : false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CSceneTokenProcessor::TokenAvailable( void ) |
|
{ |
|
return ::TokenAvailable() ? true : false; |
|
} |
|
|
|
static char *va( char const *fmt, ... ) |
|
{ |
|
static char string[ 2048 ]; |
|
va_list argptr; |
|
va_start( argptr, fmt ); |
|
Q_vsnprintf( string, sizeof(string), fmt, argptr ); |
|
va_end( argptr ); |
|
return string; |
|
} |
|
|
|
void cleanquotes( char *text ) |
|
{ |
|
char *out = text; |
|
while ( *out ) |
|
{ |
|
if ( *out == '\"' ) |
|
{ |
|
*out++ = '\''; |
|
} |
|
else |
|
{ |
|
++out; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *fmt - |
|
// ... - |
|
//----------------------------------------------------------------------------- |
|
void CSceneTokenProcessor::Error( const char *fmt, ... ) |
|
{ |
|
char string[ 2048 ]; |
|
va_list argptr; |
|
va_start( argptr, fmt ); |
|
Q_vsnprintf( string, sizeof(string), fmt, argptr ); |
|
va_end( argptr ); |
|
|
|
Warning( "%s", string ); |
|
Assert(0); |
|
} |
|
|
|
static CSceneTokenProcessor g_TokenProcessor; |
|
|
|
SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg ) |
|
{ |
|
spewed = true; |
|
|
|
printf( "%s", pMsg ); |
|
OutputDebugString( pMsg ); |
|
|
|
if ( type == SPEW_ERROR ) |
|
{ |
|
printf( "\n" ); |
|
OutputDebugString( "\n" ); |
|
} |
|
|
|
return SPEW_CONTINUE; |
|
} |
|
|
|
void logprint( char const *logfile, const char *fmt, ... ) |
|
{ |
|
char string[ 8192 ]; |
|
va_list va; |
|
va_start( va, fmt ); |
|
vsprintf( string, fmt, va ); |
|
va_end( va ); |
|
|
|
FILE *fp = NULL; |
|
static bool first = true; |
|
if ( first ) |
|
{ |
|
first = false; |
|
fp = fopen( logfile, "wb" ); |
|
} |
|
else |
|
{ |
|
fp = fopen( logfile, "ab" ); |
|
} |
|
if ( fp ) |
|
{ |
|
char *p = string; |
|
while ( *p ) |
|
{ |
|
if ( *p == '\n' ) |
|
{ |
|
fputc( '\r', fp ); |
|
} |
|
fputc( *p, fp ); |
|
p++; |
|
} |
|
fclose( fp ); |
|
} |
|
} |
|
|
|
void nuke_print( int depth, const char *fmt, ... ) |
|
{ |
|
char string[ 8192 ]; |
|
va_list va; |
|
va_start( va, fmt ); |
|
vsprintf( string, fmt, va ); |
|
va_end( va ); |
|
|
|
static bool first = false; |
|
|
|
|
|
FILE *fp = NULL; |
|
|
|
char const *nukefile = "nuke.bat"; |
|
|
|
if ( first ) |
|
{ |
|
first = false; |
|
fp = fopen( nukefile, "wb" ); |
|
} |
|
else |
|
{ |
|
fp = fopen( nukefile, "ab" ); |
|
} |
|
|
|
while ( depth-- > 0 ) |
|
{ |
|
fprintf( fp, " " ); |
|
} |
|
|
|
char *p = string; |
|
while ( *p ) |
|
{ |
|
if ( *p == '\n' ) |
|
{ |
|
fputc( '\r', fp ); |
|
} |
|
fputc( *p, fp ); |
|
p++; |
|
} |
|
|
|
fclose( fp ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : depth - |
|
// *fmt - |
|
// ... - |
|
//----------------------------------------------------------------------------- |
|
void vprint( int depth, const char *fmt, ... ) |
|
{ |
|
char string[ 8192 ]; |
|
va_list va; |
|
va_start( va, fmt ); |
|
vsprintf( string, fmt, va ); |
|
va_end( va ); |
|
|
|
FILE *fp = NULL; |
|
|
|
if ( uselogfile ) |
|
{ |
|
fp = fopen( "log.txt", "ab" ); |
|
} |
|
|
|
while ( depth-- > 0 ) |
|
{ |
|
printf( " " ); |
|
OutputDebugString( " " ); |
|
if ( fp ) |
|
{ |
|
fprintf( fp, " " ); |
|
} |
|
} |
|
|
|
::printf( "%s", string ); |
|
OutputDebugString( string ); |
|
|
|
if ( fp ) |
|
{ |
|
char *p = string; |
|
while ( *p ) |
|
{ |
|
if ( *p == '\n' ) |
|
{ |
|
fputc( '\r', fp ); |
|
} |
|
fputc( *p, fp ); |
|
p++; |
|
} |
|
fclose( fp ); |
|
} |
|
} |
|
|
|
void Con_Printf( const char *fmt, ... ) |
|
{ |
|
va_list args; |
|
static char output[1024]; |
|
|
|
va_start( args, fmt ); |
|
Q_vsnprintf( output, sizeof( output ), fmt, args ); |
|
va_end( args ); |
|
|
|
vprint( 0, output ); |
|
} |
|
|
|
void BuildFileList_R( CUtlVector< CUtlSymbol >& files, char const *dir, char const *extension ) |
|
{ |
|
WIN32_FIND_DATA wfd; |
|
|
|
char directory[ 256 ]; |
|
char filename[ MAX_PATH ]; |
|
HANDLE ff; |
|
|
|
sprintf( directory, "%s\\*.*", dir ); |
|
|
|
if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE ) |
|
return; |
|
|
|
int extlen = strlen( extension ); |
|
|
|
do |
|
{ |
|
if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) |
|
{ |
|
|
|
if ( wfd.cFileName[ 0 ] == '.' ) |
|
continue; |
|
|
|
// Recurse down directory |
|
sprintf( filename, "%s\\%s", dir, wfd.cFileName ); |
|
BuildFileList_R( files, filename, extension ); |
|
} |
|
else |
|
{ |
|
int len = strlen( wfd.cFileName ); |
|
if ( len > extlen ) |
|
{ |
|
if ( !stricmp( &wfd.cFileName[ len - extlen ], extension ) ) |
|
{ |
|
Q_snprintf( filename, sizeof( filename ), "%s\\%s", dir, wfd.cFileName ); |
|
_strlwr( filename ); |
|
|
|
Q_FixSlashes( filename ); |
|
|
|
CUtlSymbol sym = g_Analysis.symbols.AddString( filename ); |
|
files.AddToTail( sym ); |
|
|
|
if ( !( files.Count() % 3000 ) ) |
|
{ |
|
vprint( 0, "...found %i .%s files\n", files.Count(), extension ); |
|
} |
|
} |
|
} |
|
} |
|
} while ( FindNextFile( ff, &wfd ) ); |
|
} |
|
|
|
void BuildFileList( CUtlVector< CUtlSymbol >& files, char const *rootdir, char const *extension ) |
|
{ |
|
files.RemoveAll(); |
|
BuildFileList_R( files, rootdir, extension ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CheckLogFile( void ) |
|
{ |
|
if ( uselogfile ) |
|
{ |
|
_unlink( "log.txt" ); |
|
vprint( 0, " Outputting to log.txt\n" ); |
|
} |
|
} |
|
|
|
void PrintHeader() |
|
{ |
|
vprint( 0, "Valve Software - localization_check.exe (%s)\n", __DATE__ ); |
|
vprint( 0, "--- Voice Wav File .vcd Checker ---\n" ); |
|
} |
|
|
|
char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender ) |
|
{ |
|
if ( Q_stristr( soundname, ".wav" ) ) |
|
return PSkipSoundChars( soundname ); |
|
|
|
return PSkipSoundChars( g_pSoundEmitterSystem->GetWavFileForSound( soundname, gender ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Implements the RIFF i/o interface on stdio |
|
//----------------------------------------------------------------------------- |
|
class StdIOReadBinary : public IFileReadBinary |
|
{ |
|
public: |
|
int open( const char *pFileName ) |
|
{ |
|
return (int)g_pFullFileSystem->Open( pFileName, "rb" ); |
|
} |
|
|
|
int read( void *pOutput, int size, int file ) |
|
{ |
|
if ( !file ) |
|
return 0; |
|
|
|
return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file ); |
|
} |
|
|
|
void seek( int file, int pos ) |
|
{ |
|
if ( !file ) |
|
return; |
|
|
|
g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
unsigned int tell( int file ) |
|
{ |
|
if ( !file ) |
|
return 0; |
|
|
|
return g_pFullFileSystem->Tell( (FileHandle_t)file ); |
|
} |
|
|
|
unsigned int size( int file ) |
|
{ |
|
if ( !file ) |
|
return 0; |
|
|
|
return g_pFullFileSystem->Size( (FileHandle_t)file ); |
|
} |
|
|
|
void close( int file ) |
|
{ |
|
if ( !file ) |
|
return; |
|
|
|
g_pFullFileSystem->Close( (FileHandle_t)file ); |
|
} |
|
}; |
|
|
|
|
|
class StdIOWriteBinary : public IFileWriteBinary |
|
{ |
|
public: |
|
int create( const char *pFileName ) |
|
{ |
|
g_pFullFileSystem->SetFileWritable( pFileName, true, "GAME" ); |
|
return (int)g_pFullFileSystem->Open( pFileName, "wb" ); |
|
} |
|
|
|
int write( void *pData, int size, int file ) |
|
{ |
|
return g_pFullFileSystem->Write( pData, size, (FileHandle_t)file ); |
|
} |
|
|
|
void close( int file ) |
|
{ |
|
g_pFullFileSystem->Close( (FileHandle_t)file ); |
|
} |
|
|
|
void seek( int file, int pos ) |
|
{ |
|
g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
unsigned int tell( int file ) |
|
{ |
|
return g_pFullFileSystem->Tell( (FileHandle_t)file ); |
|
} |
|
}; |
|
|
|
static StdIOWriteBinary io_out; |
|
static StdIOReadBinary io_in; |
|
|
|
#define RIFF_WAVE MAKEID('W','A','V','E') |
|
#define WAVE_FMT MAKEID('f','m','t',' ') |
|
#define WAVE_DATA MAKEID('d','a','t','a') |
|
#define WAVE_FACT MAKEID('f','a','c','t') |
|
#define WAVE_CUE MAKEID('c','u','e',' ') |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &walk - |
|
//----------------------------------------------------------------------------- |
|
static void ParseSentence( CSentence& sentence, IterateRIFF &walk ) |
|
{ |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
buf.EnsureCapacity( walk.ChunkSize() ); |
|
walk.ChunkRead( buf.Base() ); |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() ); |
|
|
|
sentence.InitFromDataChunk( buf.Base(), buf.TellPut() ); |
|
} |
|
|
|
bool LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io, void *formatbuffer = NULL, int* formatsize = NULL, int *datasize = NULL ) |
|
{ |
|
int insize = 0; |
|
|
|
if ( formatsize ) |
|
{ |
|
insize = *formatsize; |
|
*formatsize = 0; |
|
} |
|
|
|
if ( datasize ) |
|
{ |
|
*datasize = 0; |
|
} |
|
|
|
sentence.Reset(); |
|
|
|
InFileRIFF riff( wavfile, io ); |
|
|
|
// UNDONE: Don't use printf to handle errors |
|
if ( riff.RIFFName() != RIFF_WAVE ) |
|
{ |
|
return false; |
|
} |
|
|
|
// set up the iterator for the whole file (root RIFF is a chunk) |
|
IterateRIFF walk( riff, riff.RIFFSize() ); |
|
|
|
// This chunk must be first as it contains the wave's format |
|
// break out when we've parsed it |
|
bool found = false; |
|
while ( walk.ChunkAvailable( ) ) |
|
{ |
|
switch( walk.ChunkName() ) |
|
{ |
|
case WAVE_FMT: |
|
{ |
|
if ( formatbuffer && formatsize ) |
|
{ |
|
if ( walk.ChunkSize() <= insize ) |
|
{ |
|
*formatsize = walk.ChunkSize(); |
|
walk.ChunkRead( formatbuffer ); |
|
} |
|
else |
|
{ |
|
Error( "oops, format tag too big!!!" ); |
|
} |
|
} |
|
} |
|
break; |
|
case WAVE_VALVEDATA: |
|
{ |
|
found = true; |
|
ParseSentence( sentence, walk ); |
|
} |
|
break; |
|
case WAVE_DATA: |
|
{ |
|
if ( datasize ) |
|
{ |
|
*datasize = walk.ChunkSize(); |
|
} |
|
} |
|
break; |
|
} |
|
walk.ChunkNext(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence, void *formatbuffer = NULL, int* formatsize = NULL, int *dataSize = NULL ) |
|
{ |
|
return LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in, formatbuffer, formatsize, dataSize ); |
|
} |
|
|
|
bool ValidateCombinedFileCheckSum( char const *outfilename, char const *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves ) |
|
{ |
|
CUtlVector< CombinerEntry > work; |
|
|
|
char actualfile[ 512 ]; |
|
g_pSoundEmitterSystem->GenderExpandString( gender, outfilename, actualfile, sizeof( actualfile ) ); |
|
if ( Q_strlen( actualfile ) <= 0 ) |
|
{ |
|
return false; |
|
} |
|
|
|
int i = sorted.FirstInorder(); |
|
if ( i != sorted.InvalidIndex() ) |
|
{ |
|
CChoreoEvent *e = sorted[ i ]; |
|
|
|
float startoffset = e->GetStartTime(); |
|
|
|
do |
|
{ |
|
e = sorted[ i ]; |
|
|
|
float curoffset = e->GetStartTime(); |
|
|
|
CombinerEntry ce; |
|
Q_snprintf( ce.wavefile, sizeof( ce.wavefile ), "sound/%s", FacePoser_TranslateSoundNameGender( e->GetParameters(), gender ) ); |
|
ce.startoffset = curoffset - startoffset; |
|
|
|
work.AddToTail( ce ); |
|
|
|
i = sorted.NextInorder( i ); |
|
} |
|
while ( i != sorted.InvalidIndex() ); |
|
|
|
int c = work.Count(); |
|
|
|
char worklist[ 2048 ]; |
|
|
|
worklist[ 0 ] = 0; |
|
|
|
for ( i = 0; i < c; ++i ) |
|
{ |
|
CombinerEntry &item = work[ i ]; |
|
Q_strncat( worklist, item.wavefile, sizeof( worklist ), COPY_ALL_CHARACTERS ); |
|
if ( i != c - 1 ) |
|
{ |
|
Q_strncat( worklist, ", ", sizeof( worklist ), COPY_ALL_CHARACTERS ); |
|
} |
|
} |
|
|
|
logprint( "cc_combined.txt", "combined .wav '%s': %s\n", actualfile, worklist ); |
|
} |
|
|
|
bool valid = soundcombiner->IsCombinedFileChecksumValid( g_pFullFileSystem, actualfile, work ); |
|
if ( !valid ) |
|
{ |
|
vprint( 0, "combined file (%s) checksum mismatch for '%s'\n event '%s' of scene '%s'\n", actualfile, cctoken, |
|
sorted[0]->GetName(), sorted[0]->GetScene()->GetFilename() ); |
|
|
|
if ( regenerate ) |
|
{ |
|
bool dothisfile = false; |
|
if ( !regenerate_all ) |
|
{ |
|
vprint( 0, "Regenerate '%s'? (Yes/No/All)", actualfile ); |
|
char ch = getch(); |
|
if ( ch == 'y' || ch == 'Y' ) |
|
{ |
|
dothisfile = true; |
|
} |
|
if ( ch == 'a' || ch == 'A' ) |
|
{ |
|
regenerate_all = true; |
|
dothisfile = true; |
|
} |
|
vprint( 0, "\n" ); |
|
} |
|
else |
|
{ |
|
dothisfile = true; |
|
} |
|
|
|
if ( dothisfile ) |
|
{ |
|
bool success = soundcombiner->CombineSoundFiles( g_pFullFileSystem, actualfile, work ); |
|
vprint( 1, "%s: %s\n", actualfile, success ? "succeeded" : "FAILED" ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( regenerate ) |
|
{ |
|
vprint( 0, "combined file (%s) checksum still matches for %s, skipping rebuild...\n", actualfile, cctoken ); |
|
} |
|
} |
|
|
|
// Mark the file as referenced |
|
// |
|
char fn[ 512 ]; |
|
Q_snprintf( fn, sizeof( fn ), "%s%s", gamedir, actualfile ); |
|
|
|
_strlwr( fn ); |
|
Q_FixSlashes( fn ); |
|
CUtlSymbol sym = g_Analysis.symbols.AddString( fn ); |
|
|
|
if ( referencedcaptionwaves.Find( sym ) == referencedcaptionwaves.InvalidIndex() ) |
|
{ |
|
referencedcaptionwaves.Insert( sym ); |
|
} |
|
|
|
return valid; |
|
} |
|
|
|
static bool EventStartTimeLessFunc( CChoreoEvent * const &p1, CChoreoEvent * const &p2 ) |
|
{ |
|
CChoreoEvent *w1; |
|
CChoreoEvent *w2; |
|
|
|
w1 = const_cast< CChoreoEvent * >( p1 ); |
|
w2 = const_cast< CChoreoEvent * >( p2 ); |
|
|
|
return w1->GetStartTime() < w2->GetStartTime(); |
|
} |
|
|
|
static bool SymbolLessFunc( const CUtlSymbol & p1, const CUtlSymbol &p2 ) |
|
{ |
|
if ( Q_stricmp( g_Analysis.symbols.String( p1 ), g_Analysis.symbols.String( p2 ) ) < 0 ) |
|
return true; |
|
return false; |
|
} |
|
|
|
bool ValidateCombinedSoundCheckSum( CChoreoEvent *e, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves ) |
|
{ |
|
if ( !e || e->GetType() != CChoreoEvent::SPEAK ) |
|
return false; |
|
|
|
bool genderwildcard = e->IsCombinedUsingGenderToken(); |
|
char outfilename[ 512 ]; |
|
Q_memset( outfilename, 0, sizeof( outfilename ) ); |
|
if ( !e->ComputeCombinedBaseFileName( outfilename, sizeof( outfilename ), genderwildcard ) ) |
|
{ |
|
vprint( 0, "Unable to regenerate wav file name for combined sound (%s)\n", e->GetCloseCaptionToken() ); |
|
return false; |
|
} |
|
|
|
bool checksumvalid = false; |
|
|
|
CUtlRBTree< CChoreoEvent * > eventList( 0, 0, EventStartTimeLessFunc ); |
|
|
|
if ( !e->GetChannel()->GetSortedCombinedEventList( e->GetCloseCaptionToken(), eventList ) ) |
|
{ |
|
vprint( 0, "Unable to generated combined event list (%s)\n", e->GetCloseCaptionToken() ); |
|
return false; |
|
} |
|
|
|
|
|
if ( genderwildcard ) |
|
{ |
|
checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_MALE, eventList, referencedcaptionwaves ); |
|
checksumvalid &= ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_FEMALE, eventList, referencedcaptionwaves ); |
|
} |
|
else |
|
{ |
|
checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_NONE, eventList, referencedcaptionwaves ); |
|
} |
|
|
|
return checksumvalid; |
|
} |
|
|
|
struct PerMapVCDS |
|
{ |
|
PerMapVCDS() |
|
{ |
|
} |
|
|
|
PerMapVCDS( const PerMapVCDS& src ) |
|
{ |
|
int i = src.vcds.FirstInorder(); |
|
while ( i != src.vcds.InvalidIndex() ) |
|
{ |
|
vcds.Insert( src.vcds[ i ] ); |
|
i = src.vcds.NextInorder( i ); |
|
} |
|
} |
|
|
|
class CTree : public CUtlRBTree< CUtlSymbol > |
|
{ |
|
public: |
|
CTree() |
|
: CUtlRBTree< CUtlSymbol >( 0, 0, DefLessFunc( CUtlSymbol ) ) |
|
{ |
|
} |
|
|
|
CTree &operator=( const CTree &from ) |
|
{ |
|
CopyFrom( from ); |
|
return *this; |
|
} |
|
}; |
|
|
|
CTree vcds; |
|
}; |
|
|
|
CUtlDict< PerMapVCDS, int > g_PerMapVCDS; |
|
CUtlDict< CUtlSymbol, int > g_FirstMapForVCD; |
|
|
|
void ParseVCDFilesFromResList( CUtlVector< CUtlSymbol >& vcdsinreslist, char const *resfile ) |
|
{ |
|
char gd[ 256 ]; |
|
Q_strncpy( gd, gamedir, sizeof( gd ) ); |
|
Q_StripTrailingSlash( gd ); |
|
_strlwr( gd ); |
|
Q_FixSlashes( gd ); |
|
|
|
int gdlen = strlen( gd ); |
|
|
|
char resbase[ 512 ]; |
|
Q_FileBase( resfile, resbase, sizeof( resbase ) ); |
|
|
|
int addedStrings = 0; |
|
int resourcesConsidered = 0; |
|
|
|
FileHandle_t resfilehandle; |
|
resfilehandle = g_pFullFileSystem->Open( resfile, "rb" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) |
|
{ |
|
// Read in the entire file |
|
int length = g_pFullFileSystem->Size(resfilehandle); |
|
if ( length > 0 ) |
|
{ |
|
char *pStart = (char *)new char[ length + 1 ]; |
|
if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) ) |
|
) |
|
{ |
|
pStart[ length ] = 0; |
|
|
|
char *pFileList = pStart; |
|
|
|
char tokenFile[512]; |
|
|
|
while ( 1 ) |
|
{ |
|
pFileList = ParseFile( pFileList, tokenFile, NULL ); |
|
if ( !pFileList ) |
|
break; |
|
|
|
if ( strlen( tokenFile ) > 0 ) |
|
{ |
|
char szFileName[ 256 ]; |
|
Q_strncpy( szFileName, tokenFile, sizeof( szFileName ) ); |
|
_strlwr( szFileName ); |
|
Q_FixSlashes( szFileName ); |
|
while ( szFileName[ strlen( szFileName ) - 1 ] == '\n' || |
|
szFileName[ strlen( szFileName ) - 1 ] == '\r' ) |
|
{ |
|
szFileName[ strlen( szFileName ) - 1 ] = 0; |
|
} |
|
|
|
char *pFile = szFileName; |
|
if ( !Q_strnicmp( szFileName, gd, gdlen ) ) |
|
{ |
|
pFile = szFileName + gdlen + 1; |
|
} |
|
else |
|
{ |
|
// Ack |
|
//vprint( 1, "File %s not under game directory but in reslist, skipping!!!\n", szFileName ); |
|
pFileList = ParseFile( pFileList, tokenFile, NULL ); |
|
continue; |
|
} |
|
|
|
++resourcesConsidered; |
|
|
|
// Is it a .vcd? |
|
if ( !Q_stristr( pFile, ".vcd" ) ) |
|
continue; |
|
|
|
char symname[ 512 ]; |
|
Q_snprintf( symname, sizeof( symname ), "%s%s", gamedir, pFile ); |
|
_strlwr( symname ); |
|
Q_FixSlashes( symname ); |
|
|
|
CUtlSymbol sym = g_Analysis.symbols.AddString( symname ); |
|
|
|
int idx = vcdsinreslist.Find( sym ); |
|
if ( idx == vcdsinreslist.InvalidIndex() ) |
|
{ |
|
++addedStrings; |
|
|
|
// This is the first time this vcd was encountered, remember which map we are in |
|
PerMapVCDS e; |
|
e.vcds.Insert( sym ); |
|
g_PerMapVCDS.Insert( resbase, e ); |
|
|
|
CUtlSymbol mapsym = g_Analysis.symbols.AddString( resbase ); |
|
g_FirstMapForVCD.Insert( symname, mapsym ); |
|
|
|
vcdsinreslist.AddToTail( sym ); |
|
} |
|
} |
|
} |
|
|
|
} |
|
delete[] pStart; |
|
} |
|
|
|
g_pFullFileSystem->Close(resfilehandle); |
|
} |
|
|
|
// int filesFound = addedStrings; |
|
// vprint( 1, "\rFound %i new resources (%7i total) in %64s", filesFound, resourcesConsidered, resfile ); |
|
} |
|
|
|
#define MAPLIST_FILE "maplist.txt" |
|
|
|
void AddFileToList( CUtlVector< CUtlSymbol >& list, char const *filename ) |
|
{ |
|
char fn[ 512 ]; |
|
Q_strncpy( fn, filename, sizeof( fn ) ); |
|
_strlwr( fn ); |
|
Q_FixSlashes( fn ); |
|
CUtlSymbol sym = g_Analysis.symbols.AddString( fn ); |
|
list.AddToTail( sym ); |
|
} |
|
|
|
void BuildVCDAndMapNameListsFromReslists( CUtlVector< CUtlSymbol >& vcdsinreslist ) |
|
{ |
|
// Load all .rst files in the reslists folder |
|
CUtlVector< CUtlSymbol > reslists; |
|
|
|
// If maplist.txt exists, use it, otherwise |
|
bool loaded = false; |
|
|
|
if ( g_pFullFileSystem->FileExists( MAPLIST_FILE ) ) |
|
{ |
|
// Parse the true list from the maplist.txt file |
|
// and add engine.lst and all.lst at the very end |
|
|
|
// Load them in |
|
FileHandle_t resfilehandle; |
|
resfilehandle = g_pFullFileSystem->Open( MAPLIST_FILE, "rb" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) |
|
{ |
|
// Read in and parse mapcycle.txt |
|
int length = g_pFullFileSystem->Size(resfilehandle); |
|
if ( length > 0 ) |
|
{ |
|
char *pStart = (char *)new char[ length + 1 ]; |
|
if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) ) |
|
) |
|
{ |
|
pStart[ length ] = 0; |
|
const char *pFileList = pStart; |
|
|
|
while ( 1 ) |
|
{ |
|
char szMap[ 512 ]; |
|
|
|
pFileList = ParseFile( pFileList, com_token, NULL ); |
|
|
|
if ( strlen( com_token ) <= 0 ) |
|
break; |
|
|
|
Q_strncpy(szMap, com_token, sizeof(szMap)); |
|
|
|
// Any more tokens on this line? |
|
//while ( TokenWaiting( pFileList ) ) |
|
//{ |
|
// pFileList = ParseFile( pFileList, com_token, NULL ); |
|
//} |
|
|
|
char fn[ 512 ]; |
|
Q_snprintf( fn, sizeof( fn ), "%sreslists/%s.lst", gamedir, szMap ); |
|
|
|
AddFileToList( reslists, fn ); |
|
} |
|
} |
|
delete[] pStart; |
|
|
|
AddFileToList( reslists, va( "%sreslists/engine.lst", gamedir ) ); |
|
AddFileToList( reslists, va( "%sreslists/all.lst", gamedir ) ); |
|
|
|
loaded = true; |
|
} |
|
|
|
g_pFullFileSystem->Close(resfilehandle); |
|
} |
|
} |
|
|
|
|
|
if ( !loaded ) |
|
{ |
|
char reslistdir[ 512 ]; |
|
Q_snprintf( reslistdir, sizeof( reslistdir ), "%sreslists", gamedir ); |
|
|
|
BuildFileList_R( reslists, reslistdir, ".lst" ); |
|
} |
|
|
|
int c = reslists.Count(); |
|
|
|
StartPacifier( "ParseVCDFilesFromResList: " ); |
|
|
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
UpdatePacifier( (float)( i + 1 ) / (float)c ); |
|
ParseVCDFilesFromResList( vcdsinreslist, g_Analysis.symbols.String( reslists[ i ] ) ); |
|
} |
|
|
|
EndPacifier( true ); |
|
} |
|
|
|
void CheckUnusedVcds( CUtlVector< CUtlSymbol >& vcdsinreslist, CUtlVector< CUtlSymbol >& vcdfiles ) |
|
{ |
|
vprint( 1, "Checking for orphaned vcd files\n" ); |
|
|
|
// For each reslist, load in the filenames, looking for .vcds |
|
vprint( 1, "Found %i .vcd files referenced (%i total)\n", vcdsinreslist.Count(), vcdfiles.Count() ); |
|
|
|
// For each vcd in the min list, see if it's in the sublist |
|
int i; |
|
int c = vcdfiles.Count(); |
|
|
|
int invalid_index = vcdsinreslist.InvalidIndex(); |
|
|
|
int unrefcount = 0; |
|
|
|
for ( i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol& sym = vcdfiles[ i ]; |
|
|
|
if ( vcdsinreslist.Find( sym ) == invalid_index ) |
|
{ |
|
++unrefcount; |
|
vprint( 1, " unref .vcd: %s\n", g_Analysis.symbols.String( sym ) ); |
|
|
|
if ( nuke ) |
|
{ |
|
nuke_print( 0, "del %s /f\n", g_Analysis.symbols.String( sym ) ); |
|
} |
|
} |
|
} |
|
|
|
// For each reslist, load in the filenames, looking for .vcds |
|
vprint( 1, "Found %i unreferenced vcds (%i total)\n", unrefcount, vcdfiles.Count() ); |
|
|
|
} |
|
|
|
void ParseUsedSoundsFromSndFile( CUtlRBTree< int, int >& usedsounds, char const *sndfile ) |
|
{ |
|
char gd[ 256 ]; |
|
Q_strncpy( gd, gamedir, sizeof( gd ) ); |
|
Q_StripTrailingSlash( gd ); |
|
_strlwr( gd ); |
|
Q_FixSlashes( gd ); |
|
|
|
int addedStrings = 0; |
|
int resourcesConsidered = 0; |
|
|
|
FileHandle_t resfilehandle; |
|
resfilehandle = g_pFullFileSystem->Open( sndfile, "rb" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) |
|
{ |
|
// Read in the entire file |
|
int length = g_pFullFileSystem->Size(resfilehandle); |
|
if ( length > 0 ) |
|
{ |
|
char *pStart = (char *)new char[ length + 1 ]; |
|
if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) ) |
|
) |
|
{ |
|
pStart[ length ] = 0; |
|
|
|
char *pFileList = pStart; |
|
|
|
char tokenFile[512]; |
|
|
|
while ( 1 ) |
|
{ |
|
pFileList = ParseFile( pFileList, tokenFile, NULL ); |
|
if ( !pFileList ) |
|
break; |
|
|
|
if ( strlen( tokenFile ) > 0 ) |
|
{ |
|
char soundname[ 256 ]; |
|
Q_strncpy( soundname, tokenFile, sizeof( soundname ) ); |
|
_strlwr( soundname ); |
|
|
|
++resourcesConsidered; |
|
|
|
int index = g_pSoundEmitterSystem->GetSoundIndex( soundname ); |
|
if ( !g_pSoundEmitterSystem->IsValidIndex( index ) ) |
|
{ |
|
vprint( 1, "---> Sound %s doesn't exist in g_pSoundEmitterSystemsystem!!!\n", soundname ); |
|
continue; |
|
} |
|
|
|
int idx = usedsounds.Find( index ); |
|
if ( idx == usedsounds.InvalidIndex() ) |
|
{ |
|
++addedStrings; |
|
usedsounds.Insert( index ); |
|
} |
|
} |
|
} |
|
|
|
} |
|
delete[] pStart; |
|
} |
|
|
|
g_pFullFileSystem->Close(resfilehandle); |
|
} |
|
|
|
vprint( 1, "Found %i new resources (%i total) in %s\n", addedStrings, resourcesConsidered, sndfile ); |
|
} |
|
|
|
bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ); |
|
|
|
void SpewDuplicatedText( char const *lang, const char *entry, const wchar_t *str ) |
|
{ |
|
const wchar_t *curpos = str; |
|
|
|
wchar_t cleaned[ 4096 ]; |
|
|
|
wchar_t *out = cleaned; |
|
|
|
for ( ; curpos && *curpos != L'\0'; ++curpos ) |
|
{ |
|
wchar_t cmd[ 256 ]; |
|
wchar_t args[ 256 ]; |
|
|
|
if ( SplitCommand( &curpos, cmd, args ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
// Only copy non command, non-whitespace characters |
|
if ( iswspace( *curpos ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
*out++ = *curpos; |
|
} |
|
|
|
*out = L'\0'; |
|
|
|
int len = wcslen( cleaned ); |
|
if ( len < 5 ) |
|
return; |
|
|
|
// Now see how many characters from the first 50% of the text are also in the second 50% |
|
int halflen = len / 2; |
|
|
|
int foundcount = 0; |
|
for ( int i = 0; i < halflen; ++i ) |
|
{ |
|
wchar_t ch[3]; |
|
ch[0] = cleaned[ i ]; |
|
ch[1] = cleaned[ i + 1 ]; |
|
ch[2] = L'\0'; |
|
|
|
if ( wcsstr( &cleaned[ halflen ], ch ) ) |
|
{ |
|
++foundcount; |
|
} |
|
} |
|
|
|
if ( foundcount > 0.7 * halflen ) |
|
{ |
|
logprint( "cc_duplicatedtext.txt", "%s: Suspect token %s\n", lang, entry ); |
|
} |
|
} |
|
|
|
void CheckDuplcatedText( void ) |
|
{ |
|
g_pFullFileSystem->RemoveFile( "cc_duplicatedtext.txt", "GAME" ); |
|
|
|
for ( int lang = 0; lang < CC_NUM_LANGUAGES; ++lang ) |
|
{ |
|
char language[ 256 ]; |
|
Q_strncpy( language, CSentence::NameForLanguage( lang ), sizeof( language ) ); |
|
|
|
vprint( 0, "adding langauge file for '%s'\n", language ); |
|
|
|
g_pVGuiLocalize->AddFile( "resource/closecaption_english.txt" ); |
|
|
|
char fn[ 256 ]; |
|
Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", language ); |
|
|
|
g_pVGuiLocalize->AddFile( fn ); |
|
|
|
// Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them |
|
StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex(); |
|
while ( str != INVALID_LOCALIZE_STRING_INDEX ) |
|
{ |
|
char const *keyname = g_pVGuiLocalize->GetNameByIndex( str ); |
|
if ( keyname ) |
|
{ |
|
const wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str ); |
|
|
|
SpewDuplicatedText( language, keyname, value ); |
|
} |
|
|
|
str = g_pVGuiLocalize->GetNextStringIndex( str ); |
|
} |
|
} |
|
} |
|
|
|
static bool IsAllSpaces( const wchar_t *stream ) |
|
{ |
|
const wchar_t *p = stream; |
|
while ( *p != L'\0' ) |
|
{ |
|
if ( !iswspace( *p ) ) |
|
return false; |
|
|
|
p++; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void SpewEnglishText( const wchar_t *str ) |
|
{ |
|
const wchar_t *curpos = str; |
|
|
|
wchar_t cleaned[ 4096 ]; |
|
|
|
wchar_t *out = cleaned; |
|
|
|
for ( ; curpos && *curpos != L'\0'; ++curpos ) |
|
{ |
|
wchar_t cmd[ 256 ]; |
|
wchar_t args[ 256 ]; |
|
|
|
if ( SplitCommand( &curpos, cmd, args ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
*out++ = *curpos; |
|
} |
|
|
|
*out = L'\0'; |
|
|
|
if ( IsAllSpaces( cleaned ) ) |
|
return; |
|
|
|
char ansi[ 4096 ]; |
|
g_pVGuiLocalize->ConvertUnicodeToANSI( cleaned, ansi, sizeof( ansi ) ); |
|
|
|
logprint( "cc_english.txt", "\"%s\"\n", ansi ); |
|
} |
|
|
|
void ExtractEnglish() |
|
{ |
|
g_pFullFileSystem->RemoveFile( "cc_english.txt", "GAME" ); |
|
// Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them |
|
StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex(); |
|
while ( str != INVALID_LOCALIZE_STRING_INDEX ) |
|
{ |
|
char const *keyname = g_pVGuiLocalize->GetNameByIndex( str ); |
|
if ( keyname ) |
|
{ |
|
const wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str ); |
|
|
|
SpewEnglishText( value ); |
|
} |
|
|
|
str = g_pVGuiLocalize->GetNextStringIndex( str ); |
|
} |
|
} |
|
|
|
void CheckUnusedSounds() |
|
{ |
|
vprint( 1, "Checking for unused sounds.txt entries\n" ); |
|
|
|
CUtlRBTree< int, int > usedsounds( 0, 0, DefLessFunc(int) ); |
|
|
|
// Load all .snd files in the reslists folder |
|
CUtlVector< CUtlSymbol > sndlists; |
|
|
|
char reslistdir[ 512 ]; |
|
Q_snprintf( reslistdir, sizeof( reslistdir ), "%sreslists", gamedir ); |
|
|
|
BuildFileList_R( sndlists, reslistdir, ".snd" ); |
|
|
|
int c = sndlists.Count(); |
|
|
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
ParseUsedSoundsFromSndFile( usedsounds, g_Analysis.symbols.String( sndlists[ i ] ) ); |
|
} |
|
|
|
// For each reslist, load in the filenames, looking for .vcds |
|
vprint( 1, "Found %i unique sounds referenced\n", usedsounds.Count() ); |
|
|
|
// For each vcd in the min list, see if it's in the sublist |
|
c = g_pSoundEmitterSystem->GetSoundCount(); |
|
|
|
int unrefcount = 0; |
|
|
|
int invalidindex = usedsounds.InvalidIndex(); |
|
|
|
CUtlRBTree< int, int > usedscripts( 0, 0, DefLessFunc(int) ); |
|
|
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
int slot = usedsounds.Find( i ); |
|
if ( invalidindex == slot ) |
|
{ |
|
++unrefcount; |
|
char const *soundname = g_pSoundEmitterSystem->GetSoundName( i ); |
|
|
|
vprint( 1, " unref: %s : %s\n", soundname, g_pSoundEmitterSystem->GetSourceFileForSound( i ) ); |
|
} |
|
else |
|
{ |
|
int scriptindex = g_pSoundEmitterSystem->FindSoundScript( g_pSoundEmitterSystem->GetSourceFileForSound( i ) ); |
|
if ( scriptindex != -1 ) |
|
{ |
|
slot = usedscripts.Find( scriptindex ); |
|
if ( usedscripts.InvalidIndex() == slot ) |
|
{ |
|
usedscripts.Insert( scriptindex ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// For each reslist, load in the filenames, looking for .vcds |
|
vprint( 1, "Found %i unreferenced sounds (%i total)\n", unrefcount, c ); |
|
|
|
c = g_pSoundEmitterSystem->GetNumSoundScripts(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *scriptname = g_pSoundEmitterSystem->GetSoundScriptName( i ); |
|
|
|
int slot = usedscripts.Find( i ); |
|
if ( usedscripts.InvalidIndex() == slot ) |
|
{ |
|
vprint( 1, " No sounds fron script %s are being used, should delete from manifest!!!\n", scriptname ); |
|
} |
|
} |
|
|
|
if ( !build_cc ) |
|
return; |
|
|
|
g_pFullFileSystem->RemoveFile( "fixed.txt", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "fixed2.txt", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "todo.csv", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "cc_add.txt", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "cc_delete.txt", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "cc_foundphonemes.txt", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "cc_combined.txt", "GAME" ); |
|
|
|
logprint( "todo.csv", "\"CC_TOKEN\",\"TEXT\",\"WAVE FILE\"\n" ); |
|
|
|
// Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them |
|
StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex(); |
|
while ( str != INVALID_LOCALIZE_STRING_INDEX ) |
|
{ |
|
char const *keyname = g_pVGuiLocalize->GetNameByIndex( str ); |
|
|
|
if ( keyname ) |
|
{ |
|
wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str ); |
|
|
|
char ansi[ 512 ]; |
|
g_pVGuiLocalize->ConvertUnicodeToANSI( value, ansi, sizeof( ansi ) ); |
|
|
|
// See if key exists in g_pSoundEmitterSystem system |
|
int soundindex = g_pSoundEmitterSystem->GetSoundIndex( keyname ); |
|
if( soundindex == -1 ) |
|
{ |
|
vprint( 1, " cc token %s not in current g_pSoundEmitterSystem scripts\n", keyname ); |
|
|
|
// Just write it back out as is... |
|
logprint( "fixed2.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi ); |
|
} |
|
else |
|
{ |
|
// See if it's referenced |
|
int slot = usedsounds.Find( soundindex ); |
|
if ( usedsounds.InvalidIndex() == slot ) |
|
{ |
|
vprint( 1, " cc token %s exists, but the sound is not used by the game\n", keyname ); |
|
|
|
logprint( "cc_delete.txt", "\"%s\"\n", keyname ); |
|
} |
|
else |
|
{ |
|
// Now try to find a better bit of text |
|
CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex ); |
|
if ( internal && internal->NumSoundNames() > 0 ) |
|
{ |
|
CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol; |
|
char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave ); |
|
if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) ) |
|
{ |
|
// See if 1) it's marked as !!! and try to figure out the text from .wav files... |
|
if ( !Q_strnicmp( ansi, "!!!", 3 ) ) |
|
{ |
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) ) |
|
{ |
|
if ( Q_strlen( sentence.GetText() ) > 0 ) |
|
{ |
|
Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() ); |
|
cleanquotes( ansi ); |
|
|
|
logprint( "cc_foundphonemes.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi ); |
|
} |
|
} |
|
} |
|
|
|
logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi ); |
|
|
|
for ( int w = 0; w < internal->NumSoundNames() ; ++w ) |
|
{ |
|
wavname = g_pSoundEmitterSystem->GetWaveName( internal->GetSoundNames()[ w ].symbol ); |
|
logprint( "todo.csv", "\"%s\",\"%s\",\"%s\"\n", |
|
keyname, ansi, va( "sound/%s", PSkipSoundChars( wavname ) ) ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi ); |
|
} |
|
} |
|
} |
|
} |
|
str = g_pVGuiLocalize->GetNextStringIndex( str ); |
|
} |
|
|
|
// Now walk through all of the sounds that were used, but not in the localization file and and those, too |
|
c = g_pSoundEmitterSystem->GetSoundCount(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
int slot = usedsounds.Find( i ); |
|
if ( usedsounds.InvalidIndex() == slot ) |
|
continue; |
|
|
|
char const *soundname = g_pSoundEmitterSystem->GetSoundName( i ); |
|
|
|
// See if it exists in the localization file |
|
wchar_t *text = g_pVGuiLocalize->Find( soundname ); |
|
if ( text ) |
|
{ |
|
continue; |
|
} |
|
else |
|
{ |
|
char ansi[ 512 ]; |
|
Q_snprintf( ansi, sizeof( ansi ), "!!!%s", soundname ); |
|
|
|
// Now try to find a better bit of text |
|
CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( i ); |
|
if ( internal && internal->NumSoundNames() > 0 ) |
|
{ |
|
CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol; |
|
char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave ); |
|
if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) ) |
|
{ |
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) ) |
|
{ |
|
if ( Q_strlen( sentence.GetText() ) > 0 ) |
|
{ |
|
Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() ); |
|
cleanquotes( ansi ); |
|
} |
|
} |
|
|
|
// Add an entry for stuff in vo/ |
|
logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", soundname, ansi ); |
|
|
|
for ( int w = 0; w < internal->NumSoundNames() ; ++w ) |
|
{ |
|
wavname = g_pSoundEmitterSystem->GetWaveName( internal->GetSoundNames()[ w ].symbol ); |
|
logprint( "todo.csv", "\"%s\",\"%s\",\"%s\"\n", |
|
soundname, ansi, va( "sound/%s", PSkipSoundChars( wavname ) ) ); |
|
} |
|
|
|
logprint( "cc_add.txt", "\"%s\"\n", soundname ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Removes commas from text |
|
void RemoveCommas( char *in ) |
|
{ |
|
char *out = in; |
|
while ( out && *out ) |
|
{ |
|
if ( *in == ',' ) |
|
{ |
|
*out++ = ';'; |
|
in++; |
|
} |
|
else |
|
{ |
|
*out++ = *in++; |
|
} |
|
} |
|
*out = 0; |
|
} |
|
|
|
void SpewScript( char const *vcdname, CUtlRBTree< CChoreoEvent *, int >& list ) |
|
{ |
|
if ( !build_script ) |
|
return; |
|
|
|
if ( list.Count() == 0 ) |
|
return; |
|
|
|
logprint( "script.txt", "VCD( %s )\n\n", vcdname ); |
|
|
|
for ( int i = list.FirstInorder(); i != list.InvalidIndex(); i = list.NextInorder( i ) ) |
|
{ |
|
CChoreoEvent *e = list[ i ]; |
|
|
|
if ( e->GetCloseCaptionType() != CChoreoEvent::CC_MASTER ) |
|
{ |
|
continue; |
|
} |
|
|
|
char actorname[ 512 ]; |
|
|
|
if ( e->GetActor() ) |
|
{ |
|
Q_strncpy( actorname, e->GetActor()->GetName(), sizeof( actorname ) ); |
|
_strupr( actorname ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( actorname, "(NULL ACTOR)", sizeof( actorname ) ); |
|
|
|
} |
|
|
|
logprint( "script.txt", "\t\t\t%s\n", actorname); |
|
|
|
|
|
// Now try to find a better bit of text |
|
|
|
char wavname[ 512 ]; |
|
wavname[ 0 ] = 0; |
|
|
|
char sentence_text[ 1024 ]; |
|
sentence_text[ 0 ] = 0; |
|
|
|
int soundindex = g_pSoundEmitterSystem->GetSoundIndex( e->GetParameters() ); |
|
if ( soundindex != -1 ) |
|
{ |
|
CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex ); |
|
if ( internal && internal->NumSoundNames() > 0 ) |
|
{ |
|
CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol; |
|
char const *pname = g_pSoundEmitterSystem->GetWaveName( symwave ); |
|
if ( pname && ( Q_stristr( pname, "vo/" ) || Q_stristr( pname, "vo\\" ) || Q_stristr( pname, "combined" ) ) ) |
|
{ |
|
Q_strncpy( wavname, pname, sizeof( wavname ) ); |
|
|
|
// Convert to regular text |
|
logprint( "script.txt", "\t\t\t\twav(%s)\n", wavname ); |
|
|
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) ) |
|
{ |
|
if ( Q_strlen( sentence.GetText() ) > 0 ) |
|
{ |
|
Q_snprintf( sentence_text, sizeof( sentence_text ), "%s", sentence.GetText() ); |
|
cleanquotes( sentence_text ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
char tok[ 256 ]; |
|
Q_strncpy( tok, e->GetParameters(), sizeof( tok ) ); |
|
|
|
char ansi[ 2048 ]; |
|
ansi[ 0 ] = 0; |
|
|
|
wchar_t *str = g_pVGuiLocalize->Find( tok ); |
|
if ( !str ) |
|
{ |
|
logprint( "script.txt", "\t\tMissing token '%s' event '%s'\n\n", tok, e->GetName() ); |
|
Q_snprintf( ansi, sizeof( ansi ), "missing '%s' for '%s'", tok, e->GetName() ); |
|
} |
|
else |
|
{ |
|
if ( !wcsncmp( str, L"!!!", wcslen( L"!!!" ) ) ) |
|
{ |
|
logprint( "script.txt", "\t\t'%s': event '%s'\n\n", tok, e->GetName() ); |
|
Q_snprintf( ansi, sizeof( ansi ), "!!! '%s' for '%s'", tok, e->GetName() ); |
|
} |
|
else |
|
{ |
|
g_pVGuiLocalize->ConvertUnicodeToANSI( str, ansi, sizeof( ansi ) ); |
|
|
|
// Convert to regular text |
|
logprint( "script.txt", "\t\t\t\tcc_token(%s)\n\n\t\t\"%s\"\n\n", tok, ansi ); |
|
} |
|
} |
|
|
|
// Now spit out the CSV version... |
|
RemoveCommas( actorname ); |
|
RemoveCommas( tok ); |
|
RemoveCommas( ansi ); |
|
|
|
char mapname[ 512 ]; |
|
|
|
mapname[ 0 ] = 0; |
|
|
|
int idx = g_FirstMapForVCD.Find( vcdname ); |
|
if ( idx != g_FirstMapForVCD.InvalidIndex() ) |
|
{ |
|
Q_strncpy( mapname, g_Analysis.symbols.String( g_FirstMapForVCD[ idx ] ), sizeof( mapname ) ); |
|
} |
|
|
|
static unsigned int sortindex = 0; |
|
|
|
logprint( "script.csv", "%u,%s,%s,%s,%6.3f,%s,%s,\"%s\",\"%s\"\n", |
|
sortindex++, mapname, vcdname, actorname, e->GetStartTime(), tok, wavname, ansi, sentence_text ); |
|
} |
|
} |
|
|
|
void CheckLocalizationEntries( CUtlVector< CUtlSymbol >& vcdfiles, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves ) |
|
{ |
|
int disabledcount = 0; |
|
int validcount = 0; |
|
int missingcount = 0; |
|
int wavfile = 0; |
|
|
|
int gamedirskip = Q_strlen( gamedir ); |
|
|
|
if ( build_script ) |
|
{ |
|
g_pFullFileSystem->RemoveFile( "script.txt", "GAME" ); |
|
g_pFullFileSystem->RemoveFile( "script.csv", "GAME" ); |
|
} |
|
|
|
int c = vcdfiles.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol& vcdname = vcdfiles[ i ]; |
|
|
|
CUtlRBTree< CChoreoEvent *, int > sortedSpeakEvents( 0, 0, EventStartTimeLessFunc ); |
|
|
|
|
|
// Load the .vcd |
|
char fullname[ 512 ]; |
|
Q_snprintf( fullname, sizeof( fullname ), "%s", g_Analysis.symbols.String( vcdname ) ); |
|
|
|
LoadScriptFile( fullname ); |
|
|
|
CChoreoScene *scene = ChoreoLoadScene( fullname, NULL, &g_TokenProcessor, Con_Printf ); |
|
if ( !scene ) |
|
{ |
|
vprint( 0, "Warning: Unable to load %s\n", fullname ); |
|
continue; |
|
} |
|
|
|
// Now iterate the events looking for speak events |
|
int numevents = scene->GetNumEvents(); |
|
for ( int j = 0; j < numevents; j++ ) |
|
{ |
|
CChoreoEvent *e = scene->GetEvent( j ); |
|
if ( e->GetType() != CChoreoEvent::SPEAK ) |
|
continue; |
|
|
|
if ( e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) |
|
{ |
|
++disabledcount; |
|
continue; |
|
} |
|
|
|
if ( build_script ) |
|
{ |
|
if ( sortedSpeakEvents.Find( e ) == sortedSpeakEvents.InvalidIndex() ) |
|
{ |
|
sortedSpeakEvents.Insert( e ); |
|
} |
|
} |
|
|
|
char tok[ 256 ]; |
|
|
|
for ( int pass = 0; pass <= 1; ++pass ) |
|
{ |
|
bool iscombined = false; |
|
|
|
if ( pass == 0 ) |
|
{ |
|
Q_strncpy( tok, e->GetParameters(), sizeof( tok ) ); |
|
} |
|
else |
|
{ |
|
if ( e->GetCloseCaptionType() != CChoreoEvent::CC_MASTER ) |
|
continue; |
|
|
|
if ( e->GetNumSlaves() <= 0 ) |
|
continue; |
|
|
|
if ( !e->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) |
|
{ |
|
++missingcount; |
|
continue;; |
|
} |
|
|
|
iscombined = true; |
|
} |
|
|
|
// Look it up |
|
wchar_t *str = g_pVGuiLocalize->Find( tok ); |
|
if ( !str ) |
|
{ |
|
char fn[ 256 ]; |
|
//Q_FileBase( g_Analysis.symbols.String( vcdname ), fn, sizeof( fn ) ); |
|
Q_strncpy( fn, &g_Analysis.symbols.String( vcdname )[ gamedirskip ], sizeof( fn ) ); |
|
|
|
|
|
if ( Q_stristr( tok, ".wav" ) ) |
|
{ |
|
if ( verbose ) |
|
{ |
|
if ( !regenerate_quiet ) |
|
{ |
|
vprint( 0, "(OBSOLETE???)missing cc token '%s' (!.wav file): vcd (%s), event (%s)\n", |
|
tok, fn, e->GetName() ); |
|
} |
|
} |
|
|
|
++wavfile; |
|
} |
|
else |
|
{ |
|
if ( !regenerate_quiet ) |
|
{ |
|
vprint( 0, "missing %s cc token '%s': vcd (%s), event (%s)\n", |
|
pass == 0 ? "normal" : "combined", |
|
tok, |
|
fn, |
|
e->GetName() ); |
|
} |
|
|
|
// Add the "!!!entry" to a temp file |
|
if ( verbose ) |
|
{ |
|
char suggested[ 4096 ]; |
|
Q_snprintf( suggested, sizeof( suggested ), "!!!%s", tok ); |
|
|
|
int soundindex = g_pSoundEmitterSystem->GetSoundIndex( tok ); |
|
if ( soundindex != -1 ) |
|
{ |
|
// Now try to find a better bit of text |
|
CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex ); |
|
if ( internal && internal->NumSoundNames() > 0 ) |
|
{ |
|
CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol; |
|
char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave ); |
|
if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) ) |
|
{ |
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) ) |
|
{ |
|
if ( Q_strlen( sentence.GetText() ) > 0 ) |
|
{ |
|
Q_snprintf( suggested, sizeof( suggested ), "%s", sentence.GetText() ); |
|
cleanquotes( suggested ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
logprint( "missing.txt", "\t\"%s\"\t\t\"%s\"\n", tok, suggested ); |
|
} |
|
|
|
++missingcount; |
|
} |
|
|
|
|
|
} |
|
else |
|
{ |
|
if ( verbose ) |
|
{ |
|
if ( !wcsncmp( str, L"!!!", wcslen( L"!!!" ) ) ) |
|
{ |
|
if ( !regenerate_quiet ) |
|
{ |
|
vprint( 0, "Autogenerated closecaption token '%s' not edited\n", tok ); |
|
} |
|
} |
|
} |
|
++validcount; |
|
} |
|
|
|
// Verify checksum |
|
if ( iscombined ) |
|
{ |
|
ValidateCombinedSoundCheckSum( e, referencedcaptionwaves ); |
|
} |
|
} |
|
} |
|
|
|
SpewScript( fullname, sortedSpeakEvents ); |
|
sortedSpeakEvents.RemoveAll(); |
|
|
|
delete scene; |
|
} |
|
|
|
int total = validcount + missingcount + wavfile + disabledcount; |
|
if ( total != 0 ) |
|
{ |
|
vprint( 0, "\n%.2f %%%% invalid (%i valid, %i missing, %i wavfile(OBSOLETE), %i disabled - total %i)\n", |
|
100.0f * (float)missingcount / (float)total, |
|
validcount, |
|
missingcount, |
|
wavfile, |
|
disabledcount, |
|
total ); |
|
} |
|
} |
|
|
|
void CheckForOrphanedCombinedWavs( CUtlVector< CUtlSymbol >& diskwaves, CUtlRBTree< CUtlSymbol, int >& captionsused ) |
|
{ |
|
if ( g_pFullFileSystem->FileExists( "orphaned.bat", "GAME" ) ) |
|
{ |
|
g_pFullFileSystem->RemoveFile( "orphaned.bat", "GAME" ); |
|
} |
|
|
|
int orphans = 0; |
|
int c = diskwaves.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol &sym = diskwaves[ i ]; |
|
|
|
if ( captionsused.Find( sym ) != captionsused.InvalidIndex() ) |
|
continue; |
|
|
|
char fn[ 256 ]; |
|
Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) ); |
|
|
|
vprint( 1, "Orphaned wav file '%s'\n", fn ); |
|
|
|
logprint( "orphaned.bat", "del \"%s\" /f\n", fn ); |
|
|
|
++orphans; |
|
} |
|
|
|
if ( orphans != 0 ) |
|
{ |
|
vprint( 0, "\n%.2f %%%% (%i/%i), orphaned combined .wav files in sound/combined/... folder\n", |
|
100.0f * (float)orphans / (float)c, |
|
orphans, c ); |
|
|
|
vprint( 0, "created orphaned.bat file\n" ); |
|
} |
|
else |
|
{ |
|
vprint( 0, "\nNo orphaned files found among %d possible disk waves\n", c ); |
|
} |
|
} |
|
|
|
float GetWaveDuration( char const *wavname ) |
|
{ |
|
if ( !g_pFullFileSystem->FileExists( wavname ) ) |
|
{ |
|
return 0.0f; |
|
} |
|
|
|
CAudioSource *wave = sound->LoadSound( wavname ); |
|
if ( !wave ) |
|
{ |
|
//vprint( 0, "unable to load %s\n", wavname ); |
|
return 0.0f; |
|
} |
|
|
|
CAudioMixer *pMixer = wave->CreateMixer(); |
|
if ( !pMixer ) |
|
{ |
|
vprint( 0, "unable to create mixer for %s\n", wavname ); |
|
delete wave; |
|
return 0.0f; |
|
} |
|
|
|
float duration = wave->GetRunningLength(); |
|
return duration; |
|
} |
|
|
|
void GetWaveSentence( char const *wavname, CSentence& sentence ) |
|
{ |
|
sentence.Reset(); |
|
if ( !g_pFullFileSystem->FileExists( wavname ) ) |
|
{ |
|
return; |
|
} |
|
|
|
CAudioSource *wave = sound->LoadSound( wavname ); |
|
if ( !wave ) |
|
{ |
|
//vprint( 0, "unable to load %s\n", wavname ); |
|
return; |
|
} |
|
|
|
sentence = *wave->GetSentence(); |
|
} |
|
|
|
void ValidateForeignLanguageWaves( char const *language, CUtlVector< CUtlSymbol >& combinedwavfiles ) |
|
{ |
|
// Need to compute the gamedir to the specified language |
|
char langdir[ 512 ]; |
|
char strippedgamedir[ 512 ]; |
|
Q_strncpy( langdir, gamedir, sizeof( langdir ) ); |
|
|
|
Q_StripTrailingSlash( langdir ); |
|
|
|
Q_strncpy( strippedgamedir, langdir, sizeof( strippedgamedir ) ); |
|
|
|
Q_strcat( langdir, "_", sizeof(langdir) ); |
|
Q_strcat( langdir, language, sizeof(langdir) ); |
|
|
|
int skipchars = Q_strlen( strippedgamedir ); |
|
|
|
// Need to add this to the file system |
|
int missing = 0; |
|
int outdated = 0; |
|
|
|
int c = combinedwavfiles.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol& sym = combinedwavfiles[ i ]; |
|
|
|
char wavname[ 512 ]; |
|
Q_strncpy( wavname, g_Analysis.symbols.String( sym ), sizeof( wavname ) ); |
|
|
|
// Now get language specific wav name |
|
char localizedwavename[ 512 ]; |
|
Q_snprintf( localizedwavename, sizeof( localizedwavename ), "%s%s", |
|
langdir, |
|
&wavname[ skipchars ] ); |
|
|
|
float duration_english = GetWaveDuration( wavname ); |
|
if ( !duration_english ) |
|
{ |
|
continue; |
|
} |
|
// Now see if the localized file exists |
|
|
|
float duration_localized = GetWaveDuration( localizedwavename ); |
|
if ( !duration_localized ) |
|
{ |
|
++missing; |
|
vprint( 0, "Missing localized file %s\n", localizedwavename ); |
|
continue; |
|
} |
|
|
|
CSentence sentence_english; |
|
GetWaveSentence( wavname, sentence_english ); |
|
CSentence sentence_localized; |
|
GetWaveSentence( localizedwavename, sentence_localized ); |
|
|
|
if ( sentence_english.GetText() && |
|
sentence_english.GetText()[0] ) |
|
{ |
|
if ( !sentence_localized.GetText() || !sentence_localized.GetText()[0] ) |
|
{ |
|
vprint( 0, "--> Localized combined file for '%s' doesn't have sentence data '%s'\n", |
|
language, localizedwavename ); |
|
} |
|
else if ( !Q_stricmp( sentence_english.GetText(), sentence_localized.GetText()) ) |
|
{ |
|
vprint( 0, "--> Localized combined file for '%s' still using english phoneme and text data '%s'\n", |
|
language, localizedwavename ); |
|
} |
|
} |
|
|
|
if ( fabs( duration_localized - duration_english ) > SOUND_DURATION_TOLERANCE ) |
|
{ |
|
++outdated; |
|
vprint( 0, "--> Mismatched localized file %s (english %.2f s./%s %.2f s.)\n", localizedwavename, |
|
duration_english, language, duration_localized ); |
|
continue; |
|
} |
|
} |
|
|
|
if ( c != 0 ) |
|
{ |
|
vprint( 0, "%.2f %%%% missing(%i)+outdated(%i)/total(%i), combined .wav files in %s closecaption/ folder\n", |
|
100.0f * (float)(missing + outdated ) / (float)c, |
|
missing, outdated, c, language ); |
|
} |
|
} |
|
|
|
void BuildReverseSoundLookup( CUtlDict< CUtlSymbol, int >& wavtosound ) |
|
{ |
|
// Build a dictionary of wav names to sound names |
|
int c = g_pSoundEmitterSystem->GetSoundCount(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *soundname = g_pSoundEmitterSystem->GetSoundName( i ); |
|
|
|
CUtlSymbol soundSymbol = g_Analysis.symbols.AddString( soundname ); |
|
|
|
CSoundParametersInternal* params = g_pSoundEmitterSystem->InternalGetParametersForSound( i ); |
|
if ( soundname && params ) |
|
{ |
|
int soundcount = params->NumSoundNames(); |
|
for ( int j = 0; j < soundcount; ++j ) |
|
{ |
|
SoundFile& sf = params->GetSoundNames()[ j ]; |
|
|
|
char const *pwavname = g_pSoundEmitterSystem->GetWaveName( sf.symbol ); |
|
char fixed[ 512 ]; |
|
Q_strncpy( fixed, PSkipSoundChars( pwavname ), sizeof( fixed ) ); |
|
_strlwr( fixed ); |
|
Q_FixSlashes( fixed ); |
|
|
|
int curidx = wavtosound.Find( fixed ); |
|
|
|
if ( curidx == wavtosound.InvalidIndex() ) |
|
{ |
|
wavtosound.Insert( fixed, soundSymbol ); |
|
|
|
//vprint( 0, "entry %s == %s\n", fixed, soundname ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
vprint( 0, "Reverse lookup has %i entries from %i available sounds\n", wavtosound.Count(), c ); |
|
} |
|
|
|
#define UNK_SOUND_ENTRY "<nosoundentry>" |
|
char const *FindSoundEntry( CUtlDict< CUtlSymbol, int >& wavtosound, char const *wavname ) |
|
{ |
|
char fixed[ 512 ]; |
|
Q_strncpy( fixed, PSkipSoundChars( wavname ), sizeof( fixed ) ); |
|
_strlwr( fixed ); |
|
Q_FixSlashes( fixed ); |
|
|
|
|
|
int idx = wavtosound.Find( fixed ); |
|
if ( idx != wavtosound.InvalidIndex() ) |
|
{ |
|
CUtlSymbol snd = wavtosound[ idx ]; |
|
return g_Analysis.symbols.String( snd ); |
|
} |
|
return UNK_SOUND_ENTRY; |
|
} |
|
|
|
void CheckWaveFile( CUtlDict< CUtlSymbol, int >& wavtosound, char const *wavname ) |
|
{ |
|
// vprint( 0, "%s\n", wavname ); |
|
|
|
char const *soundname = FindSoundEntry( wavtosound, wavname ); |
|
|
|
char ansi[ 512 ]; |
|
|
|
ansi[ 0 ] = 0; |
|
|
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) ) |
|
{ |
|
if ( Q_strlen( sentence.GetText() ) > 0 ) |
|
{ |
|
Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() ); |
|
cleanquotes( ansi ); |
|
} |
|
} |
|
|
|
// Now look up cc token |
|
wchar_t *text = g_pVGuiLocalize->Find( soundname ); |
|
|
|
char caption[ 1024 ]; |
|
Q_strncpy( caption, "!!!", sizeof( caption ) ); |
|
if ( text ) |
|
{ |
|
g_pVGuiLocalize->ConvertUnicodeToANSI( text, caption, sizeof( caption ) ); |
|
} |
|
else |
|
{ |
|
if ( !Q_stricmp( soundname, UNK_SOUND_ENTRY ) ) |
|
{ |
|
Q_snprintf( caption, sizeof( caption ), "!!!%s", soundname ); |
|
} |
|
} |
|
|
|
logprint( "wavcheck.csv", |
|
"\"%s\",\"%s\",\"%s\",\"%s\"\n", |
|
wavname, |
|
soundname, |
|
caption, |
|
ansi ); |
|
} |
|
|
|
void WavCheck( CUtlVector< CUtlSymbol >& wavfiles ) |
|
{ |
|
g_pFullFileSystem->RemoveFile( "wavcheck.csv", "GAME" ); |
|
|
|
vprint( 0, "Building reverse lookup\n" ); |
|
|
|
|
|
logprint( "wavcheck.csv", |
|
"\"%s\",\"%s\",\"%s\",\"%s\"\n", |
|
"WaveName", |
|
"Sound Script Name", |
|
"Close Caption", |
|
"Phoneme Data String" ); |
|
|
|
CUtlDict< CUtlSymbol, int > wavtosound; |
|
BuildReverseSoundLookup( wavtosound ); |
|
|
|
vprint( 0, "Performing wavcheck\n" ); |
|
int c = wavfiles.Count(); |
|
int offset = Q_strlen( gamedir ) + Q_strlen( "sound/" ); |
|
|
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] ); |
|
CheckWaveFile( wavtosound, wavname + offset ); |
|
|
|
if ( !(i % 100 ) ) |
|
{ |
|
vprint( 0, "Finished %i/%i\n", i, c ); |
|
} |
|
} |
|
} |
|
|
|
void BuildWavFileToFullPathLookup( CUtlVector< CUtlSymbol >& wavfile, CUtlDict< int, int >& wavtofullpath ) |
|
{ |
|
int c = wavfile.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol &sym = wavfile[ i ]; |
|
|
|
char shortname[ 512 ]; |
|
Q_FileBase( g_Analysis.symbols.String( sym ), shortname, sizeof( shortname ) ); |
|
|
|
Q_SetExtension( shortname, ".wav", sizeof( shortname ) ); |
|
Q_FixSlashes( shortname ); |
|
Q_strlower( shortname ); |
|
|
|
int idx = wavtofullpath.Find( shortname ); |
|
if ( idx == wavtofullpath.InvalidIndex() ) |
|
{ |
|
wavtofullpath.Insert( shortname, i ); |
|
} |
|
} |
|
} |
|
|
|
static void COM_CreatePath (const char *path) |
|
{ |
|
char temppath[512]; |
|
Q_strncpy( temppath, path, sizeof(temppath) ); |
|
|
|
for (char *ofs = temppath+1 ; *ofs ; ofs++) |
|
{ |
|
if (*ofs == '/' || *ofs == '\\') |
|
{ // create the directory |
|
char old = *ofs; |
|
*ofs = 0; |
|
mkdir (temppath); |
|
*ofs = old; |
|
} |
|
} |
|
} |
|
|
|
void MakeBatchFile( CUtlVector< CUtlSymbol >& wavfiles, char const *pchFromdir, char const *pchTodir ) |
|
{ |
|
g_pFullFileSystem->RemoveFile( "copywaves.bat", "GAME" ); |
|
|
|
vprint( 0, "Building reverse lookup\n" ); |
|
|
|
CUtlDict< int, int > wavtofullpath; |
|
BuildWavFileToFullPathLookup( wavfiles, wavtofullpath ); |
|
|
|
CUtlVector< CUtlSymbol > files; |
|
BuildFileList( files, pchFromdir, ".wav" ); |
|
|
|
int gamedirskip = Q_strlen( gamedir ) + Q_strlen( "sound//" ); |
|
|
|
int c = files.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *sname = g_Analysis.symbols.String( files[ i ] ); |
|
if ( !sname ) |
|
continue; |
|
|
|
char shortname[ 512 ]; |
|
Q_strncpy( shortname, sname, sizeof( shortname ) ); |
|
|
|
char fn[ 512 ]; |
|
Q_FileBase( shortname, fn, sizeof( fn ) ); |
|
Q_SetExtension( fn, ".wav", sizeof( fn ) ); |
|
Q_strlower( fn ); |
|
Q_FixSlashes( fn ); |
|
|
|
int slot = wavtofullpath.Find( fn ); |
|
if ( slot == wavtofullpath.InvalidIndex() ) |
|
{ |
|
vprint( 0, "Couldn't find slot for '%s'\n", sname ); |
|
continue; |
|
} |
|
|
|
char fullname[ 512 ]; |
|
Q_snprintf( fullname, sizeof( fullname ), "%s/%s", pchTodir, &g_Analysis.symbols.String( wavfiles[ wavtofullpath[ slot ] ] )[ gamedirskip ] ); |
|
Q_strlower( fullname ); |
|
Q_FixSlashes( fullname ); |
|
|
|
//logprint( "copywaves.bat", "xcopy \"%s\" \"%s\"\n", |
|
//shortname, |
|
//fullname ); |
|
|
|
|
|
COM_CreatePath( fullname ); |
|
|
|
CopyFile( shortname, fullname, TRUE ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : store - |
|
//----------------------------------------------------------------------------- |
|
void StoreValveDataChunk( CSentence& sentence, IterateOutputRIFF& store ) |
|
{ |
|
// Buffer and dump data |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
sentence.SaveToBuffer( buf ); |
|
|
|
// Copy into store |
|
store.ChunkWriteData( buf.Base(), buf.TellPut() ); |
|
} |
|
|
|
void SaveWave( char const *filename, CSentence& s ) |
|
{ |
|
char infile[ 512 ]; |
|
Q_strncpy( infile, filename, sizeof( infile ) ); |
|
|
|
Q_SetExtension( infile, ".tmp", sizeof( infile ) ); |
|
|
|
// Rename infile |
|
MoveFile( filename, infile ); |
|
SetFileAttributes( filename, FILE_ATTRIBUTE_NORMAL ); |
|
|
|
{ |
|
InFileRIFF riff( infile, io_in ); |
|
Assert( riff.RIFFName() == RIFF_WAVE ); |
|
|
|
// set up the iterator for the whole file (root RIFF is a chunk) |
|
IterateRIFF walk( riff, riff.RIFFSize() ); |
|
|
|
OutFileRIFF riffout( filename, io_out ); |
|
|
|
IterateOutputRIFF store( riffout ); |
|
|
|
bool wordtrackwritten = false; |
|
|
|
// Walk input chunks and copy to output |
|
while ( walk.ChunkAvailable() ) |
|
{ |
|
unsigned int originalPos = store.ChunkGetPosition(); |
|
|
|
store.ChunkStart( walk.ChunkName() ); |
|
|
|
bool skipchunk = false; |
|
|
|
switch ( walk.ChunkName() ) |
|
{ |
|
case WAVE_VALVEDATA: |
|
// Overwrite data |
|
StoreValveDataChunk( s, store ); |
|
wordtrackwritten = true; |
|
break; |
|
default: |
|
store.CopyChunkData( walk ); |
|
break; |
|
} |
|
|
|
store.ChunkFinish(); |
|
if ( skipchunk ) |
|
{ |
|
store.ChunkSetPosition( originalPos ); |
|
} |
|
|
|
walk.ChunkNext(); |
|
} |
|
|
|
if ( !wordtrackwritten ) |
|
{ |
|
store.ChunkStart( WAVE_VALVEDATA ); |
|
StoreValveDataChunk( s, store ); |
|
store.ChunkFinish(); |
|
} |
|
} |
|
|
|
SetFileAttributes( infile, FILE_ATTRIBUTE_NORMAL ); |
|
DeleteFile( infile ); |
|
} |
|
|
|
void ExtractPhonemesForWave( IPhonemeExtractor *extractor, char const *wavname ) |
|
{ |
|
char formatbuffer[ 1024 ]; |
|
int formatsize = sizeof( formatbuffer ); |
|
int dataSize = 0; |
|
|
|
CSentence sentence; |
|
if ( !LoadSentenceFromWavFile( wavname, sentence, formatbuffer, &formatsize, &dataSize ) ) |
|
{ |
|
vprint( 0, " skip '%s' missing\n", wavname ); |
|
return; |
|
} |
|
|
|
if ( !forceextract && |
|
sentence.m_Words.Count() > 0 ) |
|
{ |
|
vprint( 0, " skip '%s', already has phonemes\n", wavname ); |
|
return; |
|
} |
|
|
|
if ( forceextract ) |
|
{ |
|
sentence.Reset(); |
|
} |
|
|
|
if ( formatsize == 0 ) |
|
{ |
|
vprint( 0, " skip '%s', not WAVE_FMT parsed\n", wavname ); |
|
return; |
|
} |
|
|
|
const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)formatbuffer; |
|
|
|
int format = pHeader->wFormatTag; |
|
|
|
int bits = pHeader->wBitsPerSample; |
|
int rate = pHeader->nSamplesPerSec; |
|
int channels = pHeader->nChannels; |
|
|
|
int sampleSize = (bits * channels) / 8; |
|
|
|
// this can never be zero -- other functions divide by this. |
|
// This should never happen, but avoid crashing |
|
if ( sampleSize <= 0 ) |
|
sampleSize = 1; |
|
|
|
int sampleCount = 0; |
|
float truesamplesize = sampleSize; |
|
|
|
if ( format == WAVE_FORMAT_ADPCM ) |
|
{ |
|
sampleSize = 1; |
|
|
|
ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)formatbuffer; |
|
int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2; |
|
blockSize += 7 * pFormat->wfx.nChannels; |
|
|
|
int blockCount = sampleCount / blockSize; |
|
int blockRem = sampleCount % blockSize; |
|
|
|
// total samples in complete blocks |
|
sampleCount = blockCount * pFormat->wSamplesPerBlock; |
|
|
|
// add remaining in a short block |
|
if ( blockRem ) |
|
{ |
|
sampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / channels); |
|
} |
|
|
|
truesamplesize = 0.5f; |
|
} |
|
else |
|
{ |
|
sampleCount = dataSize / sampleSize; |
|
} |
|
|
|
// Do extraction |
|
// Current set of tags |
|
CSentence outsentence; |
|
|
|
char filename[ 512 ]; |
|
Q_snprintf( filename, sizeof( filename ), "%s", wavname ); |
|
|
|
int result = extractor->Extract( |
|
filename, |
|
dataSize, // (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize() ), |
|
Msg, |
|
sentence, |
|
outsentence ); |
|
|
|
if ( result != SR_RESULT_SUCCESS ) |
|
{ |
|
vprint( 0, " failed to analyze '%s', skipping\n", wavname ); |
|
return; |
|
} |
|
|
|
|
|
float bytespersecond = rate * truesamplesize; |
|
|
|
// Now convert byte offsets to times |
|
int i; |
|
for ( i = 0; i < outsentence.m_Words.Size(); i++ ) |
|
{ |
|
CWordTag *tag = outsentence.m_Words[ i ]; |
|
Assert( tag ); |
|
if ( !tag ) |
|
continue; |
|
|
|
tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond; |
|
tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond; |
|
|
|
for ( int j = 0; j < tag->m_Phonemes.Size(); j++ ) |
|
{ |
|
CPhonemeTag *ptag = tag->m_Phonemes[ j ]; |
|
Assert( ptag ); |
|
if ( !ptag ) |
|
continue; |
|
|
|
ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond ); |
|
ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond ); |
|
} |
|
} |
|
|
|
sentence = outsentence; |
|
|
|
outsentence.Reset(); |
|
|
|
// Resave it |
|
SaveWave( filename, sentence ); |
|
} |
|
|
|
struct Extractor |
|
{ |
|
PE_APITYPE apitype; |
|
CSysModule *module; |
|
IPhonemeExtractor *extractor; |
|
}; |
|
|
|
CUtlVector< Extractor > g_Extractors; |
|
|
|
void UnloadPhonemeConverters() |
|
{ |
|
int c = g_Extractors.Count(); |
|
for ( int i = c - 1; i >= 0; i-- ) |
|
{ |
|
Extractor *e = &g_Extractors[ i ]; |
|
g_pFullFileSystem->UnloadModule( e->module ); |
|
} |
|
|
|
g_Extractors.RemoveAll(); |
|
} |
|
|
|
int LoadPhonemeExtractors() |
|
{ |
|
// Enumerate modules under bin folder of exe |
|
FileFindHandle_t findHandle; |
|
const char *pFilename = g_pFullFileSystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle ); |
|
int useextractor = -1; |
|
while ( pFilename ) |
|
{ |
|
char fullpath[ 512 ]; |
|
Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename ); |
|
|
|
pFilename = g_pFullFileSystem->FindNext( findHandle ); |
|
|
|
Con_Printf( "Loading extractor from %s\n", fullpath ); |
|
|
|
Extractor e; |
|
e.module = Sys_LoadModule( fullpath ); |
|
if ( !e.module ) |
|
{ |
|
Warning( "Unable to Sys_LoadModule %s\n", fullpath ); |
|
continue; |
|
} |
|
|
|
CreateInterfaceFn factory = Sys_GetFactory( e.module ); |
|
if ( !factory ) |
|
{ |
|
Warning( "Unable to get factory from %s\n", fullpath ); |
|
continue; |
|
} |
|
|
|
e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL ); |
|
if ( !e.extractor ) |
|
{ |
|
Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath ); |
|
continue; |
|
} |
|
|
|
e.apitype = e.extractor->GetAPIType(); |
|
if ( e.apitype == SPEECH_API_LIPSINC ) |
|
{ |
|
useextractor = g_Extractors.Count(); |
|
} |
|
|
|
g_Extractors.AddToTail( e ); |
|
} |
|
|
|
g_pFullFileSystem->FindClose( findHandle ); |
|
|
|
return useextractor; |
|
} |
|
|
|
void ExtractPhonemes( CUtlVector< CUtlSymbol >& wavfiles ) |
|
{ |
|
int index = LoadPhonemeExtractors(); |
|
if ( index == -1 ) |
|
return; |
|
|
|
if ( index == 0 ) |
|
{ |
|
vprint( 0, "Couldn't find suitable extractor\n" ); |
|
return; |
|
} |
|
|
|
IPhonemeExtractor *extractor = g_Extractors[ index ].extractor; |
|
Assert( extractor ); |
|
|
|
vprint( 0, "Using %s\n", extractor->GetName() ); |
|
|
|
int c = wavfiles.Count(); |
|
|
|
vprint( 0, "Performing '%i' extractions (might take a while...)\n", c ); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] ); |
|
ExtractPhonemesForWave( extractor, wavname ); |
|
|
|
if ( !(i % 50 ) ) |
|
{ |
|
vprint( 0, "Finished %i/%i\n", i, c ); |
|
} |
|
} |
|
|
|
UnloadPhonemeConverters(); |
|
} |
|
|
|
void CheckWavForLoops( char const *wavname ) |
|
{ |
|
InFileRIFF riff( wavname, io_in ); |
|
|
|
// UNDONE: Don't use printf to handle errors |
|
if ( riff.RIFFName() != RIFF_WAVE ) |
|
{ |
|
return; |
|
} |
|
|
|
// set up the iterator for the whole file (root RIFF is a chunk) |
|
IterateRIFF walk( riff, riff.RIFFSize() ); |
|
|
|
while ( walk.ChunkAvailable( ) ) |
|
{ |
|
switch( walk.ChunkName() ) |
|
{ |
|
case WAVE_CUE: |
|
vprint( 0, "'%s' has a CUE chunk\n", wavname ); |
|
return; |
|
default: |
|
break; |
|
} |
|
walk.ChunkNext(); |
|
} |
|
} |
|
|
|
void CheckForLoops( CUtlVector< CUtlSymbol >& wavfiles ) |
|
{ |
|
int c = wavfiles.Count(); |
|
|
|
vprint( 0, "Performing '%i' extractions (might take a while...)\n", c ); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] ); |
|
CheckWavForLoops( wavname ); |
|
if ( !(i % 50 ) ) |
|
{ |
|
vprint( 0, "Finished %i/%i\n", i, c ); |
|
} |
|
} |
|
} |
|
|
|
#define MAX_LOCALIZED_CHARS 2048 |
|
//----------------------------------------------------------------------------- |
|
// Purpose: converts an unicode string to an english string |
|
//----------------------------------------------------------------------------- |
|
int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize) |
|
{ |
|
int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL); |
|
ansi[ansiBufferSize - 1] = 0; |
|
return result; |
|
} |
|
|
|
struct OrderedCaption_t |
|
{ |
|
OrderedCaption_t() : |
|
sym( UTL_INVAL_SYMBOL ), |
|
commands( NULL ), |
|
english( NULL ), |
|
blankenglish( false ) |
|
{ |
|
} |
|
|
|
OrderedCaption_t( const OrderedCaption_t& src ) |
|
{ |
|
sym = src.sym; |
|
|
|
if ( src.commands ) |
|
{ |
|
int len = wcslen( src.commands ) + 1; |
|
commands = new wchar_t[ len ]; |
|
wcscpy( commands, src.commands ); |
|
} |
|
else |
|
{ |
|
commands = NULL; |
|
} |
|
|
|
if ( src.english ) |
|
{ |
|
int len = wcslen( src.english ) + 1; |
|
english = new wchar_t[ len ]; |
|
wcscpy( english, src.english ); |
|
} |
|
else |
|
{ |
|
english = NULL; |
|
} |
|
blankenglish = src.blankenglish; |
|
} |
|
|
|
~OrderedCaption_t() |
|
{ |
|
delete[] commands; |
|
delete[] english; |
|
} |
|
|
|
CUtlSymbol sym; |
|
wchar_t *commands; // any <cmd:arg> stuff at the beginning of the US captions |
|
wchar_t *english; |
|
bool blankenglish; |
|
}; |
|
|
|
bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) |
|
{ |
|
const wchar_t *in = *ppIn; |
|
const wchar_t *oldin = in; |
|
|
|
if ( in[0] != L'<' ) |
|
{ |
|
*ppIn += ( oldin - in ); |
|
return false; |
|
} |
|
|
|
args[ 0 ] = 0; |
|
cmd[ 0 ]= 0; |
|
wchar_t *out = cmd; |
|
in++; |
|
while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) ) |
|
{ |
|
*out++ = *in++; |
|
} |
|
*out = L'\0'; |
|
|
|
if ( *in != L':' ) |
|
{ |
|
*ppIn += ( in - oldin ); |
|
return true; |
|
} |
|
|
|
in++; |
|
out = args; |
|
while ( *in != L'\0' && *in != L'>' ) |
|
{ |
|
*out++ = *in++; |
|
} |
|
*out = L'\0'; |
|
|
|
//if ( *in == L'>' ) |
|
// in++; |
|
|
|
*ppIn += ( in - oldin ); |
|
return true; |
|
} |
|
|
|
wchar_t *GetStartupCommands( const wchar_t *str ) |
|
{ |
|
const wchar_t *curpos = str; |
|
|
|
for ( ; curpos && *curpos != L'\0'; ++curpos ) |
|
{ |
|
wchar_t cmd[ 256 ]; |
|
wchar_t args[ 256 ]; |
|
|
|
if ( SplitCommand( &curpos, cmd, args ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
// Got to first non-command character |
|
break; |
|
} |
|
|
|
if ( curpos - str >= 1 ) |
|
{ |
|
int len = curpos - str; |
|
wchar_t *cmds = new wchar_t[ len + 1 ]; |
|
wcsncpy( cmds, str, len ); |
|
cmds[ len ] = L'\0'; |
|
return cmds; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
wchar_t *CopyUnicode( const wchar_t *in ) |
|
{ |
|
int len = wcslen( in ) + 1; |
|
wchar_t *out = new wchar_t[ len ]; |
|
wcsncpy( out, in, len ); |
|
out[ len - 1 ] = L'\0'; |
|
return out; |
|
} |
|
|
|
void BuildOrderedCaptionList( CUtlVector< OrderedCaption_t >& list ) |
|
{ |
|
// parse out the file |
|
FileHandle_t file = g_pFullFileSystem->Open( "resource/closecaption_english.txt", "rb"); |
|
if ( file == FILESYSTEM_INVALID_HANDLE ) |
|
{ |
|
// assert(!("CLocalizedStringTable::AddFile() failed to load file")); |
|
return; |
|
} |
|
|
|
// read into a memory block |
|
int fileSize = g_pFullFileSystem->Size(file) ; |
|
wchar_t *memBlock = (wchar_t *)malloc(fileSize + sizeof(wchar_t)); |
|
wchar_t *data = memBlock; |
|
g_pFullFileSystem->Read(memBlock, fileSize, file); |
|
|
|
// null-terminate the stream |
|
memBlock[fileSize / sizeof(wchar_t)] = 0x0000; |
|
|
|
// check the first character, make sure this a little-endian unicode file |
|
if (data[0] != 0xFEFF) |
|
{ |
|
g_pFullFileSystem->Close(file); |
|
free(memBlock); |
|
return; |
|
} |
|
data++; |
|
|
|
// parse out a token at a time |
|
enum states_e |
|
{ |
|
STATE_BASE, // looking for base settings |
|
STATE_TOKENS, // reading in unicode tokens |
|
}; |
|
|
|
bool bQuoted; |
|
bool bEnglishFile = true; |
|
|
|
states_e state = STATE_BASE; |
|
while (1) |
|
{ |
|
// read the key and the value |
|
wchar_t keytoken[128]; |
|
data = ReadUnicodeToken(data, keytoken, 128, bQuoted); |
|
if (!keytoken[0]) |
|
break; // we've hit the null terminator |
|
|
|
// convert the token to a string |
|
char key[128]; |
|
ConvertUnicodeToANSI(keytoken, key, sizeof(key)); |
|
|
|
// if we have a C++ style comment, read to end of line and continue |
|
if (!strnicmp(key, "//", 2)) |
|
{ |
|
data = ReadToEndOfLine(data); |
|
continue; |
|
} |
|
|
|
wchar_t valuetoken[ MAX_LOCALIZED_CHARS ]; |
|
data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted); |
|
if (!valuetoken[0] && !bQuoted) |
|
break; // we've hit the null terminator |
|
|
|
if (state == STATE_BASE) |
|
{ |
|
if (!stricmp(key, "Language")) |
|
{ |
|
// copy out our language setting |
|
/* |
|
char value[MAX_LOCALIZED_CHARS]; |
|
ConvertUnicodeToANSI(valuetoken, value, sizeof(value)); |
|
strncpy(m_szLanguage, value, sizeof(m_szLanguage) - 1); |
|
*/ |
|
} |
|
else if (!stricmp(key, "Tokens")) |
|
{ |
|
state = STATE_TOKENS; |
|
} |
|
else if (!stricmp(key, "}")) |
|
{ |
|
// we've hit the end |
|
break; |
|
} |
|
} |
|
else if (state == STATE_TOKENS) |
|
{ |
|
if (!stricmp(key, "}")) |
|
{ |
|
// end of tokens |
|
state = STATE_BASE; |
|
} |
|
else |
|
{ |
|
// skip our [english] beginnings (in non-english files) |
|
if ( (bEnglishFile) || (!bEnglishFile && strnicmp(key, "[english]", 9))) |
|
{ |
|
// add the string to the table |
|
//AddString(key, valuetoken, NULL); |
|
CUtlSymbol sym = g_Analysis.symbols.AddString( key ); |
|
|
|
OrderedCaption_t cap; |
|
cap.sym = sym; |
|
cap.commands = GetStartupCommands( valuetoken ); |
|
cap.english = CopyUnicode( valuetoken ); |
|
cap.blankenglish = IsAllSpaces( valuetoken ); |
|
|
|
list.AddToTail( cap ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
g_pFullFileSystem->Close(file); |
|
free(memBlock); |
|
|
|
vprint( 0, "Loaded %i captionnames from closecaption_english.txt\n", list.Count() ); |
|
} |
|
|
|
struct LookupData_t |
|
{ |
|
LookupData_t() : |
|
unicode( 0 ), |
|
caption( 0 ) |
|
{ |
|
} |
|
|
|
wchar_t *unicode; |
|
char *caption; |
|
}; |
|
|
|
void LoadImportData( char const *filename, CUtlDict< LookupData_t, int >& lookup ) |
|
{ |
|
// parse out the file |
|
FileHandle_t file = g_pFullFileSystem->Open( filename, "rb"); |
|
if ( file == FILESYSTEM_INVALID_HANDLE ) |
|
{ |
|
// assert(!("CLocalizedStringTable::AddFile() failed to load file")); |
|
return; |
|
} |
|
|
|
// read into a memory block |
|
int fileSize = g_pFullFileSystem->Size(file) ; |
|
wchar_t *memBlock = (wchar_t *)malloc(fileSize + sizeof(wchar_t)); |
|
wchar_t *data = memBlock; |
|
g_pFullFileSystem->Read(memBlock, fileSize, file); |
|
|
|
// null-terminate the stream |
|
memBlock[fileSize / sizeof(wchar_t)] = 0x0000; |
|
|
|
// check the first character, make sure this a little-endian unicode file |
|
if (data[0] != 0xFEFF) |
|
{ |
|
g_pFullFileSystem->Close(file); |
|
free(memBlock); |
|
return; |
|
} |
|
data++; |
|
|
|
bool bQuoted; |
|
|
|
while (1) |
|
{ |
|
// read the key and the value |
|
wchar_t keytoken[128]; |
|
data = ReadUnicodeTokenNoSpecial(data, keytoken, 128, bQuoted); |
|
if (!keytoken[0]) |
|
break; // we've hit the null terminator |
|
|
|
// convert the token to a string |
|
char key[128]; |
|
ConvertUnicodeToANSI(keytoken, key, sizeof(key)); |
|
|
|
// vprint( 0, "keyname %s\n", key ); |
|
|
|
// if we have a C++ style comment, read to end of line and continue |
|
if (!strnicmp(key, "//", 2)) |
|
{ |
|
data = ReadToEndOfLine(data); |
|
continue; |
|
} |
|
|
|
wchar_t valuetoken[ MAX_LOCALIZED_CHARS ]; |
|
data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted); |
|
if (!valuetoken[0] && !bQuoted) |
|
break; // we've hit the null terminator |
|
|
|
wchar_t *vcopy = new wchar_t[ wcslen( valuetoken ) + 1 ]; |
|
wcscpy( vcopy, valuetoken ); |
|
|
|
LookupData_t ld; |
|
ld.unicode = vcopy; |
|
ld.caption = NULL; |
|
|
|
lookup.Insert( key, ld ); |
|
} |
|
|
|
g_pFullFileSystem->Close(file); |
|
free(memBlock); |
|
|
|
vprint( 0, "Loaded %i wav/captions from %s\n", lookup.Count(), filename ); |
|
} |
|
|
|
#define CAPTION_OUT_FILE "resource/closecaption_test.txt" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: importfile is a unicode file contains pairs of .wav names and caption strings |
|
// we need to read in the closecaption_english.txt file |
|
// and then build a reverse lookup of .wav to caption name and build a new |
|
// closecaption_test.txt file based on the unicode caption strings |
|
// Input : *importfile - |
|
//----------------------------------------------------------------------------- |
|
void ImportCaptions( char const *pchImportfile ) |
|
{ |
|
CUtlVector< OrderedCaption_t > captionlist; |
|
BuildOrderedCaptionList( captionlist ); |
|
|
|
CUtlDict< LookupData_t, int > newCaptions; |
|
LoadImportData( pchImportfile, newCaptions ); |
|
|
|
// Now build a .wav to caption name lookup |
|
CUtlDict< CUtlSymbol, int > wavtosound; |
|
BuildReverseSoundLookup( wavtosound ); |
|
|
|
CUtlDict< wchar_t *, int > captionToUnicode; |
|
|
|
// Now walk the import data, and try to figure out the caption name for each one |
|
int c = newCaptions.Count(); |
|
for ( int i = 0; i < c ; ++i ) |
|
{ |
|
char const *wavname = newCaptions.GetElementName( i ); |
|
LookupData_t& data = newCaptions[ i ]; |
|
|
|
char fn[ 512 ]; |
|
Q_strncpy( fn, wavname, sizeof( fn ) ); |
|
Q_strlower( fn ); |
|
Q_FixSlashes( fn ); |
|
|
|
// See if we can find the wavname in the reverse lookup |
|
int idx = wavtosound.Find( fn ); |
|
if ( idx != wavtosound.InvalidIndex() ) |
|
{ |
|
data.caption = strdup( g_Analysis.symbols.String( wavtosound[ idx ] ) ); |
|
|
|
captionToUnicode.Insert( data.caption, data.unicode ); |
|
} |
|
else |
|
{ |
|
vprint( 0, "unable to find caption matching '%s'\n", wavname ); |
|
} |
|
} |
|
|
|
CUtlBuffer buf( 0, 0 ); |
|
|
|
c = captionlist.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
char const *captionname = g_Analysis.symbols.String( captionlist[ i ].sym ); |
|
|
|
// Find it in the captionToUnicodeFolder |
|
int idx = captionToUnicode.Find( captionname ); |
|
if ( idx != captionToUnicode.InvalidIndex() ) |
|
{ |
|
// Skip blank english entries |
|
if ( captionlist[ i ].blankenglish ) |
|
{ |
|
vprint( 0, "skipping %s, english caption is blank\n", captionname ); |
|
continue; |
|
} |
|
|
|
wchar_t *u = captionToUnicode[ idx ]; |
|
|
|
wchar_t *prefix = captionlist[ i ].commands; |
|
|
|
wchar_t composed[ MAX_LOCALIZED_CHARS ]; |
|
|
|
int maxlen = ( sizeof( composed ) / sizeof( wchar_t ) ) - 1; |
|
|
|
if ( prefix ) |
|
{ |
|
_snwprintf( composed, maxlen, L"%s%s", prefix, u ); |
|
} |
|
else |
|
{ |
|
wcsncpy( composed, u, maxlen ); |
|
} |
|
|
|
composed[ maxlen ] = L'\0'; |
|
|
|
// Write to buffer |
|
WriteAsciiStringAsUnicode( buf, captionname, true ); |
|
WriteAsciiStringAsUnicode( buf, "\t", false ); |
|
WriteUnicodeString( buf, composed, true ); |
|
WriteAsciiStringAsUnicode( buf, "\r\n", false ); |
|
|
|
// Now write the "[ENGLISH]" entry |
|
char engcap[ 512 ]; |
|
Q_snprintf( engcap, sizeof( engcap ), "[english]%s", captionname ); |
|
|
|
WriteAsciiStringAsUnicode( buf, engcap, true ); |
|
WriteAsciiStringAsUnicode( buf, "\t", false ); |
|
WriteUnicodeString( buf, captionlist[ i ].english ? captionlist[ i ].english : L"???", true ); |
|
WriteAsciiStringAsUnicode( buf, "\r\n", false ); |
|
} |
|
else |
|
{ |
|
vprint( 0, "no lookup for cc token '%s'\n", captionname ); |
|
} |
|
} |
|
|
|
// Now try and spit out a file like the cc english file, but with the new data |
|
FileHandle_t fh = g_pFullFileSystem->Open( CAPTION_OUT_FILE , "wb" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != fh ) |
|
{ |
|
g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh ); |
|
g_pFullFileSystem->Close( fh ); |
|
} |
|
else |
|
{ |
|
vprint( 0, "Unable to open %s for writing\n", CAPTION_OUT_FILE ); |
|
} |
|
|
|
// Cleanup memory |
|
c = newCaptions.Count(); |
|
for ( int i = 0 ; i < c; ++i ) |
|
{ |
|
LookupData_t& data = newCaptions[ i ]; |
|
delete[] data.unicode; |
|
free( data.caption ); |
|
} |
|
|
|
newCaptions.Purge(); |
|
} |
|
|
|
bool IsWavFileDucked( char const *name ) |
|
{ |
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( name , sentence ) ) |
|
{ |
|
return sentence.GetVoiceDuck(); |
|
} |
|
|
|
vprint( 0, "IsWavFileDucked: Missing .wav %s!!!\n", name ); |
|
return false; |
|
} |
|
|
|
void SetWavFileDucking( char const *name, bool ducking ) |
|
{ |
|
CSentence sentence; |
|
if ( LoadSentenceFromWavFile( name , sentence ) ) |
|
{ |
|
Assert( sentence.GetVoiceDuck() != ducking ); |
|
|
|
sentence.SetVoiceDuck( ducking ); |
|
|
|
// Save it back out |
|
SaveWave( name, sentence ); |
|
|
|
vprint( 1, "duck(%s): %s\n", ducking ? "true" : "false", name ); |
|
return; |
|
} |
|
|
|
vprint( 0, "SetWavFileDucking: Missing .wav %s!!!\n", name ); |
|
} |
|
|
|
void SyncDucking( CUtlVector< CUtlSymbol >& english, CUtlVector< CUtlSymbol >& localized ) |
|
{ |
|
int i, c; |
|
|
|
CUtlRBTree< CUtlSymbol > englishducked( 0, 0, DefLessFunc( CUtlSymbol ) ); |
|
CUtlRBTree< CUtlSymbol > englishunducked( 0, 0, DefLessFunc( CUtlSymbol ) ); |
|
|
|
int fromoffset = Q_strlen( fromdir ) + 1; |
|
int tooffset = Q_strlen( todir ) + 1; |
|
|
|
c = english.Count(); |
|
for ( i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol& sym = english[ i ]; |
|
char fn[ 512 ]; |
|
Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) ); |
|
|
|
if ( !( i % 1000 ) ) |
|
{ |
|
vprint( 1, "analyzed %i / %i (%.1f %%)\n", i, c, 100.0f * (float)i/(float)c ); |
|
} |
|
|
|
bool ducked = IsWavFileDucked( fn ); |
|
|
|
CUtlSymbol croppedSym = g_Analysis.symbols.AddString( &fn[ fromoffset ] ); |
|
|
|
if ( ducked ) |
|
{ |
|
englishducked.Insert( croppedSym ); |
|
} |
|
else |
|
{ |
|
englishunducked.Insert( croppedSym ); |
|
} |
|
} |
|
|
|
|
|
int updated = 0; |
|
|
|
// Now walk the localized tree and sync it to the english version |
|
c = localized.Count(); |
|
for ( i = 0; i < c; ++i ) |
|
{ |
|
CUtlSymbol& sym = localized[ i ]; |
|
char fn[ 512 ]; |
|
Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) ); |
|
|
|
bool ducked = IsWavFileDucked( fn ); |
|
|
|
CUtlSymbol croppedSym = g_Analysis.symbols.AddString( &fn[ tooffset ] ); |
|
|
|
bool inenglishducked = englishducked.Find( croppedSym ) != englishducked.InvalidIndex() ? true : false; |
|
bool inenglishunducked = englishunducked.Find( croppedSym ) != englishunducked.InvalidIndex() ? true : false; |
|
|
|
if ( !( i % 100 ) ) |
|
{ |
|
vprint( 1, "sync'd %i / %i (%.1f %%)\n", i, c, 100.0f * (float)i/(float)c ); |
|
} |
|
|
|
if ( ducked && inenglishducked ) |
|
continue; |
|
if ( !ducked && inenglishunducked ) |
|
continue; |
|
|
|
if ( !inenglishducked && !inenglishunducked ) |
|
{ |
|
vprint( 0, "Warning: %s is in localized tree, missing from english tree!!\n", fn ); |
|
continue; |
|
} |
|
|
|
Assert( inenglishducked ^ inenglishunducked ); |
|
|
|
SetWavFileDucking( fn, inenglishducked ); |
|
++updated; |
|
} |
|
|
|
vprint( 0, "finished, updated %i / %i (%.1f %%) localized .wavs\n", updated, c, 100.0f * (float)updated/(float)c ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// The application object |
|
//----------------------------------------------------------------------------- |
|
class CLocalizationCheckApp : public CTier3SteamApp |
|
{ |
|
typedef CTier3SteamApp BaseClass; |
|
|
|
public: |
|
// Methods of IApplication |
|
virtual bool Create(); |
|
virtual bool PreInit( ); |
|
virtual int Main(); |
|
virtual void PostShutdown( ); |
|
virtual void Destroy() {} |
|
}; |
|
|
|
DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CLocalizationCheckApp ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// The application object |
|
//----------------------------------------------------------------------------- |
|
bool CLocalizationCheckApp::Create() |
|
{ |
|
SpewOutputFunc( SpewFunc ); |
|
SpewActivate( "localization_check", 2 ); |
|
|
|
AppSystemInfo_t appSystems[] = |
|
{ |
|
{ "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, |
|
{ "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION }, |
|
{ "", "" } // Required to terminate the list |
|
}; |
|
|
|
return AddSystems( appSystems ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Init, shutdown |
|
//----------------------------------------------------------------------------- |
|
bool CLocalizationCheckApp::PreInit( ) |
|
{ |
|
if ( !BaseClass::PreInit() ) |
|
return false; |
|
|
|
g_pFileSystem = filesystem = g_pFullFileSystem; |
|
if ( !g_pFullFileSystem || !g_pSoundEmitterSystem || !g_pVGuiLocalize ) |
|
{ |
|
Error( "Unable to load required library interface!\n" ); |
|
return false; |
|
} |
|
|
|
char workingdir[ 256 ]; |
|
workingdir[0] = 0; |
|
Q_getwd( workingdir, sizeof( workingdir ) ); |
|
|
|
// If they didn't specify -game on the command line, use VPROJECT. |
|
if ( !SetupSearchPaths( workingdir, false, true ) ) |
|
{ |
|
Warning( "Unable to set up the file system!\n" ); |
|
return false; |
|
} |
|
|
|
// work out of the root directory (same as the reslists) |
|
g_pFullFileSystem->AddSearchPath(".", "root"); |
|
|
|
return true; |
|
} |
|
|
|
|
|
void CLocalizationCheckApp::PostShutdown( ) |
|
{ |
|
g_pFileSystem = filesystem = NULL; |
|
BaseClass::PostShutdown(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : argc - |
|
// argv[] - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CLocalizationCheckApp::Main() |
|
{ |
|
char language[ 256 ]; |
|
memset( language, 0, sizeof( language ) ); |
|
|
|
bool extractenglish = false; |
|
bool forceducking = false; |
|
|
|
int iArg = 1; |
|
int argc = CommandLine()->ParmCount(); |
|
for (; iArg<argc ; iArg++) |
|
{ |
|
char const *pArg = CommandLine()->GetParm( iArg ); |
|
if ( pArg[ 0 ] == '-' ) |
|
{ |
|
switch( pArg[ 1 ] ) |
|
{ |
|
case 'p': |
|
extractenglish = true; |
|
break; |
|
case 'v': |
|
verbose = true; |
|
break; |
|
case 'r': |
|
regenerate = true; |
|
break; |
|
case 'q': |
|
regenerate_quiet = true; |
|
break; |
|
case 'u': |
|
generate_usage = true; |
|
break; |
|
case 'b': |
|
nuke = true; |
|
break; |
|
case 's': |
|
checkscriptsounds = true; |
|
break; |
|
case 'c': |
|
build_cc = true; |
|
break; |
|
case 'x': |
|
build_script = true; |
|
break; |
|
case 'w': |
|
wavcheck = true; |
|
Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) ); |
|
iArg++; |
|
break; |
|
case 'e': |
|
extractphonemes = true; |
|
Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) ); |
|
iArg++; |
|
break; |
|
case 'l': |
|
if ( !Q_stricmp( &pArg[1], "loop" ) ) |
|
{ |
|
checkforloops = true; |
|
Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) ); |
|
iArg++; |
|
} |
|
else |
|
{ |
|
uselogfile = true; |
|
} |
|
break; |
|
case 'f': |
|
if ( !Q_stricmp( pArg, "-forceduck" )) |
|
{ |
|
forceducking = true; |
|
break; |
|
} |
|
forceextract = true; |
|
break; |
|
case 'd': |
|
checkfordups = true; |
|
break; |
|
case 'i': |
|
{ |
|
importcaptions = true; |
|
Q_strncpy( importfile, CommandLine()->GetParm( iArg + 1 ), sizeof( importfile ) ); |
|
iArg++; |
|
} |
|
break; |
|
case 'm': |
|
{ |
|
makecopybatch = true; |
|
Q_strncpy( fromdir, CommandLine()->GetParm( iArg + 1 ), sizeof( fromdir ) ); |
|
Q_strncpy( todir, CommandLine()->GetParm( iArg + 2 ), sizeof( todir ) ); |
|
iArg += 2; |
|
} |
|
break; |
|
case 'a': |
|
{ |
|
syncducking = true; |
|
Q_strncpy( fromdir, CommandLine()->GetParm( iArg + 1 ), sizeof( fromdir ) ); |
|
Q_strncpy( todir, CommandLine()->GetParm( iArg + 2 ), sizeof( todir ) ); |
|
iArg += 2; |
|
} |
|
break; |
|
default: |
|
printusage(); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( argc < 2 || (iArg != argc ) ) |
|
{ |
|
PrintHeader(); |
|
printusage(); |
|
} |
|
|
|
Q_strncpy( language, CommandLine()->GetParm( argc - 1 ), sizeof( language ) ); |
|
|
|
// If it's english, turn off checks. |
|
if ( !Q_stricmp( language, "english" ) ) |
|
{ |
|
language[ 0 ] = 0; |
|
} |
|
|
|
if ( !forceducking && !checkforloops && !syncducking && !extractphonemes && !importcaptions && language[0] != 0 ) |
|
{ |
|
// See if it's a valid language |
|
int idx = CSentence::LanguageForName( language ); |
|
if ( idx == -1 ) |
|
{ |
|
vprint( 0, "\nSkipping language check, '%s' is not a valid language\n", language ); |
|
|
|
vprint( 0, "Valid Language Names:\n" ); |
|
for ( int j = 0; j < CC_NUM_LANGUAGES; ++j ) |
|
{ |
|
vprint( 2, "%s\n", CSentence::NameForLanguage( j ) ); |
|
} |
|
|
|
printusage(); |
|
} |
|
} |
|
|
|
CheckLogFile(); |
|
|
|
PrintHeader(); |
|
|
|
vprint( 0, " Looking for localization inconsistencies...\n" ); |
|
|
|
if ( !checkforloops&& !syncducking && !extractphonemes && !importcaptions && language[0] != 0 ) |
|
{ |
|
vprint( 0, "\nLanguage: %s\n", language ); |
|
} |
|
|
|
vprint( 0, "Initializing stub sound system\n" ); |
|
|
|
sound->Init(); |
|
|
|
vprint( 0, "Initializing localization database system\n" ); |
|
|
|
// Always start with english |
|
g_pVGuiLocalize->AddFile( "resource/closecaption_english.txt" ); |
|
|
|
// Todo add language specific file |
|
|
|
Q_FixSlashes( gamedir ); |
|
Q_strlower( gamedir ); |
|
|
|
char vcddir[ 512 ]; |
|
Q_snprintf( vcddir, sizeof( vcddir ), "%sscenes", gamedir ); |
|
char ccdir[ 512 ]; |
|
Q_snprintf( ccdir, sizeof( ccdir ), "%ssound/combined", gamedir ); |
|
|
|
vprint( 0, "game dir %s\nvcd dir %s\n\n", |
|
gamedir, |
|
vcddir ); |
|
|
|
Q_StripTrailingSlash( sounddir ); |
|
Q_StripTrailingSlash( vcddir ); |
|
|
|
// |
|
//ProcessMaterialsDirectory( vmtdir ); |
|
|
|
vprint( 0, "Initializing sound emitter system\n" ); |
|
g_pSoundEmitterSystem->ModInit(); |
|
|
|
vprint( 0, "Loaded %i sounds\n", g_pSoundEmitterSystem->GetSoundCount() ); |
|
|
|
if ( forceducking ) |
|
{ |
|
CUtlVector< CUtlSymbol > wavefiles; |
|
|
|
char workingdir[ 256 ]; |
|
workingdir[0] = 0; |
|
Q_getwd( workingdir, sizeof( workingdir ) ); |
|
|
|
BuildFileList( wavefiles, workingdir, ".wav" ); |
|
vprint( 0, "forcing ducking on %i .wav files in %s\n\n", wavefiles.Count(), workingdir ); |
|
for ( int i = 0; i < wavefiles.Count(); i++ ) |
|
{ |
|
CUtlSymbol& sym = wavefiles[ i ]; |
|
char fn[ 512 ]; |
|
Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) ); |
|
SetWavFileDucking( fn, true ); |
|
} |
|
} |
|
else |
|
{ |
|
vprint( 0, "Building list of .vcd files\n" ); |
|
CUtlVector< CUtlSymbol > vcdfiles; |
|
|
|
BuildFileList( vcdfiles, vcddir, ".vcd" ); |
|
vprint( 0, "found %i .vcd files\n\n", vcdfiles.Count() ); |
|
|
|
if ( extractenglish && !language[0] ) |
|
{ |
|
vprint( 0, "extractenglish: pulling raw english txt from file\n" ); |
|
ExtractEnglish(); |
|
} |
|
else if ( wavcheck ) |
|
{ |
|
vprint( 0, "wavcheck: building list of known .wav files\n" ); |
|
CUtlVector< CUtlSymbol > wavfiles; |
|
BuildFileList( wavfiles, va( "%ssound/%s", gamedir, sounddir ), ".wav" ); |
|
|
|
vprint( 0, "found %i .wav files\n\n", wavfiles.Count() ); |
|
|
|
WavCheck( wavfiles ); |
|
} |
|
else if ( makecopybatch ) |
|
{ |
|
vprint( 0, "makecopybatch: building list of known .wav files\n" ); |
|
CUtlVector< CUtlSymbol > wavfiles; |
|
BuildFileList( wavfiles, va( "%ssound/%s", gamedir, sounddir ), ".wav" ); |
|
|
|
vprint( 0, "found %i .wav files\n\n", wavfiles.Count() ); |
|
|
|
MakeBatchFile( wavfiles, fromdir, todir ); |
|
} |
|
else if ( extractphonemes ) |
|
{ |
|
vprint( 0, "extractphonemes: building list of known .wav files\n" ); |
|
CUtlVector< CUtlSymbol > wavfiles; |
|
BuildFileList( wavfiles, sounddir, ".wav" ); |
|
|
|
vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", wavfiles.Count(), sounddir ); |
|
|
|
ExtractPhonemes( wavfiles ); |
|
} |
|
else if ( checkforloops ) |
|
{ |
|
vprint( 0, "checkforloops: building list of known .wav files\n" ); |
|
CUtlVector< CUtlSymbol > wavfiles; |
|
BuildFileList( wavfiles, sounddir, ".wav" ); |
|
|
|
vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", wavfiles.Count(), sounddir ); |
|
|
|
CheckForLoops( wavfiles ); |
|
} |
|
else if ( importcaptions ) |
|
{ |
|
vprint( 0, "importcaptions: importing captions from '%s'\n", importfile ); |
|
ImportCaptions( importfile ); |
|
} |
|
else if ( checkfordups ) |
|
{ |
|
vprint( 0, "checkfordups: checking for duplicate captions\n" ); |
|
CheckDuplcatedText(); |
|
} |
|
else if ( syncducking ) |
|
{ |
|
vprint( 0, "syncducking: building list of known .wav files in\n %s\n %s\n", fromdir, todir ); |
|
CUtlVector< CUtlSymbol > englishfiles; |
|
BuildFileList( englishfiles, fromdir, ".wav" ); |
|
vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", englishfiles.Count(), fromdir ); |
|
|
|
CUtlVector< CUtlSymbol > localized_files; |
|
BuildFileList( localized_files, todir, ".wav" ); |
|
vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", localized_files.Count(), todir ); |
|
|
|
SyncDucking( englishfiles, localized_files ); |
|
} |
|
else |
|
{ |
|
|
|
CUtlVector< CUtlSymbol > vcdsinreslist; |
|
|
|
BuildVCDAndMapNameListsFromReslists( vcdsinreslist ); |
|
|
|
// Check for missing localization data for all speak events not marked CC_DISABLED |
|
CUtlRBTree< CUtlSymbol, int > referencedcaptionwaves( 0, 0, SymbolLessFunc ); |
|
|
|
vprint( 0, "\nValidating close caption tokens and combined .wav file checksums\n\n" ); |
|
|
|
|
|
CheckLocalizationEntries( vcdfiles, referencedcaptionwaves ); |
|
|
|
vprint( 0, "\nChecking for orphaned combined .wav files\n\n" ); |
|
|
|
CUtlVector< CUtlSymbol > combinedwavfiles; |
|
BuildFileList( combinedwavfiles, ccdir, ".wav" ); |
|
|
|
CheckForOrphanedCombinedWavs( combinedwavfiles, referencedcaptionwaves ); |
|
|
|
if ( language[0] != 0 ) |
|
{ |
|
vprint( 0, "\nChecking for missing or out of date localized combined .wav files\n\n" ); |
|
ValidateForeignLanguageWaves( language, combinedwavfiles ); |
|
} |
|
|
|
if ( generate_usage ) |
|
{ |
|
// Figure out which .vcds are unused |
|
CheckUnusedVcds( vcdsinreslist, vcdfiles ); |
|
} |
|
|
|
if ( checkscriptsounds ) |
|
{ |
|
CheckUnusedSounds(); |
|
} |
|
} |
|
} |
|
|
|
vprint( 0, "\nCleaning up...\n" ); |
|
|
|
g_pSoundEmitterSystem->ModShutdown(); |
|
|
|
// Unload localization system |
|
g_pVGuiLocalize->RemoveAll(); |
|
|
|
sound->Shutdown(); |
|
|
|
FileSystem_Term(); |
|
|
|
g_Analysis.symbols.RemoveAll(); |
|
|
|
return 0; |
|
}
|
|
|