summaryrefslogtreecommitdiff
path: root/rbutil/rbutilqt/tts.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'rbutil/rbutilqt/tts.cpp')
-rw-r--r--rbutil/rbutilqt/tts.cpp707
1 files changed, 370 insertions, 337 deletions
diff --git a/rbutil/rbutilqt/tts.cpp b/rbutil/rbutilqt/tts.cpp
index 277022b9a1..d55ba9e739 100644
--- a/rbutil/rbutilqt/tts.cpp
+++ b/rbutil/rbutilqt/tts.cpp
@@ -18,11 +18,16 @@
18 ****************************************************************************/ 18 ****************************************************************************/
19 19
20#include "tts.h" 20#include "tts.h"
21#include "utils.h"
22/*********************************************************************
23* TTS Base
24**********************************************************************/
25QMap<QString,QString> TTSBase::ttsList;
21 26
27TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent)
28{
22 29
23// static variables 30}
24QMap<QString,QString> TTSBase::ttsList;
25QMap<QString,TTSBase*> TTSBase::ttsCache;
26 31
27// static functions 32// static functions
28void TTSBase::initTTSList() 33void TTSBase::initTTSList()
@@ -39,35 +44,29 @@ void TTSBase::initTTSList()
39} 44}
40 45
41// function to get a specific encoder 46// function to get a specific encoder
42TTSBase* TTSBase::getTTS(QString ttsName) 47TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName)
43{ 48{
44 // check cache 49
45 if(ttsCache.contains(ttsName))
46 return ttsCache.value(ttsName);
47
48 TTSBase* tts; 50 TTSBase* tts;
49#if defined(Q_OS_WIN) 51#if defined(Q_OS_WIN)
50 if(ttsName == "sapi") 52 if(ttsName == "sapi")
51 { 53 {
52 tts = new TTSSapi(); 54 tts = new TTSSapi(parent);
53 ttsCache[ttsName] = tts;
54 return tts; 55 return tts;
55 } 56 }
56 else 57 else
57#endif 58#endif
58#if defined(Q_OS_LINUX) 59#if defined(Q_OS_LINUX)
59 if (ttsName == "festival") 60 if (ttsName == "festival")
60 { 61 {
61 tts = new TTSFestival(); 62 tts = new TTSFestival(parent);
62 ttsCache[ttsName] = tts; 63 return tts;
63 return tts;
64 } 64 }
65 else 65 else
66#endif 66#endif
67 if (true) // fix for OS other than WIN or LINUX 67 if (true) // fix for OS other than WIN or LINUX
68 { 68 {
69 tts = new TTSExes(ttsName); 69 tts = new TTSExes(ttsName,parent);
70 ttsCache[ttsName] = tts;
71 return tts; 70 return tts;
72 } 71 }
73} 72}
@@ -90,69 +89,41 @@ QString TTSBase::getTTSName(QString tts)
90 return ttsList.value(tts); 89 return ttsList.value(tts);
91} 90}
92 91
93/*********************************************************************
94* TTS Base
95**********************************************************************/
96TTSBase::TTSBase(): QObject()
97{
98
99}
100 92
101/********************************************************************* 93/*********************************************************************
102* General TTS Exes 94* General TTS Exes
103**********************************************************************/ 95**********************************************************************/
104TTSExes::TTSExes(QString name) : TTSBase() 96TTSExes::TTSExes(QString name,QObject* parent) : TTSBase(parent)
105{ 97{
106 m_name = name; 98 m_name = name;
107 99
108 m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; 100 m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\"";
109 m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; 101 m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\"";
110 m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; 102 m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\"";
111 103
112} 104}
113 105
114void TTSExes::setCfg(RbSettings* sett) 106void TTSExes::generateSettings()
115{ 107{
116 // call function of base class 108 QString exepath =settings->subValue(m_name,RbSettings::TtsPath).toString();
117 TTSBase::setCfg(sett); 109 if(exepath == "") exepath = findExecutable(m_name);
118 110
119 // if the config isnt OK, try to autodetect 111 insertSetting(eEXEPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to TTS engine:",exepath,EncTtsSetting::eBROWSEBTN));
120 if(!configOk()) 112 insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,"TTS enginge options:",settings->subValue(m_name,RbSettings::TtsOptions)));
121 { 113}
122 QString exepath;
123 //try autodetect tts
124#if defined(Q_OS_LINUX) || defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
125 QStringList path = QString(getenv("PATH")).split(":", QString::SkipEmptyParts);
126#elif defined(Q_OS_WIN)
127 QStringList path = QString(getenv("PATH")).split(";", QString::SkipEmptyParts);
128#endif
129 qDebug() << path;
130 for(int i = 0; i < path.size(); i++)
131 {
132 QString executable = QDir::fromNativeSeparators(path.at(i)) + "/" + m_name;
133#if defined(Q_OS_WIN)
134 executable += ".exe";
135 QStringList ex = executable.split("\"", QString::SkipEmptyParts);
136 executable = ex.join("");
137#endif
138 qDebug() << executable;
139 if(QFileInfo(executable).isExecutable())
140 {
141 exepath= QDir::toNativeSeparators(executable);
142 break;
143 }
144 }
145 settings->setSubValue(m_name, RbSettings::TtsPath, exepath);
146 settings->sync();
147 }
148 114
115void TTSExes::saveSettings()
116{
117 settings->setSubValue(m_name,RbSettings::TtsPath,getSetting(eEXEPATH)->current().toString());
118 settings->setSubValue(m_name,RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString());
119 settings->sync();
149} 120}
150 121
151bool TTSExes::start(QString *errStr) 122bool TTSExes::start(QString *errStr)
152{ 123{
153 m_TTSexec = settings->subValue(m_name, RbSettings::TtsPath).toString(); 124 m_TTSexec = settings->subValue(m_name,RbSettings::TtsPath).toString();
154 m_TTSOpts = settings->subValue(m_name, RbSettings::TtsOptions).toString(); 125 m_TTSOpts = settings->subValue(m_name,RbSettings::TtsOptions).toString();
155 126
156 m_TTSTemplate = m_TemplateMap.value(m_name); 127 m_TTSTemplate = m_TemplateMap.value(m_name);
157 128
158 QFileInfo tts(m_TTSexec); 129 QFileInfo tts(m_TTSexec);
@@ -169,7 +140,7 @@ bool TTSExes::start(QString *errStr)
169 140
170TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) 141TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr)
171{ 142{
172 (void) errStr; 143 (void) errStr;
173 QString execstring = m_TTSTemplate; 144 QString execstring = m_TTSTemplate;
174 145
175 execstring.replace("%exe",m_TTSexec); 146 execstring.replace("%exe",m_TTSexec);
@@ -182,57 +153,84 @@ TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr)
182 153
183} 154}
184 155
185void TTSExes::showCfg()
186{
187#ifndef CONSOLE
188 TTSExesGui gui;
189#else
190 TTSExesGuiCli gui;
191#endif
192 gui.setCfg(settings);
193 gui.showCfg(m_name);
194}
195
196bool TTSExes::configOk() 156bool TTSExes::configOk()
197{ 157{
198 QString path = settings->subValue(m_name, RbSettings::TtsPath).toString(); 158 QString path = settings->subValue(m_name,RbSettings::TtsPath).toString();
199 159
200 if (QFileInfo(path).exists()) 160 if (QFileInfo(path).exists())
201 return true; 161 return true;
202 162
203 return false; 163 return false;
204} 164}
205 165
206/********************************************************************* 166/*********************************************************************
207* TTS Sapi 167* TTS Sapi
208**********************************************************************/ 168**********************************************************************/
209TTSSapi::TTSSapi() : TTSBase() 169TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent)
210{ 170{
211 m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\""; 171 m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\"";
212 defaultLanguage ="english"; 172 defaultLanguage ="english";
213 m_sapi4 =false; 173 m_sapi4 =false;
214} 174}
215 175
176void TTSSapi::generateSettings()
177{
178 // language
179 QStringList languages = settings->languages();
180 languages.sort();
181 EncTtsSetting* setting =new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,"Language:",settings->subValue("sapi",RbSettings::TtsLanguage),languages);
182 connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceList()));
183 insertSetting(eLANGUAGE,setting);
184 // voice
185 setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,"Voice:",settings->subValue("sapi",RbSettings::TtsVoice),getVoiceList(settings->subValue("sapi",RbSettings::TtsLanguage).toString()),EncTtsSetting::eREFRESHBTN);
186 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
187 insertSetting(eVOICE,setting);
188 //speed
189 insertSetting(eSPEED,new EncTtsSetting(this,EncTtsSetting::eINT,"Speed:",settings->subValue("sapi",RbSettings::TtsSpeed),-10,10));
190 // options
191 insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Options:",settings->subValue("sapi",RbSettings::TtsOptions)));
216 192
217bool TTSSapi::start(QString *errStr) 193}
194
195void TTSSapi::saveSettings()
196{
197 //save settings in user config
198 settings->setSubValue("sapi",RbSettings::TtsLanguage,getSetting(eLANGUAGE)->current().toString());
199 settings->setSubValue("sapi",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
200 settings->setSubValue("sapi",RbSettings::TtsSpeed,getSetting(eSPEED)->current().toInt());
201 settings->setSubValue("sapi",RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString());
202
203 settings->sync();
204}
205
206void TTSSapi::updateVoiceList()
218{ 207{
208 qDebug() << "update voiceList";
209 QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString());
210 getSetting(eVOICE)->setList(voiceList);
211 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
212 else getSetting(eVOICE)->setCurrent("");
213}
214
215bool TTSSapi::start(QString *errStr)
216{
219 217
220 m_TTSOpts = settings->subValue("sapi", RbSettings::TtsOptions).toString(); 218 m_TTSOpts = settings->subValue("sapi",RbSettings::TtsOptions).toString();
221 m_TTSLanguage =settings->subValue("sapi", RbSettings::TtsLanguage).toString(); 219 m_TTSLanguage =settings->subValue("sapi",RbSettings::TtsLanguage).toString();
222 m_TTSVoice=settings->subValue("sapi", RbSettings::TtsVoice).toString(); 220 m_TTSVoice=settings->subValue("sapi",RbSettings::TtsVoice).toString();
223 m_TTSSpeed=QString("%1").arg(settings->subValue("sapi", RbSettings::TtsSpeed).toInt()); 221 m_TTSSpeed=settings->subValue("sapi",RbSettings::TtsSpeed).toString();
224 m_sapi4 = settings->value(RbSettings::TtsUseSapi4).toBool(); 222 m_sapi4 = settings->subValue("sapi",RbSettings::TtsUseSapi4).toBool();
225 223
226 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); 224 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
227 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); 225 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
228 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; 226 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
229 227
230 QFileInfo tts(m_TTSexec); 228 QFileInfo tts(m_TTSexec);
231 if(!tts.exists()) 229 if(!tts.exists())
232 { 230 {
233 *errStr = tr("Could not copy the Sapi-script"); 231 *errStr = tr("Could not copy the Sapi-script");
234 return false; 232 return false;
235 } 233 }
236 // create the voice process 234 // create the voice process
237 QString execstring = m_TTSTemplate; 235 QString execstring = m_TTSTemplate;
238 execstring.replace("%exe",m_TTSexec); 236 execstring.replace("%exe",m_TTSexec);
@@ -240,31 +238,31 @@ bool TTSSapi::start(QString *errStr)
240 execstring.replace("%lang",m_TTSLanguage); 238 execstring.replace("%lang",m_TTSLanguage);
241 execstring.replace("%voice",m_TTSVoice); 239 execstring.replace("%voice",m_TTSVoice);
242 execstring.replace("%speed",m_TTSSpeed); 240 execstring.replace("%speed",m_TTSSpeed);
243 241
244 if(m_sapi4) 242 if(m_sapi4)
245 execstring.append(" /sapi4 "); 243 execstring.append(" /sapi4 ");
246 244
247 qDebug() << "init" << execstring; 245 qDebug() << "init" << execstring;
248 voicescript = new QProcess(NULL); 246 voicescript = new QProcess(NULL);
249 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error())); 247 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
250 248
251 voicescript->start(execstring); 249 voicescript->start(execstring);
252 if(!voicescript->waitForStarted()) 250 if(!voicescript->waitForStarted())
253 { 251 {
254 *errStr = tr("Could not start the Sapi-script"); 252 *errStr = tr("Could not start the Sapi-script");
255 return false; 253 return false;
256 } 254 }
257 255
258 if(!voicescript->waitForReadyRead(300)) 256 if(!voicescript->waitForReadyRead(300))
259 { 257 {
260 *errStr = voicescript->readAllStandardError(); 258 *errStr = voicescript->readAllStandardError();
261 if(*errStr != "") 259 if(*errStr != "")
262 return false; 260 return false;
263 } 261 }
264 262
265 voicestream = new QTextStream(voicescript); 263 voicestream = new QTextStream(voicescript);
266 voicestream->setCodec("UTF16-LE"); 264 voicestream->setCodec("UTF16-LE");
267 265
268 return true; 266 return true;
269} 267}
270 268
@@ -272,14 +270,14 @@ bool TTSSapi::start(QString *errStr)
272QStringList TTSSapi::getVoiceList(QString language) 270QStringList TTSSapi::getVoiceList(QString language)
273{ 271{
274 QStringList result; 272 QStringList result;
275 273
276 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); 274 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
277 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; 275 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
278 276
279 QFileInfo tts(m_TTSexec); 277 QFileInfo tts(m_TTSexec);
280 if(!tts.exists()) 278 if(!tts.exists())
281 return result; 279 return result;
282 280
283 // create the voice process 281 // create the voice process
284 QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices"; 282 QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices";
285 execstring.replace("%exe",m_TTSexec); 283 execstring.replace("%exe",m_TTSexec);
@@ -287,34 +285,34 @@ QStringList TTSSapi::getVoiceList(QString language)
287 285
288 if(settings->value(RbSettings::TtsUseSapi4).toBool()) 286 if(settings->value(RbSettings::TtsUseSapi4).toBool())
289 execstring.append(" /sapi4 "); 287 execstring.append(" /sapi4 ");
290 288
291 qDebug() << "init" << execstring; 289 qDebug() << "init" << execstring;
292 voicescript = new QProcess(NULL); 290 voicescript = new QProcess(NULL);
293 voicescript->start(execstring); 291 voicescript->start(execstring);
294 qDebug() << "wait for started"; 292 qDebug() << "wait for started";
295 if(!voicescript->waitForStarted()) 293 if(!voicescript->waitForStarted())
296 return result; 294 return result;
297 voicescript->closeWriteChannel(); 295 voicescript->closeWriteChannel();
298 voicescript->waitForReadyRead(); 296 voicescript->waitForReadyRead();
299 297
300 QString dataRaw = voicescript->readAllStandardError().data(); 298 QString dataRaw = voicescript->readAllStandardError().data();
301 result = dataRaw.split(",",QString::SkipEmptyParts); 299 result = dataRaw.split(",",QString::SkipEmptyParts);
302 result.sort(); 300 if(result.size() > 0)
303 result.removeFirst();
304 for(int i = 0; i< result.size();i++)
305 { 301 {
306 result[i] = result.at(i).simplified(); 302 result.sort();
307 } 303 result.removeFirst();
308 304 for(int i = 0; i< result.size();i++)
309 305 {
306 result[i] = result.at(i).simplified();
307 }
308 }
309
310 delete voicescript; 310 delete voicescript;
311 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", 311 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner
312 QFile::ReadOwner |QFile::WriteOwner |QFile::ExeOwner 312 |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser
313 |QFile::ReadUser |QFile::WriteUser |QFile::ExeUser 313 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup
314 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup 314 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther );
315 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther);
316 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); 315 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
317
318 return result; 316 return result;
319} 317}
320 318
@@ -322,7 +320,7 @@ QStringList TTSSapi::getVoiceList(QString language)
322 320
323TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) 321TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr)
324{ 322{
325 (void) errStr; 323 (void) errStr;
326 QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; 324 QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n";
327 qDebug() << "voicing" << query; 325 qDebug() << "voicing" << query;
328 *voicestream << query; 326 *voicestream << query;
@@ -333,37 +331,24 @@ TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr)
333} 331}
334 332
335bool TTSSapi::stop() 333bool TTSSapi::stop()
336{ 334{
337 335
338 *voicestream << "QUIT\r\n"; 336 *voicestream << "QUIT\r\n";
339 voicestream->flush(); 337 voicestream->flush();
340 voicescript->waitForFinished(); 338 voicescript->waitForFinished();
341 delete voicestream; 339 delete voicestream;
342 delete voicescript; 340 delete voicescript;
343 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", 341 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner
344 QFile::ReadOwner |QFile::WriteOwner |QFile::ExeOwner 342 |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser
345 |QFile::ReadUser |QFile::WriteUser |QFile::ExeUser 343 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup
346 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup 344 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther );
347 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther);
348 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); 345 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
349 return true; 346 return true;
350} 347}
351 348
352
353void TTSSapi::showCfg()
354{
355#ifndef CONSOLE
356 TTSSapiGui gui(this);
357#else
358 TTSSapiGuiCli gui(this);
359#endif
360 gui.setCfg(settings);
361 gui.showCfg();
362}
363
364bool TTSSapi::configOk() 349bool TTSSapi::configOk()
365{ 350{
366 if(settings->subValue("sapi", RbSettings::TtsVoice).toString().isEmpty()) 351 if(settings->subValue("sapi",RbSettings::TtsVoice).toString().isEmpty())
367 return false; 352 return false;
368 return true; 353 return true;
369} 354}
@@ -372,252 +357,300 @@ bool TTSSapi::configOk()
372 **********************************************************************/ 357 **********************************************************************/
373TTSFestival::~TTSFestival() 358TTSFestival::~TTSFestival()
374{ 359{
375 stop(); 360 stop();
376} 361}
377 362
378void TTSFestival::startServer() 363void TTSFestival::generateSettings()
379{ 364{
380 if(!configOk()) 365 // server path
381 return; 366 QString exepath = settings->subValue("festival-server",RbSettings::TtsPath).toString();
367 if(exepath == "" ) exepath = findExecutable("festival");
368 insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN));
369
370 // client path
371 QString clientpath = settings->subValue("festival-client",RbSettings::TtsPath).toString();
372 if(clientpath == "" ) clientpath = findExecutable("festival_client");
373 insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival client:",clientpath,EncTtsSetting::eBROWSEBTN));
374
375 // voice
376 EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,"Voice:",settings->subValue("festival",RbSettings::TtsVoice),getVoiceList(exepath),EncTtsSetting::eREFRESHBTN);
377 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
378 connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
379 insertSetting(eVOICE,setting);
380
381 //voice description
382 setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,"Voice description:","",EncTtsSetting::eREFRESHBTN);
383 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription()));
384 insertSetting(eVOICEDESC,setting);
385}
382 386
383 QStringList paths = settings->subValue("festival", RbSettings::TtsPath).toString().split(":"); 387void TTSFestival::saveSettings()
388{
389 //save settings in user config
390 settings->setSubValue("festival-server",RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString());
391 settings->setSubValue("festival-client",RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString());
392 settings->setSubValue("festival",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
393
394 settings->sync();
395}
384 396
385 serverProcess.start(QString("%1 --server").arg(paths[0])); 397void TTSFestival::updateVoiceDescription()
386 serverProcess.waitForStarted(); 398{
399 // get voice Info with current voice and path
400 QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),getSetting(eSERVERPATH)->current().toString());
401 getSetting(eVOICEDESC)->setCurrent(info);
402}
387 403
388 queryServer("(getpid)"); 404void TTSFestival::clearVoiceDescription()
389 if(serverProcess.state() == QProcess::Running) 405{
390 qDebug() << "Festival is up and running"; 406 getSetting(eVOICEDESC)->setCurrent("");
391 else
392 qDebug() << "Festival failed to start";
393} 407}
394 408
395void TTSFestival::ensureServerRunning() 409void TTSFestival::updateVoiceList()
396{ 410{
397 if(serverProcess.state() != QProcess::Running) 411 QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString());
398 { 412 getSetting(eVOICE)->setList(voiceList);
399 // least common denominator for all the server startup code paths 413 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
400 QProgressDialog progressDialog(tr(""), tr(""), 0, 0); 414 else getSetting(eVOICE)->setCurrent("");
401 progressDialog.setWindowTitle(tr("Starting festival")); 415}
402 progressDialog.setModal(true); 416
403 progressDialog.setLabel(0); 417void TTSFestival::startServer(QString path)
404 progressDialog.setCancelButton(0); 418{
405 progressDialog.show(); 419 if(!configOk())
420 return;
421
422 if(path == "")
423 path = settings->subValue("festival-server",RbSettings::TtsPath).toString();
424
425 serverProcess.start(QString("%1 --server").arg(path));
426 serverProcess.waitForStarted();
406 427
407 QApplication::processEvents(); // actually show the dialog 428 queryServer("(getpid)",300,path);
429 if(serverProcess.state() == QProcess::Running)
430 qDebug() << "Festival is up and running";
431 else
432 qDebug() << "Festival failed to start";
433}
408 434
409 startServer(); 435void TTSFestival::ensureServerRunning(QString path)
436{
437 if(serverProcess.state() != QProcess::Running)
438 {
439 startServer(path);
410 } 440 }
411} 441}
412 442
413bool TTSFestival::start(QString* errStr) 443bool TTSFestival::start(QString* errStr)
414{ 444{
415 (void) errStr; 445 (void) errStr;
416 ensureServerRunning(); 446 ensureServerRunning();
417 if (!settings->subValue("festival", RbSettings::TtsVoice).toString().isEmpty()) 447 if (!settings->subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
418 queryServer(QString("(voice.select '%1)") 448 queryServer(QString("(voice.select '%1)")
419 .arg(settings->subValue("festival", RbSettings::TtsVoice).toString())); 449 .arg(settings->subValue("festival", RbSettings::TtsVoice).toString()));
420 450
421 return true; 451 return true;
422} 452}
423 453
424bool TTSFestival::stop() 454bool TTSFestival::stop()
425{ 455{
426 serverProcess.terminate(); 456 serverProcess.terminate();
427 serverProcess.kill(); 457 serverProcess.kill();
428 458
429 return true; 459 return true;
430} 460}
431 461
432TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) 462TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
433{ 463{
434 qDebug() << text << "->" << wavfile; 464 qDebug() << text << "->" << wavfile;
435 465
436 QStringList paths = settings->subValue("festival", RbSettings::TtsPath).toString().split(":"); 466 QString path = settings->subValue("festival-client",RbSettings::TtsPath).toString();
437 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(paths[1]).arg(wavfile); 467 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path).arg(wavfile);
438 qDebug() << cmd; 468 qDebug() << cmd;
439 469
440 QProcess clientProcess; 470 QProcess clientProcess;
441 clientProcess.start(cmd); 471 clientProcess.start(cmd);
442 clientProcess.write(QString("%1.\n").arg(text).toAscii()); 472 clientProcess.write(QString("%1.\n").arg(text).toAscii());
443 clientProcess.waitForBytesWritten(); 473 clientProcess.waitForBytesWritten();
444 clientProcess.closeWriteChannel(); 474 clientProcess.closeWriteChannel();
445 clientProcess.waitForReadyRead(); 475 clientProcess.waitForReadyRead();
446 QString response = clientProcess.readAll(); 476 QString response = clientProcess.readAll();
447 response = response.trimmed(); 477 response = response.trimmed();
448 if(!response.contains("Utterance")) 478 if(!response.contains("Utterance"))
449 { 479 {
450 qDebug() << "Could not voice string: " << response; 480 qDebug() << "Could not voice string: " << response;
451 *errStr = tr("engine could not voice string"); 481 *errStr = tr("engine could not voice string");
452 return Warning; 482 return Warning;
453 /* do not stop the voicing process because of a single string 483 /* do not stop the voicing process because of a single string
454 TODO: needs proper settings */ 484 TODO: needs proper settings */
455 } 485 }
456 clientProcess.closeReadChannel(QProcess::StandardError); 486 clientProcess.closeReadChannel(QProcess::StandardError);
457 clientProcess.closeReadChannel(QProcess::StandardOutput); 487 clientProcess.closeReadChannel(QProcess::StandardOutput);
458 clientProcess.terminate(); 488 clientProcess.terminate();
459 clientProcess.kill(); 489 clientProcess.kill();
460 490
461 return NoError; 491 return NoError;
462} 492}
463 493
464bool TTSFestival::configOk() 494bool TTSFestival::configOk()
465{ 495{
466 QStringList paths = settings->subValue("festival", RbSettings::TtsPath).toString().split(":"); 496 QString serverPath = settings->subValue("festival-server",RbSettings::TtsPath).toString();
467 if(paths.size() != 2) 497 QString clientPath = settings->subValue("festival-client",RbSettings::TtsVoice).toString();
468 return false; 498
469 bool ret = QFileInfo(paths[0]).isExecutable() && 499 bool ret = QFileInfo(serverPath).isExecutable() &&
470 QFileInfo(paths[1]).isExecutable(); 500 QFileInfo(clientPath).isExecutable();
471 if(settings->subValue("festival", RbSettings::TtsVoice).toString().size() > 0 501 if(settings->subValue("festival",RbSettings::TtsVoice).toString().size() > 0 && voices.size() > 0)
472 && voices.size() > 0) 502 ret = ret && (voices.indexOf(settings->subValue("festival",RbSettings::TtsVoice).toString()) != -1);
473 ret = ret && (voices.indexOf(settings->subValue("festival", 503 return ret;
474 RbSettings::TtsVoice).toString()) != -1);
475 return ret;
476}
477
478void TTSFestival::showCfg()
479{
480#ifndef CONSOLE
481 TTSFestivalGui gui(this);
482#endif
483 gui.setCfg(settings);
484 gui.showCfg();
485} 504}
486 505
487QStringList TTSFestival::getVoiceList() 506QStringList TTSFestival::getVoiceList(QString path)
488{ 507{
489 if(!configOk()) 508 if(!configOk())
490 return QStringList(); 509 return QStringList();
491 510
492 if(voices.size() > 0) 511 if(voices.size() > 0)
493 { 512 {
494 qDebug() << "Using voice cache"; 513 qDebug() << "Using voice cache";
495 return voices; 514 return voices;
496 } 515 }
497 QString response = queryServer("(voice.list)"); 516
498 517 QString response = queryServer("(voice.list)",3000,path);
499 // get the 2nd line. It should be (<voice_name>, <voice_name>) 518
500 response = response.mid(response.indexOf('\n') + 1, -1); 519 // get the 2nd line. It should be (<voice_name>, <voice_name>)
501 response = response.left(response.indexOf('\n')).trimmed(); 520 response = response.mid(response.indexOf('\n') + 1, -1);
502 521 response = response.left(response.indexOf('\n')).trimmed();
503 voices = response.mid(1, response.size()-2).split(' '); 522
504 523 voices = response.mid(1, response.size()-2).split(' ');
505 voices.sort(); 524
506 if (voices.size() == 1 && voices[0].size() == 0) 525 voices.sort();
507 voices.removeAt(0); 526 if (voices.size() == 1 && voices[0].size() == 0)
508 if (voices.size() > 0) 527 voices.removeAt(0);
509 qDebug() << "Voices: " << voices; 528 if (voices.size() > 0)
510 else 529 qDebug() << "Voices: " << voices;
511 qDebug() << "No voices."; 530 else
512 return voices; 531 qDebug() << "No voices.";
532
533 return voices;
513} 534}
514 535
515QString TTSFestival::getVoiceInfo(QString voice) 536QString TTSFestival::getVoiceInfo(QString voice,QString path)
516{ 537{
517 if(!configOk()) 538 if(!configOk())
518 return ""; 539 return "";
519 540
520 if(!getVoiceList().contains(voice)) 541 if(!getVoiceList().contains(voice))
521 return ""; 542 return "";
522 543
523 if(voiceDescriptions.contains(voice)) 544 if(voiceDescriptions.contains(voice))
524 return voiceDescriptions[voice]; 545 return voiceDescriptions[voice];
525 546
526 QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000); 547 QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path);
527 548
528 if (response == "") 549 if (response == "")
529 { 550 {
530 voiceDescriptions[voice]=tr("No description available"); 551 voiceDescriptions[voice]=tr("No description available");
531 } 552 }
532 else 553 else
533 { 554 {
534 response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard)); 555 response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard));
535 qDebug() << "voiceInfo w/o descr: " << response; 556 qDebug() << "voiceInfo w/o descr: " << response;
536 response = response.remove(')'); 557 response = response.remove(')');
537 QStringList responseLines = response.split('(', QString::SkipEmptyParts); 558 QStringList responseLines = response.split('(', QString::SkipEmptyParts);
538 responseLines.removeAt(0); // the voice name itself 559 responseLines.removeAt(0); // the voice name itself
539 560
540 QString description; 561 QString description;
541 foreach(QString line, responseLines) 562 foreach(QString line, responseLines)
542 { 563 {
543 line = line.remove('('); 564 line = line.remove('(');
544 line = line.simplified(); 565 line = line.simplified();
545 566
546 line[0] = line[0].toUpper(); // capitalize the key 567 line[0] = line[0].toUpper(); // capitalize the key
547 568
548 int firstSpace = line.indexOf(' '); 569 int firstSpace = line.indexOf(' ');
549 if (firstSpace > 0) 570 if (firstSpace > 0)
550 { 571 {
551 line = line.insert(firstSpace, ':'); // add a colon between the key and the value 572 line = line.insert(firstSpace, ':'); // add a colon between the key and the value
552 line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value 573 line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value
553 } 574 }
554 575
555 description += line + "\n"; 576 description += line + "\n";
556 } 577 }
557 voiceDescriptions[voice] = description.trimmed(); 578 voiceDescriptions[voice] = description.trimmed();
558 } 579 }
559 return voiceDescriptions[voice]; 580
581 return voiceDescriptions[voice];
560} 582}
561 583
562QString TTSFestival::queryServer(QString query, int timeout) 584QString TTSFestival::queryServer(QString query, int timeout,QString path)
563{ 585{
564 if(!configOk()) 586 if(!configOk())
565 return ""; 587 return "";
566 588
567 ensureServerRunning(); 589 // this operation could take some time
568 590 emit busy();
569 qDebug() << "queryServer with " << query; 591
570 QString response; 592 ensureServerRunning(path);
571 593
572 QDateTime endTime; 594 qDebug() << "queryServer with " << query;
573 if(timeout > 0) 595 QString response;
574 endTime = QDateTime::currentDateTime().addMSecs(timeout); 596
575 597 QDateTime endTime;
576 /* Festival is *extremely* unreliable. Although at this 598 if(timeout > 0)
577 * point we are sure that SIOD is accepting commands, 599 endTime = QDateTime::currentDateTime().addMSecs(timeout);
578 * we might end up with an empty response. Hence, the loop. 600
579 */ 601 /* Festival is *extremely* unreliable. Although at this
580 while(true) 602 * point we are sure that SIOD is accepting commands,
581 { 603 * we might end up with an empty response. Hence, the loop.
582 QApplication::processEvents(QEventLoop::AllEvents, 50); 604 */
583 QTcpSocket socket; 605 while(true)
584 606 {
585 socket.connectToHost("localhost", 1314); 607 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
586 socket.waitForConnected(); 608 QTcpSocket socket;
587 609
588 if(socket.state() == QAbstractSocket::ConnectedState) 610 socket.connectToHost("localhost", 1314);
611 socket.waitForConnected();
612
613 if(socket.state() == QAbstractSocket::ConnectedState)
614 {
615 socket.write(QString("%1\n").arg(query).toAscii());
616 socket.waitForBytesWritten();
617 socket.waitForReadyRead();
618
619 response = socket.readAll().trimmed();
620
621 if (response != "LP" && response != "")
622 break;
623 }
624 socket.abort();
625 socket.disconnectFromHost();
626
627 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
589 { 628 {
590 socket.write(QString("%1\n").arg(query).toAscii()); 629 emit busyEnd();
591 socket.waitForBytesWritten(); 630 return "";
592 socket.waitForReadyRead();
593
594 response = socket.readAll().trimmed();
595
596 if (response != "LP" && response != "")
597 break;
598 } 631 }
599 socket.abort(); 632 /* make sure we wait a little as we don't want to flood the server with requests */
600 socket.disconnectFromHost(); 633 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
601 634 while(QDateTime::currentDateTime() < tmpEndTime)
602 if(timeout > 0 && QDateTime::currentDateTime() >= endTime) 635 QCoreApplication::processEvents(QEventLoop::AllEvents);
603 return ""; 636 }
604 637 if(response == "nil")
605 /* make sure we wait a little as we don't want to flood the server with requests */
606 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
607 while(QDateTime::currentDateTime() < tmpEndTime)
608 QApplication::processEvents(QEventLoop::AllEvents);
609 }
610 if(response == "nil")
611 return "";
612
613 QStringList lines = response.split('\n');
614 if(lines.size() > 2)
615 { 638 {
616 lines.removeFirst(); 639 emit busyEnd();
617 lines.removeLast(); 640 return "";
618 } 641 }
619 else 642
620 qDebug() << "Response too short: " << response; 643 QStringList lines = response.split('\n');
644 if(lines.size() > 2)
645 {
646 lines.removeFirst();
647 lines.removeLast();
648 }
649 else
650 qDebug() << "Response too short: " << response;
651
652 emit busyEnd();
621 return lines.join("\n"); 653 return lines.join("\n");
654
622} 655}
623 656