Some more TTS from Timon Wong

This commit is contained in:
Abs62 2013-04-24 20:01:44 +04:00
parent 9cc2c89087
commit 9e416c360c
10 changed files with 158 additions and 112 deletions

View file

@ -698,6 +698,11 @@ void ArticleView::linkHovered ( const QString & link, const QString & , const QS
msg = tr( "Audio" ); msg = tr( "Audio" );
} }
else else
if ( url.scheme() == "gdtts" )
{
msg = tr( "TTS Voice" );
}
else
if ( url.scheme() == "gdpicture" ) if ( url.scheme() == "gdpicture" )
{ {
msg = tr( "Picture" ); msg = tr( "Picture" );
@ -964,14 +969,13 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref,
{ {
QString itemMd5Id = QString( QCryptographicHash::hash( QString itemMd5Id = QString( QCryptographicHash::hash(
i->id.toUtf8(), i->id.toUtf8(),
QCryptographicHash::Md5 ).toHex()); QCryptographicHash::Md5 ).toHex() );
if ( itemMd5Id == md5Id ) { if ( itemMd5Id == md5Id )
{
SpeechClient * speechClient = new SpeechClient( i->id, this ); SpeechClient * speechClient = new SpeechClient( i->id, this );
connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) ); connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) );
if ( !speechClient->tell( text ) ) { speechClient->tell( text );
delete speechClient;
}
break; break;
} }
} }

View file

@ -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(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(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(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(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); DEFINE_GUID(IID_ISpObjectTokenCategory, 0x2D3D3845, 0x39AF, 0x4850, 0xbb, 0xf9, 0x40, 0xb4, 0x97, 0x80, 0x01, 0x1d);

View file

@ -172,6 +172,7 @@ typedef enum SPRUNSTATE
EXTERN_C const IID CLSID_SpVoice; EXTERN_C const IID CLSID_SpVoice;
EXTERN_C const IID IID_ISpVoice; EXTERN_C const IID IID_ISpVoice;
EXTERN_C const IID IID_ISpObjectToken; EXTERN_C const IID IID_ISpObjectToken;
EXTERN_C const IID CLSID_SpObjectToken;
EXTERN_C const IID IID_IEnumSpObjectTokens; EXTERN_C const IID IID_IEnumSpObjectTokens;
EXTERN_C const IID IID_ISpEventSource; EXTERN_C const IID IID_ISpEventSource;
EXTERN_C const IID IID_ISpNotifySource; EXTERN_C const IID IID_ISpNotifySource;

View file

@ -23,12 +23,12 @@ public:
static Engines availableEngines(); static Engines availableEngines();
const Engine & engine() const; const Engine & engine() const;
bool tell( QString const & text ) const; bool tell( QString const & text );
bool say( QString const & text ) const; bool say( QString const & text );
signals: signals:
void started( bool ok );
void finished(); void finished();
void finished(SpeechClient *client);
protected: protected:
virtual void timerEvent( QTimerEvent * evt ); virtual void timerEvent( QTimerEvent * evt );

View file

@ -1,15 +1,16 @@
#include "speechclient.hh" #include "speechclient.hh"
#include <QtCore>
#include <windows.h> #include <windows.h>
#include "speechhlp.hh" #include "speechhlp.hh"
#include <QtCore>
struct SpeechClient::InternalData struct SpeechClient::InternalData
{ {
InternalData( QString const & engineId ): InternalData( QString const & engineId ):
waitingFinish( false ) waitingFinish( false )
{ {
sp = speechCreate( reinterpret_cast<const wchar_t*>( engineId.utf16() ) ); sp = speechCreate( engineId.toStdWString().c_str() );
} }
~InternalData() ~InternalData()
@ -19,27 +20,17 @@ struct SpeechClient::InternalData
SpeechHelper sp; SpeechHelper sp;
Engine engine; Engine engine;
typedef QPointer<SpeechClient> Ptr;
static QList<Ptr> ptrs;
bool waitingFinish; bool waitingFinish;
}; };
QList<SpeechClient::InternalData::Ptr> SpeechClient::InternalData::ptrs =
QList<SpeechClient::InternalData::Ptr>();
SpeechClient::SpeechClient( QString const & engineId, QObject * parent ): SpeechClient::SpeechClient( QString const & engineId, QObject * parent ):
QObject( parent ), QObject( parent ),
internalData( new InternalData( engineId ) ) 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() SpeechClient::~SpeechClient()
{ {
internalData->ptrs.removeAll( this );
delete internalData; delete internalData;
} }
@ -48,7 +39,7 @@ static bool enumEngines( void * /* token */,
const wchar_t * name, const wchar_t * name,
void * userData ) void * userData )
{ {
SpeechClient::Engines *pEngines = (SpeechClient::Engines *)userData; SpeechClient::Engines * pEngines = ( SpeechClient::Engines * )userData;
SpeechClient::Engine engine = SpeechClient::Engine engine =
{ {
QString::fromWCharArray( id ), QString::fromWCharArray( id ),
@ -70,7 +61,7 @@ const SpeechClient::Engine & SpeechClient::engine() const
return internalData->engine; return internalData->engine;
} }
bool SpeechClient::tell( QString const & text ) const bool SpeechClient::tell( QString const & text )
{ {
if ( !speechAvailable( internalData->sp ) ) if ( !speechAvailable( internalData->sp ) )
return false; return false;
@ -78,17 +69,27 @@ bool SpeechClient::tell( QString const & text ) const
if ( internalData->waitingFinish ) if ( internalData->waitingFinish )
return false; return false;
bool ok = speechTell( internalData->sp, text.toStdWString().c_str() );
emit started( ok );
if ( ok )
{
internalData->waitingFinish = true; internalData->waitingFinish = true;
const_cast<SpeechClient *>( this )->startTimer( 50 ); startTimer( 50 );
return speechTell( internalData->sp, reinterpret_cast<const wchar_t*>( text.utf16() ) ); }
else
{
emit finished();
}
return ok;
} }
bool SpeechClient::say( QString const & text ) const bool SpeechClient::say( QString const & text )
{ {
if ( !speechAvailable( internalData->sp ) ) if ( !speechAvailable( internalData->sp ) )
return false; return false;
return speechSay( internalData->sp, reinterpret_cast<const wchar_t*>( text.utf16() ) ); return speechSay( internalData->sp, text.toStdWString().c_str() );
} }
void SpeechClient::timerEvent( QTimerEvent * evt ) void SpeechClient::timerEvent( QTimerEvent * evt )
@ -103,6 +104,5 @@ void SpeechClient::timerEvent( QTimerEvent * evt )
killTimer( evt->timerId() ) ; killTimer( evt->timerId() ) ;
internalData->waitingFinish = false; internalData->waitingFinish = false;
emit finished(); emit finished();
emit finished( this );
} }
} }

View file

@ -1,5 +1,6 @@
#define WINVER 0x0500 // At least WinXP required #define WINVER 0x0500 // At least WinXP required
#include <windows.h> #include <windows.h>
#include <limits.h>
#include "speechhlp.hh" #include "speechhlp.hh"
#include <string> #include <string>
@ -15,12 +16,13 @@ struct _SpeechHelper
wstring engineName; wstring engineName;
bool willInvokeCoUninitialize; bool willInvokeCoUninitialize;
_SpeechHelper() : willInvokeCoUninitialize(false) _SpeechHelper() :
willInvokeCoUninitialize(false)
{ {
HRESULT hr; HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
willInvokeCoUninitialize = (hr != RPC_E_CHANGED_MODE); 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() ~_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 speechCreate(const wchar_t *engineId)
{ {
SpeechHelper sp = new _SpeechHelper(); 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; sp->engineId = engineId;
speechEnumerateAvailableEngines(findByEngineName, sp); if (engineName)
{
sp->engineName = engineName;
CoTaskMemFree(engineName);
}
}
spToken->Release();
}
return sp; return sp;
} }

View file

@ -28,6 +28,30 @@ 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( inline HRESULT SpGetCategoryFromId(
const WCHAR * pszCategoryId, const WCHAR * pszCategoryId,
ISpObjectTokenCategory ** ppCategory, ISpObjectTokenCategory ** ppCategory,
@ -80,6 +104,8 @@ inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescrip
{ {
WCHAR szLangId[10]; WCHAR szLangId[10];
HRESULT hr = S_OK; HRESULT hr = S_OK;
#if _SAPI_VER >= 0x053
WCHAR* pRegKeyPath = 0; WCHAR* pRegKeyPath = 0;
WCHAR* pszTemp = 0; WCHAR* pszTemp = 0;
HKEY Handle = NULL; HKEY Handle = NULL;
@ -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 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 // 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)) if (FAILED(hr))
{ {
// Free memory allocated above if necessary // Free memory allocated above if necessary

View file

@ -32,7 +32,7 @@ void TextToSpeechSource::on_addVoiceEngine_clicked()
if ( ui.availableVoiceEngines->count() == 0 ) if ( ui.availableVoiceEngines->count() == 0 )
{ {
QMessageBox::information( this, tr( "No TTS voice available" ), QMessageBox::information( this, tr( "No TTS voice available" ),
tr( "Cannot find available TTS voice.<br>" tr( "Cannot find availble TTS voice.<br>"
"Please make sure that at least one TTS engine installed on your computer already." ) ); "Please make sure that at least one TTS engine installed on your computer already." ) );
return; return;
} }
@ -54,8 +54,9 @@ void TextToSpeechSource::on_removeVoiceEngine_clicked()
voiceEnginesModel.getCurrentVoiceEngines()[ current.row() ].name ), voiceEnginesModel.getCurrentVoiceEngines()[ current.row() ].name ),
QMessageBox::Ok, QMessageBox::Ok,
QMessageBox::Cancel ) == QMessageBox::Ok ) QMessageBox::Cancel ) == QMessageBox::Ok )
{
voiceEnginesModel.removeVoiceEngine( current.row() ); voiceEnginesModel.removeVoiceEngine( current.row() );
}
} }
void TextToSpeechSource::on_previewVoice_clicked() void TextToSpeechSource::on_previewVoice_clicked()
@ -66,23 +67,17 @@ void TextToSpeechSource::on_previewVoice_clicked()
QString engineId = ui.availableVoiceEngines->itemData( idx ).toString(); QString engineId = ui.availableVoiceEngines->itemData( idx ).toString();
QString text = ui.previewText->text(); QString text = ui.previewText->text();
SpeechClient *speechClient = new SpeechClient( engineId, this ); SpeechClient * speechClient = new SpeechClient( engineId, this );
ui.previewVoice->setEnabled( false ); connect( speechClient, SIGNAL( started( bool ) ), ui.previewVoice, SLOT( setDisabled( bool ) ) );
connect( speechClient, SIGNAL( finished( SpeechClient * ) ), connect( speechClient, SIGNAL( finished() ), this, SLOT( previewVoiceFinished() ) );
this, SLOT( previewVoiceFinished( SpeechClient * ) ) ); connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) );
if ( !speechClient->tell( text ) ) { speechClient->tell( text );
ui.previewVoice->setEnabled( true );
delete speechClient;
}
} }
void TextToSpeechSource::previewVoiceFinished( SpeechClient * speechClient ) void TextToSpeechSource::previewVoiceFinished()
{ {
ui.previewVoice->setEnabled( true ); ui.previewVoice->setDisabled( false );
if (speechClient)
speechClient->deleteLater();
} }
void TextToSpeechSource::fitSelectedVoiceEnginesColumns() void TextToSpeechSource::fitSelectedVoiceEnginesColumns()
@ -136,7 +131,8 @@ Qt::ItemFlags VoiceEnginesModel::flags( QModelIndex const & index ) const
if ( index.isValid() ) if ( index.isValid() )
{ {
switch ( index.column() ) { switch ( index.column() )
{
case kColumnEnabled: case kColumnEnabled:
result |= Qt::ItemIsUserCheckable; result |= Qt::ItemIsUserCheckable;
break; break;
@ -168,7 +164,7 @@ QVariant VoiceEnginesModel::headerData( int section, Qt::Orientation /*orientati
{ {
if ( role == Qt::DisplayRole ) if ( role == Qt::DisplayRole )
{ {
switch( section ) switch ( section )
{ {
case kColumnEnabled: case kColumnEnabled:
return tr( "Enabled" ); return tr( "Enabled" );
@ -276,8 +272,10 @@ void VoiceEngineEditor::setEngineId( QString const & engineId )
{ {
// Find index for the id // Find index for the id
int idx = -1; int idx = -1;
for ( int i = 0; i < count(); ++i ) { for ( int i = 0; i < count(); ++i )
if ( engineId == itemData(i).toString() ) { {
if ( engineId == itemData( i ).toString() )
{
idx = i; idx = i;
break; break;
} }
@ -285,22 +283,22 @@ void VoiceEngineEditor::setEngineId( QString const & engineId )
setCurrentIndex( idx ); setCurrentIndex( idx );
} }
VoiceEngineItemDelegate::VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject *parent ) : VoiceEngineItemDelegate::VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject * parent ) :
QStyledItemDelegate( parent ), QStyledItemDelegate( parent ),
engines( engines ) engines( engines )
{ {
} }
QWidget * VoiceEngineItemDelegate::createEditor( QWidget *parent, QWidget * VoiceEngineItemDelegate::createEditor( QWidget * parent,
QStyleOptionViewItem const & option, QStyleOptionViewItem const & option,
QModelIndex const & index ) const QModelIndex const & index ) const
{ {
if( index.column() != VoiceEnginesModel::kColumnEngineName ) if ( index.column() != VoiceEnginesModel::kColumnEngineName )
return QStyledItemDelegate::createEditor( parent, option, index ); return QStyledItemDelegate::createEditor( parent, option, index );
return new VoiceEngineEditor( engines, parent ); 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 ); VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor );
if ( !editor ) if ( !editor )
@ -312,7 +310,7 @@ void VoiceEngineItemDelegate::setEditorData( QWidget *uncastedEditor, const QMod
editor->setEngineId( engineId ); editor->setEngineId( engineId );
} }
void VoiceEngineItemDelegate::setModelData( QWidget *uncastedEditor, QAbstractItemModel * model, void VoiceEngineItemDelegate::setModelData( QWidget * uncastedEditor, QAbstractItemModel * model,
const QModelIndex & index ) const const QModelIndex & index ) const
{ {
VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor ); VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor );

View file

@ -93,7 +93,7 @@ private slots:
void on_addVoiceEngine_clicked(); void on_addVoiceEngine_clicked();
void on_removeVoiceEngine_clicked(); void on_removeVoiceEngine_clicked();
void on_previewVoice_clicked(); void on_previewVoice_clicked();
void previewVoiceFinished( SpeechClient * speechClient ); void previewVoiceFinished();
private: private:
Ui::TextToSpeechSource ui; Ui::TextToSpeechSource ui;

View file

@ -7,21 +7,24 @@
#include "utf8.hh" #include "utf8.hh"
#include "wstring_qt.hh" #include "wstring_qt.hh"
#include <string>
#include <map>
#include <QUrl> #include <QUrl>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QCryptographicHash>
namespace VoiceEngines { namespace VoiceEngines
{
using namespace Dictionary; using namespace Dictionary;
using std::string;
using std::map;
namespace StringConv { inline string toMd5( QByteArray const & b )
inline QString 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 class VoiceEnginesDictionary: public Dictionary::Class
@ -34,7 +37,7 @@ public:
VoiceEnginesDictionary( Config::VoiceEngine const & voiceEngine ): VoiceEnginesDictionary( Config::VoiceEngine const & voiceEngine ):
Dictionary::Class( Dictionary::Class(
StringConv::toMd5( voiceEngine.id.toUtf8() ).toStdString(), toMd5( voiceEngine.id.toUtf8() ),
vector< string >() ), vector< string >() ),
voiceEngine( voiceEngine ) voiceEngine( voiceEngine )
{ {
@ -70,7 +73,7 @@ sptr< WordSearchRequest > VoiceEnginesDictionary::prefixMatch( wstring const & /
unsigned long /*maxResults*/ ) unsigned long /*maxResults*/ )
throw( std::exception ) throw( std::exception )
{ {
WordSearchRequestInstant *sr = new WordSearchRequestInstant(); WordSearchRequestInstant * sr = new WordSearchRequestInstant();
sr->setUncertain( true ); sr->setUncertain( true );
return sr; return sr;
} }
@ -102,7 +105,7 @@ sptr< Dictionary::DataRequest > VoiceEnginesDictionary::getArticle(
sptr< DataRequestInstant > ret = new DataRequestInstant( true ); sptr< DataRequestInstant > ret = new DataRequestInstant( true );
ret->getData().resize( result.size() ); ret->getData().resize( result.size() );
memcpy( &(ret->getData().front()), result.data(), result.size() ); memcpy( &( ret->getData().front() ), result.data(), result.size() );
return ret; return ret;
} }
@ -128,7 +131,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries(
{ {
vector< sptr< Dictionary::Class > > result; 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 ) if ( i->enabled )
result.push_back( new VoiceEnginesDictionary( *i ) ); result.push_back( new VoiceEnginesDictionary( *i ) );