summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominik Wenger <domonoky@googlemail.com>2009-03-27 19:18:14 +0000
committerDominik Wenger <domonoky@googlemail.com>2009-03-27 19:18:14 +0000
commit8f2aaaf79d15614c2a6ebd30f461457fff2bf836 (patch)
treed6515790f343df31050f6ae28b1defb50e4b24ac
parent75a5b9321bc9c5da20de023f84dd96dcc5297f1a (diff)
downloadrockbox-8f2aaaf79d15614c2a6ebd30f461457fff2bf836.tar.gz
rockbox-8f2aaaf79d15614c2a6ebd30f461457fff2bf836.zip
rbutil: commit FS#9983 by Delyan Kratunov. It adds support for the Festival TTS on Linux.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@20559 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--rbutil/rbutilqt/CREDITS1
-rw-r--r--rbutil/rbutilqt/rbutilqt.pro1
-rw-r--r--rbutil/rbutilqt/talkfile.cpp55
-rw-r--r--rbutil/rbutilqt/talkfile.h4
-rw-r--r--rbutil/rbutilqt/tts.cpp277
-rw-r--r--rbutil/rbutilqt/tts.h42
-rw-r--r--rbutil/rbutilqt/ttsgui.cpp156
-rw-r--r--rbutil/rbutilqt/ttsgui.h30
-rw-r--r--rbutil/rbutilqt/voicefile.cpp5
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
8Yoshihisa Uchida 8Yoshihisa Uchida
9Alexander Spyridakis 9Alexander Spyridakis
10Rui Araújo 10Rui Araújo
11Delyan 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
250bool TalkFileCreator::voiceList(QStringList toSpeak,QString* errString) 249TTSStatus 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
282bool TalkFileCreator::encodeList(QStringList toEncode,QString* errString) 297bool 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
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
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 40enum TTSStatus{ FatalError, NoError, Warning };
41class TTSSapi;
42#if defined(Q_OS_LINUX)
43class TTSFestival;
44#endif
37class TTSBase : public QObject 45class 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
126class TTSFestival : public TTSBase
127{
128 Q_OBJECT
129public:
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);
139private:
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
184TTSFestivalGui::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
200void 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
221void 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
234void TTSFestivalGui::reject(void)
235{
236 this->done(0);
237}
238
239void 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
263void 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
287void 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
310void TTSFestivalGui::onShowDescription(int state)
311{
312 if(state == Qt::Unchecked)
313 ui.descriptionLabel->setText("");
314 else
315 updateDescription(ui.voicesBox->currentText());
316}
317
318void 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
332void 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
30class RbSettings; 31class RbSettings;
31class TTSSapi; 32class TTSSapi;
33class TTSFestival;
32 34
33class TTSSapiGui : public QDialog 35class TTSSapiGui : public QDialog
34{ 36{
@@ -71,4 +73,32 @@ private:
71 QString m_name; 73 QString m_name;
72}; 74};
73 75
76class TTSFestivalGui : public QDialog
77{
78 Q_OBJECT
79public:
80 TTSFestivalGui(TTSFestival* festival, QDialog* parent = NULL);
81
82 void showCfg();
83 void setCfg(RbSettings* sett){settings = sett;}
84
85public 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();
94private:
95 Ui::TTSFestivalCfgFrm ui;
96 RbSettings* settings;
97 TTSFestival* festival;
98
99 void updateVoices();
100private 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