summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominik Wenger <domonoky@googlemail.com>2009-10-13 19:54:27 +0000
committerDominik Wenger <domonoky@googlemail.com>2009-10-13 19:54:27 +0000
commit3215c09462fe90c35dc6d9e1979e970280b052b1 (patch)
tree1fc7be334470d6d21b746a40bc7dbe440a722d35
parent31c18116de2105deed8aa8dab84bf08a930f2112 (diff)
downloadrockbox-3215c09462fe90c35dc6d9e1979e970280b052b1.tar.gz
rockbox-3215c09462fe90c35dc6d9e1979e970280b052b1.zip
rbutil: split tts.cpp/h into individual files.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@23158 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--rbutil/rbutilqt/base/talkgenerator.h2
-rw-r--r--rbutil/rbutilqt/base/tts.cpp666
-rw-r--r--rbutil/rbutilqt/base/tts.h180
-rw-r--r--rbutil/rbutilqt/base/ttsbase.cpp92
-rw-r--r--rbutil/rbutilqt/base/ttsbase.h74
-rw-r--r--rbutil/rbutilqt/base/ttsexes.cpp94
-rw-r--r--rbutil/rbutilqt/base/ttsexes.h55
-rw-r--r--rbutil/rbutilqt/base/ttsfestival.cpp325
-rw-r--r--rbutil/rbutilqt/base/ttsfestival.h67
-rw-r--r--rbutil/rbutilqt/base/ttssapi.cpp213
-rw-r--r--rbutil/rbutilqt/base/ttssapi.h72
-rw-r--r--rbutil/rbutilqt/configure.cpp2
-rw-r--r--rbutil/rbutilqt/rbutilqt.pro10
13 files changed, 1002 insertions, 850 deletions
diff --git a/rbutil/rbutilqt/base/talkgenerator.h b/rbutil/rbutilqt/base/talkgenerator.h
index eb08df8d51..b139c1879b 100644
--- a/rbutil/rbutilqt/base/talkgenerator.h
+++ b/rbutil/rbutilqt/base/talkgenerator.h
@@ -27,7 +27,7 @@
27#include "progressloggerinterface.h" 27#include "progressloggerinterface.h"
28 28
29#include "encoders.h" 29#include "encoders.h"
30#include "tts.h" 30#include "ttsbase.h"
31 31
32//! \brief Talk generator, generates .wav and .talk files out of a list. 32//! \brief Talk generator, generates .wav and .talk files out of a list.
33class TalkGenerator :public QObject 33class TalkGenerator :public QObject
diff --git a/rbutil/rbutilqt/base/tts.cpp b/rbutil/rbutilqt/base/tts.cpp
deleted file mode 100644
index 852edc33d0..0000000000
--- a/rbutil/rbutilqt/base/tts.cpp
+++ /dev/null
@@ -1,666 +0,0 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2007 by Dominik Wenger
10 * $Id$
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#include "tts.h"
21#include "utils.h"
22#include "rbsettings.h"
23/*********************************************************************
24* TTS Base
25**********************************************************************/
26QMap<QString,QString> TTSBase::ttsList;
27
28TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent)
29{
30
31}
32
33// static functions
34void TTSBase::initTTSList()
35{
36 ttsList["espeak"] = "Espeak TTS Engine";
37 ttsList["flite"] = "Flite TTS Engine";
38 ttsList["swift"] = "Swift TTS Engine";
39#if defined(Q_OS_WIN)
40 ttsList["sapi"] = "Sapi TTS Engine";
41#endif
42#if defined(Q_OS_LINUX)
43 ttsList["festival"] = "Festival TTS Engine";
44#endif
45}
46
47// function to get a specific encoder
48TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName)
49{
50
51 TTSBase* tts;
52#if defined(Q_OS_WIN)
53 if(ttsName == "sapi")
54 {
55 tts = new TTSSapi(parent);
56 return tts;
57 }
58 else
59#endif
60#if defined(Q_OS_LINUX)
61 if (ttsName == "festival")
62 {
63 tts = new TTSFestival(parent);
64 return tts;
65 }
66 else
67#endif
68 if (true) // fix for OS other than WIN or LINUX
69 {
70 tts = new TTSExes(ttsName,parent);
71 return tts;
72 }
73}
74
75// get the list of encoders, nice names
76QStringList TTSBase::getTTSList()
77{
78 // init list if its empty
79 if(ttsList.count() == 0)
80 initTTSList();
81
82 return ttsList.keys();
83}
84
85// get nice name of a specific tts
86QString TTSBase::getTTSName(QString tts)
87{
88 if(ttsList.isEmpty())
89 initTTSList();
90 return ttsList.value(tts);
91}
92
93
94/*********************************************************************
95* General TTS Exes
96**********************************************************************/
97TTSExes::TTSExes(QString name,QObject* parent) : TTSBase(parent)
98{
99 m_name = name;
100
101 m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\"";
102 m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\"";
103 m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\"";
104
105}
106
107void TTSExes::generateSettings()
108{
109 QString exepath =RbSettings::subValue(m_name,RbSettings::TtsPath).toString();
110 if(exepath == "") exepath = findExecutable(m_name);
111
112 insertSetting(eEXEPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,
113 tr("Path to TTS engine:"),exepath,EncTtsSetting::eBROWSEBTN));
114 insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,
115 tr("TTS engine options:"),RbSettings::subValue(m_name,RbSettings::TtsOptions)));
116}
117
118void TTSExes::saveSettings()
119{
120 RbSettings::setSubValue(m_name,RbSettings::TtsPath,getSetting(eEXEPATH)->current().toString());
121 RbSettings::setSubValue(m_name,RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString());
122 RbSettings::sync();
123}
124
125bool TTSExes::start(QString *errStr)
126{
127 m_TTSexec = RbSettings::subValue(m_name,RbSettings::TtsPath).toString();
128 m_TTSOpts = RbSettings::subValue(m_name,RbSettings::TtsOptions).toString();
129
130 m_TTSTemplate = m_TemplateMap.value(m_name);
131
132 QFileInfo tts(m_TTSexec);
133 if(tts.exists())
134 {
135 return true;
136 }
137 else
138 {
139 *errStr = tr("TTS executable not found");
140 return false;
141 }
142}
143
144TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr)
145{
146 (void) errStr;
147 QString execstring = m_TTSTemplate;
148
149 execstring.replace("%exe",m_TTSexec);
150 execstring.replace("%options",m_TTSOpts);
151 execstring.replace("%wavfile",wavfile);
152 execstring.replace("%text",text);
153 //qDebug() << "voicing" << execstring;
154 QProcess::execute(execstring);
155 return NoError;
156
157}
158
159bool TTSExes::configOk()
160{
161 QString path = RbSettings::subValue(m_name,RbSettings::TtsPath).toString();
162
163 if (QFileInfo(path).exists())
164 return true;
165
166 return false;
167}
168
169/*********************************************************************
170* TTS Sapi
171**********************************************************************/
172TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent)
173{
174 m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\"";
175 defaultLanguage ="english";
176 m_sapi4 =false;
177}
178
179void TTSSapi::generateSettings()
180{
181 // language
182 QStringList languages = RbSettings::languages();
183 languages.sort();
184 EncTtsSetting* setting =new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
185 tr("Language:"),RbSettings::subValue("sapi",RbSettings::TtsLanguage),languages);
186 connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceList()));
187 insertSetting(eLANGUAGE,setting);
188 // voice
189 setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
190 tr("Voice:"),RbSettings::subValue("sapi",RbSettings::TtsVoice),getVoiceList(RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString()),EncTtsSetting::eREFRESHBTN);
191 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
192 insertSetting(eVOICE,setting);
193 //speed
194 insertSetting(eSPEED,new EncTtsSetting(this,EncTtsSetting::eINT,
195 tr("Speed:"),RbSettings::subValue("sapi",RbSettings::TtsSpeed),-10,10));
196 // options
197 insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,
198 tr("Options:"),RbSettings::subValue("sapi",RbSettings::TtsOptions)));
199
200}
201
202void TTSSapi::saveSettings()
203{
204 //save settings in user config
205 RbSettings::setSubValue("sapi",RbSettings::TtsLanguage,getSetting(eLANGUAGE)->current().toString());
206 RbSettings::setSubValue("sapi",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
207 RbSettings::setSubValue("sapi",RbSettings::TtsSpeed,getSetting(eSPEED)->current().toInt());
208 RbSettings::setSubValue("sapi",RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString());
209
210 RbSettings::sync();
211}
212
213void TTSSapi::updateVoiceList()
214{
215 qDebug() << "update voiceList";
216 QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString());
217 getSetting(eVOICE)->setList(voiceList);
218 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
219 else getSetting(eVOICE)->setCurrent("");
220}
221
222bool TTSSapi::start(QString *errStr)
223{
224
225 m_TTSOpts = RbSettings::subValue("sapi",RbSettings::TtsOptions).toString();
226 m_TTSLanguage =RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString();
227 m_TTSVoice=RbSettings::subValue("sapi",RbSettings::TtsVoice).toString();
228 m_TTSSpeed=RbSettings::subValue("sapi",RbSettings::TtsSpeed).toString();
229 m_sapi4 = RbSettings::subValue("sapi",RbSettings::TtsUseSapi4).toBool();
230
231 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
232 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
233 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
234
235 QFileInfo tts(m_TTSexec);
236 if(!tts.exists())
237 {
238 *errStr = tr("Could not copy the Sapi-script");
239 return false;
240 }
241 // create the voice process
242 QString execstring = m_TTSTemplate;
243 execstring.replace("%exe",m_TTSexec);
244 execstring.replace("%options",m_TTSOpts);
245 execstring.replace("%lang",m_TTSLanguage);
246 execstring.replace("%voice",m_TTSVoice);
247 execstring.replace("%speed",m_TTSSpeed);
248
249 if(m_sapi4)
250 execstring.append(" /sapi4 ");
251
252 qDebug() << "init" << execstring;
253 voicescript = new QProcess(NULL);
254 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
255
256 voicescript->start(execstring);
257 if(!voicescript->waitForStarted())
258 {
259 *errStr = tr("Could not start the Sapi-script");
260 return false;
261 }
262
263 if(!voicescript->waitForReadyRead(300))
264 {
265 *errStr = voicescript->readAllStandardError();
266 if(*errStr != "")
267 return false;
268 }
269
270 voicestream = new QTextStream(voicescript);
271 voicestream->setCodec("UTF16-LE");
272
273 return true;
274}
275
276
277QStringList TTSSapi::getVoiceList(QString language)
278{
279 QStringList result;
280
281 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
282 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
283
284 QFileInfo tts(m_TTSexec);
285 if(!tts.exists())
286 return result;
287
288 // create the voice process
289 QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices";
290 execstring.replace("%exe",m_TTSexec);
291 execstring.replace("%lang",language);
292
293 if(RbSettings::value(RbSettings::TtsUseSapi4).toBool())
294 execstring.append(" /sapi4 ");
295
296 qDebug() << "init" << execstring;
297 voicescript = new QProcess(NULL);
298 voicescript->start(execstring);
299 qDebug() << "wait for started";
300 if(!voicescript->waitForStarted())
301 return result;
302 voicescript->closeWriteChannel();
303 voicescript->waitForReadyRead();
304
305 QString dataRaw = voicescript->readAllStandardError().data();
306 result = dataRaw.split(",",QString::SkipEmptyParts);
307 if(result.size() > 0)
308 {
309 result.sort();
310 result.removeFirst();
311 for(int i = 0; i< result.size();i++)
312 {
313 result[i] = result.at(i).simplified();
314 }
315 }
316
317 delete voicescript;
318 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner
319 |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser
320 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup
321 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther );
322 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
323 return result;
324}
325
326
327
328TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr)
329{
330 (void) errStr;
331 QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n";
332 qDebug() << "voicing" << query;
333 *voicestream << query;
334 *voicestream << "SYNC\tbla\r\n";
335 voicestream->flush();
336 voicescript->waitForReadyRead();
337 return NoError;
338}
339
340bool TTSSapi::stop()
341{
342
343 *voicestream << "QUIT\r\n";
344 voicestream->flush();
345 voicescript->waitForFinished();
346 delete voicestream;
347 delete voicescript;
348 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner
349 |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser
350 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup
351 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther );
352 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
353 return true;
354}
355
356bool TTSSapi::configOk()
357{
358 if(RbSettings::subValue("sapi",RbSettings::TtsVoice).toString().isEmpty())
359 return false;
360 return true;
361}
362/**********************************************************************
363 * TSSFestival - client-server wrapper
364 **********************************************************************/
365TTSFestival::~TTSFestival()
366{
367 stop();
368}
369
370void TTSFestival::generateSettings()
371{
372 // server path
373 QString exepath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
374 if(exepath == "" ) exepath = findExecutable("festival");
375 insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN));
376
377 // client path
378 QString clientpath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
379 if(clientpath == "" ) clientpath = findExecutable("festival_client");
380 insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,
381 tr("Path to Festival client:"),clientpath,EncTtsSetting::eBROWSEBTN));
382
383 // voice
384 EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
385 tr("Voice:"),RbSettings::subValue("festival",RbSettings::TtsVoice),getVoiceList(exepath),EncTtsSetting::eREFRESHBTN);
386 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
387 connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
388 insertSetting(eVOICE,setting);
389
390 //voice description
391 setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,
392 tr("Voice description:"),"",EncTtsSetting::eREFRESHBTN);
393 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription()));
394 insertSetting(eVOICEDESC,setting);
395}
396
397void TTSFestival::saveSettings()
398{
399 //save settings in user config
400 RbSettings::setSubValue("festival-server",RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString());
401 RbSettings::setSubValue("festival-client",RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString());
402 RbSettings::setSubValue("festival",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
403
404 RbSettings::sync();
405}
406
407void TTSFestival::updateVoiceDescription()
408{
409 // get voice Info with current voice and path
410 QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),getSetting(eSERVERPATH)->current().toString());
411 getSetting(eVOICEDESC)->setCurrent(info);
412}
413
414void TTSFestival::clearVoiceDescription()
415{
416 getSetting(eVOICEDESC)->setCurrent("");
417}
418
419void TTSFestival::updateVoiceList()
420{
421 QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString());
422 getSetting(eVOICE)->setList(voiceList);
423 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
424 else getSetting(eVOICE)->setCurrent("");
425}
426
427void TTSFestival::startServer(QString path)
428{
429 if(!configOk())
430 return;
431
432 if(path == "")
433 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
434
435 serverProcess.start(QString("%1 --server").arg(path));
436 serverProcess.waitForStarted();
437
438 queryServer("(getpid)",300,path);
439 if(serverProcess.state() == QProcess::Running)
440 qDebug() << "Festival is up and running";
441 else
442 qDebug() << "Festival failed to start";
443}
444
445void TTSFestival::ensureServerRunning(QString path)
446{
447 if(serverProcess.state() != QProcess::Running)
448 {
449 startServer(path);
450 }
451}
452
453bool TTSFestival::start(QString* errStr)
454{
455 (void) errStr;
456 ensureServerRunning();
457 if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
458 queryServer(QString("(voice.select '%1)")
459 .arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString()));
460
461 return true;
462}
463
464bool TTSFestival::stop()
465{
466 serverProcess.terminate();
467 serverProcess.kill();
468
469 return true;
470}
471
472TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
473{
474 qDebug() << text << "->" << wavfile;
475
476 QString path = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
477 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path).arg(wavfile);
478 qDebug() << cmd;
479
480 QProcess clientProcess;
481 clientProcess.start(cmd);
482 clientProcess.write(QString("%1.\n").arg(text).toAscii());
483 clientProcess.waitForBytesWritten();
484 clientProcess.closeWriteChannel();
485 clientProcess.waitForReadyRead();
486 QString response = clientProcess.readAll();
487 response = response.trimmed();
488 if(!response.contains("Utterance"))
489 {
490 qDebug() << "Could not voice string: " << response;
491 *errStr = tr("engine could not voice string");
492 return Warning;
493 /* do not stop the voicing process because of a single string
494 TODO: needs proper settings */
495 }
496 clientProcess.closeReadChannel(QProcess::StandardError);
497 clientProcess.closeReadChannel(QProcess::StandardOutput);
498 clientProcess.terminate();
499 clientProcess.kill();
500
501 return NoError;
502}
503
504bool TTSFestival::configOk()
505{
506 QString serverPath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
507 QString clientPath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
508
509 bool ret = QFileInfo(serverPath).isExecutable() &&
510 QFileInfo(clientPath).isExecutable();
511 if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0 && voices.size() > 0)
512 ret = ret && (voices.indexOf(RbSettings::subValue("festival",RbSettings::TtsVoice).toString()) != -1);
513 return ret;
514}
515
516QStringList TTSFestival::getVoiceList(QString path)
517{
518 if(!configOk())
519 return QStringList();
520
521 if(voices.size() > 0)
522 {
523 qDebug() << "Using voice cache";
524 return voices;
525 }
526
527 QString response = queryServer("(voice.list)",3000,path);
528
529 // get the 2nd line. It should be (<voice_name>, <voice_name>)
530 response = response.mid(response.indexOf('\n') + 1, -1);
531 response = response.left(response.indexOf('\n')).trimmed();
532
533 voices = response.mid(1, response.size()-2).split(' ');
534
535 voices.sort();
536 if (voices.size() == 1 && voices[0].size() == 0)
537 voices.removeAt(0);
538 if (voices.size() > 0)
539 qDebug() << "Voices: " << voices;
540 else
541 qDebug() << "No voices.";
542
543 return voices;
544}
545
546QString TTSFestival::getVoiceInfo(QString voice,QString path)
547{
548 if(!configOk())
549 return "";
550
551 if(!getVoiceList().contains(voice))
552 return "";
553
554 if(voiceDescriptions.contains(voice))
555 return voiceDescriptions[voice];
556
557 QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path);
558
559 if (response == "")
560 {
561 voiceDescriptions[voice]=tr("No description available");
562 }
563 else
564 {
565 response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard));
566 qDebug() << "voiceInfo w/o descr: " << response;
567 response = response.remove(')');
568 QStringList responseLines = response.split('(', QString::SkipEmptyParts);
569 responseLines.removeAt(0); // the voice name itself
570
571 QString description;
572 foreach(QString line, responseLines)
573 {
574 line = line.remove('(');
575 line = line.simplified();
576
577 line[0] = line[0].toUpper(); // capitalize the key
578
579 int firstSpace = line.indexOf(' ');
580 if (firstSpace > 0)
581 {
582 line = line.insert(firstSpace, ':'); // add a colon between the key and the value
583 line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value
584 }
585
586 description += line + "\n";
587 }
588 voiceDescriptions[voice] = description.trimmed();
589 }
590
591 return voiceDescriptions[voice];
592}
593
594QString TTSFestival::queryServer(QString query, int timeout,QString path)
595{
596 if(!configOk())
597 return "";
598
599 // this operation could take some time
600 emit busy();
601
602 ensureServerRunning(path);
603
604 qDebug() << "queryServer with " << query;
605 QString response;
606
607 QDateTime endTime;
608 if(timeout > 0)
609 endTime = QDateTime::currentDateTime().addMSecs(timeout);
610
611 /* Festival is *extremely* unreliable. Although at this
612 * point we are sure that SIOD is accepting commands,
613 * we might end up with an empty response. Hence, the loop.
614 */
615 while(true)
616 {
617 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
618 QTcpSocket socket;
619
620 socket.connectToHost("localhost", 1314);
621 socket.waitForConnected();
622
623 if(socket.state() == QAbstractSocket::ConnectedState)
624 {
625 socket.write(QString("%1\n").arg(query).toAscii());
626 socket.waitForBytesWritten();
627 socket.waitForReadyRead();
628
629 response = socket.readAll().trimmed();
630
631 if (response != "LP" && response != "")
632 break;
633 }
634 socket.abort();
635 socket.disconnectFromHost();
636
637 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
638 {
639 emit busyEnd();
640 return "";
641 }
642 /* make sure we wait a little as we don't want to flood the server with requests */
643 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
644 while(QDateTime::currentDateTime() < tmpEndTime)
645 QCoreApplication::processEvents(QEventLoop::AllEvents);
646 }
647 if(response == "nil")
648 {
649 emit busyEnd();
650 return "";
651 }
652
653 QStringList lines = response.split('\n');
654 if(lines.size() > 2)
655 {
656 lines.removeFirst();
657 lines.removeLast();
658 }
659 else
660 qDebug() << "Response too short: " << response;
661
662 emit busyEnd();
663 return lines.join("\n");
664
665}
666
diff --git a/rbutil/rbutilqt/base/tts.h b/rbutil/rbutilqt/base/tts.h
deleted file mode 100644
index f665ed2865..0000000000
--- a/rbutil/rbutilqt/base/tts.h
+++ /dev/null
@@ -1,180 +0,0 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2007 by Dominik Wenger
10 * $Id$
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22
23#ifndef TTS_H
24#define TTS_H
25
26#include <QtCore>
27#include <QProcess>
28#include <QDateTime>
29#include <QRegExp>
30#include <QTcpSocket>
31
32#include "encttssettings.h"
33
34enum TTSStatus{ FatalError, NoError, Warning };
35
36class TTSBase : public EncTtsSettingInterface
37{
38 Q_OBJECT
39 public:
40 TTSBase(QObject *parent);
41 //! Child class should generate a clip
42 virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) =0;
43 //! Child class should do startup
44 virtual bool start(QString *errStr) =0;
45 //! child class should stop
46 virtual bool stop() =0;
47
48 // configuration
49 //! Child class should return true, when configuration is good
50 virtual bool configOk()=0;
51 //! Child class should generate and insertSetting(..) its settings
52 virtual void generateSettings() = 0;
53 //! Chlid class should commit the Settings to permanent storage
54 virtual void saveSettings() = 0;
55
56 // static functions
57 static TTSBase* getTTS(QObject* parent,QString ttsname);
58 static QStringList getTTSList();
59 static QString getTTSName(QString tts);
60
61 private:
62 //inits the tts List
63 static void initTTSList();
64
65 protected:
66 static QMap<QString,QString> ttsList;
67};
68
69class TTSSapi : public TTSBase
70{
71 //! Enum to identify the settings
72 enum ESettings
73 {
74 eLANGUAGE,
75 eVOICE,
76 eSPEED,
77 eOPTIONS
78 };
79
80 Q_OBJECT
81 public:
82 TTSSapi(QObject* parent=NULL);
83
84 TTSStatus voice(QString text,QString wavfile, QString *errStr);
85 bool start(QString *errStr);
86 bool stop();
87
88 // for settings
89 bool configOk();
90 void generateSettings();
91 void saveSettings();
92
93 private slots:
94 void updateVoiceList();
95
96 private:
97 QStringList getVoiceList(QString language);
98
99 QProcess* voicescript;
100 QTextStream* voicestream;
101 QString defaultLanguage;
102
103 QString m_TTSexec;
104 QString m_TTSOpts;
105 QString m_TTSTemplate;
106 QString m_TTSLanguage;
107 QString m_TTSVoice;
108 QString m_TTSSpeed;
109 bool m_sapi4;
110};
111
112
113class TTSExes : public TTSBase
114{
115 enum ESettings
116 {
117 eEXEPATH,
118 eOPTIONS
119 };
120
121 Q_OBJECT
122 public:
123 TTSExes(QString name,QObject* parent=NULL);
124 TTSStatus voice(QString text,QString wavfile, QString *errStr);
125 bool start(QString *errStr);
126 bool stop() {return true;}
127
128 // for settings
129 void generateSettings();
130 void saveSettings();
131 bool configOk();
132
133 private:
134 QString m_name;
135 QString m_TTSexec;
136 QString m_TTSOpts;
137 QString m_TTSTemplate;
138 QMap<QString,QString> m_TemplateMap;
139};
140
141class TTSFestival : public TTSBase
142{
143 enum ESettings
144 {
145 eSERVERPATH,
146 eCLIENTPATH,
147 eVOICE,
148 eVOICEDESC
149 };
150
151 Q_OBJECT
152public:
153 TTSFestival(QObject* parent=NULL) :TTSBase(parent) {}
154 ~TTSFestival();
155 bool start(QString *errStr);
156 bool stop();
157 TTSStatus voice(QString text,QString wavfile, QString *errStr);
158
159 // for settings
160 bool configOk();
161 void generateSettings();
162 void saveSettings();
163
164private slots:
165 void updateVoiceList();
166 void updateVoiceDescription();
167 void clearVoiceDescription();
168private:
169 QStringList getVoiceList(QString path ="");
170 QString getVoiceInfo(QString voice,QString path ="");
171
172 inline void startServer(QString path="");
173 inline void ensureServerRunning(QString path="");
174 QString queryServer(QString query, int timeout = -1,QString path="");
175 QProcess serverProcess;
176 QStringList voices;
177 QMap<QString, QString> voiceDescriptions;
178};
179
180#endif
diff --git a/rbutil/rbutilqt/base/ttsbase.cpp b/rbutil/rbutilqt/base/ttsbase.cpp
new file mode 100644
index 0000000000..1f4060fc72
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsbase.cpp
@@ -0,0 +1,92 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2007 by Dominik Wenger
10 * $Id$
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#include "ttsbase.h"
21
22#include "ttsfestival.h"
23#include "ttssapi.h"
24#include "ttsexes.h"
25
26// list of tts names and identifiers
27QMap<QString,QString> TTSBase::ttsList;
28
29TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent)
30{
31
32}
33
34// static functions
35void TTSBase::initTTSList()
36{
37 ttsList["espeak"] = "Espeak TTS Engine";
38 ttsList["flite"] = "Flite TTS Engine";
39 ttsList["swift"] = "Swift TTS Engine";
40#if defined(Q_OS_WIN)
41 ttsList["sapi"] = "Sapi TTS Engine";
42#endif
43#if defined(Q_OS_LINUX)
44 ttsList["festival"] = "Festival TTS Engine";
45#endif
46}
47
48// function to get a specific encoder
49TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName)
50{
51
52 TTSBase* tts;
53#if defined(Q_OS_WIN)
54 if(ttsName == "sapi")
55 {
56 tts = new TTSSapi(parent);
57 return tts;
58 }
59 else
60#endif
61#if defined(Q_OS_LINUX)
62 if (ttsName == "festival")
63 {
64 tts = new TTSFestival(parent);
65 return tts;
66 }
67 else
68#endif
69 if (true) // fix for OS other than WIN or LINUX
70 {
71 tts = new TTSExes(ttsName,parent);
72 return tts;
73 }
74}
75
76// get the list of encoders, nice names
77QStringList TTSBase::getTTSList()
78{
79 // init list if its empty
80 if(ttsList.count() == 0)
81 initTTSList();
82
83 return ttsList.keys();
84}
85
86// get nice name of a specific tts
87QString TTSBase::getTTSName(QString tts)
88{
89 if(ttsList.isEmpty())
90 initTTSList();
91 return ttsList.value(tts);
92}
diff --git a/rbutil/rbutilqt/base/ttsbase.h b/rbutil/rbutilqt/base/ttsbase.h
new file mode 100644
index 0000000000..7c5932401f
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsbase.h
@@ -0,0 +1,74 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2007 by Dominik Wenger
10 * $Id$
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22
23#ifndef TTSBASE_H
24#define TTSBASE_H
25
26#include <QtCore>
27#include <QProcess>
28#include <QDateTime>
29#include <QRegExp>
30#include <QTcpSocket>
31
32#include "encttssettings.h"
33
34enum TTSStatus{ FatalError, NoError, Warning };
35
36class TTSBase : public EncTtsSettingInterface
37{
38 Q_OBJECT
39 public:
40 TTSBase(QObject *parent);
41 //! Child class should generate a clip
42 virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) =0;
43 //! Child class should do startup
44 virtual bool start(QString *errStr) =0;
45 //! child class should stop
46 virtual bool stop() =0;
47
48 // configuration
49 //! Child class should return true, when configuration is good
50 virtual bool configOk()=0;
51 //! Child class should generate and insertSetting(..) its settings
52 virtual void generateSettings() = 0;
53 //! Chlid class should commit the Settings to permanent storage
54 virtual void saveSettings() = 0;
55
56 // static functions
57 static TTSBase* getTTS(QObject* parent,QString ttsname);
58 static QStringList getTTSList();
59 static QString getTTSName(QString tts);
60
61 private:
62 //inits the tts List
63 static void initTTSList();
64
65 protected:
66 static QMap<QString,QString> ttsList;
67};
68
69
70
71
72
73
74#endif
diff --git a/rbutil/rbutilqt/base/ttsexes.cpp b/rbutil/rbutilqt/base/ttsexes.cpp
new file mode 100644
index 0000000000..d116290161
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsexes.cpp
@@ -0,0 +1,94 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2007 by Dominik Wenger
10* $Id$
11*
12* All files in this archive are subject to the GNU General Public License.
13* See the file COPYING in the source tree root for full license agreement.
14*
15* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16* KIND, either express or implied.
17*
18****************************************************************************/
19
20#include "ttsexes.h"
21#include "utils.h"
22#include "rbsettings.h"
23
24TTSExes::TTSExes(QString name,QObject* parent) : TTSBase(parent)
25{
26 m_name = name;
27
28 m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\"";
29 m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\"";
30 m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\"";
31
32}
33
34void TTSExes::generateSettings()
35{
36 QString exepath =RbSettings::subValue(m_name,RbSettings::TtsPath).toString();
37 if(exepath == "") exepath = findExecutable(m_name);
38
39 insertSetting(eEXEPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,
40 tr("Path to TTS engine:"),exepath,EncTtsSetting::eBROWSEBTN));
41 insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,
42 tr("TTS engine options:"),RbSettings::subValue(m_name,RbSettings::TtsOptions)));
43}
44
45void TTSExes::saveSettings()
46{
47 RbSettings::setSubValue(m_name,RbSettings::TtsPath,getSetting(eEXEPATH)->current().toString());
48 RbSettings::setSubValue(m_name,RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString());
49 RbSettings::sync();
50}
51
52bool TTSExes::start(QString *errStr)
53{
54 m_TTSexec = RbSettings::subValue(m_name,RbSettings::TtsPath).toString();
55 m_TTSOpts = RbSettings::subValue(m_name,RbSettings::TtsOptions).toString();
56
57 m_TTSTemplate = m_TemplateMap.value(m_name);
58
59 QFileInfo tts(m_TTSexec);
60 if(tts.exists())
61 {
62 return true;
63 }
64 else
65 {
66 *errStr = tr("TTS executable not found");
67 return false;
68 }
69}
70
71TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr)
72{
73 (void) errStr;
74 QString execstring = m_TTSTemplate;
75
76 execstring.replace("%exe",m_TTSexec);
77 execstring.replace("%options",m_TTSOpts);
78 execstring.replace("%wavfile",wavfile);
79 execstring.replace("%text",text);
80 //qDebug() << "voicing" << execstring;
81 QProcess::execute(execstring);
82 return NoError;
83
84}
85
86bool TTSExes::configOk()
87{
88 QString path = RbSettings::subValue(m_name,RbSettings::TtsPath).toString();
89
90 if (QFileInfo(path).exists())
91 return true;
92
93 return false;
94}
diff --git a/rbutil/rbutilqt/base/ttsexes.h b/rbutil/rbutilqt/base/ttsexes.h
new file mode 100644
index 0000000000..ab966f5a7e
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsexes.h
@@ -0,0 +1,55 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2009 by Dominik Wenger
10* $Id$
11*
12* This program is free software; you can redistribute it and/or
13* modify it under the terms of the GNU General Public License
14* as published by the Free Software Foundation; either version 2
15* of the License, or (at your option) any later version.
16*
17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18* KIND, either express or implied.
19*
20****************************************************************************/
21
22#ifndef TTSEXES_H
23#define TTSEXES_H
24
25#include "ttsbase.h"
26
27class TTSExes : public TTSBase
28{
29 enum ESettings
30 {
31 eEXEPATH,
32 eOPTIONS
33 };
34
35 Q_OBJECT
36 public:
37 TTSExes(QString name,QObject* parent=NULL);
38 TTSStatus voice(QString text,QString wavfile, QString *errStr);
39 bool start(QString *errStr);
40 bool stop() {return true;}
41
42 // for settings
43 void generateSettings();
44 void saveSettings();
45 bool configOk();
46
47 private:
48 QString m_name;
49 QString m_TTSexec;
50 QString m_TTSOpts;
51 QString m_TTSTemplate;
52 QMap<QString,QString> m_TemplateMap;
53};
54
55#endif
diff --git a/rbutil/rbutilqt/base/ttsfestival.cpp b/rbutil/rbutilqt/base/ttsfestival.cpp
new file mode 100644
index 0000000000..37d263a932
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsfestival.cpp
@@ -0,0 +1,325 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2007 by Dominik Wenger
10* $Id$
11*
12* All files in this archive are subject to the GNU General Public License.
13* See the file COPYING in the source tree root for full license agreement.
14*
15* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16* KIND, either express or implied.
17*
18****************************************************************************/
19
20#include "ttsfestival.h"
21#include "utils.h"
22#include "rbsettings.h"
23
24TTSFestival::~TTSFestival()
25{
26 stop();
27}
28
29void TTSFestival::generateSettings()
30{
31 // server path
32 QString exepath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
33 if(exepath == "" ) exepath = findExecutable("festival");
34 insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN));
35
36 // client path
37 QString clientpath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
38 if(clientpath == "" ) clientpath = findExecutable("festival_client");
39 insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,
40 tr("Path to Festival client:"),clientpath,EncTtsSetting::eBROWSEBTN));
41
42 // voice
43 EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
44 tr("Voice:"),RbSettings::subValue("festival",RbSettings::TtsVoice),getVoiceList(exepath),EncTtsSetting::eREFRESHBTN);
45 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
46 connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
47 insertSetting(eVOICE,setting);
48
49 //voice description
50 setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,
51 tr("Voice description:"),"",EncTtsSetting::eREFRESHBTN);
52 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription()));
53 insertSetting(eVOICEDESC,setting);
54}
55
56void TTSFestival::saveSettings()
57{
58 //save settings in user config
59 RbSettings::setSubValue("festival-server",RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString());
60 RbSettings::setSubValue("festival-client",RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString());
61 RbSettings::setSubValue("festival",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
62
63 RbSettings::sync();
64}
65
66void TTSFestival::updateVoiceDescription()
67{
68 // get voice Info with current voice and path
69 QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),getSetting(eSERVERPATH)->current().toString());
70 getSetting(eVOICEDESC)->setCurrent(info);
71}
72
73void TTSFestival::clearVoiceDescription()
74{
75 getSetting(eVOICEDESC)->setCurrent("");
76}
77
78void TTSFestival::updateVoiceList()
79{
80 QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString());
81 getSetting(eVOICE)->setList(voiceList);
82 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
83 else getSetting(eVOICE)->setCurrent("");
84}
85
86void TTSFestival::startServer(QString path)
87{
88 if(!configOk())
89 return;
90
91 if(path == "")
92 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
93
94 serverProcess.start(QString("%1 --server").arg(path));
95 serverProcess.waitForStarted();
96
97 queryServer("(getpid)",300,path);
98 if(serverProcess.state() == QProcess::Running)
99 qDebug() << "Festival is up and running";
100 else
101 qDebug() << "Festival failed to start";
102}
103
104void TTSFestival::ensureServerRunning(QString path)
105{
106 if(serverProcess.state() != QProcess::Running)
107 {
108 startServer(path);
109 }
110}
111
112bool TTSFestival::start(QString* errStr)
113{
114 (void) errStr;
115 ensureServerRunning();
116 if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
117 queryServer(QString("(voice.select '%1)")
118 .arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString()));
119
120 return true;
121}
122
123bool TTSFestival::stop()
124{
125 serverProcess.terminate();
126 serverProcess.kill();
127
128 return true;
129}
130
131TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
132{
133 qDebug() << text << "->" << wavfile;
134
135 QString path = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
136 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path).arg(wavfile);
137 qDebug() << cmd;
138
139 QProcess clientProcess;
140 clientProcess.start(cmd);
141 clientProcess.write(QString("%1.\n").arg(text).toAscii());
142 clientProcess.waitForBytesWritten();
143 clientProcess.closeWriteChannel();
144 clientProcess.waitForReadyRead();
145 QString response = clientProcess.readAll();
146 response = response.trimmed();
147 if(!response.contains("Utterance"))
148 {
149 qDebug() << "Could not voice string: " << response;
150 *errStr = tr("engine could not voice string");
151 return Warning;
152 /* do not stop the voicing process because of a single string
153 TODO: needs proper settings */
154 }
155 clientProcess.closeReadChannel(QProcess::StandardError);
156 clientProcess.closeReadChannel(QProcess::StandardOutput);
157 clientProcess.terminate();
158 clientProcess.kill();
159
160 return NoError;
161}
162
163bool TTSFestival::configOk()
164{
165 QString serverPath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
166 QString clientPath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
167
168 bool ret = QFileInfo(serverPath).isExecutable() &&
169 QFileInfo(clientPath).isExecutable();
170 if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0 && voices.size() > 0)
171 ret = ret && (voices.indexOf(RbSettings::subValue("festival",RbSettings::TtsVoice).toString()) != -1);
172 return ret;
173}
174
175QStringList TTSFestival::getVoiceList(QString path)
176{
177 if(!configOk())
178 return QStringList();
179
180 if(voices.size() > 0)
181 {
182 qDebug() << "Using voice cache";
183 return voices;
184 }
185
186 QString response = queryServer("(voice.list)",3000,path);
187
188 // get the 2nd line. It should be (<voice_name>, <voice_name>)
189 response = response.mid(response.indexOf('\n') + 1, -1);
190 response = response.left(response.indexOf('\n')).trimmed();
191
192 voices = response.mid(1, response.size()-2).split(' ');
193
194 voices.sort();
195 if (voices.size() == 1 && voices[0].size() == 0)
196 voices.removeAt(0);
197 if (voices.size() > 0)
198 qDebug() << "Voices: " << voices;
199 else
200 qDebug() << "No voices.";
201
202 return voices;
203}
204
205QString TTSFestival::getVoiceInfo(QString voice,QString path)
206{
207 if(!configOk())
208 return "";
209
210 if(!getVoiceList().contains(voice))
211 return "";
212
213 if(voiceDescriptions.contains(voice))
214 return voiceDescriptions[voice];
215
216 QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path);
217
218 if (response == "")
219 {
220 voiceDescriptions[voice]=tr("No description available");
221 }
222 else
223 {
224 response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard));
225 qDebug() << "voiceInfo w/o descr: " << response;
226 response = response.remove(')');
227 QStringList responseLines = response.split('(', QString::SkipEmptyParts);
228 responseLines.removeAt(0); // the voice name itself
229
230 QString description;
231 foreach(QString line, responseLines)
232 {
233 line = line.remove('(');
234 line = line.simplified();
235
236 line[0] = line[0].toUpper(); // capitalize the key
237
238 int firstSpace = line.indexOf(' ');
239 if (firstSpace > 0)
240 {
241 line = line.insert(firstSpace, ':'); // add a colon between the key and the value
242 line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value
243 }
244
245 description += line + "\n";
246 }
247 voiceDescriptions[voice] = description.trimmed();
248 }
249
250 return voiceDescriptions[voice];
251}
252
253QString TTSFestival::queryServer(QString query, int timeout,QString path)
254{
255 if(!configOk())
256 return "";
257
258 // this operation could take some time
259 emit busy();
260
261 ensureServerRunning(path);
262
263 qDebug() << "queryServer with " << query;
264 QString response;
265
266 QDateTime endTime;
267 if(timeout > 0)
268 endTime = QDateTime::currentDateTime().addMSecs(timeout);
269
270 /* Festival is *extremely* unreliable. Although at this
271 * point we are sure that SIOD is accepting commands,
272 * we might end up with an empty response. Hence, the loop.
273 */
274 while(true)
275 {
276 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
277 QTcpSocket socket;
278
279 socket.connectToHost("localhost", 1314);
280 socket.waitForConnected();
281
282 if(socket.state() == QAbstractSocket::ConnectedState)
283 {
284 socket.write(QString("%1\n").arg(query).toAscii());
285 socket.waitForBytesWritten();
286 socket.waitForReadyRead();
287
288 response = socket.readAll().trimmed();
289
290 if (response != "LP" && response != "")
291 break;
292 }
293 socket.abort();
294 socket.disconnectFromHost();
295
296 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
297 {
298 emit busyEnd();
299 return "";
300 }
301 /* make sure we wait a little as we don't want to flood the server with requests */
302 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
303 while(QDateTime::currentDateTime() < tmpEndTime)
304 QCoreApplication::processEvents(QEventLoop::AllEvents);
305 }
306 if(response == "nil")
307 {
308 emit busyEnd();
309 return "";
310 }
311
312 QStringList lines = response.split('\n');
313 if(lines.size() > 2)
314 {
315 lines.removeFirst();
316 lines.removeLast();
317 }
318 else
319 qDebug() << "Response too short: " << response;
320
321 emit busyEnd();
322 return lines.join("\n");
323
324}
325
diff --git a/rbutil/rbutilqt/base/ttsfestival.h b/rbutil/rbutilqt/base/ttsfestival.h
new file mode 100644
index 0000000000..9d644c1857
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsfestival.h
@@ -0,0 +1,67 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2009 by Dominik Wenger
10* $Id$
11*
12* This program is free software; you can redistribute it and/or
13* modify it under the terms of the GNU General Public License
14* as published by the Free Software Foundation; either version 2
15* of the License, or (at your option) any later version.
16*
17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18* KIND, either express or implied.
19*
20****************************************************************************/
21
22#ifndef TTSFESTIVAL_H
23#define TTSFESTIVAL_H
24
25#include "ttsbase.h"
26
27class TTSFestival : public TTSBase
28{
29 enum ESettings
30 {
31 eSERVERPATH,
32 eCLIENTPATH,
33 eVOICE,
34 eVOICEDESC
35 };
36
37 Q_OBJECT
38public:
39 TTSFestival(QObject* parent=NULL) :TTSBase(parent) {}
40 ~TTSFestival();
41 bool start(QString *errStr);
42 bool stop();
43 TTSStatus voice(QString text,QString wavfile, QString *errStr);
44
45 // for settings
46 bool configOk();
47 void generateSettings();
48 void saveSettings();
49
50private slots:
51 void updateVoiceList();
52 void updateVoiceDescription();
53 void clearVoiceDescription();
54private:
55 QStringList getVoiceList(QString path ="");
56 QString getVoiceInfo(QString voice,QString path ="");
57
58 inline void startServer(QString path="");
59 inline void ensureServerRunning(QString path="");
60 QString queryServer(QString query, int timeout = -1,QString path="");
61 QProcess serverProcess;
62 QStringList voices;
63 QMap<QString, QString> voiceDescriptions;
64};
65
66
67#endif
diff --git a/rbutil/rbutilqt/base/ttssapi.cpp b/rbutil/rbutilqt/base/ttssapi.cpp
new file mode 100644
index 0000000000..e356d613de
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttssapi.cpp
@@ -0,0 +1,213 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2007 by Dominik Wenger
10* $Id$
11*
12* All files in this archive are subject to the GNU General Public License.
13* See the file COPYING in the source tree root for full license agreement.
14*
15* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16* KIND, either express or implied.
17*
18****************************************************************************/
19
20#include "ttssapi.h"
21#include "utils.h"
22#include "rbsettings.h"
23
24TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent)
25{
26 m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\"";
27 defaultLanguage ="english";
28 m_sapi4 =false;
29}
30
31void TTSSapi::generateSettings()
32{
33 // language
34 QStringList languages = RbSettings::languages();
35 languages.sort();
36 EncTtsSetting* setting =new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
37 tr("Language:"),RbSettings::subValue("sapi",RbSettings::TtsLanguage),languages);
38 connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceList()));
39 insertSetting(eLANGUAGE,setting);
40 // voice
41 setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
42 tr("Voice:"),RbSettings::subValue("sapi",RbSettings::TtsVoice),getVoiceList(RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString()),EncTtsSetting::eREFRESHBTN);
43 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
44 insertSetting(eVOICE,setting);
45 //speed
46 insertSetting(eSPEED,new EncTtsSetting(this,EncTtsSetting::eINT,
47 tr("Speed:"),RbSettings::subValue("sapi",RbSettings::TtsSpeed),-10,10));
48 // options
49 insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,
50 tr("Options:"),RbSettings::subValue("sapi",RbSettings::TtsOptions)));
51
52}
53
54void TTSSapi::saveSettings()
55{
56 //save settings in user config
57 RbSettings::setSubValue("sapi",RbSettings::TtsLanguage,getSetting(eLANGUAGE)->current().toString());
58 RbSettings::setSubValue("sapi",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
59 RbSettings::setSubValue("sapi",RbSettings::TtsSpeed,getSetting(eSPEED)->current().toInt());
60 RbSettings::setSubValue("sapi",RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString());
61
62 RbSettings::sync();
63}
64
65void TTSSapi::updateVoiceList()
66{
67 qDebug() << "update voiceList";
68 QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString());
69 getSetting(eVOICE)->setList(voiceList);
70 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
71 else getSetting(eVOICE)->setCurrent("");
72}
73
74bool TTSSapi::start(QString *errStr)
75{
76
77 m_TTSOpts = RbSettings::subValue("sapi",RbSettings::TtsOptions).toString();
78 m_TTSLanguage =RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString();
79 m_TTSVoice=RbSettings::subValue("sapi",RbSettings::TtsVoice).toString();
80 m_TTSSpeed=RbSettings::subValue("sapi",RbSettings::TtsSpeed).toString();
81 m_sapi4 = RbSettings::subValue("sapi",RbSettings::TtsUseSapi4).toBool();
82
83 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
84 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
85 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
86
87 QFileInfo tts(m_TTSexec);
88 if(!tts.exists())
89 {
90 *errStr = tr("Could not copy the Sapi-script");
91 return false;
92 }
93 // create the voice process
94 QString execstring = m_TTSTemplate;
95 execstring.replace("%exe",m_TTSexec);
96 execstring.replace("%options",m_TTSOpts);
97 execstring.replace("%lang",m_TTSLanguage);
98 execstring.replace("%voice",m_TTSVoice);
99 execstring.replace("%speed",m_TTSSpeed);
100
101 if(m_sapi4)
102 execstring.append(" /sapi4 ");
103
104 qDebug() << "init" << execstring;
105 voicescript = new QProcess(NULL);
106 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
107
108 voicescript->start(execstring);
109 if(!voicescript->waitForStarted())
110 {
111 *errStr = tr("Could not start the Sapi-script");
112 return false;
113 }
114
115 if(!voicescript->waitForReadyRead(300))
116 {
117 *errStr = voicescript->readAllStandardError();
118 if(*errStr != "")
119 return false;
120 }
121
122 voicestream = new QTextStream(voicescript);
123 voicestream->setCodec("UTF16-LE");
124
125 return true;
126}
127
128
129QStringList TTSSapi::getVoiceList(QString language)
130{
131 QStringList result;
132
133 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
134 m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
135
136 QFileInfo tts(m_TTSexec);
137 if(!tts.exists())
138 return result;
139
140 // create the voice process
141 QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices";
142 execstring.replace("%exe",m_TTSexec);
143 execstring.replace("%lang",language);
144
145 if(RbSettings::value(RbSettings::TtsUseSapi4).toBool())
146 execstring.append(" /sapi4 ");
147
148 qDebug() << "init" << execstring;
149 voicescript = new QProcess(NULL);
150 voicescript->start(execstring);
151 qDebug() << "wait for started";
152 if(!voicescript->waitForStarted())
153 return result;
154 voicescript->closeWriteChannel();
155 voicescript->waitForReadyRead();
156
157 QString dataRaw = voicescript->readAllStandardError().data();
158 result = dataRaw.split(",",QString::SkipEmptyParts);
159 if(result.size() > 0)
160 {
161 result.sort();
162 result.removeFirst();
163 for(int i = 0; i< result.size();i++)
164 {
165 result[i] = result.at(i).simplified();
166 }
167 }
168
169 delete voicescript;
170 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner
171 |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser
172 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup
173 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther );
174 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
175 return result;
176}
177
178
179
180TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr)
181{
182 (void) errStr;
183 QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n";
184 qDebug() << "voicing" << query;
185 *voicestream << query;
186 *voicestream << "SYNC\tbla\r\n";
187 voicestream->flush();
188 voicescript->waitForReadyRead();
189 return NoError;
190}
191
192bool TTSSapi::stop()
193{
194
195 *voicestream << "QUIT\r\n";
196 voicestream->flush();
197 voicescript->waitForFinished();
198 delete voicestream;
199 delete voicescript;
200 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner
201 |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser
202 |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup
203 |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther );
204 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
205 return true;
206}
207
208bool TTSSapi::configOk()
209{
210 if(RbSettings::subValue("sapi",RbSettings::TtsVoice).toString().isEmpty())
211 return false;
212 return true;
213}
diff --git a/rbutil/rbutilqt/base/ttssapi.h b/rbutil/rbutilqt/base/ttssapi.h
new file mode 100644
index 0000000000..5e81575585
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttssapi.h
@@ -0,0 +1,72 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2009 by Dominik Wenger
10* $Id$
11*
12* This program is free software; you can redistribute it and/or
13* modify it under the terms of the GNU General Public License
14* as published by the Free Software Foundation; either version 2
15* of the License, or (at your option) any later version.
16*
17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18* KIND, either express or implied.
19*
20****************************************************************************/
21
22#ifndef TTSSAPI_H
23#define TTSSAPI_H
24
25#include "ttsbase.h"
26
27class TTSSapi : public TTSBase
28{
29 //! Enum to identify the settings
30 enum ESettings
31 {
32 eLANGUAGE,
33 eVOICE,
34 eSPEED,
35 eOPTIONS
36 };
37
38 Q_OBJECT
39 public:
40 TTSSapi(QObject* parent=NULL);
41
42 TTSStatus voice(QString text,QString wavfile, QString *errStr);
43 bool start(QString *errStr);
44 bool stop();
45
46 // for settings
47 bool configOk();
48 void generateSettings();
49 void saveSettings();
50
51 private slots:
52 void updateVoiceList();
53
54 private:
55 QStringList getVoiceList(QString language);
56
57 QProcess* voicescript;
58 QTextStream* voicestream;
59 QString defaultLanguage;
60
61 QString m_TTSexec;
62 QString m_TTSOpts;
63 QString m_TTSTemplate;
64 QString m_TTSLanguage;
65 QString m_TTSVoice;
66 QString m_TTSSpeed;
67 bool m_sapi4;
68};
69
70
71
72#endif
diff --git a/rbutil/rbutilqt/configure.cpp b/rbutil/rbutilqt/configure.cpp
index 9552aa0614..3e7b5f729e 100644
--- a/rbutil/rbutilqt/configure.cpp
+++ b/rbutil/rbutilqt/configure.cpp
@@ -25,7 +25,7 @@
25#include "ui_configurefrm.h" 25#include "ui_configurefrm.h"
26#include "browsedirtree.h" 26#include "browsedirtree.h"
27#include "encoders.h" 27#include "encoders.h"
28#include "tts.h" 28#include "ttsbase.h"
29#include "system.h" 29#include "system.h"
30#include "encttscfggui.h" 30#include "encttscfggui.h"
31#include "rbsettings.h" 31#include "rbsettings.h"
diff --git a/rbutil/rbutilqt/rbutilqt.pro b/rbutil/rbutilqt/rbutilqt.pro
index 576d59961d..4b170cd01e 100644
--- a/rbutil/rbutilqt/rbutilqt.pro
+++ b/rbutil/rbutilqt/rbutilqt.pro
@@ -98,7 +98,10 @@ SOURCES += rbutilqt.cpp \
98 base/encoders.cpp \ 98 base/encoders.cpp \
99 encttscfggui.cpp \ 99 encttscfggui.cpp \
100 base/encttssettings.cpp \ 100 base/encttssettings.cpp \
101 base/tts.cpp \ 101 base/ttsbase.cpp \
102 base/ttsexes.cpp \
103 base/ttssapi.cpp \
104 base/ttsfestival.cpp \
102 ../../tools/wavtrim.c \ 105 ../../tools/wavtrim.c \
103 ../../tools/voicefont.c \ 106 ../../tools/voicefont.c \
104 base/voicefile.cpp \ 107 base/voicefile.cpp \
@@ -155,7 +158,10 @@ HEADERS += rbutilqt.h \
155 base/encoders.h \ 158 base/encoders.h \
156 encttscfggui.h \ 159 encttscfggui.h \
157 base/encttssettings.h \ 160 base/encttssettings.h \
158 base/tts.h \ 161 base/ttsbase.h \
162 base/ttsexes.h \
163 base/ttsfestival.h \
164 base/ttssapi.h \
159 ../../tools/wavtrim.h \ 165 ../../tools/wavtrim.h \
160 ../../tools/voicefont.h \ 166 ../../tools/voicefont.h \
161 base/voicefile.h \ 167 base/voicefile.h \