📄 phonemeeditor_closecaption.cpp
字号:
#include <stdio.h>
#include <math.h>
#include "hlfaceposer.h"
#include "PhonemeEditor.h"
#include "PhonemeEditorColors.h"
#include "snd_audio_source.h"
#include "snd_wave_source.h"
#include "ifaceposersound.h"
#include "choreowidgetdrawhelper.h"
#include "mxBitmapButton.h"
#include "phonemeproperties.h"
#include "riff.h"
#include "cmdlib.h"
#include "scriplib.h"
#include "StudioModel.h"
#include "expressions.h"
#include "expclass.h"
#include "InputProperties.h"
#include "PhonemeExtractor.h"
#include "PhonemeConverter.h"
#include "choreoevent.h"
#include "choreoscene.h"
#include "ChoreoView.h"
#include "FileSystem.h"
#include "UtlBuffer.h"
#include "AudioWaveOutput.h"
#include "StudioModel.h"
#include "viewerSettings.h"
#include "ControlPanel.h"
#include "faceposer_models.h"
#include "vstdlib/strtools.h"
#include "tabwindow.h"
#include "MatSysWin.h"
#include "iclosecaptionmanager.h"
#include "EditPhrase.h"
#define PLENTY_OF_TIME 99999.9
#define MINIMUM_PHRASE_GAP 0.02f
#define DEFAULT_PHRASE_LENGTH 1.0f
// Close captioning
void PhonemeEditor::CloseCaption_EditInsertFirstPhrase( void )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
if ( m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ) != 0 )
{
Con_Printf( "Can't insert first phrase into sentence, already has data\n" );
return;
}
CEditPhraseParams params;
memset( ¶ms, 0, sizeof( params ) );
strcpy( params.m_szDialogTitle, "Edit Phrase" );
strcpy( params.m_szPrompt, "Current Phrase:" );
ConvertANSIToUnicode( m_Tags.GetText(), params.m_szInputText, sizeof( params.m_szInputText ) );
params.m_nLeft = -1;
params.m_nTop = -1;
params.m_bPositionDialog = false;
if ( !EditPhrase( ¶ms ) )
{
SetFocus( (HWND)getHandle() );
return;
}
if ( wcslen( params.m_szInputText ) <= 0 )
{
return;
}
float start, end;
m_Tags.GetEstimatedTimes( start, end );
SetDirty( true );
PushUndo();
CCloseCaptionPhrase *phrase = new CCloseCaptionPhrase( params.m_szInputText );
phrase->SetSelected( true );
phrase->SetStartTime( start );
phrase->SetEndTime( end );
m_Tags.AddCloseCaptionPhrase( CC_ENGLISH, phrase );
PushRedo();
// Add it
redraw();
}
void PhonemeEditor::CloseCaption_EditPhrase( CCloseCaptionPhrase *phrase )
{
CEditPhraseParams params;
memset( ¶ms, 0, sizeof( params ) );
strcpy( params.m_szDialogTitle, "Edit Phrase" );
strcpy( params.m_szPrompt, "Current Phrase:" );
wcscpy( params.m_szInputText, phrase->GetStream() );
params.m_nLeft = -1;
params.m_nTop = -1;
params.m_bPositionDialog = true;
if ( params.m_bPositionDialog )
{
RECT rcPhrase;
CloseCaption_GetPhraseRect( phrase, rcPhrase );
// Convert to screen coords
POINT pt;
pt.x = rcPhrase.left;
pt.y = rcPhrase.top;
ClientToScreen( (HWND)getHandle(), &pt );
params.m_nLeft = pt.x;
params.m_nTop = pt.y;
}
if ( !EditPhrase( ¶ms ) )
{
SetFocus( (HWND)getHandle() );
return;
}
SetFocus( (HWND)getHandle() );
SetDirty( true );
PushUndo();
phrase->SetStream( params.m_szInputText );
PushRedo();
redraw();
}
void PhonemeEditor::CloseCaption_EditPhrase( void )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
CCloseCaptionPhrase *pPhrase = CloseCaption_GetClickedPhrase();
if ( !pPhrase )
return;
CloseCaption_EditPhrase( pPhrase );
}
void PhonemeEditor::CloseCaption_EditInsertBefore( void )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
CCloseCaptionPhrase *phrase = CloseCaption_GetSelectedPhrase();
if ( !phrase )
return;
float gap = CloseCaption_GetTimeGapToNextPhrase( false, phrase );
if ( gap < MINIMUM_PHRASE_GAP )
{
Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap );
return;
}
// Don't have really long phrases
gap = min( gap, DEFAULT_PHRASE_LENGTH );
int clicked = CloseCaption_IndexOfPhrase( phrase );
Assert( clicked >= 0 );
CEditPhraseParams params;
memset( ¶ms, 0, sizeof( params ) );
strcpy( params.m_szDialogTitle, "Insert Phrase" );
strcpy( params.m_szPrompt, "Phrase:" );
wcscpy( params.m_szInputText, L"" );
params.m_nLeft = -1;
params.m_nTop = -1;
params.m_bPositionDialog = true;
if ( params.m_bPositionDialog )
{
RECT rcPhrase;
CloseCaption_GetPhraseRect( phrase, rcPhrase );
// Convert to screen coords
POINT pt;
pt.x = rcPhrase.left;
pt.y = rcPhrase.top;
ClientToScreen( (HWND)getHandle(), &pt );
params.m_nLeft = pt.x;
params.m_nTop = pt.y;
}
int iret = EditPhrase( ¶ms );
SetFocus( (HWND)getHandle() );
if ( !iret )
{
return;
}
if ( wcslen( params.m_szInputText ) <= 0 )
{
return;
}
SetDirty( true );
PushUndo();
CCloseCaptionPhrase *newphrase = new CCloseCaptionPhrase( params.m_szInputText );
newphrase->SetEndTime( phrase->GetStartTime() );
newphrase->SetStartTime( phrase->GetStartTime() - gap );
newphrase->SetSelected( true );
phrase->SetSelected( false );
m_Tags.m_CloseCaption[ CC_ENGLISH ].InsertBefore( clicked, newphrase );
PushRedo();
// Add it
redraw();
}
void PhonemeEditor::CloseCaption_EditInsertAfter( void )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
CCloseCaptionPhrase *phrase = CloseCaption_GetSelectedPhrase();
if ( !phrase )
return;
float gap = CloseCaption_GetTimeGapToNextPhrase( true, phrase );
if ( gap < MINIMUM_PHRASE_GAP )
{
Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap );
return;
}
// Don't have really long phrases
gap = min( gap, DEFAULT_PHRASE_LENGTH );
int clicked = CloseCaption_IndexOfPhrase( phrase );
Assert( clicked >= 0 );
CEditPhraseParams params;
memset( ¶ms, 0, sizeof( params ) );
strcpy( params.m_szDialogTitle, "Insert Phrase" );
strcpy( params.m_szPrompt, "Phrase:" );
wcscpy( params.m_szInputText, L"" );
params.m_nLeft = -1;
params.m_nTop = -1;
params.m_bPositionDialog = true;
if ( params.m_bPositionDialog )
{
RECT rcPhrase;
CloseCaption_GetPhraseRect( phrase, rcPhrase );
// Convert to screen coords
POINT pt;
pt.x = rcPhrase.left;
pt.y = rcPhrase.top;
ClientToScreen( (HWND)getHandle(), &pt );
params.m_nLeft = pt.x;
params.m_nTop = pt.y;
}
int iret = EditPhrase( ¶ms );
SetFocus( (HWND)getHandle() );
if ( !iret )
{
return;
}
if ( wcslen( params.m_szInputText ) <= 0 )
{
return;
}
SetDirty( true );
PushUndo();
CCloseCaptionPhrase *newphrase = new CCloseCaptionPhrase( params.m_szInputText );
newphrase->SetEndTime( phrase->GetEndTime() + gap );
newphrase->SetStartTime( phrase->GetEndTime() );
newphrase->SetSelected( true );
phrase->SetSelected( false );
m_Tags.m_CloseCaption[ CC_ENGLISH ].InsertAfter( clicked, newphrase );
PushRedo();
// Add it
redraw();
}
void PhonemeEditor::CloseCaption_EditDelete( void )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
CountSelected();
if ( m_nSelectedPhraseCount < 1 )
return;
SetDirty( true );
PushUndo();
for ( int i = m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH )- 1; i >= 0; i-- )
{
CCloseCaptionPhrase *phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
if ( !phrase || !phrase->GetSelected() )
continue;
m_Tags.RemoveCloseCaptionPhrase( CC_ENGLISH, i );
}
PushRedo();
redraw();
}
void PhonemeEditor::CloseCaption_Select( bool forward )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
CountSelected();
if ( m_nSelectedPhraseCount != 1 )
return;
// Figure out it's phrase and index
CCloseCaptionPhrase *phrase = CloseCaption_GetSelectedPhrase();
if ( !phrase )
return;
int phraseNum = CloseCaption_IndexOfPhrase( phrase );
if ( phraseNum == -1 )
return;
if ( forward )
{
phraseNum++;
for ( ; phraseNum < m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ); phraseNum++ )
{
phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, phraseNum );
phrase->SetSelected( true );
}
}
else
{
phraseNum--;
for ( ; phraseNum >= 0; phraseNum-- )
{
phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, phraseNum );
phrase->SetSelected( true );
}
}
redraw();
}
char const *PhonemeEditor::CloseCaption_StreamToShortName( CCloseCaptionPhrase *phrase )
{
static char shortname[ 32 ];
char converted[ 1024 ];
ConvertUnicodeToANSI( phrase->GetStream(), converted, sizeof( converted ) );
Q_strncpy( shortname, converted, sizeof( shortname ) );
if ( strlen( shortname ) > 16 )
{
shortname[ 16 ] = 0;
strcat( shortname, "..." );
}
return shortname;
}
void PhonemeEditor::CloseCaption_ShowMenu( CCloseCaptionPhrase *phrase, int mx, int my, mxPopupMenu *pop )
{
CountSelected();
bool showmenu = false;
if ( !pop )
{
pop = new mxPopupMenu();
showmenu = true;
}
Assert( pop );
if ( m_nSelectedPhraseCount <= 0 )
{
if ( m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ) == 0 )
{
pop->add( va( "Add phrase" ), IDC_EDIT_CC_ADDFIRSTPHRASE );
pop->add( "Create default", IDC_EDIT_CC_DEFAULT_PHRASE );
}
}
else
{
pop->add( va( "Delete %s", m_nSelectedPhraseCount > 1 ? "phrases" : va( "'%s'", CloseCaption_StreamToShortName( phrase ) ) ), IDC_EDIT_CC_DELETEPHRASE );
if ( m_nSelectedPhraseCount == 1 )
{
int index = CloseCaption_IndexOfPhrase( phrase );
bool valid = false;
if ( index != -1 )
{
int token = CloseCaption_GetPhraseTokenUnderMouse( phrase, mx );
CloseCaption_SetClickedPhrase( index, token );
valid = true;
}
if ( valid )
{
pop->add( va( "Edit phrase '%s'...", CloseCaption_StreamToShortName( phrase ) ), IDC_EDIT_CC_PHRASE );
float nextGap = CloseCaption_GetTimeGapToNextPhrase( true, phrase );
float prevGap = CloseCaption_GetTimeGapToNextPhrase( false, phrase );
if ( nextGap > MINIMUM_PHRASE_GAP ||
prevGap > MINIMUM_PHRASE_GAP )
{
pop->addSeparator();
if ( prevGap > MINIMUM_PHRASE_GAP )
{
pop->add( va( "Insert phrase before '%s'...", CloseCaption_StreamToShortName( phrase ) ), IDC_EDIT_CC_INSERTPHRASEBEFORE );
}
if ( nextGap > MINIMUM_PHRASE_GAP )
{
pop->add( va( "Insert phrase after '%s'...", CloseCaption_StreamToShortName( phrase ) ), IDC_EDIT_CC_INSERTPHRASEAFTER );
}
}
pop->addSeparator();
pop->add( va( "Select all phrases after '%s'", CloseCaption_StreamToShortName( phrase ) ), IDC_SELECT_CC_PHRASESRIGHT );
pop->add( va( "Select all phrases before '%s'", CloseCaption_StreamToShortName( phrase ) ), IDC_SELECT_CC_PHRASESLEFT );
int count = phrase->CountTokens();
int clicked = CloseCaption_GetClickedPhraseToken();
if ( clicked >= 0 && clicked < count - 1 )
{
pop->add( va( "Split phrase '%s' after '%s'",
CloseCaption_StreamToShortName( phrase ),
phrase->GetToken( CloseCaption_GetClickedPhraseToken() ) ), IDC_EDIT_CC_SPLIT_PHRASE );
}
}
}
}
if ( CloseCaption_AreSelectedPhrasesContiguous() && m_nSelectedPhraseCount > 1 )
{
pop->addSeparator();
pop->add( va( "Merge phrases" ), IDC_EDIT_CC_MERGE_PHRASES );
pop->add( va( "Snap phrases" ), IDC_CC_SNAPPHRASES );
if ( m_nSelectedPhraseCount == 2 )
{
pop->add( va( "Separate phrases" ), IDC_CC_SEPARATEPHRASES );
}
}
if ( m_nSelectedPhraseCount > 0 )
{
pop->addSeparator();
pop->add( va( "Deselect all" ), IDC_DESELECT_CC_PHRASES );
}
if ( m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH )> 0 )
{
pop->addSeparator();
pop->add( va( "Select all" ), IDC_SELECT_CC_ALLPHRASES );
pop->add( va( "Cleanup phrases" ), IDC_CC_CLEANUP );
}
if ( showmenu )
{
pop->popup( this, mx, my );
}
}
void PhonemeEditor::ShowContextMenu_CloseCaption( int mx, int my )
{
CountSelected();
// Construct main
mxPopupMenu *pop = new mxPopupMenu();
if ( m_nSelectedPhraseCount > 0 )
{
CCloseCaptionPhrase *phrase = CloseCaption_GetSelectedPhrase();
if ( !phrase )
{
// find first one...
int c = m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH );
for ( int i = 0; i < c; i++ )
{
phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
if ( phrase->GetSelected() )
break;
}
if ( i >= c )
{
phrase = NULL;
}
}
if ( phrase )
{
CloseCaption_ShowMenu( phrase, mx, my, pop );
}
}
else
{
if ( m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ) > 0 )
{
pop->add( va( "Select all" ), IDC_SELECT_CC_ALLPHRASES );
}
}
if ( m_nUndoLevel != 0 || m_nUndoLevel != m_UndoStack.Size() )
{
pop->addSeparator();
if ( m_nUndoLevel != 0 )
{
pop->add( va( "Undo" ), IDC_UNDO );
}
if ( m_nUndoLevel != m_UndoStack.Size() )
{
pop->add( va( "Redo" ), IDC_REDO );
}
pop->add( va( "Clear Undo Info" ), IDC_CLEARUNDO );
}
pop->popup( this, mx, my );
}
bool PhonemeEditor::CloseCaption_IsMouseOverRow( int my )
{
if ( GetMode() != MODE_CLOSECAPTION )
return false;
RECT rc;
CloseCaption_GetTrayTopBottom( rc );
if ( my < rc.top )
return false;
if ( my > rc.bottom )
return false;
return true;
}
void PhonemeEditor::CloseCaption_FinishMove( int startx, int endx )
{
float clicktime = GetTimeForPixel( startx );
float endtime = GetTimeForPixel( endx );
// Find the phonemes who have the closest start/endtime to the starting click time
CCloseCaptionPhrase *current, *next;
if ( !CloseCaption_FindSpanningPhrases( clicktime, ¤t, &next ) )
{
return;
}
SetDirty( true );
PushUndo();
if ( current && !next )
{
// cap movement
current->SetEndTime( current->GetEndTime() + ( endtime - clicktime ) );
}
else if ( !current && next )
{
// cap movement
next->SetStartTime( next->GetStartTime() + ( endtime - clicktime ) );
}
else
{
// cap movement
endtime = min( endtime, next->GetEndTime() - 1.0f / GetPixelsPerSecond() );
endtime = max( endtime, current->GetStartTime() + 1.0f / GetPixelsPerSecond() );
current->SetEndTime( endtime );
next->SetStartTime( endtime );
}
CloseCaption_CleanupPhrases( false );
PushRedo();
redraw();
}
void PhonemeEditor::CloseCaption_FinishDrag( int startx, int endx )
{
float clicktime = GetTimeForPixel( startx );
float endtime = GetTimeForPixel( endx );
float dt = endtime - clicktime;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -