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" );
}
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;
}
}

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(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);

View file

@ -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;

View file

@ -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 );

View file

@ -1,15 +1,16 @@
#include "speechclient.hh"
#include <QtCore>
#include <windows.h>
#include "speechhlp.hh"
#include <QtCore>
struct SpeechClient::InternalData
{
InternalData( QString const & engineId ):
waitingFinish( false )
{
sp = speechCreate( reinterpret_cast<const wchar_t*>( engineId.utf16() ) );
sp = speechCreate( engineId.toStdWString().c_str() );
}
~InternalData()
@ -19,27 +20,17 @@ struct SpeechClient::InternalData
SpeechHelper sp;
Engine engine;
typedef QPointer<SpeechClient> Ptr;
static QList<Ptr> ptrs;
bool waitingFinish;
};
QList<SpeechClient::InternalData::Ptr> SpeechClient::InternalData::ptrs =
QList<SpeechClient::InternalData::Ptr>();
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<SpeechClient *>( this )->startTimer( 50 );
return speechTell( internalData->sp, reinterpret_cast<const wchar_t*>( 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<const wchar_t*>( 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();
}
}

View file

@ -1,5 +1,6 @@
#define WINVER 0x0500 // At least WinXP required
#include <windows.h>
#include <limits.h>
#include "speechhlp.hh"
#include <string>
@ -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;
}

View file

@ -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

View file

@ -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.<br>"
tr( "Cannot find availble TTS voice.<br>"
"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 );

View file

@ -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;

View file

@ -7,21 +7,24 @@
#include "utf8.hh"
#include "wstring_qt.hh"
#include <string>
#include <map>
#include <QUrl>
#include <QDir>
#include <QFileInfo>
#include <QCryptographicHash>
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 ) );