Index: rbutil/rbutilqt/base/rbsettings.cpp =================================================================== --- rbutil/rbutilqt/base/rbsettings.cpp (revision 22893) +++ rbutil/rbutilqt/base/rbsettings.cpp (working copy) @@ -81,14 +81,16 @@ { RbSettings::TtsOptions, ":tts:/options", "" }, { RbSettings::TtsPath, ":tts:/path", "" }, { RbSettings::TtsVoice, ":tts:/voice", "" }, + { RbSettings::TtsSpeed, ":tts:/speed", "0" }, + { RbSettings::TtsVolume, ":tts:/volume", "70" }, + { RbSettings::TtsPitch, ":tts:/pitch", "0" }, + { RbSettings::TtsUseSapi4, "sapi/useSapi4", "false" }, { RbSettings::EncoderPath, ":encoder:/encoderpath", "" }, { RbSettings::EncoderOptions, ":encoder:/encoderoptions", "" }, { RbSettings::CacheOffline, "offline", "false" }, { RbSettings::CacheDisabled, "cachedisable", "false" }, - { RbSettings::TtsUseSapi4, "sapi/useSapi4", "false" }, { RbSettings::EncoderNarrowBand, ":encoder:/narrowband", "false" }, { RbSettings::WavtrimThreshold, "wavtrimthreshold", "500"}, - { RbSettings::TtsSpeed, ":tts:/speed", "0" }, { RbSettings::EncoderComplexity, ":encoder:/complexity", "10" }, { RbSettings::EncoderQuality, ":encoder:/quality", "8.0" }, { RbSettings::EncoderVolume, ":encoder:/volume", "1.0" }, Index: rbutil/rbutilqt/base/rbsettings.h =================================================================== --- rbutil/rbutilqt/base/rbsettings.h (revision 22893) +++ rbutil/rbutilqt/base/rbsettings.h (working copy) @@ -56,14 +56,16 @@ TtsOptions, TtsPath, TtsVoice, + TtsPitch, + TtsVolume, + TtsSpeed, + TtsUseSapi4, EncoderPath, EncoderOptions, WavtrimThreshold, EncoderComplexity, - TtsSpeed, CacheOffline, CacheDisabled, - TtsUseSapi4, EncoderNarrowBand, EncoderQuality, EncoderVolume, Index: rbutil/rbutilqt/base/tts.cpp =================================================================== --- rbutil/rbutilqt/base/tts.cpp (revision 22893) +++ rbutil/rbutilqt/base/tts.cpp (working copy) @@ -36,6 +36,7 @@ ttsList["espeak"] = "Espeak TTS Engine"; ttsList["flite"] = "Flite TTS Engine"; ttsList["swift"] = "Swift TTS Engine"; + ttsList["opensapi"] = "Opensapi TTS Engine"; #if defined(Q_OS_WIN) ttsList["sapi"] = "Sapi TTS Engine"; #endif @@ -65,8 +66,13 @@ } else #endif - if (true) // fix for OS other than WIN or LINUX + if(ttsName == "opensapi") { + tts = new TTSOpenSapi(parent); + return tts; + } + else + { tts = new TTSExes(ttsName,parent); return tts; } @@ -664,3 +670,361 @@ } +/********************************************************************** + * TSSOpenSapi - client-server wrapper + **********************************************************************/ + +TTSOpenSapi::Command TTSOpenSapi::m_cmdList[] = { + {TTSOpenSapi::eREADYSERVER,"readyServer",true,204}, + {TTSOpenSapi::eVOICELIST,"getEngine",true,297}, + {TTSOpenSapi::eKILLSERVER,"killServer",true,299}, + {TTSOpenSapi::eFILENAME,"outFile",true,202}, + {TTSOpenSapi::eSPEAK,"speakMe",true,205}, + {TTSOpenSapi::eSETVOICE,"setEngine",true,202}, + {TTSOpenSapi::eSETVOL,"setVol",false,0}, + {TTSOpenSapi::eSETPITCH,"setPitch",false,0}, + {TTSOpenSapi::eSETRATE,"setRate",false,0}, + //has to be the last entry + {TTSOpenSapi::eLASTENTRY,"",false,0}, +}; + +TTSOpenSapi::TTSOpenSapi(QObject* parent) : TTSBase(parent) +{ + +} + +TTSOpenSapi::~TTSOpenSapi() +{ + stop(); +} + +void TTSOpenSapi::generateSettings() +{ + QString errstr; + start(&errstr); + //status + insertSetting(eSTATUS,new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,tr("Status:"),errstr)); + + // voice + QStringList voiceList = getVoiceList(); + QString defaultvoice = RbSettings::subValue("opensapi",RbSettings::TtsVoice).toString(); + if(defaultvoice == "" && voiceList.size() > 0) defaultvoice=voiceList.at(0); + else + { + for(int i=0; i< m_voiceList.size(); i++) + { + if(m_voiceList[i].id == defaultvoice.toInt()) + { + defaultvoice = m_voiceList[i].name; + break; + } + } + } + EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, + tr("Voice:"),defaultvoice,voiceList); + connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceDescription())); + insertSetting(eVOICE,setting); + + //voice descriptions + insertSetting(eVOICEGENDER,new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,tr("Voice gender:"),"")); + insertSetting(eVOICELANGUAGE,new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,tr("Voice language:"),"")); + insertSetting(eVOICEVENDOR,new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,tr("Voice vendor:"),"")); + insertSetting(eVOICEAGE,new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,tr("Voice age:"),"")); + + updateVoiceDescription(); + + //volume + insertSetting(eVOLUME,new EncTtsSetting(this,EncTtsSetting::eINT, + tr("Volume:"),RbSettings::subValue("opensapi",RbSettings::TtsVolume),0,100)); + //rate + insertSetting(eRATE,new EncTtsSetting(this,EncTtsSetting::eINT, + tr("Rate:"),RbSettings::subValue("opensapi",RbSettings::TtsSpeed),-10,10)); + //pitch + insertSetting(ePITCH,new EncTtsSetting(this,EncTtsSetting::eINT, + tr("Pitch:"),RbSettings::subValue("opensapi",RbSettings::TtsPitch),-10,10)); + //wavformat + + stop(); +} + +void TTSOpenSapi::saveSettings() +{ + //save settings in user config + //voice + QString voice = getSetting(eVOICE)->current().toString(); + //find voice entry + for(int i=0;i< m_voiceList.size();i++) + { + if(m_voiceList[i].name == voice) + { + RbSettings::setSubValue("opensapi",RbSettings::TtsVoice,m_voiceList[i].id); + break; + } + } + + //volume + RbSettings::setSubValue("opensapi",RbSettings::TtsVolume,getSetting(eVOLUME)->current().toString()); + + //rate + RbSettings::setSubValue("opensapi",RbSettings::TtsSpeed,getSetting(eRATE)->current().toString()); + + //pitch + RbSettings::setSubValue("opensapi",RbSettings::TtsPitch,getSetting(ePITCH)->current().toString()); + + //wavformat + + RbSettings::sync(); +} + +bool TTSOpenSapi::start(QString *errStr) +{ + if(startServer(errStr)) + { + //set current settings + QString voice = RbSettings::subValue("opensapi",RbSettings::TtsVoice).toString(); + if(voice == "") voice = "0"; + serverCommand(eSETVOICE,&voice); + QString pitch = RbSettings::subValue("opensapi",RbSettings::TtsPitch).toString(); + serverCommand(eSETPITCH,&pitch); + QString vol = RbSettings::subValue("opensapi",RbSettings::TtsVolume).toString(); + serverCommand(eSETVOL,&vol); + QString rate = RbSettings::subValue("opensapi",RbSettings::TtsSpeed).toString(); + serverCommand(eSETRATE,&rate); + return true; + } + else return false; +} + +bool TTSOpenSapi::startServer(QString *errStr) +{ + emit busy(); +#ifndef Q_OS_WIN32 + //we need wine on non-windows systems + QString wine = findExecutable("wine"); + if(wine == "") + { + (*errStr) = tr("Could not find wine"); + emit busyEnd(); + return false; + } +#endif + + //copy the server to temp location + QFile::remove(QDir::tempPath() +"/opensapi-server.exe"); + QFile::copy(":/sapi/opensapi-server.exe",QDir::tempPath() + "/opensapi-server.exe"); + + //start server +#ifdef Q_OS_WIN32 + m_serverProcess.start(QDir::tempPath() +"/opensapi-server.exe"); +#else + m_serverProcess.start(wine,QDir::tempPath() +"/opensapi-server.exe"); +#endif + if(!m_serverProcess.waitForStarted()) + { + (*errStr) = tr("Could not start opensapi server"); + emit busyEnd(); + return false; + } + + //open connection to server + m_clientSocket.connectToHost("127.0.0.1",5491); + + if(!m_clientSocket.waitForConnected(5000)) + { + (*errStr) = tr("Could not connect to open-sapi server:") + m_clientSocket.errorString(); + emit busyEnd(); + return false; + } + + //poll server - 6 tries, 10s apart + for(int i=0; i < 6; i++) + { + //try to ask server for ready. + QString returnstring; + if(serverCommand(eREADYSERVER,NULL,&returnstring)) + { + (*errStr) = returnstring; + emit busyEnd(); + return true; + } + else if(returnstring != "") + { + (*errStr) = returnstring; + emit busyEnd(); + return false; + } + + // wait 10 seconds + QDateTime endTime = QDateTime::currentDateTime().addMSecs(10000); + while(QDateTime::currentDateTime() < endTime) + QCoreApplication::processEvents(QEventLoop::AllEvents); + } + + (*errStr) = tr("timeout: Could not start opensapi server"); + emit busyEnd(); + return false; +} + +bool TTSOpenSapi::serverCommand(ECommands cmd,QString* parameter,QString* returnstring) +{ + //find command + int cmdpos =-1; + for(int i=0;m_cmdList[i].cmd != eLASTENTRY;i++) + { + if(m_cmdList[i].cmd == cmd) + { + cmdpos = i; + break; + } + } + if(cmdpos == -1) + { + qDebug() << "[Opensapi] Command " << cmd << " not found."; + return false; + } + + //construct command string + QString command; + command = m_cmdList[cmdpos].cmdString; + if(parameter != NULL) + { + command.append(" "); + command.append(*parameter); + } + qDebug() << "[Opensapi] cmd: "<current().toString(); + //find corresponding description + for(int i=0;i< m_voiceList.size();i++) + { + if(m_voiceList[i].name == voice) + { + getSetting(eVOICEGENDER)->setCurrent(m_voiceList[i].gender); + getSetting(eVOICELANGUAGE)->setCurrent(m_voiceList[i].language); + getSetting(eVOICEVENDOR)->setCurrent(m_voiceList[i].vendor); + getSetting(eVOICEAGE)->setCurrent(m_voiceList[i].age); + break; + } + } +} + +TTSStatus TTSOpenSapi::voice(QString text,QString wavfile, QString *errStr) +{ + QString returnstring; + + //set output file + serverCommand(eFILENAME,&wavfile); + + //speak text + if(!serverCommand(eSPEAK,&text,&returnstring)) + { + (*errStr) = tr("Could not voice: %1 - %2").arg(text).arg(returnstring); + return Warning; + } + + return NoError; +} + +bool TTSOpenSapi::stop() +{ + QString returnstring; + if(!serverCommand(eKILLSERVER,NULL,&returnstring)) + { + qDebug() << "[Opensapi] unable to stop server" << returnstring; + return false; + } + + m_clientSocket.disconnectFromHost(); + + m_serverProcess.terminate(); + m_serverProcess.kill(); + + // remove file + QFile::setPermissions(QDir::tempPath() +"/opensapi-server.exe",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner + |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser + |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup + |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); + QFile::remove(QDir::tempPath() +"/opensapi-server.exe"); + + return true; +} + +bool TTSOpenSapi::configOk() +{ +#ifndef Q_OS_WIN32 + if(findExecutable("wine") == "") + return false; + + //TODO check more things +#endif + return true; +} Index: rbutil/rbutilqt/base/tts.h =================================================================== --- rbutil/rbutilqt/base/tts.h (revision 22893) +++ rbutil/rbutilqt/base/tts.h (working copy) @@ -109,7 +109,82 @@ bool m_sapi4; }; +class TTSOpenSapi : public TTSBase +{ + //! Enum to identify the settings + enum ESettings + { + eSTATUS, + eVOICE, + eVOICEGENDER, + eVOICELANGUAGE, + eVOICEVENDOR, + eVOICEAGE, + eVOLUME, + eRATE, + ePITCH, + eWAVFORMAT, + }; + + Q_OBJECT + public: + TTSOpenSapi(QObject* parent=NULL); + ~TTSOpenSapi(); + TTSStatus voice(QString text,QString wavfile, QString *errStr); + bool start(QString *errStr); + bool stop(); + + // for settings + bool configOk(); + void generateSettings(); + void saveSettings(); + + //! Enum for commands + enum ECommands + { + eREADYSERVER, + eVOICELIST, + eKILLSERVER, + eFILENAME, + eSPEAK, + eSETVOICE, + eSETRATE, + eSETVOL, + eSETPITCH, + eLASTENTRY, + }; + + struct Command + { + ECommands cmd; + QString cmdString; + bool hasAnswer; + int goodCode; + }; + + struct Voice + { + int id; + QString name; + QString gender; + QString language; + QString vendor; + QString age; + }; + private slots: + void updateVoiceDescription(); + private: + QStringList getVoiceList(); + bool serverCommand(ECommands cmd,QString* parameter=NULL,QString* returnstring=NULL); + bool startServer(QString *errStr); + + QProcess m_serverProcess; + QTcpSocket m_clientSocket; + static Command m_cmdList[]; + QList m_voiceList; +}; + class TTSExes : public TTSBase { enum ESettings Index: rbutil/rbutilqt/rbutilqt.qrc =================================================================== --- rbutil/rbutilqt/rbutilqt.qrc (revision 22893) +++ rbutil/rbutilqt/rbutilqt.qrc (working copy) @@ -38,4 +38,7 @@ rbutil.ini + + opensapi-server.exe +