diff options
-rw-r--r-- | rbutil/rbutilqt/CREDITS | 1 | ||||
-rw-r--r-- | rbutil/rbutilqt/rbutilqt.pro | 1 | ||||
-rw-r--r-- | rbutil/rbutilqt/talkfile.cpp | 55 | ||||
-rw-r--r-- | rbutil/rbutilqt/talkfile.h | 4 | ||||
-rw-r--r-- | rbutil/rbutilqt/tts.cpp | 277 | ||||
-rw-r--r-- | rbutil/rbutilqt/tts.h | 42 | ||||
-rw-r--r-- | rbutil/rbutilqt/ttsgui.cpp | 156 | ||||
-rw-r--r-- | rbutil/rbutilqt/ttsgui.h | 30 | ||||
-rw-r--r-- | rbutil/rbutilqt/voicefile.cpp | 5 |
9 files changed, 540 insertions, 31 deletions
diff --git a/rbutil/rbutilqt/CREDITS b/rbutil/rbutilqt/CREDITS index 9c755d7b51..d76a7ded1c 100644 --- a/rbutil/rbutilqt/CREDITS +++ b/rbutil/rbutilqt/CREDITS | |||
@@ -8,3 +8,4 @@ Tomer Shalev | |||
8 | Yoshihisa Uchida | 8 | Yoshihisa Uchida |
9 | Alexander Spyridakis | 9 | Alexander Spyridakis |
10 | Rui Araújo | 10 | Rui Araújo |
11 | Delyan Kratunov \ No newline at end of file | ||
diff --git a/rbutil/rbutilqt/rbutilqt.pro b/rbutil/rbutilqt/rbutilqt.pro index 392cf1e18f..72fead2c04 100644 --- a/rbutil/rbutilqt/rbutilqt.pro +++ b/rbutil/rbutilqt/rbutilqt.pro | |||
@@ -171,6 +171,7 @@ FORMS += rbutilqtfrm.ui \ | |||
171 | encexescfgfrm.ui \ | 171 | encexescfgfrm.ui \ |
172 | ttsexescfgfrm.ui \ | 172 | ttsexescfgfrm.ui \ |
173 | sapicfgfrm.ui \ | 173 | sapicfgfrm.ui \ |
174 | ttsfestivalcfgform.ui \ | ||
174 | createvoicefrm.ui \ | 175 | createvoicefrm.ui \ |
175 | sysinfofrm.ui | 176 | sysinfofrm.ui |
176 | 177 | ||
diff --git a/rbutil/rbutilqt/talkfile.cpp b/rbutil/rbutilqt/talkfile.cpp index 7980e7cbd0..43993be436 100644 --- a/rbutil/rbutilqt/talkfile.cpp +++ b/rbutil/rbutilqt/talkfile.cpp | |||
@@ -34,7 +34,7 @@ bool TalkFileCreator::createTalkFiles(ProgressloggerInterface* logger) | |||
34 | 34 | ||
35 | QMultiMap<QString,QString> fileList; | 35 | QMultiMap<QString,QString> fileList; |
36 | QMultiMap<QString,QString> dirList; | 36 | QMultiMap<QString,QString> dirList; |
37 | QStringList toSpeakList; | 37 | QStringList toSpeakList, voicedEntries, encodedEntries; |
38 | QString errStr; | 38 | QString errStr; |
39 | 39 | ||
40 | m_logger->addItem(tr("Starting Talk file generation"),LOGINFO); | 40 | m_logger->addItem(tr("Starting Talk file generation"),LOGINFO); |
@@ -103,20 +103,19 @@ bool TalkFileCreator::createTalkFiles(ProgressloggerInterface* logger) | |||
103 | } | 103 | } |
104 | } | 104 | } |
105 | 105 | ||
106 | // Voice entryies | 106 | // Voice entries |
107 | m_logger->addItem(tr("Voicing entries..."),LOGINFO); | 107 | m_logger->addItem(tr("Voicing entries..."),LOGINFO); |
108 | if(voiceList(toSpeakList,&errStr) == false) | 108 | TTSStatus voiceStatus= voiceList(toSpeakList,voicedEntries); |
109 | if(voiceStatus == FatalError) | ||
109 | { | 110 | { |
110 | m_logger->addItem(errStr,LOGERROR); | ||
111 | doAbort(toSpeakList); | 111 | doAbort(toSpeakList); |
112 | return false; | 112 | return false; |
113 | } | 113 | } |
114 | 114 | ||
115 | // Encoding Entries | 115 | // Encoding Entries |
116 | m_logger->addItem(tr("Encoding files..."),LOGINFO); | 116 | m_logger->addItem(tr("Encoding files..."),LOGINFO); |
117 | if(encodeList(toSpeakList,&errStr) == false) | 117 | if(encodeList(voicedEntries,encodedEntries) == false) |
118 | { | 118 | { |
119 | m_logger->addItem(errStr,LOGERROR); | ||
120 | doAbort(toSpeakList); | 119 | doAbort(toSpeakList); |
121 | return false; | 120 | return false; |
122 | } | 121 | } |
@@ -247,29 +246,45 @@ bool TalkFileCreator::createDirAndFileMaps(QDir startDir,QMultiMap<QString,QStri | |||
247 | //! \param toSpeak QStringList with the Entries to voice. | 246 | //! \param toSpeak QStringList with the Entries to voice. |
248 | //! \param errString pointer to where the Error cause is written | 247 | //! \param errString pointer to where the Error cause is written |
249 | //! \returns true on success, false on error or user abort | 248 | //! \returns true on success, false on error or user abort |
250 | bool TalkFileCreator::voiceList(QStringList toSpeak,QString* errString) | 249 | TTSStatus TalkFileCreator::voiceList(QStringList toSpeak,QStringList& voicedEntries) |
251 | { | 250 | { |
252 | resetProgress(toSpeak.size()); | 251 | resetProgress(toSpeak.size()); |
252 | QStringList errors; | ||
253 | 253 | ||
254 | bool warnings = false; | ||
254 | for(int i=0; i < toSpeak.size(); i++) | 255 | for(int i=0; i < toSpeak.size(); i++) |
255 | { | 256 | { |
256 | if(m_abort) | 257 | if(m_abort) |
257 | { | 258 | { |
258 | *errString = tr("Talk file creation aborted"); | 259 | m_logger->addItem(tr("Talk file creation aborted"), LOGERROR); |
259 | return false; | 260 | return FatalError; |
260 | } | 261 | } |
261 | 262 | ||
262 | QString filename = QDir::tempPath()+ "/"+ toSpeak[i] + ".wav"; | 263 | QString filename = QDir::tempPath()+ "/"+ toSpeak[i] + ".wav"; |
263 | 264 | ||
264 | if(!m_tts->voice(toSpeak[i],filename)) | 265 | QString error; |
266 | TTSStatus status = m_tts->voice(toSpeak[i],filename, &error); | ||
267 | if(status == Warning) | ||
265 | { | 268 | { |
266 | *errString =tr("Voicing of %s failed").arg(toSpeak[i]); | 269 | warnings = true; |
267 | return false; | 270 | m_logger->addItem(tr("Voicing of %1 failed: %2").arg(toSpeak[i]).arg(error), |
271 | LOGWARNING); | ||
272 | } | ||
273 | else if (status == FatalError) | ||
274 | { | ||
275 | m_logger->addItem(tr("Voicing of %1 failed: %2").arg(toSpeak[i]).arg(error), | ||
276 | LOGERROR); | ||
277 | return FatalError; | ||
268 | } | 278 | } |
279 | else | ||
280 | voicedEntries.append(toSpeak[i]); | ||
269 | m_logger->setProgressValue(++m_progress); | 281 | m_logger->setProgressValue(++m_progress); |
270 | QCoreApplication::processEvents(); | 282 | QCoreApplication::processEvents(); |
271 | } | 283 | } |
272 | return true; | 284 | if(warnings) |
285 | return Warning; | ||
286 | else | ||
287 | return NoError; | ||
273 | } | 288 | } |
274 | 289 | ||
275 | 290 | ||
@@ -279,14 +294,14 @@ bool TalkFileCreator::voiceList(QStringList toSpeak,QString* errString) | |||
279 | //! \param toSpeak QStringList with the Entries to encode. | 294 | //! \param toSpeak QStringList with the Entries to encode. |
280 | //! \param errString pointer to where the Error cause is written | 295 | //! \param errString pointer to where the Error cause is written |
281 | //! \returns true on success, false on error or user abort | 296 | //! \returns true on success, false on error or user abort |
282 | bool TalkFileCreator::encodeList(QStringList toEncode,QString* errString) | 297 | bool TalkFileCreator::encodeList(QStringList toEncode,QStringList& encodedEntries) |
283 | { | 298 | { |
284 | resetProgress(toEncode.size()); | 299 | resetProgress(toEncode.size()); |
285 | for(int i=0; i < toEncode.size(); i++) | 300 | for(int i=0; i < toEncode.size(); i++) |
286 | { | 301 | { |
287 | if(m_abort) | 302 | if(m_abort) |
288 | { | 303 | { |
289 | *errString = tr("Talk file creation aborted"); | 304 | m_logger->addItem(tr("Talk file creation aborted"), LOGERROR); |
290 | return false; | 305 | return false; |
291 | } | 306 | } |
292 | 307 | ||
@@ -295,9 +310,10 @@ bool TalkFileCreator::encodeList(QStringList toEncode,QString* errString) | |||
295 | 310 | ||
296 | if(!m_enc->encode(wavfilename,filename)) | 311 | if(!m_enc->encode(wavfilename,filename)) |
297 | { | 312 | { |
298 | *errString =tr("Encoding of %1 failed").arg(filename); | 313 | m_logger->addItem(tr("Encoding of %1 failed").arg(filename), LOGERROR); |
299 | return false; | 314 | return false; |
300 | } | 315 | } |
316 | encodedEntries.append(toEncode[i]); | ||
301 | m_logger->setProgressValue(++m_progress); | 317 | m_logger->setProgressValue(++m_progress); |
302 | QCoreApplication::processEvents(); | 318 | QCoreApplication::processEvents(); |
303 | } | 319 | } |
@@ -327,6 +343,10 @@ bool TalkFileCreator::copyTalkDirFiles(QMultiMap<QString,QString> dirMap,QString | |||
327 | } | 343 | } |
328 | 344 | ||
329 | QString source = QDir::tempPath()+ "/"+ it.value() + ".talk"; | 345 | QString source = QDir::tempPath()+ "/"+ it.value() + ".talk"; |
346 | |||
347 | if(!QFileInfo(source).exists()) | ||
348 | continue; // this file was skipped in one of the previous steps | ||
349 | |||
330 | QString target = it.key() + "/" + "_dirname.talk"; | 350 | QString target = it.key() + "/" + "_dirname.talk"; |
331 | 351 | ||
332 | // remove target if it exists, and if we should overwrite it | 352 | // remove target if it exists, and if we should overwrite it |
@@ -383,6 +403,9 @@ bool TalkFileCreator::copyTalkFileFiles(QMultiMap<QString,QString> fileMap,QStri | |||
383 | else | 403 | else |
384 | source = QDir::tempPath()+ "/"+ it.value() + ".talk"; | 404 | source = QDir::tempPath()+ "/"+ it.value() + ".talk"; |
385 | 405 | ||
406 | if(!QFileInfo(source).exists()) | ||
407 | continue; // this file was skipped in one of the previous steps | ||
408 | |||
386 | // remove target if it exists, and if we should overwrite it | 409 | // remove target if it exists, and if we should overwrite it |
387 | if(m_overwriteTalk && QFile::exists(target)) | 410 | if(m_overwriteTalk && QFile::exists(target)) |
388 | QFile::remove(target); | 411 | QFile::remove(target); |
diff --git a/rbutil/rbutilqt/talkfile.h b/rbutil/rbutilqt/talkfile.h index d869c32880..08c076132d 100644 --- a/rbutil/rbutilqt/talkfile.h +++ b/rbutil/rbutilqt/talkfile.h | |||
@@ -58,8 +58,8 @@ private: | |||
58 | void doAbort(QStringList cleanupList); | 58 | void doAbort(QStringList cleanupList); |
59 | void resetProgress(int max); | 59 | void resetProgress(int max); |
60 | bool createDirAndFileMaps(QDir startDir,QMultiMap<QString,QString> *dirMap,QMultiMap<QString,QString> *fileMap); | 60 | bool createDirAndFileMaps(QDir startDir,QMultiMap<QString,QString> *dirMap,QMultiMap<QString,QString> *fileMap); |
61 | bool voiceList(QStringList toSpeak,QString* errString); | 61 | TTSStatus voiceList(QStringList toSpeak,QStringList& voicedEntries); |
62 | bool encodeList(QStringList toEncode,QString* errString); | 62 | bool encodeList(QStringList toEncode,QStringList& encodedEntries); |
63 | bool copyTalkDirFiles(QMultiMap<QString,QString> dirMap,QString* errString); | 63 | bool copyTalkDirFiles(QMultiMap<QString,QString> dirMap,QString* errString); |
64 | bool copyTalkFileFiles(QMultiMap<QString,QString> fileMap,QString* errString); | 64 | bool copyTalkFileFiles(QMultiMap<QString,QString> fileMap,QString* errString); |
65 | 65 | ||
diff --git a/rbutil/rbutilqt/tts.cpp b/rbutil/rbutilqt/tts.cpp index 252608f53e..48555cc114 100644 --- a/rbutil/rbutilqt/tts.cpp +++ b/rbutil/rbutilqt/tts.cpp | |||
@@ -33,7 +33,9 @@ void TTSBase::initTTSList() | |||
33 | #if defined(Q_OS_WIN) | 33 | #if defined(Q_OS_WIN) |
34 | ttsList["sapi"] = "Sapi TTS Engine"; | 34 | ttsList["sapi"] = "Sapi TTS Engine"; |
35 | #endif | 35 | #endif |
36 | 36 | #if defined(Q_OS_LINUX) | |
37 | ttsList["festival"] = "Festival TTS Engine"; | ||
38 | #endif | ||
37 | } | 39 | } |
38 | 40 | ||
39 | // function to get a specific encoder | 41 | // function to get a specific encoder |
@@ -44,6 +46,7 @@ TTSBase* TTSBase::getTTS(QString ttsName) | |||
44 | return ttsCache.value(ttsName); | 46 | return ttsCache.value(ttsName); |
45 | 47 | ||
46 | TTSBase* tts; | 48 | TTSBase* tts; |
49 | #if defined(Q_OS_WIN) | ||
47 | if(ttsName == "sapi") | 50 | if(ttsName == "sapi") |
48 | { | 51 | { |
49 | tts = new TTSSapi(); | 52 | tts = new TTSSapi(); |
@@ -51,6 +54,17 @@ TTSBase* TTSBase::getTTS(QString ttsName) | |||
51 | return tts; | 54 | return tts; |
52 | } | 55 | } |
53 | else | 56 | else |
57 | #endif | ||
58 | #if defined(Q_OS_LINUX) | ||
59 | if (ttsName == "festival") | ||
60 | { | ||
61 | tts = new TTSFestival(); | ||
62 | ttsCache[ttsName] = tts; | ||
63 | return tts; | ||
64 | } | ||
65 | else | ||
66 | #endif | ||
67 | if (true) // fix for OS other than WIN or LINUX | ||
54 | { | 68 | { |
55 | tts = new TTSExes(ttsName); | 69 | tts = new TTSExes(ttsName); |
56 | ttsCache[ttsName] = tts; | 70 | ttsCache[ttsName] = tts; |
@@ -92,7 +106,7 @@ TTSExes::TTSExes(QString name) : TTSBase() | |||
92 | m_name = name; | 106 | m_name = name; |
93 | 107 | ||
94 | m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; | 108 | m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; |
95 | m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; | 109 | m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; |
96 | m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; | 110 | m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; |
97 | 111 | ||
98 | } | 112 | } |
@@ -153,8 +167,9 @@ bool TTSExes::start(QString *errStr) | |||
153 | } | 167 | } |
154 | } | 168 | } |
155 | 169 | ||
156 | bool TTSExes::voice(QString text,QString wavfile) | 170 | TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) |
157 | { | 171 | { |
172 | (void) errStr; | ||
158 | QString execstring = m_TTSTemplate; | 173 | QString execstring = m_TTSTemplate; |
159 | 174 | ||
160 | execstring.replace("%exe",m_TTSexec); | 175 | execstring.replace("%exe",m_TTSexec); |
@@ -163,7 +178,7 @@ bool TTSExes::voice(QString text,QString wavfile) | |||
163 | execstring.replace("%text",text); | 178 | execstring.replace("%text",text); |
164 | //qDebug() << "voicing" << execstring; | 179 | //qDebug() << "voicing" << execstring; |
165 | QProcess::execute(execstring); | 180 | QProcess::execute(execstring); |
166 | return true; | 181 | return NoError; |
167 | 182 | ||
168 | } | 183 | } |
169 | 184 | ||
@@ -304,15 +319,16 @@ QStringList TTSSapi::getVoiceList(QString language) | |||
304 | 319 | ||
305 | 320 | ||
306 | 321 | ||
307 | bool TTSSapi::voice(QString text,QString wavfile) | 322 | TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) |
308 | { | 323 | { |
324 | (void) errStr; | ||
309 | QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; | 325 | QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; |
310 | qDebug() << "voicing" << query; | 326 | qDebug() << "voicing" << query; |
311 | *voicestream << query; | 327 | *voicestream << query; |
312 | *voicestream << "SYNC\tbla\r\n"; | 328 | *voicestream << "SYNC\tbla\r\n"; |
313 | voicestream->flush(); | 329 | voicestream->flush(); |
314 | voicescript->waitForReadyRead(); | 330 | voicescript->waitForReadyRead(); |
315 | return true; | 331 | return NoError; |
316 | } | 332 | } |
317 | 333 | ||
318 | bool TTSSapi::stop() | 334 | bool TTSSapi::stop() |
@@ -349,5 +365,254 @@ bool TTSSapi::configOk() | |||
349 | return false; | 365 | return false; |
350 | return true; | 366 | return true; |
351 | } | 367 | } |
368 | /********************************************************************** | ||
369 | * TSSFestival - client-server wrapper | ||
370 | **********************************************************************/ | ||
371 | TTSFestival::~TTSFestival() | ||
372 | { | ||
373 | stop(); | ||
374 | } | ||
375 | |||
376 | void TTSFestival::startServer() | ||
377 | { | ||
378 | if(!configOk()) | ||
379 | return; | ||
380 | |||
381 | QStringList paths = settings->ttsPath("festival").split(":"); | ||
382 | |||
383 | serverProcess.start(QString("%1 --server").arg(paths[0])); | ||
384 | serverProcess.waitForStarted(); | ||
385 | |||
386 | queryServer("(getpid)"); | ||
387 | if(serverProcess.state() == QProcess::Running) | ||
388 | qDebug() << "Festival is up and running"; | ||
389 | else | ||
390 | qDebug() << "Festival failed to start"; | ||
391 | } | ||
392 | |||
393 | void TTSFestival::ensureServerRunning() | ||
394 | { | ||
395 | if(serverProcess.state() != QProcess::Running) | ||
396 | { | ||
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(); | ||
404 | |||
405 | QApplication::processEvents(); // actually show the dialog | ||
406 | |||
407 | startServer(); | ||
408 | } | ||
409 | } | ||
410 | |||
411 | bool TTSFestival::start(QString* errStr) | ||
412 | { | ||
413 | (void) errStr; | ||
414 | ensureServerRunning(); | ||
415 | if (!settings->ttsVoice("festival").isEmpty()) | ||
416 | queryServer(QString("(voice.select '%1)").arg(settings->ttsVoice("festival"))); | ||
417 | |||
418 | return true; | ||
419 | } | ||
352 | 420 | ||
421 | bool TTSFestival::stop() | ||
422 | { | ||
423 | serverProcess.terminate(); | ||
424 | serverProcess.kill(); | ||
425 | |||
426 | return true; | ||
427 | } | ||
428 | |||
429 | TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) | ||
430 | { | ||
431 | qDebug() << text << "->" << wavfile; | ||
432 | |||
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); | ||
435 | qDebug() << cmd; | ||
436 | |||
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")) | ||
446 | { | ||
447 | qDebug() << "Could not voice string: " << response; | ||
448 | *errStr = tr("engine could not voice string"); | ||
449 | return Warning; | ||
450 | /* do not stop the voicing process because of a single string | ||
451 | TODO: needs proper settings */ | ||
452 | } | ||
453 | clientProcess.closeReadChannel(QProcess::StandardError); | ||
454 | clientProcess.closeReadChannel(QProcess::StandardOutput); | ||
455 | clientProcess.terminate(); | ||
456 | clientProcess.kill(); | ||
457 | |||
458 | return NoError; | ||
459 | } | ||
460 | |||
461 | bool TTSFestival::configOk() | ||
462 | { | ||
463 | QStringList paths = settings->ttsPath("festival").split(":"); | ||
464 | if(paths.size() != 2) | ||
465 | return false; | ||
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); | ||
470 | return ret; | ||
471 | } | ||
472 | |||
473 | void TTSFestival::showCfg() | ||
474 | { | ||
475 | #ifndef CONSOLE | ||
476 | TTSFestivalGui gui(this); | ||
477 | #endif | ||
478 | gui.setCfg(settings); | ||
479 | gui.showCfg(); | ||
480 | } | ||
481 | |||
482 | QStringList TTSFestival::getVoiceList() | ||
483 | { | ||
484 | if(!configOk()) | ||
485 | return QStringList(); | ||
486 | |||
487 | if(voices.size() > 0) | ||
488 | { | ||
489 | qDebug() << "Using voice cache"; | ||
490 | return voices; | ||
491 | } | ||
492 | QString response = queryServer("(voice.list)"); | ||
493 | |||
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(); | ||
497 | |||
498 | voices = response.mid(1, response.size()-2).split(' '); | ||
499 | |||
500 | voices.sort(); | ||
501 | if (voices.size() == 1 && voices[0].size() == 0) | ||
502 | voices.removeAt(0); | ||
503 | if (voices.size() > 0) | ||
504 | qDebug() << "Voices: " << voices; | ||
505 | else | ||
506 | qDebug() << "No voices."; | ||
507 | return voices; | ||
508 | } | ||
509 | |||
510 | QString TTSFestival::getVoiceInfo(QString voice) | ||
511 | { | ||
512 | if(!configOk()) | ||
513 | return ""; | ||
514 | |||
515 | if(!getVoiceList().contains(voice)) | ||
516 | return ""; | ||
517 | |||
518 | if(voiceDescriptions.contains(voice)) | ||
519 | return voiceDescriptions[voice]; | ||
520 | |||
521 | QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000); | ||
522 | |||
523 | if (response == "") | ||
524 | { | ||
525 | voiceDescriptions[voice]=tr("No description available"); | ||
526 | } | ||
527 | else | ||
528 | { | ||
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 | ||
534 | |||
535 | QString description; | ||
536 | foreach(QString line, responseLines) | ||
537 | { | ||
538 | line = line.remove('('); | ||
539 | line = line.simplified(); | ||
540 | |||
541 | line[0] = line[0].toUpper(); // capitalize the key | ||
542 | |||
543 | int firstSpace = line.indexOf(' '); | ||
544 | if (firstSpace > 0) | ||
545 | { | ||
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 | ||
548 | } | ||
549 | |||
550 | description += line + "\n"; | ||
551 | } | ||
552 | voiceDescriptions[voice] = description.trimmed(); | ||
553 | } | ||
554 | return voiceDescriptions[voice]; | ||
555 | } | ||
556 | |||
557 | QString TTSFestival::queryServer(QString query, int timeout) | ||
558 | { | ||
559 | if(!configOk()) | ||
560 | return ""; | ||
561 | |||
562 | ensureServerRunning(); | ||
563 | |||
564 | qDebug() << "queryServer with " << query; | ||
565 | QString response; | ||
566 | |||
567 | QDateTime endTime; | ||
568 | if(timeout > 0) | ||
569 | endTime = QDateTime::currentDateTime().addMSecs(timeout); | ||
570 | |||
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. | ||
574 | */ | ||
575 | while(true) | ||
576 | { | ||
577 | QApplication::processEvents(QEventLoop::AllEvents, 50); | ||
578 | QTcpSocket socket; | ||
579 | |||
580 | socket.connectToHost("localhost", 1314); | ||
581 | socket.waitForConnected(); | ||
582 | |||
583 | if(socket.state() == QAbstractSocket::ConnectedState) | ||
584 | { | ||
585 | socket.write(QString("%1\n").arg(query).toAscii()); | ||
586 | socket.waitForBytesWritten(); | ||
587 | socket.waitForReadyRead(); | ||
588 | |||
589 | response = socket.readAll().trimmed(); | ||
590 | |||
591 | if (response != "LP" && response != "") | ||
592 | break; | ||
593 | } | ||
594 | socket.abort(); | ||
595 | socket.disconnectFromHost(); | ||
596 | |||
597 | if(timeout > 0 && QDateTime::currentDateTime() >= endTime) | ||
598 | return ""; | ||
599 | |||
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); | ||
604 | } | ||
605 | if(response == "nil") | ||
606 | return ""; | ||
607 | |||
608 | QStringList lines = response.split('\n'); | ||
609 | if(lines.size() > 2) | ||
610 | { | ||
611 | lines.removeFirst(); | ||
612 | lines.removeLast(); | ||
613 | } | ||
614 | else | ||
615 | qDebug() << "Response too short: " << response; | ||
616 | return lines.join("\n"); | ||
617 | } | ||
353 | 618 | ||
diff --git a/rbutil/rbutilqt/tts.h b/rbutil/rbutilqt/tts.h index 7c21fd0d65..d225d46853 100644 --- a/rbutil/rbutilqt/tts.h +++ b/rbutil/rbutilqt/tts.h | |||
@@ -23,9 +23,13 @@ | |||
23 | #ifndef TTS_H | 23 | #ifndef TTS_H |
24 | #define TTS_H | 24 | #define TTS_H |
25 | 25 | ||
26 | |||
27 | #include "rbsettings.h" | 26 | #include "rbsettings.h" |
28 | #include <QtCore> | 27 | #include <QtCore> |
28 | #include <QProcess> | ||
29 | #include <QProgressDialog> | ||
30 | #include <QDateTime> | ||
31 | #include <QRegExp> | ||
32 | #include <QTcpSocket> | ||
29 | 33 | ||
30 | #ifndef CONSOLE | 34 | #ifndef CONSOLE |
31 | #include "ttsgui.h" | 35 | #include "ttsgui.h" |
@@ -33,14 +37,18 @@ | |||
33 | #include "ttsguicli.h" | 37 | #include "ttsguicli.h" |
34 | #endif | 38 | #endif |
35 | 39 | ||
36 | 40 | enum TTSStatus{ FatalError, NoError, Warning }; | |
41 | class TTSSapi; | ||
42 | #if defined(Q_OS_LINUX) | ||
43 | class TTSFestival; | ||
44 | #endif | ||
37 | class TTSBase : public QObject | 45 | class TTSBase : public QObject |
38 | { | 46 | { |
39 | Q_OBJECT | 47 | Q_OBJECT |
40 | public: | 48 | public: |
41 | TTSBase(); | 49 | TTSBase(); |
42 | virtual bool voice(QString text,QString wavfile) | 50 | virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) |
43 | { (void)text; (void)wavfile; return false; } | 51 | { (void) text; (void) wavfile; (void) errStr; return FatalError;} |
44 | virtual bool start(QString *errStr) { (void)errStr; return false; } | 52 | virtual bool start(QString *errStr) { (void)errStr; return false; } |
45 | virtual bool stop() { return false; } | 53 | virtual bool stop() { return false; } |
46 | virtual void showCfg(){} | 54 | virtual void showCfg(){} |
@@ -72,7 +80,7 @@ class TTSSapi : public TTSBase | |||
72 | Q_OBJECT | 80 | Q_OBJECT |
73 | public: | 81 | public: |
74 | TTSSapi(); | 82 | TTSSapi(); |
75 | virtual bool voice(QString text,QString wavfile); | 83 | virtual TTSStatus voice(QString text,QString wavfile, QString *errStr); |
76 | virtual bool start(QString *errStr); | 84 | virtual bool start(QString *errStr); |
77 | virtual bool stop(); | 85 | virtual bool stop(); |
78 | virtual void showCfg(); | 86 | virtual void showCfg(); |
@@ -99,7 +107,7 @@ class TTSExes : public TTSBase | |||
99 | Q_OBJECT | 107 | Q_OBJECT |
100 | public: | 108 | public: |
101 | TTSExes(QString name); | 109 | TTSExes(QString name); |
102 | virtual bool voice(QString text,QString wavfile); | 110 | virtual TTSStatus voice(QString text,QString wavfile, QString *errStr); |
103 | virtual bool start(QString *errStr); | 111 | virtual bool start(QString *errStr); |
104 | virtual bool stop() {return true;} | 112 | virtual bool stop() {return true;} |
105 | virtual void showCfg(); | 113 | virtual void showCfg(); |
@@ -115,4 +123,26 @@ class TTSExes : public TTSBase | |||
115 | QMap<QString,QString> m_TemplateMap; | 123 | QMap<QString,QString> m_TemplateMap; |
116 | }; | 124 | }; |
117 | 125 | ||
126 | class TTSFestival : public TTSBase | ||
127 | { | ||
128 | Q_OBJECT | ||
129 | public: | ||
130 | ~TTSFestival(); | ||
131 | virtual bool configOk(); | ||
132 | virtual bool start(QString *errStr); | ||
133 | virtual bool stop(); | ||
134 | virtual void showCfg(); | ||
135 | virtual TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
136 | |||
137 | QStringList getVoiceList(); | ||
138 | QString getVoiceInfo(QString voice); | ||
139 | private: | ||
140 | inline void startServer(); | ||
141 | inline void ensureServerRunning(); | ||
142 | QString queryServer(QString query, int timeout = -1); | ||
143 | QProcess serverProcess; | ||
144 | QStringList voices; | ||
145 | QMap<QString, QString> voiceDescriptions; | ||
146 | }; | ||
147 | |||
118 | #endif | 148 | #endif |
diff --git a/rbutil/rbutilqt/ttsgui.cpp b/rbutil/rbutilqt/ttsgui.cpp index 0a59b25d86..45dd3a86ef 100644 --- a/rbutil/rbutilqt/ttsgui.cpp +++ b/rbutil/rbutilqt/ttsgui.cpp | |||
@@ -181,3 +181,159 @@ void TTSExesGui::browse() | |||
181 | } | 181 | } |
182 | } | 182 | } |
183 | 183 | ||
184 | TTSFestivalGui::TTSFestivalGui(TTSFestival* api, QDialog* parent) : | ||
185 | QDialog(parent), festival(api) | ||
186 | { | ||
187 | ui.setupUi(this); | ||
188 | this->setModal(true); | ||
189 | this->setDisabled(true); | ||
190 | this->show(); | ||
191 | |||
192 | connect(ui.clientButton, SIGNAL(clicked()), this, SLOT(onBrowseClient())); | ||
193 | connect(ui.serverButton, SIGNAL(clicked()), this, SLOT(onBrowseServer())); | ||
194 | |||
195 | connect(ui.refreshButton, SIGNAL(clicked()), this, SLOT(onRefreshButton())); | ||
196 | connect(ui.voicesBox, SIGNAL(activated(QString)), this, SLOT(updateDescription(QString))); | ||
197 | connect(ui.showDescriptionCheckbox, SIGNAL(stateChanged(int)), this, SLOT(onShowDescription(int))); | ||
198 | } | ||
199 | |||
200 | void TTSFestivalGui::showCfg() | ||
201 | { | ||
202 | qDebug() << "show\tpaths: " << settings->ttsPath("festival") << "\n" | ||
203 | << "\tvoice: " << settings->ttsVoice("festival"); | ||
204 | |||
205 | // will populate the voices if the paths are correct, | ||
206 | // otherwise, it will require the user to press Refresh | ||
207 | updateVoices(); | ||
208 | |||
209 | // try to get config from settings | ||
210 | QStringList paths = settings->ttsPath("festival").split(":"); | ||
211 | if(paths.size() == 2) | ||
212 | { | ||
213 | ui.serverPath->setText(paths[0]); | ||
214 | ui.clientPath->setText(paths[1]); | ||
215 | } | ||
216 | |||
217 | this->setEnabled(true); | ||
218 | this->exec(); | ||
219 | } | ||
220 | |||
221 | void TTSFestivalGui::accept(void) | ||
222 | { | ||
223 | //save settings in user config | ||
224 | QString newPath = QString("%1:%2").arg(ui.serverPath->text().trimmed()).arg(ui.clientPath->text().trimmed()); | ||
225 | qDebug() << "set\tpaths: " << newPath << "\n\tvoice: " << ui.voicesBox->currentText(); | ||
226 | settings->setTTSPath("festival", newPath); | ||
227 | settings->setTTSVoice("festival", ui.voicesBox->currentText()); | ||
228 | |||
229 | settings->sync(); | ||
230 | |||
231 | this->done(0); | ||
232 | } | ||
233 | |||
234 | void TTSFestivalGui::reject(void) | ||
235 | { | ||
236 | this->done(0); | ||
237 | } | ||
238 | |||
239 | void TTSFestivalGui::onBrowseClient() | ||
240 | { | ||
241 | BrowseDirtree browser(this); | ||
242 | browser.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); | ||
243 | |||
244 | QFileInfo currentPath(ui.clientPath->text().trimmed()); | ||
245 | if(currentPath.isDir()) | ||
246 | { | ||
247 | browser.setDir(ui.clientPath->text()); | ||
248 | } | ||
249 | else if (currentPath.isFile()) | ||
250 | { | ||
251 | browser.setDir(currentPath.dir().absolutePath()); | ||
252 | } | ||
253 | if(browser.exec() == QDialog::Accepted) | ||
254 | { | ||
255 | qDebug() << browser.getSelected(); | ||
256 | QString exe = browser.getSelected(); | ||
257 | if(!QFileInfo(exe).isExecutable()) | ||
258 | return; | ||
259 | ui.clientPath->setText(exe); | ||
260 | } | ||
261 | } | ||
262 | |||
263 | void TTSFestivalGui::onBrowseServer() | ||
264 | { | ||
265 | BrowseDirtree browser(this); | ||
266 | browser.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); | ||
267 | |||
268 | QFileInfo currentPath(ui.serverPath->text().trimmed()); | ||
269 | if(currentPath.isDir()) | ||
270 | { | ||
271 | browser.setDir(ui.serverPath->text()); | ||
272 | } | ||
273 | else if (currentPath.isFile()) | ||
274 | { | ||
275 | browser.setDir(currentPath.dir().absolutePath()); | ||
276 | } | ||
277 | if(browser.exec() == QDialog::Accepted) | ||
278 | { | ||
279 | qDebug() << browser.getSelected(); | ||
280 | QString exe = browser.getSelected(); | ||
281 | if(!QFileInfo(exe).isExecutable()) | ||
282 | return; | ||
283 | ui.serverPath->setText(exe); | ||
284 | } | ||
285 | } | ||
286 | |||
287 | void TTSFestivalGui::onRefreshButton() | ||
288 | { | ||
289 | /* Temporarily commit the settings so that we get the new path when we check for voices */ | ||
290 | QString newPath = QString("%1:%2").arg(ui.serverPath->text().trimmed()).arg(ui.clientPath->text().trimmed()); | ||
291 | QString oldPath = settings->ttsPath("festival"); | ||
292 | qDebug() << "new path: " << newPath << "\n" << "old path: " << oldPath << "\nuse new: " << (newPath != oldPath); | ||
293 | |||
294 | if(newPath != oldPath) | ||
295 | { | ||
296 | qDebug() << "Using new paths for getVoiceList"; | ||
297 | settings->setTTSPath("festival", newPath); | ||
298 | settings->sync(); | ||
299 | } | ||
300 | |||
301 | updateVoices(); | ||
302 | |||
303 | if(newPath != oldPath) | ||
304 | { | ||
305 | settings->setTTSPath("festival", oldPath); | ||
306 | settings->sync(); | ||
307 | } | ||
308 | } | ||
309 | |||
310 | void TTSFestivalGui::onShowDescription(int state) | ||
311 | { | ||
312 | if(state == Qt::Unchecked) | ||
313 | ui.descriptionLabel->setText(""); | ||
314 | else | ||
315 | updateDescription(ui.voicesBox->currentText()); | ||
316 | } | ||
317 | |||
318 | void TTSFestivalGui::updateVoices() | ||
319 | { | ||
320 | ui.voicesBox->clear(); | ||
321 | ui.voicesBox->addItem(tr("Loading..")); | ||
322 | |||
323 | QStringList voiceList = festival->getVoiceList(); | ||
324 | ui.voicesBox->clear(); | ||
325 | ui.voicesBox->addItems(voiceList); | ||
326 | |||
327 | ui.voicesBox->setCurrentIndex(ui.voicesBox->findText(settings->ttsVoice("festival"))); | ||
328 | |||
329 | updateDescription(settings->ttsVoice("festival")); | ||
330 | } | ||
331 | |||
332 | void TTSFestivalGui::updateDescription(QString value) | ||
333 | { | ||
334 | if(ui.showDescriptionCheckbox->checkState() == Qt::Checked) | ||
335 | { | ||
336 | ui.descriptionLabel->setText(tr("Querying festival")); | ||
337 | ui.descriptionLabel->setText(festival->getVoiceInfo(value)); | ||
338 | } | ||
339 | } | ||
diff --git a/rbutil/rbutilqt/ttsgui.h b/rbutil/rbutilqt/ttsgui.h index 68990b83c0..ce7be408ad 100644 --- a/rbutil/rbutilqt/ttsgui.h +++ b/rbutil/rbutilqt/ttsgui.h | |||
@@ -26,9 +26,11 @@ | |||
26 | 26 | ||
27 | #include "ui_ttsexescfgfrm.h" | 27 | #include "ui_ttsexescfgfrm.h" |
28 | #include "ui_sapicfgfrm.h" | 28 | #include "ui_sapicfgfrm.h" |
29 | #include "ui_ttsfestivalcfgform.h" | ||
29 | 30 | ||
30 | class RbSettings; | 31 | class RbSettings; |
31 | class TTSSapi; | 32 | class TTSSapi; |
33 | class TTSFestival; | ||
32 | 34 | ||
33 | class TTSSapiGui : public QDialog | 35 | class TTSSapiGui : public QDialog |
34 | { | 36 | { |
@@ -71,4 +73,32 @@ private: | |||
71 | QString m_name; | 73 | QString m_name; |
72 | }; | 74 | }; |
73 | 75 | ||
76 | class TTSFestivalGui : public QDialog | ||
77 | { | ||
78 | Q_OBJECT | ||
79 | public: | ||
80 | TTSFestivalGui(TTSFestival* festival, QDialog* parent = NULL); | ||
81 | |||
82 | void showCfg(); | ||
83 | void setCfg(RbSettings* sett){settings = sett;} | ||
84 | |||
85 | public slots: | ||
86 | virtual void accept(void); | ||
87 | virtual void reject(void); | ||
88 | //virtual void reset(void); | ||
89 | |||
90 | void onRefreshButton(); | ||
91 | void onShowDescription(int state); | ||
92 | void onBrowseServer(); | ||
93 | void onBrowseClient(); | ||
94 | private: | ||
95 | Ui::TTSFestivalCfgFrm ui; | ||
96 | RbSettings* settings; | ||
97 | TTSFestival* festival; | ||
98 | |||
99 | void updateVoices(); | ||
100 | private slots: | ||
101 | void updateDescription(QString value); | ||
102 | }; | ||
103 | |||
74 | #endif | 104 | #endif |
diff --git a/rbutil/rbutilqt/voicefile.cpp b/rbutil/rbutilqt/voicefile.cpp index 65c9bee8dc..6a04107cea 100644 --- a/rbutil/rbutilqt/voicefile.cpp +++ b/rbutil/rbutilqt/voicefile.cpp | |||
@@ -230,7 +230,10 @@ void VoiceFileCreator::downloadDone(bool error) | |||
230 | 230 | ||
231 | m_logger->addItem(tr("creating ")+toSpeak,LOGINFO); | 231 | m_logger->addItem(tr("creating ")+toSpeak,LOGINFO); |
232 | QCoreApplication::processEvents(); | 232 | QCoreApplication::processEvents(); |
233 | m_tts->voice(toSpeak,wavname); // generate wav | 233 | |
234 | // TODO: add support for aborting the operation | ||
235 | QString errStr; | ||
236 | m_tts->voice(toSpeak,wavname, &errStr); // generate wav | ||
234 | } | 237 | } |
235 | 238 | ||
236 | // todo strip | 239 | // todo strip |