summaryrefslogtreecommitdiff
path: root/utils/sfmgen
diff options
context:
space:
mode:
Diffstat (limited to 'utils/sfmgen')
-rw-r--r--utils/sfmgen/sfmgen.cpp974
-rw-r--r--utils/sfmgen/sfmgen.vpc43
2 files changed, 1017 insertions, 0 deletions
diff --git a/utils/sfmgen/sfmgen.cpp b/utils/sfmgen/sfmgen.cpp
new file mode 100644
index 0000000..7bb95ba
--- /dev/null
+++ b/utils/sfmgen/sfmgen.cpp
@@ -0,0 +1,974 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// The copyright to the contents herein is the property of Valve, L.L.C.
+// The contents may be used and/or copied only with the written permission of
+// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
+// the agreement/contract under which the contents have been supplied.
+//
+// $Header: $
+// $NoKeywords: $
+//
+//=============================================================================
+
+
+// Valve includes
+#include "appframework/tier3app.h"
+#include "datamodel/idatamodel.h"
+#include "filesystem.h"
+#include "filesystem_init.h"
+#include "icommandline.h"
+#include "materialsystem/imaterialsystem.h"
+#include "istudiorender.h"
+#include "mathlib/mathlib.h"
+#include "vstdlib/vstdlib.h"
+#include "vstdlib/iprocessutils.h"
+#include "tier2/p4helpers.h"
+#include "p4lib/ip4.h"
+#include "tier1/utlbuffer.h"
+#include "tier1/utlstringmap.h"
+#include "sfmobjects/sfmsession.h"
+#include "datacache/idatacache.h"
+#include "datacache/imdlcache.h"
+#include "vphysics_interface.h"
+#include "movieobjects/dmeclip.h"
+#include "movieobjects/dmetrack.h"
+#include "movieobjects/dmetrackgroup.h"
+#include "movieobjects/dmegamemodel.h"
+#include "movieobjects/dmesound.h"
+#include "movieobjects/dmedag.h"
+#include "movieobjects/dmechannel.h"
+#include "movieobjects/dmeanimationset.h"
+#include "studio.h"
+#include "sfmobjects/sfmanimationsetutils.h"
+#include "sfmobjects/flexcontrolbuilder.h"
+#include "sfmobjects/sfmphonemeextractor.h"
+#include "sfmobjects/exportfacialanimation.h"
+#include "soundemittersystem/isoundemittersystembase.h"
+#include "phonemeconverter.h"
+#include "tier2/riff.h"
+#include "tier2/soundutils.h"
+#include "soundchars.h"
+#include <ctype.h>
+
+#ifdef _DEBUG
+#include <windows.h>
+#undef GetCurrentDirectory
+#endif
+
+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 )
+ {
+ 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 StdIOReadBinary io_in;
+static StdIOWriteBinary io_out;
+
+#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 SceneManager_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 SceneManager_LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io )
+{
+ 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() && !found )
+ {
+ switch ( walk.ChunkName() )
+ {
+ case WAVE_VALVEDATA:
+ {
+ found = true;
+ SceneManager_ParseSentence( sentence, walk );
+ }
+ break;
+ }
+ walk.ChunkNext();
+ }
+
+ return found;
+}
+
+bool SceneManager_LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence )
+{
+ return SceneManager_LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in );
+}
+
+
+//-----------------------------------------------------------------------------
+// Standard spew functions
+//-----------------------------------------------------------------------------
+static SpewRetval_t SpewStdout( SpewType_t spewType, char const *pMsg )
+{
+ if ( !pMsg )
+ return SPEW_CONTINUE;
+
+#ifdef _DEBUG
+ OutputDebugString( pMsg );
+#endif
+
+ printf( pMsg );
+ fflush( stdout );
+
+ return ( spewType == SPEW_ASSERT ) ? SPEW_DEBUGGER : SPEW_CONTINUE;
+}
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+class CSFMGenApp : public CTier3DmSteamApp
+{
+ typedef CTier3DmSteamApp BaseClass;
+
+public:
+ // Methods of IApplication
+ virtual bool Create();
+ virtual bool PreInit( );
+ virtual int Main();
+ virtual void Destroy() {}
+
+ void PrintHelp( );
+
+private:
+ struct SFMInfo_t
+ {
+ CUtlString m_GameSound;
+ CUtlString m_Text;
+ CUtlString m_DMXFileName;
+ };
+
+ struct SFMGenInfo_t
+ {
+ const char *m_pCSVFile;
+ const char *m_pModelName;
+ const char *m_pOutputDirectory;
+ const char *m_pExportFacDirectory;
+ bool m_bWritePhonemesInWavs;
+ bool m_bUsePhonemesInWavs;
+ float m_flSampleRateHz;
+ float m_flSampleFilterSize;
+ bool m_bGenerateSFMFiles;
+ bool m_bExtractPhonemeFromWavsForMp3;
+ };
+
+ enum TokenRetVal_t
+ {
+ TOKEN_COMMA = 0,
+ TOKEN_RETURN,
+ TOKEN_COMMENT,
+ TOKEN_EOF,
+ };
+
+ // Parses the excel .csv file
+ TokenRetVal_t ParseToken( CUtlBuffer &buf, char *pToken, int nMaxTokenLen );
+
+ // Parses the excel file
+ void ParseCSVFile( CUtlBuffer &buf, CUtlVector< SFMInfo_t > &infoList, int nSkipLines );
+
+ // The application object
+ void GenerateSFMFiles( SFMGenInfo_t &info );
+
+ // Makes the names unique
+ void UniqueifyNames( CUtlVector< SFMInfo_t > &infoList );
+
+ // Creates a single sfm file
+ void GenerateSFMFile( const SFMGenInfo_t &sfmGenInfo, const SFMInfo_t &info, studiohdr_t *pStudioHdr, const char *pOutputDirectory, const char *pExportFacPath );
+
+ // Saves an SFM file
+ void SaveSFMFile( CDmElement *pRoot, const char *pRelativeScenePath, const char *pFileName );
+
+ // Generates a sound clip for the game sound
+ CDmeSoundClip *CreateSoundClip( CDmeFilmClip *pShot, const char *pAnimationSetName, const char *pGameSound, studiohdr_t *pStudioHdr, CDmeGameSound **ppGameSound );
+
+ // Builds the list of all controls in the animation set contributing to facial animation
+ void BuildFacialControlList( CDmeFilmClip *pShot, CDmeAnimationSet *pAnimationSet, CUtlVector< LogPreview_t > &list );
+};
+
+
+DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CSFMGenApp );
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+bool CSFMGenApp::Create()
+{
+ SpewOutputFunc( SpewStdout );
+
+ AppSystemInfo_t appSystems[] =
+ {
+ { "vstdlib.dll", PROCESS_UTILS_INTERFACE_VERSION },
+ { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION },
+ { "p4lib.dll", P4_INTERFACE_VERSION },
+ { "datacache.dll", DATACACHE_INTERFACE_VERSION },
+ { "datacache.dll", MDLCACHE_INTERFACE_VERSION },
+ { "studiorender.dll", STUDIO_RENDER_INTERFACE_VERSION },
+ { "vphysics.dll", VPHYSICS_INTERFACE_VERSION },
+ { "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION },
+ { "", "" } // Required to terminate the list
+ };
+
+ AddSystems( appSystems );
+
+ IMaterialSystem *pMaterialSystem = reinterpret_cast< IMaterialSystem * >( FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION ) );
+ if ( !pMaterialSystem )
+ {
+ Error( "// ERROR: Unable to connect to material system interface!\n" );
+ return false;
+ }
+
+ pMaterialSystem->SetShaderAPI( "shaderapiempty.dll" );
+ SetupDefaultFlexController();
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CSFMGenApp::PreInit( )
+{
+ MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
+
+ if ( !BaseClass::PreInit() )
+ return false;
+
+ if ( !g_pFullFileSystem || !g_pDataModel )
+ {
+ Error( "// ERROR: sfmgen is missing a required interface!\n" );
+ return false;
+ }
+
+ // Add paths...
+ if ( !SetupSearchPaths( NULL, false, true ) )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Print help
+//-----------------------------------------------------------------------------
+void CSFMGenApp::PrintHelp( )
+{
+ Msg( "Usage: sfmgen -i <in .csv file> -m <.mdl relative path> -o <output dir>\n" );
+ Msg( " [-f <fac output dir>] [-p] [-w] [-r <sample rate in hz>] [-s <sample filter size>]\n" );
+ Msg( " [-nop4] [-vproject <path to gameinfo.txt>]\n" );
+ Msg( "\t-i\t: Source .CSV file indicating game sound names, text for phoneme extraction, output sfm file names.\n" );
+ Msg( "\t-m\t: Indicates the path of the .mdl file under game/mod/models/ to use in the sfm files.\n" );
+ Msg( "\t-o\t: Indicates output directory to place generated files in. Required if Generating SFM files.\n" );
+ Msg( "\t-f\t: [Optional] Indicates that facial files should be created in the specified fac output dir.\n" );
+ Msg( "\t-p\t: [Optional] Indicates the extracted phonemes should be written into the wav file.\n" );
+ Msg( "\t-w\t: [Optional] Indicates the phonemes should be read from the wav file. Cannot also have -p specified.\n" );
+ Msg( "\t-r\t: [Optional] Specifies the phoneme extraction sample rate (default = 20)\n" );
+ Msg( "\t-s\t: [Optional] Specifies the phoneme extraction sample filter size (default = 0.08)\n" );
+ Msg( "\t-nop4\t: Disables auto perforce checkout/add.\n" );
+ Msg( "\t-nosfm\t: Disables generating of SFM files (dmx).\n" );
+ Msg( "\t-vproject\t: Specifies path to a gameinfo.txt file (which mod to build for).\n" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Parses a token from the excel .csv file
+//-----------------------------------------------------------------------------
+CSFMGenApp::TokenRetVal_t CSFMGenApp::ParseToken( CUtlBuffer &buf, char *pToken, int nMaxTokenLen )
+{
+ *pToken = 0;
+
+ if ( !buf.IsValid() )
+ return TOKEN_EOF;
+
+ int nLen = 0;
+ char c = buf.GetChar();
+ bool bIsQuoted = false;
+ bool bIsComment = false;
+ while ( true )
+ {
+ if ( c == '#' )
+ {
+ bIsComment = true;
+ }
+
+ if ( c == '"' )
+ {
+ bIsQuoted = !bIsQuoted;
+ }
+ else if ( ( c == ',' || c == '\n' ) && !bIsQuoted )
+ {
+ pToken[nLen] = 0;
+ if ( bIsComment )
+ return TOKEN_COMMENT;
+
+ return ( c == '\n' ) ? TOKEN_RETURN : TOKEN_COMMA;
+ }
+
+ if ( nLen < nMaxTokenLen - 1 )
+ {
+ if ( c != '"' )
+ {
+ pToken[nLen++] = c;
+ }
+ }
+ if ( !buf.IsValid() )
+ {
+ pToken[nLen] = 0;
+ return TOKEN_EOF;
+ }
+ c = buf.GetChar();
+ }
+
+ // Should never get here
+ return TOKEN_EOF;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parses the excel file
+//-----------------------------------------------------------------------------
+void CSFMGenApp::ParseCSVFile( CUtlBuffer &buf, CUtlVector< SFMInfo_t > &infoList, int nSkipLines )
+{
+ char pToken[512];
+ for( int nLine = 0; buf.IsValid(); ++nLine )
+ {
+ SFMInfo_t info;
+ TokenRetVal_t nTokenRetVal = ParseToken( buf, pToken, sizeof(pToken) );
+ if ( nTokenRetVal == TOKEN_EOF )
+ return;
+
+ if ( nTokenRetVal == TOKEN_COMMENT )
+ {
+ continue;
+ }
+
+ if ( nTokenRetVal != TOKEN_COMMA )
+ {
+ Warning( "sfmgen: Missing Column at line %d\n", nLine );
+ continue;
+ }
+
+ info.m_DMXFileName = pToken;
+
+ if ( ParseToken( buf, pToken, sizeof(pToken) ) != TOKEN_COMMA )
+ {
+ Warning( "sfmgen: Missing Column at line %d\n", nLine );
+ continue;
+ }
+
+ if ( ParseToken( buf, pToken, sizeof(pToken) ) != TOKEN_COMMA )
+ {
+ Warning( "sfmgen: Missing Column at line %d\n", nLine );
+ continue;
+ }
+
+ info.m_GameSound = pToken;
+
+ nTokenRetVal = ParseToken( buf, pToken, sizeof(pToken) );
+ info.m_Text = pToken;
+ if ( nTokenRetVal == TOKEN_COMMENT )
+ {
+ Warning( "sfmgen: No VO Transcript on line %d - Skipping \n", nLine );
+ continue;
+ }
+
+ // extract the VO Transcript
+ while ( nTokenRetVal == TOKEN_COMMA )
+ {
+ nTokenRetVal = ParseToken( buf, pToken, sizeof(pToken) );
+ }
+
+ if ( nSkipLines > nLine )
+ continue;
+
+ infoList.AddToTail( info );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Makes the names unique
+//-----------------------------------------------------------------------------
+void CSFMGenApp::UniqueifyNames( CUtlVector< SFMInfo_t > &infoList )
+{
+ CUtlStringMap<int> foundNames;
+ int nCount = infoList.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ const char *pName = infoList[i].m_DMXFileName;
+
+ int nFoundCount;
+ if ( !foundNames.Defined( pName ) )
+ {
+ nFoundCount = foundNames[ pName ] = 1;
+ }
+ else
+ {
+ nFoundCount = ++foundNames[ pName ];
+ }
+
+ // Remove whitespace, change to lowercase, uniqueify.
+ int nLen = Q_strlen(pName);
+ int nDigits = (int)log10( (float)nFoundCount ) + 1;
+ char *pTemp = (char*)_alloca( nLen + nDigits + 1 );
+ for ( int j = 0; j < nLen; ++j )
+ {
+ if ( isspace( pName[j] ) )
+ {
+ pTemp[j] = '_';
+ }
+ else
+ {
+ pTemp[j] = tolower( pName[j] );
+ }
+ }
+ if ( nFoundCount > 1 )
+ {
+ Q_snprintf( &pTemp[nLen], nDigits + 1, "%d", nFoundCount );
+ nLen += nDigits;
+ }
+ pTemp[nLen] = 0;
+ infoList[i].m_DMXFileName = pTemp;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Saves an SFM file
+//-----------------------------------------------------------------------------
+void CSFMGenApp::SaveSFMFile( CDmElement *pRoot, const char *pOutputDir, const char *pFileName )
+{
+ // Construct actual file name from the model name + dmx file name
+ char pFullPathBuf[MAX_PATH];
+ Q_snprintf( pFullPathBuf, sizeof(pFullPathBuf), "%s/%s.dmx", pOutputDir, pFileName );
+ const char *pFullPath = pFullPathBuf;
+
+ CP4AutoEditAddFile checkout( pFullPath );
+
+ if ( !g_pDataModel->SaveToFile( pFullPath, NULL, g_pDataModel->GetDefaultEncoding( "sfm_session" ), "sfm_session", pRoot ) )
+ {
+ Warning( "sfmgen: Unable to write file %s\n", pFullPath );
+ return;
+ }
+
+ Msg( "sfmgen: Wrote file %s\n", pFullPath );
+}
+
+
+//-----------------------------------------------------------------------------
+// Generates a sound clip for the game sound
+//-----------------------------------------------------------------------------
+CDmeSoundClip *CSFMGenApp::CreateSoundClip( CDmeFilmClip *pShot, const char *pAnimationSetName, const char *pGameSound, studiohdr_t *pStudioHdr, CDmeGameSound **ppGameSound )
+{
+ *ppGameSound = NULL;
+
+ CDmeTrackGroup *pTrackGroup = pShot->FindOrAddTrackGroup( "audio" );
+ CDmeTrack *pTrack = pTrackGroup->FindOrAddTrack( pAnimationSetName, DMECLIP_SOUND );
+
+ // Get the gender for the model
+ gender_t actorGender = g_pSoundEmitterSystem->GetActorGender( pStudioHdr->pszName() );
+
+ // Get the wav file for the gamesound.
+ CSoundParameters params;
+ if ( !g_pSoundEmitterSystem->GetParametersForSound( pGameSound, params, actorGender ) )
+ {
+ Warning( "Unable to determine .wav file for gamesound %s!\n", pGameSound );
+ return NULL;
+ }
+
+ // Get the sound duration
+ char pFullPath[MAX_PATH];
+ char pRelativePath[MAX_PATH];
+ const char *pWavFile = PSkipSoundChars( params.soundname );
+ Q_ComposeFileName( "sound", pWavFile, pRelativePath, sizeof(pRelativePath) );
+ g_pFullFileSystem->RelativePathToFullPath( pRelativePath, "GAME", pFullPath, sizeof(pFullPath) );
+ DmeTime_t duration( GetWavSoundDuration( pFullPath ) );
+
+ CDmeGameSound *pDmeSound = CreateElement< CDmeGameSound >( pGameSound, pTrack->GetFileId() );
+ Assert( pDmeSound );
+ pDmeSound->m_GameSoundName = pGameSound;
+ pDmeSound->m_SoundName = params.soundname;
+ pDmeSound->m_Volume = params.volume;
+ pDmeSound->m_Level = params.soundlevel;
+ pDmeSound->m_Pitch = params.pitch;
+ pDmeSound->m_IsStatic = true;
+ pDmeSound->m_Channel = CHAN_STATIC;
+ pDmeSound->m_Flags = 0;
+ pDmeSound->m_Origin = vec3_origin;
+ pDmeSound->m_Direction = vec3_origin;
+
+ CDmeSoundClip *pSubClip = CreateElement< CDmeSoundClip >( pGameSound, pTrack->GetFileId() );
+ pSubClip->m_Sound = pDmeSound;
+ pSubClip->SetStartTime( DMETIME_ZERO );
+ pSubClip->SetTimeOffset( DmeTime_t( -params.delay_msec / 1000.0f ) );
+ pSubClip->SetDuration( duration );
+
+ pTrack->AddClip( pSubClip );
+ *ppGameSound = pDmeSound;
+
+ return pSubClip;
+}
+
+
+//-----------------------------------------------------------------------------
+// Builds the list of all controls in the animation set contributing to facial animation
+//-----------------------------------------------------------------------------
+void CSFMGenApp::BuildFacialControlList( CDmeFilmClip *pShot, CDmeAnimationSet *pAnimationSet, CUtlVector< LogPreview_t > &list )
+{
+ const CDmaElementArray< CDmElement > &controls = pAnimationSet->GetControls();
+ int nCount = controls.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ CDmElement *pControl = controls[i];
+ if ( !pControl || pControl->GetValue<bool>( "transform" ) )
+ continue;
+
+ LogPreview_t preview;
+ preview.m_hControl = pControl;
+ preview.m_hShot = pShot;
+ preview.m_bActiveLog = preview.m_bSelected = false;
+
+ if ( pControl->GetValue< bool >( "combo" ) )
+ {
+ preview.m_hChannels[ LOG_PREVIEW_VALUE ] = pControl->GetValueElement< CDmeChannel >( "valuechannel" );
+ preview.m_hChannels[ LOG_PREVIEW_BALANCE ] = pControl->GetValueElement< CDmeChannel >( "balancechannel" );
+ }
+ else
+ {
+ preview.m_hChannels[ LOG_PREVIEW_VALUE ] = pControl->GetValueElement< CDmeChannel >( "channel" );
+ preview.m_hChannels[ LOG_PREVIEW_BALANCE ] = NULL;
+ }
+
+ if ( pControl->GetValue< bool >( "multi" ) )
+ {
+ preview.m_hChannels[ LOG_PREVIEW_MULTILEVEL ] = pControl->GetValueElement< CDmeChannel >( "multilevelchannel" );
+ }
+ else
+ {
+ preview.m_hChannels[ LOG_PREVIEW_MULTILEVEL ] = NULL;
+ }
+
+ preview.m_hOwner = preview.m_hChannels[ LOG_PREVIEW_VALUE ]->FindOwnerClipForChannel( pShot );
+
+ list.AddToTail( preview );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates a single sfm file
+//-----------------------------------------------------------------------------
+void CSFMGenApp::GenerateSFMFile( const SFMGenInfo_t& sfmGenInfo, const SFMInfo_t &info,
+ studiohdr_t *pStudioHdr, const char *pOutputDirectory, const char *pExportFacPath )
+{
+ CSFMSession session;
+ session.Init();
+
+ char pAnimationSetName[256];
+ Q_FileBase( pStudioHdr->pszName(), pAnimationSetName, sizeof(pAnimationSetName) );
+
+ // Set the file id( necessary for phoneme extraction)
+ DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( pAnimationSetName );
+ session.Root()->SetFileId( fileid, TD_DEEP );
+ g_pModelPresetGroupMgr->AssociatePresetsWithFile( fileid );
+
+ // Get the shot.
+ CDmeFilmClip *pMovie = session.Root()->GetValueElement<CDmeFilmClip>( "activeClip" );
+ CDmeFilmClip* pShot = CastElement< CDmeFilmClip >( pMovie->GetFilmTrack()->GetClip( 0 ) );
+
+ // Create a camera for the shot
+ DmeCameraParams_t cameraParams( pAnimationSetName, vec3_origin, QAngle( 0, 180, 0 ) );
+ cameraParams.origin.x = pStudioHdr->hull_max.x + 20;
+ cameraParams.origin.z = Lerp( 0.95f, pStudioHdr->hull_min.z, pStudioHdr->hull_max.z );
+ cameraParams.fov = 75.0f;
+ CDmeCamera* pCamera = session.CreateCamera( cameraParams );
+ pShot->SetCamera( pCamera );
+
+ // Create a game model for the studio hdr
+ CDmeGameModel *pGameModel = session.CreateEditorGameModel( pStudioHdr, vec3_origin, Quaternion( 0, 0, 0, 1 ) );
+
+ // Create a scene for the shot
+ CDmeDag *pScene = session.FindOrCreateScene( pShot, pAnimationSetName );
+ pScene->AddChild( pGameModel );
+
+ // Create a sound clip
+ CDmeGameSound *pGameSound;
+ CDmeSoundClip *pSoundClip = CreateSoundClip( pMovie, pAnimationSetName, info.m_GameSound, pStudioHdr, &pGameSound );
+
+ if ( pSoundClip )
+ {
+ pShot->SetDuration( pSoundClip->GetDuration() );
+ pMovie->SetDuration( pSoundClip->GetDuration() );
+
+ // Create an animation set
+ CDmeAnimationSet *pAnimationSet = CreateAnimationSet( pMovie, pShot, pGameModel, pAnimationSetName, 0, false );
+
+ // Extract phonemes
+ CExtractInfo extractInfo;
+ extractInfo.m_pClip = pSoundClip;
+ extractInfo.m_pSound = pGameSound;
+ extractInfo.m_sHintText = info.m_Text;
+ extractInfo.m_bUseSentence = sfmGenInfo.m_bUsePhonemesInWavs;
+
+ ExtractDesc_t extractDesc;
+ extractDesc.m_nExtractType = EXTRACT_WIPE_CLIP;
+ extractDesc.m_pMovie = pMovie;
+ extractDesc.m_pShot = pShot;
+ extractDesc.m_pSet = pAnimationSet;
+ extractDesc.m_flSampleRateHz = sfmGenInfo.m_flSampleRateHz;
+ extractDesc.m_flSampleFilterSize = sfmGenInfo.m_flSampleFilterSize;
+ extractDesc.m_WorkList.AddToTail( extractInfo );
+ BuildFacialControlList( pShot, pAnimationSet, extractDesc.m_ControlList );
+ sfm_phonemeextractor->Extract( SPEECH_API_LIPSINC, extractDesc, sfmGenInfo.m_bWritePhonemesInWavs );
+
+ CExtractInfo &results = extractDesc.m_WorkList[ 0 ];
+ CDmElement *pExtractionSettings = pGameSound->FindOrAddPhonemeExtractionSettings();
+ pExtractionSettings->SetValue< float >( "duration", results.m_flDuration );
+ // Store off phonemes
+ if ( !pExtractionSettings->HasAttribute( "results" ) )
+ {
+ pExtractionSettings->AddAttribute( "results", AT_ELEMENT_ARRAY );
+ }
+
+ CDmrElementArray< CDmElement > resultsArray( pExtractionSettings, "results" );
+ resultsArray.RemoveAll();
+ for ( int i = 0; i < results.m_ApplyTags.Count(); ++i )
+ {
+ CBasePhonemeTag *tag = results.m_ApplyTags[ i ];
+ CDmElement *tagElement = CreateElement< CDmElement >( ConvertPhoneme( tag->GetPhonemeCode() ), pGameSound->GetFileId() );
+ tagElement->SetValue< float >( "start", tag->GetStartTime() );
+ tagElement->SetValue< float >( "end", tag->GetEndTime() );
+ resultsArray.AddToTail( tagElement );
+ }
+
+ if ( sfmGenInfo.m_bGenerateSFMFiles && pExportFacPath )
+ {
+ char pFACFileName[MAX_PATH];
+ Q_ComposeFileName( pExportFacPath, info.m_DMXFileName, pFACFileName, sizeof(pFACFileName) );
+ Q_SetExtension( pFACFileName, ".dmx", sizeof(pFACFileName) );
+ ExportFacialAnimation( pFACFileName, pMovie, pShot, pAnimationSet );
+ }
+ }
+
+ if ( sfmGenInfo.m_bGenerateSFMFiles )
+ {
+ SaveSFMFile( session.Root(), pOutputDirectory, info.m_DMXFileName );
+ }
+
+ session.Shutdown();
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes a full directory
+//-----------------------------------------------------------------------------
+static void ComputeFullPath( const char *pRelativeDir, char *pFullPath, int nBufLen )
+{
+ if ( !Q_IsAbsolutePath( pRelativeDir ) )
+ {
+ char pDir[MAX_PATH];
+ if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
+ {
+ Q_ComposeFileName( pDir, pRelativeDir, pFullPath, nBufLen );
+ }
+ }
+ else
+ {
+ Q_strncpy( pFullPath, pRelativeDir, nBufLen );
+ }
+
+ Q_StripTrailingSlash( pFullPath );
+
+ // Ensure the output directory exists
+ g_pFullFileSystem->CreateDirHierarchy( pFullPath );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+void CSFMGenApp::GenerateSFMFiles( SFMGenInfo_t& info )
+{
+ char pRelativeModelPath[MAX_PATH];
+ Q_ComposeFileName( "models", info.m_pModelName, pRelativeModelPath, sizeof(pRelativeModelPath) );
+ Q_SetExtension( pRelativeModelPath, ".mdl", sizeof(pRelativeModelPath) );
+ MDLHandle_t hMDL = g_pMDLCache->FindMDL( pRelativeModelPath );
+ if ( hMDL == MDLHANDLE_INVALID )
+ {
+ Warning( "sfmgen: Model %s doesn't exist!\n", pRelativeModelPath );
+ return;
+ }
+
+ studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( hMDL );
+ if ( !pStudioHdr || g_pMDLCache->IsErrorModel( hMDL ) )
+ {
+ Warning( "sfmgen: Model %s doesn't exist!\n", pRelativeModelPath );
+ return;
+ }
+
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+ if ( !g_pFullFileSystem->ReadFile( info.m_pCSVFile, NULL, buf ) )
+ {
+ Warning( "sfmgen: Unable to load file %s\n", info.m_pCSVFile );
+ return;
+ }
+
+ CUtlVector< SFMInfo_t > infoList;
+ ParseCSVFile( buf, infoList, 1 );
+
+ int nCount = infoList.Count();
+ if ( nCount == 0 )
+ {
+ Warning( "sfmgen: no files to create!\n" );
+ return;
+ }
+
+ UniqueifyNames( infoList );
+
+ // Construct full path to the output directories
+ char pFullPath[MAX_PATH];
+ char pFullFacPathBuf[MAX_PATH];
+ const char *pExportFacPath = NULL;
+ if ( info.m_pExportFacDirectory )
+ {
+ ComputeFullPath( info.m_pExportFacDirectory, pFullFacPathBuf, sizeof( pFullFacPathBuf ) );
+ pExportFacPath = pFullFacPathBuf;
+ }
+
+ if ( info.m_pOutputDirectory )
+ {
+ ComputeFullPath( info.m_pOutputDirectory, pFullPath, sizeof( pFullPath ) );
+ }
+ else
+ {
+ pFullPath[0] = '\0';
+ }
+
+
+ if (!info.m_bExtractPhonemeFromWavsForMp3)
+ {
+ for (int i = 0; i < nCount; ++i)
+ {
+ GenerateSFMFile(info, infoList[i], pStudioHdr, pFullPath, pExportFacPath);
+ }
+ }
+ else
+ {
+ // Extra Phoneme Data from .wav files for .mp3
+ CUtlBuffer bufOutput(0, 0, CUtlBuffer::TEXT_BUFFER); // Phoneme outout
+ CUtlBuffer bufFilenames(0, 0, CUtlBuffer::TEXT_BUFFER); // Affected Files for reference (files with Phoneme data)
+ for (int i = 0; i < nCount; ++i)
+ {
+ CSentence sSentence;
+ if (SceneManager_LoadSentenceFromWavFile(infoList[i].m_GameSound, sSentence))
+ {
+ // Save Phoneme Data
+ CUtlString strTemp(infoList[i].m_DMXFileName);
+ // //bufOutput.Printf( "\n" );
+ bufOutput.Printf(strTemp.Replace(".wav", ".mp3").Get());
+ bufOutput.Printf("\n{\n");
+ sSentence.SaveToBuffer(bufOutput);
+ bufOutput.Printf("}\n\n");
+
+ // Save file name
+ bufFilenames.Printf(infoList[i].m_GameSound);
+ bufFilenames.Printf("\n");
+
+ Msg("%s\n", infoList[i].m_GameSound.Get());
+ }
+ }
+
+ // output
+ //if ( sfm_phonemeextractor->GetSentence( pGameSound, sSentence ) )
+ {
+ // write this to a text file
+ FileHandle_t fh = g_pFullFileSystem->Open("tf_PhonemeData.txt", "wb");
+ if (fh)
+ {
+ g_pFullFileSystem->Write(bufOutput.Base(), bufOutput.TellPut(), fh);
+ g_pFullFileSystem->Close(fh);
+ }
+ }
+
+ {
+ // write this to a text file
+ FileHandle_t fh = g_pFullFileSystem->Open("tf_PhonemeFiles.txt", "wb");
+ if (fh)
+ {
+ g_pFullFileSystem->Write(bufFilenames.Base(), bufFilenames.TellPut(), fh);
+ g_pFullFileSystem->Close(fh);
+ }
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+int CSFMGenApp::Main()
+{
+ g_pDataModel->SetUndoEnabled( false );
+
+ // This bit of hackery allows us to access files on the harddrive
+ g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD );
+
+ if ( CommandLine()->CheckParm( "-h" ) || CommandLine()->CheckParm( "-help" ) )
+ {
+ PrintHelp();
+ return 0;
+ }
+
+ // Do Perforce Stuff
+ if ( CommandLine()->FindParm( "-nop4" ) )
+ {
+ g_p4factory->SetDummyMode( true );
+ }
+
+ g_p4factory->SetOpenFileChangeList( "Automatically Generated SFM files" );
+
+ SFMGenInfo_t info;
+ info.m_pCSVFile = CommandLine()->ParmValue( "-i" );
+ info.m_pModelName = CommandLine()->ParmValue( "-m" );
+ info.m_pOutputDirectory = CommandLine()->ParmValue( "-o" );
+ info.m_pExportFacDirectory = CommandLine()->ParmValue( "-f" );
+ info.m_bWritePhonemesInWavs = CommandLine()->FindParm( "-p" ) != 0;
+ info.m_bUsePhonemesInWavs = CommandLine()->FindParm( "-w" ) != 0;
+ info.m_flSampleRateHz = CommandLine()->ParmValue( "-r", 20.0f );
+ info.m_flSampleFilterSize = CommandLine()->ParmValue( "-s", 0.08f );
+ info.m_bGenerateSFMFiles = CommandLine()->FindParm( "-nosfm" ) == 0;
+ info.m_bExtractPhonemeFromWavsForMp3 = CommandLine()->FindParm("-mp3");
+
+ if ( !info.m_pCSVFile || !info.m_pModelName )
+ {
+ PrintHelp();
+ return 0;
+ }
+
+ if ( !info.m_pOutputDirectory && info.m_bGenerateSFMFiles )
+ {
+ PrintHelp();
+ return 0;
+ }
+
+ if ( info.m_bUsePhonemesInWavs && info.m_bWritePhonemesInWavs )
+ {
+ Warning( "Cannot simultaneously read the phones from wavs and also write them into the wavs!\n" );
+ return 0;
+ }
+
+ if ( info.m_pExportFacDirectory && !Q_stricmp( info.m_pExportFacDirectory, info.m_pOutputDirectory ) )
+ {
+ Warning( "Must specify different directories for output + facial export!\n" );
+ return 0;
+ }
+
+ g_pSoundEmitterSystem->ModInit();
+ sfm_phonemeextractor->Init();
+ GenerateSFMFiles( info );
+ sfm_phonemeextractor->Shutdown();
+ g_pSoundEmitterSystem->ModShutdown();
+ return -1;
+} \ No newline at end of file
diff --git a/utils/sfmgen/sfmgen.vpc b/utils/sfmgen/sfmgen.vpc
new file mode 100644
index 0000000..d9d74f9
--- /dev/null
+++ b/utils/sfmgen/sfmgen.vpc
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// SFMGEN.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR "..\.."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc"
+
+$Project "sfmgen"
+{
+ $Folder "Source Files"
+ {
+ $File "sfmgen.cpp"
+ }
+
+ $Folder "External"
+ {
+ $File "$SRCDIR\public\movieobjects/movieobjects.cpp"
+ $File "$SRCDIR\public\interpolatortypes.cpp"
+ $File "$SRCDIR\public\sentence.cpp"
+ }
+
+ $Folder "External Header Files"
+ {
+ $File "$SRCDIR\public\sentence.h"
+ $File "$SRCDIR\public\interpolatortypes.h"
+ }
+
+ $Folder "Link Libraries"
+ {
+ $Lib appframework
+ $Lib datamodel
+ $Lib dmserializers
+ $Lib mathlib
+ $Lib tier2
+ $Lib tier3
+ $Lib movieobjects
+ $Lib sfmobjects
+ }
+}