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 /*********************************************************************
24 **********************************************************************/
25 QMap
<QString
,QString
> TTSBase::ttsList
;
27 TTSBase::TTSBase(QObject
* parent
): EncTtsSettingInterface(parent
)
33 void TTSBase::initTTSList()
35 ttsList
["espeak"] = "Espeak TTS Engine";
36 ttsList
["flite"] = "Flite TTS Engine";
37 ttsList
["swift"] = "Swift TTS Engine";
39 ttsList
["sapi"] = "Sapi TTS Engine";
41 #if defined(Q_OS_LINUX)
42 ttsList
["festival"] = "Festival TTS Engine";
46 // function to get a specific encoder
47 TTSBase
* TTSBase::getTTS(QObject
* parent
,QString ttsName
)
54 tts
= new TTSSapi(parent
);
59 #if defined(Q_OS_LINUX)
60 if (ttsName
== "festival")
62 tts
= new TTSFestival(parent
);
67 if (true) // fix for OS other than WIN or LINUX
69 tts
= new TTSExes(ttsName
,parent
);
74 // get the list of encoders, nice names
75 QStringList
TTSBase::getTTSList()
77 // init list if its empty
78 if(ttsList
.count() == 0)
81 return ttsList
.keys();
84 // get nice name of a specific tts
85 QString
TTSBase::getTTSName(QString tts
)
89 return ttsList
.value(tts
);
93 /*********************************************************************
95 **********************************************************************/
96 TTSExes::TTSExes(QString name
,QObject
* parent
) : TTSBase(parent
)
100 m_TemplateMap
["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\"";
101 m_TemplateMap
["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\"";
102 m_TemplateMap
["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\"";
106 void TTSExes::generateSettings()
108 QString exepath
=settings
->subValue(m_name
,RbSettings::TtsPath
).toString();
109 if(exepath
== "") exepath
= findExecutable(m_name
);
111 insertSetting(eEXEPATH
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,"Path to TTS engine:",exepath
,EncTtsSetting::eBROWSEBTN
));
112 insertSetting(eOPTIONS
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,"TTS enginge options:",settings
->subValue(m_name
,RbSettings::TtsOptions
)));
115 void TTSExes::saveSettings()
117 settings
->setSubValue(m_name
,RbSettings::TtsPath
,getSetting(eEXEPATH
)->current().toString());
118 settings
->setSubValue(m_name
,RbSettings::TtsOptions
,getSetting(eOPTIONS
)->current().toString());
122 bool TTSExes::start(QString
*errStr
)
124 m_TTSexec
= settings
->subValue(m_name
,RbSettings::TtsPath
).toString();
125 m_TTSOpts
= settings
->subValue(m_name
,RbSettings::TtsOptions
).toString();
127 m_TTSTemplate
= m_TemplateMap
.value(m_name
);
129 QFileInfo
tts(m_TTSexec
);
136 *errStr
= tr("TTS executable not found");
141 TTSStatus
TTSExes::voice(QString text
,QString wavfile
, QString
*errStr
)
144 QString execstring
= m_TTSTemplate
;
146 execstring
.replace("%exe",m_TTSexec
);
147 execstring
.replace("%options",m_TTSOpts
);
148 execstring
.replace("%wavfile",wavfile
);
149 execstring
.replace("%text",text
);
150 //qDebug() << "voicing" << execstring;
151 QProcess::execute(execstring
);
156 bool TTSExes::configOk()
158 QString path
= settings
->subValue(m_name
,RbSettings::TtsPath
).toString();
160 if (QFileInfo(path
).exists())
166 /*********************************************************************
168 **********************************************************************/
169 TTSSapi::TTSSapi(QObject
* parent
) : TTSBase(parent
)
171 m_TTSTemplate
= "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\"";
172 defaultLanguage
="english";
176 void TTSSapi::generateSettings()
179 QStringList languages
= settings
->languages();
181 EncTtsSetting
* setting
=new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST
,"Language:",settings
->subValue("sapi",RbSettings::TtsLanguage
),languages
);
182 connect(setting
,SIGNAL(dataChanged()),this,SLOT(updateVoiceList()));
183 insertSetting(eLANGUAGE
,setting
);
185 setting
= new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST
,"Voice:",settings
->subValue("sapi",RbSettings::TtsVoice
),getVoiceList(settings
->subValue("sapi",RbSettings::TtsLanguage
).toString()),EncTtsSetting::eREFRESHBTN
);
186 connect(setting
,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
187 insertSetting(eVOICE
,setting
);
189 insertSetting(eSPEED
,new EncTtsSetting(this,EncTtsSetting::eINT
,"Speed:",settings
->subValue("sapi",RbSettings::TtsSpeed
),-10,10));
191 insertSetting(eOPTIONS
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,"Options:",settings
->subValue("sapi",RbSettings::TtsOptions
)));
195 void TTSSapi::saveSettings()
197 //save settings in user config
198 settings
->setSubValue("sapi",RbSettings::TtsLanguage
,getSetting(eLANGUAGE
)->current().toString());
199 settings
->setSubValue("sapi",RbSettings::TtsVoice
,getSetting(eVOICE
)->current().toString());
200 settings
->setSubValue("sapi",RbSettings::TtsSpeed
,getSetting(eSPEED
)->current().toInt());
201 settings
->setSubValue("sapi",RbSettings::TtsOptions
,getSetting(eOPTIONS
)->current().toString());
206 void TTSSapi::updateVoiceList()
208 qDebug() << "update voiceList";
209 QStringList voiceList
= getVoiceList(getSetting(eLANGUAGE
)->current().toString());
210 getSetting(eVOICE
)->setList(voiceList
);
211 if(voiceList
.size() > 0) getSetting(eVOICE
)->setCurrent(voiceList
.at(0));
212 else getSetting(eVOICE
)->setCurrent("");
215 bool TTSSapi::start(QString
*errStr
)
218 m_TTSOpts
= settings
->subValue("sapi",RbSettings::TtsOptions
).toString();
219 m_TTSLanguage
=settings
->subValue("sapi",RbSettings::TtsLanguage
).toString();
220 m_TTSVoice
=settings
->subValue("sapi",RbSettings::TtsVoice
).toString();
221 m_TTSSpeed
=settings
->subValue("sapi",RbSettings::TtsSpeed
).toString();
222 m_sapi4
= settings
->subValue("sapi",RbSettings::TtsUseSapi4
).toBool();
224 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
225 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
226 m_TTSexec
= QDir::tempPath() +"/sapi_voice.vbs";
228 QFileInfo
tts(m_TTSexec
);
231 *errStr
= tr("Could not copy the Sapi-script");
234 // create the voice process
235 QString execstring
= m_TTSTemplate
;
236 execstring
.replace("%exe",m_TTSexec
);
237 execstring
.replace("%options",m_TTSOpts
);
238 execstring
.replace("%lang",m_TTSLanguage
);
239 execstring
.replace("%voice",m_TTSVoice
);
240 execstring
.replace("%speed",m_TTSSpeed
);
243 execstring
.append(" /sapi4 ");
245 qDebug() << "init" << execstring
;
246 voicescript
= new QProcess(NULL
);
247 //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
249 voicescript
->start(execstring
);
250 if(!voicescript
->waitForStarted())
252 *errStr
= tr("Could not start the Sapi-script");
256 if(!voicescript
->waitForReadyRead(300))
258 *errStr
= voicescript
->readAllStandardError();
263 voicestream
= new QTextStream(voicescript
);
264 voicestream
->setCodec("UTF16-LE");
270 QStringList
TTSSapi::getVoiceList(QString language
)
274 QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
275 m_TTSexec
= QDir::tempPath() +"/sapi_voice.vbs";
277 QFileInfo
tts(m_TTSexec
);
281 // create the voice process
282 QString execstring
= "cscript //nologo \"%exe\" /language:%lang /listvoices";
283 execstring
.replace("%exe",m_TTSexec
);
284 execstring
.replace("%lang",language
);
286 if(settings
->value(RbSettings::TtsUseSapi4
).toBool())
287 execstring
.append(" /sapi4 ");
289 qDebug() << "init" << execstring
;
290 voicescript
= new QProcess(NULL
);
291 voicescript
->start(execstring
);
292 qDebug() << "wait for started";
293 if(!voicescript
->waitForStarted())
295 voicescript
->closeWriteChannel();
296 voicescript
->waitForReadyRead();
298 QString dataRaw
= voicescript
->readAllStandardError().data();
299 result
= dataRaw
.split(",",QString::SkipEmptyParts
);
300 if(result
.size() > 0)
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");
321 TTSStatus
TTSSapi::voice(QString text
,QString wavfile
, QString
*errStr
)
324 QString query
= "SPEAK\t"+wavfile
+"\t"+text
+"\r\n";
325 qDebug() << "voicing" << query
;
326 *voicestream
<< query
;
327 *voicestream
<< "SYNC\tbla\r\n";
328 voicestream
->flush();
329 voicescript
->waitForReadyRead();
336 *voicestream
<< "QUIT\r\n";
337 voicestream
->flush();
338 voicescript
->waitForFinished();
341 QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner
|QFile::WriteOwner
|QFile::ExeOwner
342 |QFile::ReadUser
| QFile::WriteUser
| QFile::ExeUser
343 |QFile::ReadGroup
|QFile::WriteGroup
|QFile::ExeGroup
344 |QFile::ReadOther
|QFile::WriteOther
|QFile::ExeOther
);
345 QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
349 bool TTSSapi::configOk()
351 if(settings
->subValue("sapi",RbSettings::TtsVoice
).toString().isEmpty())
355 /**********************************************************************
356 * TSSFestival - client-server wrapper
357 **********************************************************************/
358 TTSFestival::~TTSFestival()
363 void TTSFestival::generateSettings()
366 QString exepath
= settings
->subValue("festival-server",RbSettings::TtsPath
).toString();
367 if(exepath
== "" ) exepath
= findExecutable("festival");
368 insertSetting(eSERVERPATH
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,"Path to Festival server:",exepath
,EncTtsSetting::eBROWSEBTN
));
371 QString clientpath
= settings
->subValue("festival-client",RbSettings::TtsPath
).toString();
372 if(clientpath
== "" ) clientpath
= findExecutable("festival_client");
373 insertSetting(eCLIENTPATH
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,"Path to Festival client:",clientpath
,EncTtsSetting::eBROWSEBTN
));
376 EncTtsSetting
* setting
= new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST
,"Voice:",settings
->subValue("festival",RbSettings::TtsVoice
),getVoiceList(exepath
),EncTtsSetting::eREFRESHBTN
);
377 connect(setting
,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
378 connect(setting
,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
379 insertSetting(eVOICE
,setting
);
382 setting
= new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING
,"Voice description:","",EncTtsSetting::eREFRESHBTN
);
383 connect(setting
,SIGNAL(refresh()),this,SLOT(updateVoiceDescription()));
384 insertSetting(eVOICEDESC
,setting
);
387 void TTSFestival::saveSettings()
389 //save settings in user config
390 settings
->setSubValue("festival-server",RbSettings::TtsPath
,getSetting(eSERVERPATH
)->current().toString());
391 settings
->setSubValue("festival-client",RbSettings::TtsPath
,getSetting(eCLIENTPATH
)->current().toString());
392 settings
->setSubValue("festival",RbSettings::TtsVoice
,getSetting(eVOICE
)->current().toString());
397 void TTSFestival::updateVoiceDescription()
399 // get voice Info with current voice and path
400 QString info
= getVoiceInfo(getSetting(eVOICE
)->current().toString(),getSetting(eSERVERPATH
)->current().toString());
401 getSetting(eVOICEDESC
)->setCurrent(info
);
404 void TTSFestival::clearVoiceDescription()
406 getSetting(eVOICEDESC
)->setCurrent("");
409 void TTSFestival::updateVoiceList()
411 QStringList voiceList
= getVoiceList(getSetting(eSERVERPATH
)->current().toString());
412 getSetting(eVOICE
)->setList(voiceList
);
413 if(voiceList
.size() > 0) getSetting(eVOICE
)->setCurrent(voiceList
.at(0));
414 else getSetting(eVOICE
)->setCurrent("");
417 void TTSFestival::startServer(QString path
)
423 path
= settings
->subValue("festival-server",RbSettings::TtsPath
).toString();
425 serverProcess
.start(QString("%1 --server").arg(path
));
426 serverProcess
.waitForStarted();
428 queryServer("(getpid)",300,path
);
429 if(serverProcess
.state() == QProcess::Running
)
430 qDebug() << "Festival is up and running";
432 qDebug() << "Festival failed to start";
435 void TTSFestival::ensureServerRunning(QString path
)
437 if(serverProcess
.state() != QProcess::Running
)
443 bool TTSFestival::start(QString
* errStr
)
446 ensureServerRunning();
447 if (!settings
->subValue("festival",RbSettings::TtsVoice
).toString().isEmpty())
448 queryServer(QString("(voice.select '%1)")
449 .arg(settings
->subValue("festival", RbSettings::TtsVoice
).toString()));
454 bool TTSFestival::stop()
456 serverProcess
.terminate();
457 serverProcess
.kill();
462 TTSStatus
TTSFestival::voice(QString text
, QString wavfile
, QString
* errStr
)
464 qDebug() << text
<< "->" << wavfile
;
466 QString path
= settings
->subValue("festival-client",RbSettings::TtsPath
).toString();
467 QString cmd
= QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path
).arg(wavfile
);
470 QProcess clientProcess
;
471 clientProcess
.start(cmd
);
472 clientProcess
.write(QString("%1.\n").arg(text
).toAscii());
473 clientProcess
.waitForBytesWritten();
474 clientProcess
.closeWriteChannel();
475 clientProcess
.waitForReadyRead();
476 QString response
= clientProcess
.readAll();
477 response
= response
.trimmed();
478 if(!response
.contains("Utterance"))
480 qDebug() << "Could not voice string: " << response
;
481 *errStr
= tr("engine could not voice string");
483 /* do not stop the voicing process because of a single string
484 TODO: needs proper settings */
486 clientProcess
.closeReadChannel(QProcess::StandardError
);
487 clientProcess
.closeReadChannel(QProcess::StandardOutput
);
488 clientProcess
.terminate();
489 clientProcess
.kill();
494 bool TTSFestival::configOk()
496 QString serverPath
= settings
->subValue("festival-server",RbSettings::TtsPath
).toString();
497 QString clientPath
= settings
->subValue("festival-client",RbSettings::TtsVoice
).toString();
499 bool ret
= QFileInfo(serverPath
).isExecutable() &&
500 QFileInfo(clientPath
).isExecutable();
501 if(settings
->subValue("festival",RbSettings::TtsVoice
).toString().size() > 0 && voices
.size() > 0)
502 ret
= ret
&& (voices
.indexOf(settings
->subValue("festival",RbSettings::TtsVoice
).toString()) != -1);
506 QStringList
TTSFestival::getVoiceList(QString path
)
509 return QStringList();
511 if(voices
.size() > 0)
513 qDebug() << "Using voice cache";
517 QString response
= queryServer("(voice.list)",3000,path
);
519 // get the 2nd line. It should be (<voice_name>, <voice_name>)
520 response
= response
.mid(response
.indexOf('\n') + 1, -1);
521 response
= response
.left(response
.indexOf('\n')).trimmed();
523 voices
= response
.mid(1, response
.size()-2).split(' ');
526 if (voices
.size() == 1 && voices
[0].size() == 0)
528 if (voices
.size() > 0)
529 qDebug() << "Voices: " << voices
;
531 qDebug() << "No voices.";
536 QString
TTSFestival::getVoiceInfo(QString voice
,QString path
)
541 if(!getVoiceList().contains(voice
))
544 if(voiceDescriptions
.contains(voice
))
545 return voiceDescriptions
[voice
];
547 QString response
= queryServer(QString("(voice.description '%1)").arg(voice
), 3000,path
);
551 voiceDescriptions
[voice
]=tr("No description available");
555 response
= response
.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive
, QRegExp::Wildcard
));
556 qDebug() << "voiceInfo w/o descr: " << response
;
557 response
= response
.remove(')');
558 QStringList responseLines
= response
.split('(', QString::SkipEmptyParts
);
559 responseLines
.removeAt(0); // the voice name itself
562 foreach(QString line
, responseLines
)
564 line
= line
.remove('(');
565 line
= line
.simplified();
567 line
[0] = line
[0].toUpper(); // capitalize the key
569 int firstSpace
= line
.indexOf(' ');
572 line
= line
.insert(firstSpace
, ':'); // add a colon between the key and the value
573 line
[firstSpace
+2] = line
[firstSpace
+2].toUpper(); // capitalize the value
576 description
+= line
+ "\n";
578 voiceDescriptions
[voice
] = description
.trimmed();
581 return voiceDescriptions
[voice
];
584 QString
TTSFestival::queryServer(QString query
, int timeout
,QString path
)
589 // this operation could take some time
592 ensureServerRunning(path
);
594 qDebug() << "queryServer with " << query
;
599 endTime
= QDateTime::currentDateTime().addMSecs(timeout
);
601 /* Festival is *extremely* unreliable. Although at this
602 * point we are sure that SIOD is accepting commands,
603 * we might end up with an empty response. Hence, the loop.
607 QCoreApplication::processEvents(QEventLoop::AllEvents
, 50);
610 socket
.connectToHost("localhost", 1314);
611 socket
.waitForConnected();
613 if(socket
.state() == QAbstractSocket::ConnectedState
)
615 socket
.write(QString("%1\n").arg(query
).toAscii());
616 socket
.waitForBytesWritten();
617 socket
.waitForReadyRead();
619 response
= socket
.readAll().trimmed();
621 if (response
!= "LP" && response
!= "")
625 socket
.disconnectFromHost();
627 if(timeout
> 0 && QDateTime::currentDateTime() >= endTime
)
632 /* make sure we wait a little as we don't want to flood the server with requests */
633 QDateTime tmpEndTime
= QDateTime::currentDateTime().addMSecs(500);
634 while(QDateTime::currentDateTime() < tmpEndTime
)
635 QCoreApplication::processEvents(QEventLoop::AllEvents
);
637 if(response
== "nil")
643 QStringList lines
= response
.split('\n');
650 qDebug() << "Response too short: " << response
;
653 return lines
.join("\n");