From 9e416c360cd9f0b252ee7f57b6f1b53b22a5e768 Mon Sep 17 00:00:00 2001 From: Abs62 Date: Wed, 24 Apr 2013 20:01:44 +0400 Subject: [PATCH] Some more TTS from Timon Wong --- articleview.cc | 14 ++++++--- guids.c | 1 + sapi.hh | 1 + speechclient.hh | 6 ++-- speechclient_win.cc | 46 +++++++++++++-------------- speechhlp.cc | 44 ++++++++++++++++---------- sphelper.hh | 55 +++++++++++++++++++++++++-------- texttospeechsource.cc | 72 +++++++++++++++++++++---------------------- texttospeechsource.hh | 2 +- voiceengines.cc | 29 +++++++++-------- 10 files changed, 158 insertions(+), 112 deletions(-) diff --git a/articleview.cc b/articleview.cc index e6ddda86..7464e5a3 100644 --- a/articleview.cc +++ b/articleview.cc @@ -698,6 +698,11 @@ void ArticleView::linkHovered ( const QString & link, const QString & , const QS msg = tr( "Audio" ); } else + if ( url.scheme() == "gdtts" ) + { + msg = tr( "TTS Voice" ); + } + else if ( url.scheme() == "gdpicture" ) { msg = tr( "Picture" ); @@ -964,14 +969,13 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, { QString itemMd5Id = QString( QCryptographicHash::hash( i->id.toUtf8(), - QCryptographicHash::Md5 ).toHex()); + QCryptographicHash::Md5 ).toHex() ); - if ( itemMd5Id == md5Id ) { + if ( itemMd5Id == md5Id ) + { SpeechClient * speechClient = new SpeechClient( i->id, this ); connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) ); - if ( !speechClient->tell( text ) ) { - delete speechClient; - } + speechClient->tell( text ); break; } } diff --git a/guids.c b/guids.c index 14a7885a..57d374ae 100644 --- a/guids.c +++ b/guids.c @@ -10,6 +10,7 @@ DEFINE_GUID(IID_IUIAutomationTreeWalker, 0x4042c624, 0x389c, 0x4afc, 0xa6, 0x30 DEFINE_GUID(CLSID_SpVoice, 0x96749377, 0x3391, 0x11D2, 0x9e, 0xe3, 0x00, 0xc0, 0x4f, 0x79, 0x73, 0x96); DEFINE_GUID(IID_ISpVoice, 0x6C44DF74, 0x72B9, 0x4992, 0xa1, 0xec, 0xef, 0x99, 0x6e, 0x04, 0x22, 0xd4); +DEFINE_GUID(CLSID_SpObjectToken, 0xEF411752, 0x3736, 0x4CB4, 0x9c, 0x8c, 0x8e, 0xf4, 0xcc, 0xb5, 0x8e, 0xfe); DEFINE_GUID(IID_ISpObjectToken, 0x14056589, 0xE16C, 0x11D2, 0xbb, 0x90, 0x00, 0xc0, 0x4f, 0x8e, 0xe6, 0xc0); DEFINE_GUID(CLSID_SpObjectTokenCategory, 0xA910187F, 0x0C7A, 0x45AC, 0x92, 0xcc, 0x59, 0xed, 0xaf, 0xb7, 0x7b, 0x53); DEFINE_GUID(IID_ISpObjectTokenCategory, 0x2D3D3845, 0x39AF, 0x4850, 0xbb, 0xf9, 0x40, 0xb4, 0x97, 0x80, 0x01, 0x1d); diff --git a/sapi.hh b/sapi.hh index 8616ddaf..6d1bee2a 100644 --- a/sapi.hh +++ b/sapi.hh @@ -172,6 +172,7 @@ typedef enum SPRUNSTATE EXTERN_C const IID CLSID_SpVoice; EXTERN_C const IID IID_ISpVoice; EXTERN_C const IID IID_ISpObjectToken; +EXTERN_C const IID CLSID_SpObjectToken; EXTERN_C const IID IID_IEnumSpObjectTokens; EXTERN_C const IID IID_ISpEventSource; EXTERN_C const IID IID_ISpNotifySource; diff --git a/speechclient.hh b/speechclient.hh index 8a2803b5..2c744706 100644 --- a/speechclient.hh +++ b/speechclient.hh @@ -23,12 +23,12 @@ public: static Engines availableEngines(); const Engine & engine() const; - bool tell( QString const & text ) const; - bool say( QString const & text ) const; + bool tell( QString const & text ); + bool say( QString const & text ); signals: + void started( bool ok ); void finished(); - void finished(SpeechClient *client); protected: virtual void timerEvent( QTimerEvent * evt ); diff --git a/speechclient_win.cc b/speechclient_win.cc index 59ef9531..d202813c 100644 --- a/speechclient_win.cc +++ b/speechclient_win.cc @@ -1,15 +1,16 @@ #include "speechclient.hh" -#include #include #include "speechhlp.hh" +#include + struct SpeechClient::InternalData { InternalData( QString const & engineId ): waitingFinish( false ) { - sp = speechCreate( reinterpret_cast( engineId.utf16() ) ); + sp = speechCreate( engineId.toStdWString().c_str() ); } ~InternalData() @@ -19,27 +20,17 @@ struct SpeechClient::InternalData SpeechHelper sp; Engine engine; - typedef QPointer Ptr; - static QList ptrs; bool waitingFinish; }; -QList SpeechClient::InternalData::ptrs = - QList(); - SpeechClient::SpeechClient( QString const & engineId, QObject * parent ): QObject( parent ), internalData( new InternalData( engineId ) ) { - Engine engine; - engine.id = QString::fromWCharArray( speechEngineId( internalData->sp ) ); - engine.name = QString::fromWCharArray( speechEngineName( internalData->sp ) ); - internalData->ptrs.push_back( this ); } SpeechClient::~SpeechClient() { - internalData->ptrs.removeAll( this ); delete internalData; } @@ -48,7 +39,7 @@ static bool enumEngines( void * /* token */, const wchar_t * name, void * userData ) { - SpeechClient::Engines *pEngines = (SpeechClient::Engines *)userData; + SpeechClient::Engines * pEngines = ( SpeechClient::Engines * )userData; SpeechClient::Engine engine = { QString::fromWCharArray( id ), @@ -70,7 +61,7 @@ const SpeechClient::Engine & SpeechClient::engine() const return internalData->engine; } -bool SpeechClient::tell( QString const & text ) const +bool SpeechClient::tell( QString const & text ) { if ( !speechAvailable( internalData->sp ) ) return false; @@ -78,17 +69,27 @@ bool SpeechClient::tell( QString const & text ) const if ( internalData->waitingFinish ) return false; - internalData->waitingFinish = true; - const_cast( this )->startTimer( 50 ); - return speechTell( internalData->sp, reinterpret_cast( text.utf16() ) ); + bool ok = speechTell( internalData->sp, text.toStdWString().c_str() ); + emit started( ok ); + + if ( ok ) + { + internalData->waitingFinish = true; + startTimer( 50 ); + } + else + { + emit finished(); + } + return ok; } -bool SpeechClient::say( QString const & text ) const +bool SpeechClient::say( QString const & text ) { if ( !speechAvailable( internalData->sp ) ) return false; - return speechSay( internalData->sp, reinterpret_cast( text.utf16() ) ); + return speechSay( internalData->sp, text.toStdWString().c_str() ); } void SpeechClient::timerEvent( QTimerEvent * evt ) @@ -100,9 +101,8 @@ void SpeechClient::timerEvent( QTimerEvent * evt ) if ( speechTellFinished( internalData->sp ) ) { - killTimer( evt->timerId() ) ; - internalData->waitingFinish = false; - emit finished(); - emit finished( this ); + killTimer( evt->timerId() ) ; + internalData->waitingFinish = false; + emit finished(); } } diff --git a/speechhlp.cc b/speechhlp.cc index 1b8e1678..c4a52dba 100644 --- a/speechhlp.cc +++ b/speechhlp.cc @@ -1,5 +1,6 @@ #define WINVER 0x0500 // At least WinXP required #include +#include #include "speechhlp.hh" #include @@ -15,12 +16,13 @@ struct _SpeechHelper wstring engineName; bool willInvokeCoUninitialize; - _SpeechHelper() : willInvokeCoUninitialize(false) + _SpeechHelper() : + willInvokeCoUninitialize(false) { HRESULT hr; hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); willInvokeCoUninitialize = (hr != RPC_E_CHANGED_MODE); - CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&voice ); + CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&voice); } ~_SpeechHelper() @@ -33,25 +35,33 @@ struct _SpeechHelper } }; -static bool findByEngineName(void *token, const wchar_t *id, const wchar_t *name, void *userData) -{ - SpeechHelper sp = (SpeechHelper)userData; - if (sp->engineId == id) - { - sp->voice->SetVoice((ISpObjectToken *)token); - sp->engineName = name; - return false; - } - - return true; -} - SpeechHelper speechCreate(const wchar_t *engineId) { SpeechHelper sp = new _SpeechHelper(); + HRESULT hr; + ISpObjectToken * spToken; + + hr = SpGetTokenFromId(engineId, &spToken); + + if (SUCCEEDED(hr)) + { + if (SUCCEEDED(hr) && sp->voice) + { + sp->voice->SetVoice(spToken); + + WCHAR * engineName = NULL; + SpGetDescription( spToken, &engineName ); + sp->engineId = engineId; + if (engineName) + { + sp->engineName = engineName; + CoTaskMemFree(engineName); + } + } + + spToken->Release(); + } - sp->engineId = engineId; - speechEnumerateAvailableEngines(findByEngineName, sp); return sp; } diff --git a/sphelper.hh b/sphelper.hh index ffa93e04..0588eaed 100644 --- a/sphelper.hh +++ b/sphelper.hh @@ -28,17 +28,41 @@ inline void SpHexFromUlong(WCHAR * psz, ULONG ul) } } +inline HRESULT SpGetTokenFromId( + const WCHAR * pszTokenId, + ISpObjectToken ** ppToken, + BOOL fCreateIfNotExist = FALSE) +{ + HRESULT hr; + ISpObjectToken * cpToken; + hr = CoCreateInstance(CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, + IID_ISpObjectToken, (void**)&cpToken); + + if (SUCCEEDED(hr)) + { + hr = cpToken->SetId(NULL, pszTokenId, fCreateIfNotExist); + if (SUCCEEDED(hr)) + { + *ppToken = cpToken; + } + else + cpToken->Release(); + } + + return hr; +} + inline HRESULT SpGetCategoryFromId( const WCHAR * pszCategoryId, ISpObjectTokenCategory ** ppCategory, BOOL fCreateIfNotExist = FALSE) { HRESULT hr; - + ISpObjectTokenCategory * cpTokenCategory; - hr = CoCreateInstance(CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, + hr = CoCreateInstance(CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, IID_ISpObjectTokenCategory, (void**)&cpTokenCategory ); - + if (SUCCEEDED(hr)) { hr = cpTokenCategory->SetId(pszCategoryId, fCreateIfNotExist); @@ -49,21 +73,21 @@ inline HRESULT SpGetCategoryFromId( else cpTokenCategory->Release(); } - + return hr; } HRESULT SpEnumTokens( - const WCHAR * pszCategoryId, - const WCHAR * pszReqAttribs, - const WCHAR * pszOptAttribs, + const WCHAR * pszCategoryId, + const WCHAR * pszReqAttribs, + const WCHAR * pszOptAttribs, IEnumSpObjectTokens ** ppEnum) { HRESULT hr = S_OK; - + ISpObjectTokenCategory * cpCategory; hr = SpGetCategoryFromId(pszCategoryId, &cpCategory); - + if (SUCCEEDED(hr)) { hr = cpCategory->EnumTokens( @@ -72,7 +96,7 @@ HRESULT SpEnumTokens( ppEnum); cpCategory->Release(); } - + return hr; } @@ -80,6 +104,8 @@ inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescrip { WCHAR szLangId[10]; HRESULT hr = S_OK; + +#if _SAPI_VER >= 0x053 WCHAR* pRegKeyPath = 0; WCHAR* pszTemp = 0; HKEY Handle = NULL; @@ -101,7 +127,7 @@ inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescrip HMODULE hmodAdvapi32Dll = NULL; typedef HRESULT (WINAPI* LPFN_RegLoadMUIStringW)(HKEY, LPCWSTR, LPWSTR, DWORD, LPDWORD, DWORD, LPCWSTR); LPFN_RegLoadMUIStringW pfnRegLoadMUIStringW = NULL; - + // Delay bind with RegLoadMUIStringW since this function is not supported on previous versions of advapi32.dll // RegLoadMUIStringW is supported only on advapi32.dll that ships with Windows Vista and above // Calling RegLoadMUIStringW directly makes the loader try to resolve the function reference at load time which breaks, @@ -121,7 +147,7 @@ inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescrip { hr = HRESULT_FROM_WIN32(ERROR_DLL_NOT_FOUND); } - + if (SUCCEEDED(hr)) { hr = pObjToken->GetId(&pszTemp); @@ -151,7 +177,7 @@ inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescrip { lErrorCode = ERROR_BAD_ARGUMENTS; } - + // Use MUI RegLoadMUIStringW API to load the localized string if(ERROR_SUCCESS == lErrorCode) { @@ -190,6 +216,9 @@ inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescrip // If running on OSes released before Windows Vista query the localized string from the registry // If RegLoadMUIStringW failed to retrieved the localized Engine name retrieve the localized string from the fallback (Default) attribute +#else + hr = E_FAIL; +#endif // _SAPI_VER >= 0x053 if (FAILED(hr)) { // Free memory allocated above if necessary diff --git a/texttospeechsource.cc b/texttospeechsource.cc index 0dab6553..94fecded 100644 --- a/texttospeechsource.cc +++ b/texttospeechsource.cc @@ -32,7 +32,7 @@ void TextToSpeechSource::on_addVoiceEngine_clicked() if ( ui.availableVoiceEngines->count() == 0 ) { QMessageBox::information( this, tr( "No TTS voice available" ), - tr( "Cannot find available TTS voice.
" + tr( "Cannot find availble TTS voice.
" "Please make sure that at least one TTS engine installed on your computer already." ) ); return; } @@ -54,8 +54,9 @@ void TextToSpeechSource::on_removeVoiceEngine_clicked() voiceEnginesModel.getCurrentVoiceEngines()[ current.row() ].name ), QMessageBox::Ok, QMessageBox::Cancel ) == QMessageBox::Ok ) - - voiceEnginesModel.removeVoiceEngine( current.row() ); + { + voiceEnginesModel.removeVoiceEngine( current.row() ); + } } void TextToSpeechSource::on_previewVoice_clicked() @@ -66,23 +67,17 @@ void TextToSpeechSource::on_previewVoice_clicked() QString engineId = ui.availableVoiceEngines->itemData( idx ).toString(); QString text = ui.previewText->text(); - SpeechClient *speechClient = new SpeechClient( engineId, this ); + SpeechClient * speechClient = new SpeechClient( engineId, this ); - ui.previewVoice->setEnabled( false ); - connect( speechClient, SIGNAL( finished( SpeechClient * ) ), - this, SLOT( previewVoiceFinished( SpeechClient * ) ) ); - if ( !speechClient->tell( text ) ) { - ui.previewVoice->setEnabled( true ); - delete speechClient; - } + connect( speechClient, SIGNAL( started( bool ) ), ui.previewVoice, SLOT( setDisabled( bool ) ) ); + connect( speechClient, SIGNAL( finished() ), this, SLOT( previewVoiceFinished() ) ); + connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) ); + speechClient->tell( text ); } -void TextToSpeechSource::previewVoiceFinished( SpeechClient * speechClient ) +void TextToSpeechSource::previewVoiceFinished() { - ui.previewVoice->setEnabled( true ); - - if (speechClient) - speechClient->deleteLater(); + ui.previewVoice->setDisabled( false ); } void TextToSpeechSource::fitSelectedVoiceEnginesColumns() @@ -100,7 +95,7 @@ VoiceEnginesModel::VoiceEnginesModel( QWidget * parent, void VoiceEnginesModel::removeVoiceEngine( int index ) { - beginRemoveRows( QModelIndex(), index, index ); + beginRemoveRows( QModelIndex(), index, index ); voiceEngines.erase( voiceEngines.begin() + index ); endRemoveRows(); } @@ -111,9 +106,9 @@ void VoiceEnginesModel::addNewVoiceEngine( QString const & id, QString const & n return; Config::VoiceEngine v; - v.enabled = true; - v.id = id; - v.name = name; + v.enabled = true; + v.id = id; + v.name = name; beginInsertRows( QModelIndex(), voiceEngines.size(), voiceEngines.size() ); voiceEngines.push_back( v ); @@ -136,14 +131,15 @@ Qt::ItemFlags VoiceEnginesModel::flags( QModelIndex const & index ) const if ( index.isValid() ) { - switch ( index.column() ) { - case kColumnEnabled: - result |= Qt::ItemIsUserCheckable; - break; - case kColumnEngineName: - case kColumnIcon: - result |= Qt::ItemIsEditable; - break; + switch ( index.column() ) + { + case kColumnEnabled: + result |= Qt::ItemIsUserCheckable; + break; + case kColumnEngineName: + case kColumnIcon: + result |= Qt::ItemIsEditable; + break; } } @@ -168,7 +164,7 @@ QVariant VoiceEnginesModel::headerData( int section, Qt::Orientation /*orientati { if ( role == Qt::DisplayRole ) { - switch( section ) + switch ( section ) { case kColumnEnabled: return tr( "Enabled" ); @@ -211,7 +207,7 @@ QVariant VoiceEnginesModel::data( QModelIndex const & index, int role ) const } bool VoiceEnginesModel::setData( QModelIndex const & index, const QVariant & value, - int role ) + int role ) { if ( index.row() >= voiceEngines.size() ) return false; @@ -276,8 +272,10 @@ void VoiceEngineEditor::setEngineId( QString const & engineId ) { // Find index for the id int idx = -1; - for ( int i = 0; i < count(); ++i ) { - if ( engineId == itemData(i).toString() ) { + for ( int i = 0; i < count(); ++i ) + { + if ( engineId == itemData( i ).toString() ) + { idx = i; break; } @@ -285,22 +283,22 @@ void VoiceEngineEditor::setEngineId( QString const & engineId ) setCurrentIndex( idx ); } -VoiceEngineItemDelegate::VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject *parent ) : +VoiceEngineItemDelegate::VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject * parent ) : QStyledItemDelegate( parent ), engines( engines ) { } -QWidget * VoiceEngineItemDelegate::createEditor( QWidget *parent, +QWidget * VoiceEngineItemDelegate::createEditor( QWidget * parent, QStyleOptionViewItem const & option, QModelIndex const & index ) const { - if( index.column() != VoiceEnginesModel::kColumnEngineName ) + if ( index.column() != VoiceEnginesModel::kColumnEngineName ) return QStyledItemDelegate::createEditor( parent, option, index ); return new VoiceEngineEditor( engines, parent ); } -void VoiceEngineItemDelegate::setEditorData( QWidget *uncastedEditor, const QModelIndex & index ) const +void VoiceEngineItemDelegate::setEditorData( QWidget * uncastedEditor, const QModelIndex & index ) const { VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor ); if ( !editor ) @@ -312,7 +310,7 @@ void VoiceEngineItemDelegate::setEditorData( QWidget *uncastedEditor, const QMod editor->setEngineId( engineId ); } -void VoiceEngineItemDelegate::setModelData( QWidget *uncastedEditor, QAbstractItemModel * model, +void VoiceEngineItemDelegate::setModelData( QWidget * uncastedEditor, QAbstractItemModel * model, const QModelIndex & index ) const { VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor ); diff --git a/texttospeechsource.hh b/texttospeechsource.hh index 1edca845..42eaf8e2 100644 --- a/texttospeechsource.hh +++ b/texttospeechsource.hh @@ -93,7 +93,7 @@ private slots: void on_addVoiceEngine_clicked(); void on_removeVoiceEngine_clicked(); void on_previewVoice_clicked(); - void previewVoiceFinished( SpeechClient * speechClient ); + void previewVoiceFinished(); private: Ui::TextToSpeechSource ui; diff --git a/voiceengines.cc b/voiceengines.cc index f23afee2..e12adb3a 100644 --- a/voiceengines.cc +++ b/voiceengines.cc @@ -7,21 +7,24 @@ #include "utf8.hh" #include "wstring_qt.hh" +#include +#include + #include #include #include +#include -namespace VoiceEngines { +namespace VoiceEngines +{ using namespace Dictionary; +using std::string; +using std::map; -namespace StringConv { - -inline QString toMd5( QByteArray const & b ) +inline string toMd5( QByteArray const & b ) { - return QString( QCryptographicHash::hash( b, QCryptographicHash::Md5 ).toHex() ); -} - + return string( QCryptographicHash::hash( b, QCryptographicHash::Md5 ).toHex().constData() ); } class VoiceEnginesDictionary: public Dictionary::Class @@ -34,7 +37,7 @@ public: VoiceEnginesDictionary( Config::VoiceEngine const & voiceEngine ): Dictionary::Class( - StringConv::toMd5( voiceEngine.id.toUtf8() ).toStdString(), + toMd5( voiceEngine.id.toUtf8() ), vector< string >() ), voiceEngine( voiceEngine ) { @@ -67,10 +70,10 @@ protected: }; sptr< WordSearchRequest > VoiceEnginesDictionary::prefixMatch( wstring const & /*word*/, - unsigned long /*maxResults*/ ) + unsigned long /*maxResults*/ ) throw( std::exception ) { - WordSearchRequestInstant *sr = new WordSearchRequestInstant(); + WordSearchRequestInstant * sr = new WordSearchRequestInstant(); sr->setUncertain( true ); return sr; } @@ -102,7 +105,7 @@ sptr< Dictionary::DataRequest > VoiceEnginesDictionary::getArticle( sptr< DataRequestInstant > ret = new DataRequestInstant( true ); ret->getData().resize( result.size() ); - memcpy( &(ret->getData().front()), result.data(), result.size() ); + memcpy( &( ret->getData().front() ), result.data(), result.size() ); return ret; } @@ -123,12 +126,12 @@ void VoiceEnginesDictionary::loadIcon() throw() } vector< sptr< Dictionary::Class > > makeDictionaries( - Config::VoiceEngines const & voiceEngines ) + Config::VoiceEngines const & voiceEngines ) throw( std::exception ) { vector< sptr< Dictionary::Class > > result; - for( Config::VoiceEngines::const_iterator i = voiceEngines.begin(); i != voiceEngines.end(); ++i ) + for ( Config::VoiceEngines::const_iterator i = voiceEngines.begin(); i != voiceEngines.end(); ++i ) { if ( i->enabled ) result.push_back( new VoiceEnginesDictionary( *i ) );