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 ****************************************************************************/
22 #include "rbsettings.h"
23 /*********************************************************************
25 **********************************************************************/
26 QMap
<QString
,QString
> TTSBase::ttsList
;
28 TTSBase::TTSBase(QObject
* parent
): EncTtsSettingInterface(parent
)
34 void TTSBase::initTTSList()
36 ttsList
["espeak"] = "Espeak TTS Engine";
37 ttsList
["flite"] = "Flite TTS Engine";
38 ttsList
["swift"] = "Swift TTS Engine";
40 ttsList
["sapi"] = "Sapi TTS Engine";
42 #if defined(Q_OS_LINUX)
43 ttsList
["festival"] = "Festival TTS Engine";
47 // function to get a specific encoder
48 TTSBase
* TTSBase::getTTS(QObject
* parent
,QString ttsName
)
55 tts
= new TTSSapi(parent
);
60 #if defined(Q_OS_LINUX)
61 if (ttsName
== "festival")
63 tts
= new TTSFestival(parent
);
68 if (true) // fix for OS other than WIN or LINUX
70 tts
= new TTSExes(ttsName
,parent
);
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
);
94 /*********************************************************************
96 **********************************************************************/
97 TTSExes::TTSExes(QString name
,QObject
* parent
) : TTSBase(parent
)
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\"";
107 void TTSExes::generateSettings()
109 QString exepath
=RbSettings::subValue(m_name
,RbSettings::TtsPath
).toString();
110 if(exepath
== "") exepath
= findExecutable(m_name
);
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
)));
118 void TTSExes::saveSettings()
120 RbSettings::setSubValue(m_name
,RbSettings::TtsPath
,getSetting(eEXEPATH
)->current().toString());
121 RbSettings::setSubValue(m_name
,RbSettings::TtsOptions
,getSetting(eOPTIONS
)->current().toString());
125 bool TTSExes::start(QString
*errStr
)
127 m_TTSexec
= RbSettings::subValue(m_name
,RbSettings::TtsPath
).toString();
128 m_TTSOpts
= RbSettings::subValue(m_name
,RbSettings::TtsOptions
).toString();
130 m_TTSTemplate
= m_TemplateMap
.value(m_name
);
132 QFileInfo
tts(m_TTSexec
);
139 *errStr
= tr("TTS executable not found");
144 TTSStatus
TTSExes::voice(QString text
,QString wavfile
, QString
*errStr
)
147 QString execstring
= m_TTSTemplate
;
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
);
159 bool TTSExes::configOk()
161 QString path
= RbSettings::subValue(m_name
,RbSettings::TtsPath
).toString();
163 if (QFileInfo(path
).exists())
169 /*********************************************************************
171 **********************************************************************/
172 TTSSapi::TTSSapi(QObject
* parent
) : TTSBase(parent
)
174 m_TTSTemplate
= "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\"";
175 defaultLanguage
="english";
179 void TTSSapi::generateSettings()
182 QStringList languages
= RbSettings::languages();
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
);
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
);
194 insertSetting(eSPEED
,new EncTtsSetting(this,EncTtsSetting::eINT
,
195 tr("Speed:"),RbSettings::subValue("sapi",RbSettings::TtsSpeed
),-10,10));
197 insertSetting(eOPTIONS
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,
198 tr("Options:"),RbSettings::subValue("sapi",RbSettings::TtsOptions
)));
202 void TTSSapi::saveSettings()
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());
213 void TTSSapi::updateVoiceList()
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("");
222 bool TTSSapi::start(QString
*errStr
)
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();
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";
235 QFileInfo
tts(m_TTSexec
);
238 *errStr
= tr("Could not copy the Sapi-script");
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
);
250 execstring
.append(" /sapi4 ");
252 qDebug() << "init" << execstring
;
253 voicescript
= new QProcess(NULL
);
254 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
256 voicescript
->start(execstring
);
257 if(!voicescript
->waitForStarted())
259 *errStr
= tr("Could not start the Sapi-script");
263 if(!voicescript
->waitForReadyRead(300))
265 *errStr
= voicescript
->readAllStandardError();
270 voicestream
= new QTextStream(voicescript
);
271 voicestream
->setCodec("UTF16-LE");
277 QStringList
TTSSapi::getVoiceList(QString language
)
281 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
282 m_TTSexec
= QDir::tempPath() +"/sapi_voice.vbs";
284 QFileInfo
tts(m_TTSexec
);
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
);
293 if(RbSettings::value(RbSettings::TtsUseSapi4
).toBool())
294 execstring
.append(" /sapi4 ");
296 qDebug() << "init" << execstring
;
297 voicescript
= new QProcess(NULL
);
298 voicescript
->start(execstring
);
299 qDebug() << "wait for started";
300 if(!voicescript
->waitForStarted())
302 voicescript
->closeWriteChannel();
303 voicescript
->waitForReadyRead();
305 QString dataRaw
= voicescript
->readAllStandardError().data();
306 result
= dataRaw
.split(",",QString::SkipEmptyParts
);
307 if(result
.size() > 0)
310 result
.removeFirst();
311 for(int i
= 0; i
< result
.size();i
++)
313 result
[i
] = result
.at(i
).simplified();
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");
328 TTSStatus
TTSSapi::voice(QString text
,QString wavfile
, QString
*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();
343 *voicestream
<< "QUIT\r\n";
344 voicestream
->flush();
345 voicescript
->waitForFinished();
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");
356 bool TTSSapi::configOk()
358 if(RbSettings::subValue("sapi",RbSettings::TtsVoice
).toString().isEmpty())
362 /**********************************************************************
363 * TSSFestival - client-server wrapper
364 **********************************************************************/
365 TTSFestival::~TTSFestival()
370 void TTSFestival::generateSettings()
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
));
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
));
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
);
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
);
397 void TTSFestival::saveSettings()
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());
407 void TTSFestival::updateVoiceDescription()
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
);
414 void TTSFestival::clearVoiceDescription()
416 getSetting(eVOICEDESC
)->setCurrent("");
419 void TTSFestival::updateVoiceList()
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("");
427 void TTSFestival::startServer(QString path
)
433 path
= RbSettings::subValue("festival-server",RbSettings::TtsPath
).toString();
435 serverProcess
.start(QString("%1 --server").arg(path
));
436 serverProcess
.waitForStarted();
438 queryServer("(getpid)",300,path
);
439 if(serverProcess
.state() == QProcess::Running
)
440 qDebug() << "Festival is up and running";
442 qDebug() << "Festival failed to start";
445 void TTSFestival::ensureServerRunning(QString path
)
447 if(serverProcess
.state() != QProcess::Running
)
453 bool TTSFestival::start(QString
* 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()));
464 bool TTSFestival::stop()
466 serverProcess
.terminate();
467 serverProcess
.kill();
472 TTSStatus
TTSFestival::voice(QString text
, QString wavfile
, QString
* errStr
)
474 qDebug() << text
<< "->" << wavfile
;
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
);
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"))
490 qDebug() << "Could not voice string: " << response
;
491 *errStr
= tr("engine could not voice string");
493 /* do not stop the voicing process because of a single string
494 TODO: needs proper settings */
496 clientProcess
.closeReadChannel(QProcess::StandardError
);
497 clientProcess
.closeReadChannel(QProcess::StandardOutput
);
498 clientProcess
.terminate();
499 clientProcess
.kill();
504 bool TTSFestival::configOk()
506 QString serverPath
= RbSettings::subValue("festival-server",RbSettings::TtsPath
).toString();
507 QString clientPath
= RbSettings::subValue("festival-client",RbSettings::TtsVoice
).toString();
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);
516 QStringList
TTSFestival::getVoiceList(QString path
)
519 return QStringList();
521 if(voices
.size() > 0)
523 qDebug() << "Using voice cache";
527 QString response
= queryServer("(voice.list)",3000,path
);
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();
533 voices
= response
.mid(1, response
.size()-2).split(' ');
536 if (voices
.size() == 1 && voices
[0].size() == 0)
538 if (voices
.size() > 0)
539 qDebug() << "Voices: " << voices
;
541 qDebug() << "No voices.";
546 QString
TTSFestival::getVoiceInfo(QString voice
,QString path
)
551 if(!getVoiceList().contains(voice
))
554 if(voiceDescriptions
.contains(voice
))
555 return voiceDescriptions
[voice
];
557 QString response
= queryServer(QString("(voice.description '%1)").arg(voice
), 3000,path
);
561 voiceDescriptions
[voice
]=tr("No description available");
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
572 foreach(QString line
, responseLines
)
574 line
= line
.remove('(');
575 line
= line
.simplified();
577 line
[0] = line
[0].toUpper(); // capitalize the key
579 int firstSpace
= line
.indexOf(' ');
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
586 description
+= line
+ "\n";
588 voiceDescriptions
[voice
] = description
.trimmed();
591 return voiceDescriptions
[voice
];
594 QString
TTSFestival::queryServer(QString query
, int timeout
,QString path
)
599 // this operation could take some time
602 ensureServerRunning(path
);
604 qDebug() << "queryServer with " << query
;
609 endTime
= QDateTime::currentDateTime().addMSecs(timeout
);
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.
617 QCoreApplication::processEvents(QEventLoop::AllEvents
, 50);
620 socket
.connectToHost("localhost", 1314);
621 socket
.waitForConnected();
623 if(socket
.state() == QAbstractSocket::ConnectedState
)
625 socket
.write(QString("%1\n").arg(query
).toAscii());
626 socket
.waitForBytesWritten();
627 socket
.waitForReadyRead();
629 response
= socket
.readAll().trimmed();
631 if (response
!= "LP" && response
!= "")
635 socket
.disconnectFromHost();
637 if(timeout
> 0 && QDateTime::currentDateTime() >= endTime
)
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
);
647 if(response
== "nil")
653 QStringList lines
= response
.split('\n');
660 qDebug() << "Response too short: " << response
;
663 return lines
.join("\n");