Index: rbutil/rbutilqt/base/rbsettings.cpp =================================================================== --- rbutil/rbutilqt/base/rbsettings.cpp (revision 26670) +++ rbutil/rbutilqt/base/rbsettings.cpp (working copy) @@ -48,6 +48,9 @@ { RbSettings::TtsOptions, ":tts:/options", "" }, { RbSettings::TtsPath, ":tts:/path", "" }, { RbSettings::TtsVoice, ":tts:/voice", "" }, + { RbSettings::TtsVolume, ":tts:/volume", "70" }, + { RbSettings::TtsPitch, ":tts:/pitch", "0" }, + { RbSettings::TtsFormat, ":tts:/format", "" }, { RbSettings::EncoderPath, ":encoder:/encoderpath", "" }, { RbSettings::EncoderOptions, ":encoder:/encoderoptions", "" }, { RbSettings::CacheOffline, "offline", "false" }, Index: rbutil/rbutilqt/base/rbsettings.h =================================================================== --- rbutil/rbutilqt/base/rbsettings.h (revision 26670) +++ rbutil/rbutilqt/base/rbsettings.h (working copy) @@ -48,14 +48,17 @@ TtsOptions, TtsPath, TtsVoice, + TtsPitch, + TtsVolume, + TtsSpeed, + TtsUseSapi4, + TtsFormat, EncoderPath, EncoderOptions, WavtrimThreshold, EncoderComplexity, - TtsSpeed, CacheOffline, CacheDisabled, - TtsUseSapi4, EncoderNarrowBand, EncoderQuality, EncoderVolume, Index: rbutil/rbutilqt/base/ttsbase.cpp =================================================================== --- rbutil/rbutilqt/base/ttsbase.cpp (revision 26670) +++ rbutil/rbutilqt/base/ttsbase.cpp (working copy) @@ -22,6 +22,7 @@ #include "ttsfestival.h" #include "ttssapi.h" #include "ttsexes.h" +#include "ttsopensapi.h" #if defined(Q_OS_MACX) #include "ttscarbon.h" #endif @@ -40,6 +41,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 @@ -80,8 +82,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; } Index: rbutil/rbutilqt/base/ttsopensapi.cpp =================================================================== --- rbutil/rbutilqt/base/ttsopensapi.cpp (revision 0) +++ rbutil/rbutilqt/base/ttsopensapi.cpp (revision 0) @@ -0,0 +1,547 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* +* Copyright (C) 2007 by Dominik Wenger +* $Id: ttsfestival.cpp 23158 2009-10-13 19:54:27Z Domonoky $ +* +* All files in this archive are subject to the GNU General Public License. +* See the file COPYING in the source tree root for full license agreement. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied. +* +****************************************************************************/ + +#include "ttsopensapi.h" +#include "utils.h" +#include "rbsettings.h" +#include "httpget.h" + +//list of server commands +TTSOpenSapi::Command TTSOpenSapi::m_cmdList[] = { + {TTSOpenSapi::eREADYSERVER,"readyServer",204,false}, + {TTSOpenSapi::eVOICELIST,"getEngine",297,true}, + {TTSOpenSapi::eKILLSERVER,"killServer",299,false}, + {TTSOpenSapi::eFILENAME,"outFile",202,false}, + {TTSOpenSapi::eSPEAK,"speakMe",201,false}, + {TTSOpenSapi::eSETVOICE,"setEngine",202,false}, + {TTSOpenSapi::eSETVOL,"setVol",202,false}, + {TTSOpenSapi::eSETPITCH,"setPitch",202,false}, + {TTSOpenSapi::eSETRATE,"setRate",202,false}, + {TTSOpenSapi::eFORMATLIST,"getFormat",298,true}, + //has to be the last entry + {TTSOpenSapi::eLASTENTRY,"",false,0}, +}; + +TTSOpenSapi::TTSOpenSapi(QObject* parent) : TTSBase(parent) +{ + +} + +TTSOpenSapi::~TTSOpenSapi() +{ + +} + +void TTSOpenSapi::generateSettings() +{ + //TODO better error handling + QString errstr; + if(!start(&errstr)) + return; + + //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 + QStringList formatList = getFormatList(); + QString defaultformat = RbSettings::subValue("opensapi",RbSettings::TtsFormat).toString(); + if(defaultformat == "" && formatList.size() > 0) defaultformat=formatList.at(0); + else + { + for(int i=0; i< m_formatList.size(); i++) + { + if(m_formatList[i].id == defaultformat.toInt()) + { + defaultformat = m_formatList[i].name; + break; + } + } + } + insertSetting(eWAVFORMAT,new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, + tr("Wav format:"),defaultformat,formatList)); + 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 + QString format = getSetting(eWAVFORMAT)->current().toString(); + //find format entry + for(int i=0;i< m_formatList.size();i++) + { + if(m_formatList[i].name == format) + { + RbSettings::setSubValue("opensapi",RbSettings::TtsFormat,m_formatList[i].id); + break; + } + } + + 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) +{ + m_serverStarted = false; + + emit busy(); + +#ifndef Q_OS_WIN32 + //we need wine on non-windows systems + QString wine = Utils::findExecutable("wine"); + if(wine == "") + { + (*errStr) = tr("Could not find wine"); + emit busyEnd(); + return false; + } +#endif + + //Download server if needed + if(!downloadServer()) + { + (*errStr) = tr("Could not download opensapi server"); + emit busyEnd(); + return false; + } + //start server +#ifdef Q_OS_WIN32 + m_serverProcess.start(QDir::tempPath() +"/open-sapi-server.exe"); +#else + m_serverProcess.start(wine,QStringList(QDir::tempPath() +"/open-sapi-server.exe")); +#endif + if(!m_serverProcess.waitForStarted()) + { + (*errStr) = tr("Could not start opensapi server"); + emit busyEnd(); + return false; + } + + bool connected = false; + //open connection to server - 10 tries, 2s apart + for(int i=0; i < 10; i++) + { + m_clientSocket.connectToHost("127.0.0.1",5491); + if(m_clientSocket.waitForConnected(1000)) + { + connected = true; + break; + } + // wait 2 seconds + QDateTime endTime = QDateTime::currentDateTime().addMSecs(2000); + while(QDateTime::currentDateTime() < endTime) + QCoreApplication::processEvents(QEventLoop::AllEvents); + } + if(!connected) + { + qDebug() << "[Opensapi] could not connect to server:" << m_clientSocket.errorString(); + (*errStr) = tr("Could not connect to open-sapi server:") + m_clientSocket.errorString(); + emit busyEnd(); + return false; + } + + //poll server - 10 tries, 2s apart + for(int i=0; i < 10; i++) + { + //try to ask server for ready. + QString returnstring; + if(serverCommand(eREADYSERVER,NULL,&returnstring)) + { + (*errStr) = returnstring; + emit busyEnd(); + m_serverStarted = true; + return true; + } + else if(returnstring != "") + { + (*errStr) = returnstring; + emit busyEnd(); + return false; + } + + // wait 10 seconds + QDateTime endTime = QDateTime::currentDateTime().addMSecs(2000); + while(QDateTime::currentDateTime() < endTime) + QCoreApplication::processEvents(QEventLoop::AllEvents); + } + + (*errStr) = tr("timeout: Could not start opensapi server"); + emit busyEnd(); + return false; +} + +bool TTSOpenSapi::downloadServer() +{ + //read in old version txt if it exists + int oldVersion=0; + int newVersion=0; + if(QFileInfo(QDir::tempPath() +"/open-sapi-server.exe.version.txt").exists()) + { + QFile foldversion(QDir::tempPath() +"/open-sapi-server.exe.version.txt"); + foldversion.open(QIODevice::ReadOnly); + oldVersion = QString(foldversion.readLine()).toInt(); + + foldversion.close(); + } + + //download new version file + HttpGet getter(this); + getter.setCache(false); + connect(&getter, SIGNAL(done(bool)), this, SLOT(downloadDone(bool))); + m_downloadDone =0; + //get version file + QFile versionFile(QDir::tempPath() +"/open-sapi-server.exe.version.txt"); + getter.setFile(&versionFile); + getter.getFile(QUrl("http://open-sapi.googlecode.com/files/open-sapi-server.exe.version.txt")); + while(m_downloadDone == 0) + { + QCoreApplication::processEvents(); + } + if(m_downloadDone == -1) + { + qDebug() << "Downloading of opensapi version file failed"; + return false; + } + //read in new version + versionFile.open(QIODevice::ReadOnly); + newVersion = QString(versionFile.readLine()).toInt(); + versionFile.close(); + + qDebug() << "Opensapi versions:" << newVersion << oldVersion; + + //check if we need to download the server + if(!QFileInfo(QDir::tempPath() +"/open-sapi-server.exe").exists() || oldVersion != newVersion) + { + m_downloadDone =0; + QFile opensapiServer(QDir::tempPath() +"/open-sapi-server.exe"); + getter.setFile(&opensapiServer); + getter.getFile(QUrl("http://open-sapi.googlecode.com/files/open-sapi-server.exe")); + while(m_downloadDone == 0) + { + QCoreApplication::processEvents(); + } + if(m_downloadDone == -1) + { + qDebug() << "Downloading of opensapi server failed"; + return false; + } + } + + return true; +} + +void TTSOpenSapi::downloadDone(bool error) +{ + if(error) m_downloadDone = -1; + else m_downloadDone = 1; +} + +bool TTSOpenSapi::serverCommand(ECommands cmd,QString* parameter,QString* returnstring) +{ + //remove potential old data + QString old = m_clientSocket.readAll(); + if(old != "") + qDebug() << "[Opensapi] old data detected:" << old; + + //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: "< endTime) + { + qDebug() << "[OpenSapi] serverCommand timout:" << answer; + return false; + } + //try read data + if(m_clientSocket.canReadLine()) + { + answer += m_clientSocket.readAll(); + if((!m_cmdList[cmdpos].isList) || //if its not a list, we only need 1 line + (m_cmdList[cmdpos].isList && answer.contains("293:End of List:"))) // lists end with this string + { + break; + } + } + QCoreApplication::processEvents(QEventLoop::AllEvents); + } + qDebug() << "[Opensapi] answer:" << answer; + int answercode = answer.left(answer.indexOf(':')).toInt(); + + //set return string + if(returnstring!=NULL) + (*returnstring) = answer; + + //check answer + if(answercode != m_cmdList[cmdpos].goodCode) + { + return false; + } + + return true; +} + +QStringList TTSOpenSapi::getVoiceList() +{ + QStringList voices; + m_voiceList.clear(); + QString returnstring; + + if(!serverCommand(eVOICELIST,NULL,&returnstring)) + { + qDebug() << "[Opensapi] could not get voicelist" << returnstring; + return voices; + } + + //split on ":" or line ends + QStringList templist = returnstring.remove("\r").split(QRegExp("[:\\n]")); + + for(int i=0; i < templist.size();i += 8) + { + if(templist[i] == "297" && templist[i+1] == "Voice List") + { + //insert display voice + voices.append(templist[i+3]); + //remember voice description + Voice voice; + voice.id = templist[i+2].toInt(); + voice.name = templist[i+3]; + voice.gender = templist[i+4]; + voice.language = templist[i+5]; + voice.vendor = templist[i+6]; + voice.age = templist[i+7]; + m_voiceList.append(voice); + } + } + + return voices; +} + +QStringList TTSOpenSapi::getFormatList() +{ + QStringList formats; + m_formatList.clear(); + QString returnstring; + + if(!serverCommand(eFORMATLIST,NULL,&returnstring)) + { + qDebug() << "[Opensapi] could not get formatlist" << returnstring; + return formats; + } + + //split on ":" or line ends + QStringList templist = returnstring.remove("\r").split(QRegExp("[:\\n]")); + + for(int i=0; i < templist.size();i += 4) + { + if(templist[i] == "298" && templist[i+1] == "Format List") + { + //insert display voice + formats.append(templist[i+3]); + //remember voice description + Format format; + format.id = templist[i+2].toInt(); + format.name = templist[i+3]; + + m_formatList.append(format); + } + } + + return formats; +} + +void TTSOpenSapi::updateVoiceDescription() +{ + QString voice = getSetting(eVOICE)->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 + if(!serverCommand(eFILENAME,&wavfile)) + { + (*errStr) = tr("Could not set outputfile."); + return FatalError; + } + + //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() +{ + if(!m_serverStarted) return true; + + 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(); + m_serverStarted = false; + + return true; +} + +bool TTSOpenSapi::configOk() +{ +#ifndef Q_OS_WIN32 + if(Utils::findExecutable("wine") == "") + return false; + + //TODO check more things +#endif + return true; +} + Index: rbutil/rbutilqt/base/ttsopensapi.h =================================================================== --- rbutil/rbutilqt/base/ttsopensapi.h (revision 0) +++ rbutil/rbutilqt/base/ttsopensapi.h (revision 0) @@ -0,0 +1,118 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* +* Copyright (C) 2009 by Dominik Wenger +* $Id: ttssapi.h 23158 2009-10-13 19:54:27Z Domonoky $ +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied. +* +****************************************************************************/ + +#ifndef TTSOPENSAPI_H +#define TTSOPENSAPI_H + +#include "ttsbase.h" + +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(); + + Capabilities capabilities() {return None;} + + //! Enum for commands + enum ECommands + { + eREADYSERVER, + eVOICELIST, + eFORMATLIST, + eKILLSERVER, + eFILENAME, + eSPEAK, + eSETVOICE, + eSETRATE, + eSETVOL, + eSETPITCH, + eLASTENTRY, + }; + + struct Command + { + ECommands cmd; + QString cmdString; + int goodCode; + bool isList; + }; + + struct Voice + { + int id; + QString name; + QString gender; + QString language; + QString vendor; + QString age; + }; + + struct Format + { + int id; + QString name; + }; + private slots: + void updateVoiceDescription(); + void downloadDone(bool); + private: + QStringList getVoiceList(); + QStringList getFormatList(); + bool serverCommand(ECommands cmd,QString* parameter=NULL,QString* returnstring=NULL); + bool startServer(QString *errStr); + bool downloadServer(); + + QProcess m_serverProcess; + QTcpSocket m_clientSocket; + static Command m_cmdList[]; + QList m_voiceList; + QList m_formatList; + + volatile int m_downloadDone; + bool m_serverStarted; +}; + +#endif Index: rbutil/rbutilqt/rbutilqt.pri =================================================================== --- rbutil/rbutilqt/rbutilqt.pri (revision 26670) +++ rbutil/rbutilqt/rbutilqt.pri (working copy) @@ -45,6 +45,7 @@ base/ttsbase.cpp \ base/ttsexes.cpp \ base/ttssapi.cpp \ + base/ttsopensapi.cpp \ base/ttsfestival.cpp \ ../../tools/wavtrim.c \ ../../tools/voicefont.c \ @@ -113,6 +114,7 @@ base/ttsexes.h \ base/ttsfestival.h \ base/ttssapi.h \ + base/ttsopensapi.h \ ../../tools/wavtrim.h \ ../../tools/voicefont.h \ base/voicefile.h \