1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
9 * Copyright (C) 2007 by Dominik Wenger
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.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
24 QMap
<QString
,QString
> TTSBase::ttsList
;
25 QMap
<QString
,TTSBase
*> TTSBase::ttsCache
;
28 void TTSBase::initTTSList()
30 ttsList
["espeak"] = "Espeak TTS Engine";
31 ttsList
["flite"] = "Flite TTS Engine";
32 ttsList
["swift"] = "Swift TTS Engine";
34 ttsList
["sapi"] = "Sapi TTS Engine";
36 #if defined(Q_OS_LINUX)
37 ttsList
["festival"] = "Festival TTS Engine";
41 // function to get a specific encoder
42 TTSBase
* TTSBase::getTTS(QString ttsName
)
45 if(ttsCache
.contains(ttsName
))
46 return ttsCache
.value(ttsName
);
53 ttsCache
[ttsName
] = tts
;
58 #if defined(Q_OS_LINUX)
59 if (ttsName
== "festival")
61 tts
= new TTSFestival();
62 ttsCache
[ttsName
] = tts
;
67 if (true) // fix for OS other than WIN or LINUX
69 tts
= new TTSExes(ttsName
);
70 ttsCache
[ttsName
] = tts
;
75 // get the list of encoders, nice names
76 QStringList
TTSBase::getTTSList()
78 // init list if its empty
79 if(ttsList
.count() == 0)
82 return ttsList
.keys();
85 // get nice name of a specific tts
86 QString
TTSBase::getTTSName(QString tts
)
90 return ttsList
.value(tts
);
93 /*********************************************************************
95 **********************************************************************/
96 TTSBase::TTSBase(): QObject()
101 /*********************************************************************
103 **********************************************************************/
104 TTSExes::TTSExes(QString name
) : TTSBase()
108 m_TemplateMap
["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\"";
109 m_TemplateMap
["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\"";
110 m_TemplateMap
["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\"";
114 void TTSExes::setCfg(RbSettings
* sett
)
116 // call function of base class
117 TTSBase::setCfg(sett
);
119 // if the config isnt OK, try to autodetect
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
);
130 for(int i
= 0; i
< path
.size(); i
++)
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("");
138 qDebug() << executable
;
139 if(QFileInfo(executable
).isExecutable())
141 exepath
= QDir::toNativeSeparators(executable
);
145 settings
->setTTSPath(m_name
,exepath
);
151 bool TTSExes::start(QString
*errStr
)
153 m_TTSexec
= settings
->ttsPath(m_name
);
154 m_TTSOpts
= settings
->ttsOptions(m_name
);
156 m_TTSTemplate
= m_TemplateMap
.value(m_name
);
158 QFileInfo
tts(m_TTSexec
);
165 *errStr
= tr("TTS executable not found");
170 TTSStatus
TTSExes::voice(QString text
,QString wavfile
, QString
*errStr
)
173 QString execstring
= m_TTSTemplate
;
175 execstring
.replace("%exe",m_TTSexec
);
176 execstring
.replace("%options",m_TTSOpts
);
177 execstring
.replace("%wavfile",wavfile
);
178 execstring
.replace("%text",text
);
179 //qDebug() << "voicing" << execstring;
180 QProcess::execute(execstring
);
185 void TTSExes::showCfg()
192 gui
.setCfg(settings
);
196 bool TTSExes::configOk()
198 QString path
= settings
->ttsPath(m_name
);
200 if (QFileInfo(path
).exists())
206 /*********************************************************************
208 **********************************************************************/
209 TTSSapi::TTSSapi() : TTSBase()
211 m_TTSTemplate
= "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\"";
212 defaultLanguage
="english";
217 bool TTSSapi::start(QString
*errStr
)
220 m_TTSOpts
= settings
->ttsOptions("sapi");
221 m_TTSLanguage
=settings
->ttsLang("sapi");
222 m_TTSVoice
=settings
->ttsVoice("sapi");
223 m_TTSSpeed
=QString("%1").arg(settings
->ttsSpeed("sapi"));
224 m_sapi4
= settings
->ttsUseSapi4();
226 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
227 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
228 m_TTSexec
= QDir::tempPath() +"/sapi_voice.vbs";
230 QFileInfo
tts(m_TTSexec
);
233 *errStr
= tr("Could not copy the Sapi-script");
236 // create the voice process
237 QString execstring
= m_TTSTemplate
;
238 execstring
.replace("%exe",m_TTSexec
);
239 execstring
.replace("%options",m_TTSOpts
);
240 execstring
.replace("%lang",m_TTSLanguage
);
241 execstring
.replace("%voice",m_TTSVoice
);
242 execstring
.replace("%speed",m_TTSSpeed
);
245 execstring
.append(" /sapi4 ");
247 qDebug() << "init" << execstring
;
248 voicescript
= new QProcess(NULL
);
249 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
251 voicescript
->start(execstring
);
252 if(!voicescript
->waitForStarted())
254 *errStr
= tr("Could not start the Sapi-script");
258 if(!voicescript
->waitForReadyRead(300))
260 *errStr
= voicescript
->readAllStandardError();
265 voicestream
= new QTextStream(voicescript
);
266 voicestream
->setCodec("UTF16-LE");
272 QStringList
TTSSapi::getVoiceList(QString language
)
276 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
277 m_TTSexec
= QDir::tempPath() +"/sapi_voice.vbs";
279 QFileInfo
tts(m_TTSexec
);
283 // create the voice process
284 QString execstring
= "cscript //nologo \"%exe\" /language:%lang /listvoices";
285 execstring
.replace("%exe",m_TTSexec
);
286 execstring
.replace("%lang",language
);
288 if(settings
->ttsUseSapi4())
289 execstring
.append(" /sapi4 ");
291 qDebug() << "init" << execstring
;
292 voicescript
= new QProcess(NULL
);
293 voicescript
->start(execstring
);
294 qDebug() << "wait for started";
295 if(!voicescript
->waitForStarted())
297 voicescript
->closeWriteChannel();
298 voicescript
->waitForReadyRead();
300 QString dataRaw
= voicescript
->readAllStandardError().data();
301 result
= dataRaw
.split(",",QString::SkipEmptyParts
);
303 result
.removeFirst();
304 for(int i
= 0; i
< result
.size();i
++)
306 result
[i
] = result
.at(i
).simplified();
311 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner
|QFile::WriteOwner
|QFile::ExeOwner
312 |QFile::ReadUser
| QFile::WriteUser
| QFile::ExeUser
313 |QFile::ReadGroup
|QFile::WriteGroup
|QFile::ExeGroup
314 |QFile::ReadOther
|QFile::WriteOther
|QFile::ExeOther
);
315 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
322 TTSStatus
TTSSapi::voice(QString text
,QString wavfile
, QString
*errStr
)
325 QString query
= "SPEAK\t"+wavfile
+"\t"+text
+"\r\n";
326 qDebug() << "voicing" << query
;
327 *voicestream
<< query
;
328 *voicestream
<< "SYNC\tbla\r\n";
329 voicestream
->flush();
330 voicescript
->waitForReadyRead();
337 *voicestream
<< "QUIT\r\n";
338 voicestream
->flush();
339 voicescript
->waitForFinished();
342 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner
|QFile::WriteOwner
|QFile::ExeOwner
343 |QFile::ReadUser
| QFile::WriteUser
| QFile::ExeUser
344 |QFile::ReadGroup
|QFile::WriteGroup
|QFile::ExeGroup
345 |QFile::ReadOther
|QFile::WriteOther
|QFile::ExeOther
);
346 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
351 void TTSSapi::showCfg()
354 TTSSapiGui
gui(this);
356 TTSSapiGuiCli
gui(this);
358 gui
.setCfg(settings
);
362 bool TTSSapi::configOk()
364 if(settings
->ttsVoice("sapi").isEmpty())
368 /**********************************************************************
369 * TSSFestival - client-server wrapper
370 **********************************************************************/
371 TTSFestival::~TTSFestival()
376 void TTSFestival::startServer()
381 QStringList paths
= settings
->ttsPath("festival").split(":");
383 serverProcess
.start(QString("%1 --server").arg(paths
[0]));
384 serverProcess
.waitForStarted();
386 queryServer("(getpid)");
387 if(serverProcess
.state() == QProcess::Running
)
388 qDebug() << "Festival is up and running";
390 qDebug() << "Festival failed to start";
393 void TTSFestival::ensureServerRunning()
395 if(serverProcess
.state() != QProcess::Running
)
397 // least common denominator for all the server startup code paths
398 QProgressDialog
progressDialog(tr(""), tr(""), 0, 0);
399 progressDialog
.setWindowTitle(tr("Starting festival"));
400 progressDialog
.setModal(true);
401 progressDialog
.setLabel(0);
402 progressDialog
.setCancelButton(0);
403 progressDialog
.show();
405 QApplication::processEvents(); // actually show the dialog
411 bool TTSFestival::start(QString
* errStr
)
414 ensureServerRunning();
415 if (!settings
->ttsVoice("festival").isEmpty())
416 queryServer(QString("(voice.select '%1)").arg(settings
->ttsVoice("festival")));
421 bool TTSFestival::stop()
423 serverProcess
.terminate();
424 serverProcess
.kill();
429 TTSStatus
TTSFestival::voice(QString text
, QString wavfile
, QString
* errStr
)
431 qDebug() << text
<< "->" << wavfile
;
433 QStringList paths
= settings
->ttsPath("festival").split(":");
434 QString cmd
= QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(paths
[1]).arg(wavfile
);
437 QProcess clientProcess
;
438 clientProcess
.start(cmd
);
439 clientProcess
.write(QString("%1.\n").arg(text
).toAscii());
440 clientProcess
.waitForBytesWritten();
441 clientProcess
.closeWriteChannel();
442 clientProcess
.waitForReadyRead();
443 QString response
= clientProcess
.readAll();
444 response
= response
.trimmed();
445 if(!response
.contains("Utterance"))
447 qDebug() << "Could not voice string: " << response
;
448 *errStr
= tr("engine could not voice string");
450 /* do not stop the voicing process because of a single string
451 TODO: needs proper settings */
453 clientProcess
.closeReadChannel(QProcess::StandardError
);
454 clientProcess
.closeReadChannel(QProcess::StandardOutput
);
455 clientProcess
.terminate();
456 clientProcess
.kill();
461 bool TTSFestival::configOk()
463 QStringList paths
= settings
->ttsPath("festival").split(":");
464 if(paths
.size() != 2)
466 bool ret
= QFileInfo(paths
[0]).isExecutable() &&
467 QFileInfo(paths
[1]).isExecutable();
468 if(settings
->ttsVoice("festival").size() > 0 && voices
.size() > 0)
469 ret
= ret
&& (voices
.indexOf(settings
->ttsVoice("festival")) != -1);
473 void TTSFestival::showCfg()
476 TTSFestivalGui
gui(this);
478 gui
.setCfg(settings
);
482 QStringList
TTSFestival::getVoiceList()
485 return QStringList();
487 if(voices
.size() > 0)
489 qDebug() << "Using voice cache";
492 QString response
= queryServer("(voice.list)");
494 // get the 2nd line. It should be (<voice_name>, <voice_name>)
495 response
= response
.mid(response
.indexOf('\n') + 1, -1);
496 response
= response
.left(response
.indexOf('\n')).trimmed();
498 voices
= response
.mid(1, response
.size()-2).split(' ');
501 if (voices
.size() == 1 && voices
[0].size() == 0)
503 if (voices
.size() > 0)
504 qDebug() << "Voices: " << voices
;
506 qDebug() << "No voices.";
510 QString
TTSFestival::getVoiceInfo(QString voice
)
515 if(!getVoiceList().contains(voice
))
518 if(voiceDescriptions
.contains(voice
))
519 return voiceDescriptions
[voice
];
521 QString response
= queryServer(QString("(voice.description '%1)").arg(voice
), 3000);
525 voiceDescriptions
[voice
]=tr("No description available");
529 response
= response
.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive
, QRegExp::Wildcard
));
530 qDebug() << "voiceInfo w/o descr: " << response
;
531 response
= response
.remove(')');
532 QStringList responseLines
= response
.split('(', QString::SkipEmptyParts
);
533 responseLines
.removeAt(0); // the voice name itself
536 foreach(QString line
, responseLines
)
538 line
= line
.remove('(');
539 line
= line
.simplified();
541 line
[0] = line
[0].toUpper(); // capitalize the key
543 int firstSpace
= line
.indexOf(' ');
546 line
= line
.insert(firstSpace
, ':'); // add a colon between the key and the value
547 line
[firstSpace
+2] = line
[firstSpace
+2].toUpper(); // capitalize the value
550 description
+= line
+ "\n";
552 voiceDescriptions
[voice
] = description
.trimmed();
554 return voiceDescriptions
[voice
];
557 QString
TTSFestival::queryServer(QString query
, int timeout
)
562 ensureServerRunning();
564 qDebug() << "queryServer with " << query
;
569 endTime
= QDateTime::currentDateTime().addMSecs(timeout
);
571 /* Festival is *extremely* unreliable. Although at this
572 * point we are sure that SIOD is accepting commands,
573 * we might end up with an empty response. Hence, the loop.
577 QApplication::processEvents(QEventLoop::AllEvents
, 50);
580 socket
.connectToHost("localhost", 1314);
581 socket
.waitForConnected();
583 if(socket
.state() == QAbstractSocket::ConnectedState
)
585 socket
.write(QString("%1\n").arg(query
).toAscii());
586 socket
.waitForBytesWritten();
587 socket
.waitForReadyRead();
589 response
= socket
.readAll().trimmed();
591 if (response
!= "LP" && response
!= "")
595 socket
.disconnectFromHost();
597 if(timeout
> 0 && QDateTime::currentDateTime() >= endTime
)
600 /* make sure we wait a little as we don't want to flood the server with requests */
601 QDateTime tmpEndTime
= QDateTime::currentDateTime().addMSecs(500);
602 while(QDateTime::currentDateTime() < tmpEndTime
)
603 QApplication::processEvents(QEventLoop::AllEvents
);
605 if(response
== "nil")
608 QStringList lines
= response
.split('\n');
615 qDebug() << "Response too short: " << response
;
616 return lines
.join("\n");