summaryrefslogtreecommitdiff
path: root/rbutil/rbutilqt/tts.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'rbutil/rbutilqt/tts.cpp')
-rw-r--r--rbutil/rbutilqt/tts.cpp277
1 files changed, 271 insertions, 6 deletions
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
156bool TTSExes::voice(QString text,QString wavfile) 170TTSStatus 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
307bool TTSSapi::voice(QString text,QString wavfile) 322TTSStatus 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
318bool TTSSapi::stop() 334bool 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 **********************************************************************/
371TTSFestival::~TTSFestival()
372{
373 stop();
374}
375
376void 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
393void 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
411bool 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
421bool TTSFestival::stop()
422{
423 serverProcess.terminate();
424 serverProcess.kill();
425
426 return true;
427}
428
429TTSStatus 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
461bool 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
473void TTSFestival::showCfg()
474{
475#ifndef CONSOLE
476 TTSFestivalGui gui(this);
477#endif
478 gui.setCfg(settings);
479 gui.showCfg();
480}
481
482QStringList 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
510QString 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
557QString 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