diff options
Diffstat (limited to 'rbutil/rbutilqt/base/tts.cpp')
-rw-r--r-- | rbutil/rbutilqt/base/tts.cpp | 666 |
1 files changed, 0 insertions, 666 deletions
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 | **********************************************************************/ | ||
26 | QMap<QString,QString> TTSBase::ttsList; | ||
27 | |||
28 | TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent) | ||
29 | { | ||
30 | |||
31 | } | ||
32 | |||
33 | // static functions | ||
34 | void 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 | ||
48 | TTSBase* 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 | ||
76 | QStringList 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 | ||
86 | QString 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 | **********************************************************************/ | ||
97 | TTSExes::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 | |||
107 | void 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 | |||
118 | void 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 | |||
125 | bool 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 | |||
144 | TTSStatus 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 | |||
159 | bool 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 | **********************************************************************/ | ||
172 | TTSSapi::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 | |||
179 | void 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 | |||
202 | void 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 | |||
213 | void 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 | |||
222 | bool 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 | |||
277 | QStringList 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 | |||
328 | TTSStatus 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 | |||
340 | bool 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 | |||
356 | bool 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 | **********************************************************************/ | ||
365 | TTSFestival::~TTSFestival() | ||
366 | { | ||
367 | stop(); | ||
368 | } | ||
369 | |||
370 | void 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 | |||
397 | void 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 | |||
407 | void 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 | |||
414 | void TTSFestival::clearVoiceDescription() | ||
415 | { | ||
416 | getSetting(eVOICEDESC)->setCurrent(""); | ||
417 | } | ||
418 | |||
419 | void 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 | |||
427 | void 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 | |||
445 | void TTSFestival::ensureServerRunning(QString path) | ||
446 | { | ||
447 | if(serverProcess.state() != QProcess::Running) | ||
448 | { | ||
449 | startServer(path); | ||
450 | } | ||
451 | } | ||
452 | |||
453 | bool 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 | |||
464 | bool TTSFestival::stop() | ||
465 | { | ||
466 | serverProcess.terminate(); | ||
467 | serverProcess.kill(); | ||
468 | |||
469 | return true; | ||
470 | } | ||
471 | |||
472 | TTSStatus 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 | |||
504 | bool 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 | |||
516 | QStringList 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 | |||
546 | QString 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 | |||
594 | QString 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 | |||