diff --git a/rbutil/rbutilqt/installtalkwindow.cpp b/rbutil/rbutilqt/installtalkwindow.cpp index 0d2267e..aba848b 100644 diff --git a/rbutil/rbutilqt/rbutilqt.pro b/rbutil/rbutilqt/rbutilqt.pro index 392cf1e..574b339 100644 --- a/rbutil/rbutilqt/rbutilqt.pro +++ b/rbutil/rbutilqt/rbutilqt.pro @@ -2,11 +2,12 @@ unix:!mac { CCACHE = $$system(which ccache) !isEmpty(CCACHE) { message("using ccache") - QMAKE_CXX = ccache g++ - QMAKE_CC = ccache gcc + QMAKE_CXX = ccache \ + g++ + QMAKE_CC = ccache \ + gcc } } - OBJECTS_DIR = build/o UI_DIR = build/ui MOC_DIR = build/moc @@ -21,28 +22,33 @@ isEmpty(VER) { message("Qt version used:" $$VER) # add a custom rule for pre-building librbspeex -!mac { -rbspeex.commands = @$(MAKE) -C ../../tools/rbspeex librbspeex.a -} -mac { -rbspeex.commands = @$(MAKE) -C ../../tools/rbspeex librbspeex-universal -} +!mac:rbspeex.commands = @$(MAKE) \ + -C \ + ../../tools/rbspeex \ + librbspeex.a +mac:rbspeex.commands = @$(MAKE) \ + -C \ + ../../tools/rbspeex \ + librbspeex-universal QMAKE_EXTRA_TARGETS += rbspeex PRE_TARGETDEPS += rbspeex # rule for creating ctags file -tags.commands = ctags -R --c++-kinds=+p --fields=+iaS --extra=+q $(SOURCES) +tags.commands = ctags \ + -R \ + --c++-kinds=+p \ + --fields=+iaS \ + --extra=+q \ + $(SOURCES) tags.depends = $(SOURCES) QMAKE_EXTRA_TARGETS += tags # add a custom rule for making the translations -lrelease.commands = $$[QT_INSTALL_BINS]/lrelease -silent rbutilqt.pro +lrelease.commands = $$[QT_INSTALL_BINS]/lrelease \ + -silent \ + rbutilqt.pro QMAKE_EXTRA_TARGETS += lrelease -!dbg { - PRE_TARGETDEPS += lrelease -} - - +!dbg:PRE_TARGETDEPS += lrelease SOURCES += rbutilqt.cpp \ main.cpp \ install.cpp \ @@ -84,7 +90,6 @@ SOURCES += rbutilqt.cpp \ base/bootloaderinstallfile.cpp \ ../../tools/mkboot.c \ ../../tools/iriver.c - HEADERS += rbutilqt.h \ install.h \ base/httpget.h \ @@ -138,26 +143,36 @@ HEADERS += rbutilqt.h \ ../../tools/iriver.h # Needed by QT on Win -INCLUDEPATH = . irivertools zip zlib ../ipodpatcher ../sansapatcher ../../tools/rbspeex ../../tools +INCLUDEPATH = . \ + irivertools \ + zip \ + zlib \ + ../ipodpatcher \ + ../sansapatcher \ + ../../tools/rbspeex \ + ../../tools INCLUDEPATH += base - -LIBS += -L../../tools/rbspeex -lrbspeex - +LIBS += -L../../tools/rbspeex \ + -lrbspeex TEMPLATE = app dbg { - CONFIG += debug thread qt warn_on + CONFIG += debug \ + thread \ + qt \ + warn_on DEFINES -= QT_NO_DEBUG_OUTPUT message("debug") } !dbg { - CONFIG += release thread qt + CONFIG += release \ + thread \ + qt DEFINES += QT_NO_DEBUG_OUTPUT message("release") } - TARGET = rbutilqt - -FORMS += rbutilqtfrm.ui \ +FORMS += ttsfestivalcfgform.ui \ + rbutilqtfrm.ui \ aboutbox.ui \ installfrm.ui \ progressloggerfrm.ui \ @@ -173,15 +188,9 @@ FORMS += rbutilqtfrm.ui \ sapicfgfrm.ui \ createvoicefrm.ui \ sysinfofrm.ui - RESOURCES += rbutilqt.qrc -win32 { - RESOURCES += rbutilqt-win.qrc -} -!dbg { - RESOURCES += rbutilqt-lang.qrc -} - +win32:RESOURCES += rbutilqt-win.qrc +!dbg:RESOURCES += rbutilqt-lang.qrc TRANSLATIONS += lang/rbutil_de.ts \ lang/rbutil_fi.ts \ lang/rbutil_fr.ts \ @@ -192,59 +201,63 @@ TRANSLATIONS += lang/rbutil_de.ts \ lang/rbutil_pt.ts \ lang/rbutil_tr.ts \ lang/rbutil_zh_CN.ts \ - lang/rbutil_zh_TW.ts \ - - + lang/rbutil_zh_TW.ts QT += network -DEFINES += RBUTIL _LARGEFILE64_SOURCE - +DEFINES += RBUTIL \ + _LARGEFILE64_SOURCE win32 { SOURCES += ../ipodpatcher/ipodio-win32.c SOURCES += ../sansapatcher/sansaio-win32.c RC_FILE = rbutilqt.rc - LIBS += -lsetupapi -lnetapi32 + LIBS += -lsetupapi \ + -lnetapi32 } - unix { SOURCES += ../ipodpatcher/ipodio-posix.c SOURCES += ../sansapatcher/sansaio-posix.c } -unix:!static { - LIBS += -lusb -} -unix:static { - # force statically linking of libusb. Libraries that are appended - # later will get linked dynamically again. - LIBS += -Wl,-Bstatic -lusb -Wl,-Bdynamic -} +unix:!static:LIBS += -lusb +unix:static:# force statically linking of libusb. Libraries that are appended + # later will get linked dynamically again. +LIBS += -Wl,-Bstatic \ + -lusb \ + -Wl,-Bdynamic macx { QMAKE_MAC_SDK=/Developer/SDKs/MacOSX10.4u.sdk - QMAKE_LFLAGS_PPC=-mmacosx-version-min=10.4 -arch ppc - QMAKE_LFLAGS_X86=-mmacosx-version-min=10.4 -arch i386 - CONFIG+=x86 ppc - LIBS += -L/usr/local/lib -framework IOKit + QMAKE_LFLAGS_PPC = -mmacosx-version-min=10.4 \ + -arch \ + ppc + QMAKE_LFLAGS_X86 = -mmacosx-version-min=10.4 \ + -arch \ + i386 + CONFIG += x86 \ + ppc + LIBS += -L/usr/local/lib \ + -framework \ + IOKit INCLUDEPATH += /usr/local/include QMAKE_INFO_PLIST = Info.plist RC_FILE = icons/rbutilqt.icns # rule for creating a dmg file - dmg.commands = hdiutil create -ov -srcfolder rbutilqt.app/ rbutil.dmg + dmg.commands = hdiutil \ + create \ + -ov \ + -srcfolder \ + rbutilqt.app/ \ + rbutil.dmg QMAKE_EXTRA_TARGETS += dmg } - static { QTPLUGIN += qtaccessiblewidgets - LIBS += -L$$(QT_BUILD_TREE)/plugins/accessible -lqtaccessiblewidgets + LIBS += -L$$(QT_BUILD_TREE)/plugins/accessible \ + -lqtaccessiblewidgets LIBS += -L. DEFINES += STATIC message("using static plugin") } - unix { target.path = /usr/local/bin INSTALLS += target } - - - diff --git a/rbutil/rbutilqt/talkfile.cpp b/rbutil/rbutilqt/talkfile.cpp index 7980e7c..20b582c 100644 --- a/rbutil/rbutilqt/talkfile.cpp +++ b/rbutil/rbutilqt/talkfile.cpp @@ -34,7 +34,7 @@ bool TalkFileCreator::createTalkFiles(ProgressloggerInterface* logger) QMultiMap fileList; QMultiMap dirList; - QStringList toSpeakList; + QStringList toSpeakList, voicedEntries, encodedEntries; QString errStr; m_logger->addItem(tr("Starting Talk file generation"),LOGINFO); @@ -103,20 +103,19 @@ bool TalkFileCreator::createTalkFiles(ProgressloggerInterface* logger) } } - // Voice entryies + // Voice entries m_logger->addItem(tr("Voicing entries..."),LOGINFO); - if(voiceList(toSpeakList,&errStr) == false) + TTSStatus voiceStatus= voiceList(toSpeakList,voicedEntries); + if(voiceStatus == FatalError) { - m_logger->addItem(errStr,LOGERROR); doAbort(toSpeakList); return false; } // Encoding Entries m_logger->addItem(tr("Encoding files..."),LOGINFO); - if(encodeList(toSpeakList,&errStr) == false) + if(encodeList(voicedEntries,encodedEntries) == false) { - m_logger->addItem(errStr,LOGERROR); doAbort(toSpeakList); return false; } @@ -247,29 +246,45 @@ bool TalkFileCreator::createDirAndFileMaps(QDir startDir,QMultiMapaddItem(tr("Talk file creation aborted"), LOGERROR); + return FatalError; } QString filename = QDir::tempPath()+ "/"+ toSpeak[i] + ".wav"; - if(!m_tts->voice(toSpeak[i],filename)) + QString error; + TTSStatus status = m_tts->voice(toSpeak[i],filename, &error); + if(status == Warning) { - *errString =tr("Voicing of %s failed").arg(toSpeak[i]); - return false; + warnings = true; + m_logger->addItem(tr("Voicing of %1 failed: %2").arg(toSpeak[i]).arg(error), + LOGWARNING); + } + else if (status == FatalError) + { + m_logger->addItem(tr("Voicing of %1 failed: %2").arg(toSpeak[i]).arg(error), + LOGERROR); + return FatalError; } + else + voicedEntries.append(toSpeak[i]); m_logger->setProgressValue(++m_progress); QCoreApplication::processEvents(); } - return true; + if(warnings) + return Warning; + else + return NoError; } @@ -279,14 +294,14 @@ bool TalkFileCreator::voiceList(QStringList toSpeak,QString* errString) //! \param toSpeak QStringList with the Entries to encode. //! \param errString pointer to where the Error cause is written //! \returns true on success, false on error or user abort -bool TalkFileCreator::encodeList(QStringList toEncode,QString* errString) +bool TalkFileCreator::encodeList(QStringList toEncode,QStringList& encodedEntries) { resetProgress(toEncode.size()); for(int i=0; i < toEncode.size(); i++) { if(m_abort) { - *errString = tr("Talk file creation aborted"); + m_logger->addItem(tr("Talk file creation aborted"), LOGERROR); return false; } @@ -295,9 +310,10 @@ bool TalkFileCreator::encodeList(QStringList toEncode,QString* errString) if(!m_enc->encode(wavfilename,filename)) { - *errString =tr("Encoding of %1 failed").arg(filename); + m_logger->addItem(tr("Encoding of %1 failed").arg(filename), LOGERROR); return false; } + encodedEntries.append(toEncode[i]); m_logger->setProgressValue(++m_progress); QCoreApplication::processEvents(); } @@ -327,6 +343,10 @@ bool TalkFileCreator::copyTalkDirFiles(QMultiMap dirMap,QString } QString source = QDir::tempPath()+ "/"+ it.value() + ".talk"; + + if(!QFileInfo(source).exists()) + continue; // this file was skipped in one of the previous steps + QString target = it.key() + "/" + "_dirname.talk"; // remove target if it exists, and if we should overwrite it @@ -383,6 +403,9 @@ bool TalkFileCreator::copyTalkFileFiles(QMultiMap fileMap,QStri else source = QDir::tempPath()+ "/"+ it.value() + ".talk"; + if(!QFileInfo(source).exists()) + continue; // this file was skipped in one of the previous steps + // remove target if it exists, and if we should overwrite it if(m_overwriteTalk && QFile::exists(target)) QFile::remove(target); diff --git a/rbutil/rbutilqt/talkfile.h b/rbutil/rbutilqt/talkfile.h index d869c32..989386a 100644 --- a/rbutil/rbutilqt/talkfile.h +++ b/rbutil/rbutilqt/talkfile.h @@ -58,8 +58,8 @@ private: void doAbort(QStringList cleanupList); void resetProgress(int max); bool createDirAndFileMaps(QDir startDir,QMultiMap *dirMap,QMultiMap *fileMap); - bool voiceList(QStringList toSpeak,QString* errString); - bool encodeList(QStringList toEncode,QString* errString); + TTSStatus voiceList(QStringList toSpeak,QStringList& voicedEntries); + bool encodeList(QStringList toEncode,QStringList& encodedEntries); bool copyTalkDirFiles(QMultiMap dirMap,QString* errString); bool copyTalkFileFiles(QMultiMap fileMap,QString* errString); diff --git a/rbutil/rbutilqt/tts.cpp b/rbutil/rbutilqt/tts.cpp index 252608f..4a9dae3 100644 --- a/rbutil/rbutilqt/tts.cpp +++ b/rbutil/rbutilqt/tts.cpp @@ -33,7 +33,9 @@ void TTSBase::initTTSList() #if defined(Q_OS_WIN) ttsList["sapi"] = "Sapi TTS Engine"; #endif - +#if defined(Q_OS_LINUX) + ttsList["festival"] = "Festival TTS Engine"; +#endif } // function to get a specific encoder @@ -44,6 +46,7 @@ TTSBase* TTSBase::getTTS(QString ttsName) return ttsCache.value(ttsName); TTSBase* tts; +#if defined(Q_OS_WIN) if(ttsName == "sapi") { tts = new TTSSapi(); @@ -51,6 +54,17 @@ TTSBase* TTSBase::getTTS(QString ttsName) return tts; } else +#endif +#if defined(Q_OS_LINUX) + if (ttsName == "festival") + { + tts = new TTSFestival(); + ttsCache[ttsName] = tts; + return tts; + } + else +#endif + if (true) // fix for OS other than WIN or LINUX { tts = new TTSExes(ttsName); ttsCache[ttsName] = tts; @@ -92,7 +106,7 @@ TTSExes::TTSExes(QString name) : TTSBase() m_name = name; m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; - m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; + m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; } @@ -153,8 +167,9 @@ bool TTSExes::start(QString *errStr) } } -bool TTSExes::voice(QString text,QString wavfile) +TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) { + (void) errStr; QString execstring = m_TTSTemplate; execstring.replace("%exe",m_TTSexec); @@ -163,7 +178,7 @@ bool TTSExes::voice(QString text,QString wavfile) execstring.replace("%text",text); //qDebug() << "voicing" << execstring; QProcess::execute(execstring); - return true; + return NoError; } @@ -304,15 +319,16 @@ QStringList TTSSapi::getVoiceList(QString language) -bool TTSSapi::voice(QString text,QString wavfile) +TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) { + (void) errStr; QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; qDebug() << "voicing" << query; *voicestream << query; *voicestream << "SYNC\tbla\r\n"; voicestream->flush(); voicescript->waitForReadyRead(); - return true; + return NoError; } bool TTSSapi::stop() @@ -349,5 +365,254 @@ bool TTSSapi::configOk() return false; return true; } +/********************************************************************** + * TSSFestival - client-server wrapper + **********************************************************************/ +TTSFestival::~TTSFestival() +{ + stop(); +} +void TTSFestival::startServer() +{ + if(!configOk()) + return; + + QStringList paths = settings->ttsPath("festival").split(":"); + + serverProcess.start(QString("%1 --server").arg(paths[0])); + serverProcess.waitForStarted(); + + queryServer("(getpid)"); + if(serverProcess.state() == QProcess::Running) + qDebug() << "Festival is up and running"; + else + qDebug() << "Festival failed to start"; +} + +void TTSFestival::ensureServerRunning() +{ + if(serverProcess.state() != QProcess::Running) + { + // least common denominator for all the server startup code paths + QProgressDialog progressDialog(tr(""), tr(""), 0, 0); + progressDialog.setWindowTitle(tr("Starting festival")); + progressDialog.setModal(true); + progressDialog.setLabel(0); + progressDialog.setCancelButton(0); + progressDialog.show(); + + QApplication::processEvents(); // actually show the dialog + + startServer(); + } +} + +bool TTSFestival::start(QString* errStr) +{ + (void) errStr; + ensureServerRunning(); + if (!settings->ttsVoice("festival").isEmpty()) + queryServer(QString("(voice.select '%1)").arg(settings->ttsVoice("festival"))); + + return true; +} + +bool TTSFestival::stop() +{ + serverProcess.terminate(); + serverProcess.kill(); + + return true; +} + +TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) +{ + qDebug() << text << "->" << wavfile; + + QStringList paths = settings->ttsPath("festival").split(":"); + QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(paths[1]).arg(wavfile); + qDebug() << cmd; + + QProcess clientProcess; + clientProcess.start(cmd); + clientProcess.write(QString("%1.\n").arg(text).toAscii()); + clientProcess.waitForBytesWritten(); + clientProcess.closeWriteChannel(); + clientProcess.waitForReadyRead(); + QString response = clientProcess.readAll(); + response = response.trimmed(); + if(!response.contains("Utterance")) + { + qDebug() << "Could not voice string: " << response; + *errStr = tr("engine could not voice string"); + return Warning; + /* do not stop the voicing process because of a single string + TODO: needs proper settings */ + } + clientProcess.closeReadChannel(QProcess::StandardError); + clientProcess.closeReadChannel(QProcess::StandardOutput); + clientProcess.terminate(); + clientProcess.kill(); + + return NoError; +} + +bool TTSFestival::configOk() +{ + QStringList paths = settings->ttsPath("festival").split(":"); + if(paths.size() != 2) + return false; + bool ret = QFileInfo(paths[0]).isExecutable() && + QFileInfo(paths[1]).isExecutable(); + if(settings->ttsVoice("festival").size() > 0 && voices.size() > 0) + ret = ret && (voices.indexOf(settings->ttsVoice("festival")) != -1); + return ret; +} + +void TTSFestival::showCfg() +{ +#ifndef CONSOLE + TTSFestivalGui gui(this); +#endif + gui.setCfg(settings); + gui.showCfg(); +} + +QStringList TTSFestival::getVoiceList() +{ + if(!configOk()) + return QStringList(); + + if(voices.size() > 0) + { + qDebug() << "Using voice cache"; + return voices; + } + QString response = queryServer("(voice.list)"); + + // get the 2nd line. It should be (, ) + response = response.mid(response.indexOf('\n') + 1, -1); + response = response.left(response.indexOf('\n')).trimmed(); + + voices = response.mid(1, response.size()-2).split(' '); + + voices.sort(); + if (voices.size() == 1 && voices[0].size() == 0) + voices.removeAt(0); + if (voices.size() > 0) + qDebug() << "Voices: " << voices; + else + qDebug() << "No voices."; + return voices; +} + +QString TTSFestival::getVoiceInfo(QString voice) +{ + if(!configOk()) + return ""; + + if(!getVoiceList().contains(voice)) + return ""; + + if(voiceDescriptions.contains(voice)) + return voiceDescriptions[voice]; + + QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000); + + if (response == "") + { + voiceDescriptions[voice]=tr("No description available"); + } + else + { + response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard)); + qDebug() << "voiceInfo w/o descr: " << response; + response = response.remove(')'); + QStringList responseLines = response.split('(', QString::SkipEmptyParts); + responseLines.removeAt(0); // the voice name itself + + QString description; + foreach(QString line, responseLines) + { + line = line.remove('('); + line = line.simplified(); + + line[0] = line[0].toUpper(); // capitalize the key + + int firstSpace = line.indexOf(' '); + if (firstSpace > 0) + { + line = line.insert(firstSpace, ':'); // add a colon between the key and the value + line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value + } + + description += line + "\n"; + } + voiceDescriptions[voice] = description.trimmed(); + } + return voiceDescriptions[voice]; +} + +QString TTSFestival::queryServer(QString query, int timeout) +{ + if(!configOk()) + return ""; + + ensureServerRunning(); + + qDebug() << "queryServer with " << query; + QString response; + + QDateTime endTime; + if(timeout > 0) + endTime = QDateTime::currentDateTime().addMSecs(timeout); + + /* Festival is *extremely* unreliable. Although at this + * point we are sure that SIOD is accepting commands, + * we might end up with an empty response. Hence, the loop. + */ + while(true) + { + QApplication::processEvents(QEventLoop::AllEvents, 50); + QTcpSocket socket; + + socket.connectToHost("localhost", 1314); + socket.waitForConnected(); + + if(socket.state() == QAbstractSocket::ConnectedState) + { + socket.write(QString("%1\n").arg(query).toAscii()); + socket.waitForBytesWritten(); + socket.waitForReadyRead(); + + response = socket.readAll().trimmed(); + + if (response != "LP" && response != "") + break; + } + socket.abort(); + socket.disconnectFromHost(); + + if(timeout > 0 && QDateTime::currentDateTime() >= endTime) + return ""; + + /* make sure we wait a little as we don't want to flood the server with requests */ + QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500); + while(QDateTime::currentDateTime() < tmpEndTime) + QApplication::processEvents(QEventLoop::AllEvents); + } + if(response == "nil") + return ""; + + QStringList lines = response.split('\n'); + if(lines.size() > 2) + { + lines.removeFirst(); + lines.removeLast(); + } + else + qDebug() << "Response too short: " << response; + return lines.join("\n"); +} diff --git a/rbutil/rbutilqt/tts.h b/rbutil/rbutilqt/tts.h index 7c21fd0..26d2d51 100644 --- a/rbutil/rbutilqt/tts.h +++ b/rbutil/rbutilqt/tts.h @@ -23,9 +23,13 @@ #ifndef TTS_H #define TTS_H - #include "rbsettings.h" #include +#include +#include +#include +#include +#include #ifndef CONSOLE #include "ttsgui.h" @@ -33,14 +37,18 @@ #include "ttsguicli.h" #endif - +enum TTSStatus{ FatalError, NoError, Warning }; +class TTSSapi; +#if defined(Q_OS_LINUX) +class TTSFestival; +#endif class TTSBase : public QObject { Q_OBJECT public: TTSBase(); - virtual bool voice(QString text,QString wavfile) - { (void)text; (void)wavfile; return false; } + virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) + { (void) text; (void) wavfile; (void) errStr; return FatalError;} virtual bool start(QString *errStr) { (void)errStr; return false; } virtual bool stop() { return false; } virtual void showCfg(){} @@ -72,7 +80,7 @@ class TTSSapi : public TTSBase Q_OBJECT public: TTSSapi(); - virtual bool voice(QString text,QString wavfile); + virtual TTSStatus voice(QString text,QString wavfile, QString *errStr); virtual bool start(QString *errStr); virtual bool stop(); virtual void showCfg(); @@ -99,7 +107,7 @@ class TTSExes : public TTSBase Q_OBJECT public: TTSExes(QString name); - virtual bool voice(QString text,QString wavfile); + virtual TTSStatus voice(QString text,QString wavfile, QString *errStr); virtual bool start(QString *errStr); virtual bool stop() {return true;} virtual void showCfg(); @@ -115,4 +123,26 @@ class TTSExes : public TTSBase QMap m_TemplateMap; }; +class TTSFestival : public TTSBase +{ + Q_OBJECT +public: + ~TTSFestival(); + virtual bool configOk(); + virtual bool start(QString *errStr); + virtual bool stop(); + virtual void showCfg(); + virtual TTSStatus voice(QString text,QString wavfile, QString *errStr); + + QStringList getVoiceList(); + QString getVoiceInfo(QString voice); +private: + inline void startServer(); + inline void ensureServerRunning(); + QString queryServer(QString query, int timeout = -1); + QProcess serverProcess; + QStringList voices; + QMap voiceDescriptions; +}; + #endif diff --git a/rbutil/rbutilqt/ttsfestivalcfgform.ui b/rbutil/rbutilqt/ttsfestivalcfgform.ui new file mode 100644 index 0000000..bdebf3f --- /dev/null +++ b/rbutil/rbutilqt/ttsfestivalcfgform.ui @@ -0,0 +1,322 @@ + + TTSFestivalCfgFrm + + + + 0 + 0 + 340 + 316 + + + + Configuration + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + 1 + 0 + + + + Executables + + + + 0 + + + 0 + + + + + QLayout::SetMinimumSize + + + 6 + + + 0 + + + + + + 0 + 0 + + + + Path to Festival server + + + + + + + + 1 + 0 + + + + + 215 + 0 + + + + + + + + Browse + + + + + + + + 0 + 0 + + + + Path to Festival client + + + + + + + + 1 + 0 + + + + + 215 + 0 + + + + + + + + Browse + + + + + + + + + + + + Qt::Horizontal + + + + 70 + 20 + + + + + + + + Qt::Vertical + + + + 153 + 43 + + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + Server voice + + + + 6 + + + 6 + + + 6 + + + 6 + + + 0 + + + + + + 0 + 0 + + + + Select a voice + + + + + + + + 0 + 0 + + + + &Refresh + + + + :/icons/view-refresh.png:/icons/view-refresh.png + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + Show voice description + + + + + + + + buttonBox + execsBox + horizontalSpacer + verticalSpacer + groupBox2 + + + serverPath + serverButton + clientPath + clientButton + refreshButton + voicesBox + buttonBox + + + + + + + buttonBox + accepted() + TTSFestivalCfgFrm + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TTSFestivalCfgFrm + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/rbutil/rbutilqt/ttsgui.cpp b/rbutil/rbutilqt/ttsgui.cpp index 0a59b25..b46d752 100644 --- a/rbutil/rbutilqt/ttsgui.cpp +++ b/rbutil/rbutilqt/ttsgui.cpp @@ -181,3 +181,108 @@ void TTSExesGui::browse() } } +TTSFestivalGui::TTSFestivalGui(TTSFestival* api, QDialog* parent) : + QDialog(parent), festival(api) +{ + ui.setupUi(this); + this->setModal(true); + this->setDisabled(true); + this->show(); + + connect(ui.refreshButton, SIGNAL(clicked()), this, SLOT(onRefreshButton())); + connect(ui.showDescriptionCheckbox, SIGNAL(stateChanged(int)), this, SLOT(onShowDescription(int))); + connect(ui.voicesBox, SIGNAL(activated(QString)), this, SLOT(updateDescription(QString))); +} + +void TTSFestivalGui::showCfg() +{ + qDebug() << "show\tpaths: " << settings->ttsPath("festival") << "\n" + << "\tvoice: " << settings->ttsVoice("festival"); + + // will populate the voices if the paths are correct, + // otherwise, it will require the user to press Refresh + updateVoices(); + + // try to get config from settings + QStringList paths = settings->ttsPath("festival").split(":"); + if(paths.size() == 2) + { + ui.serverPath->setText(paths[0]); + ui.clientPath->setText(paths[1]); + } + + this->setEnabled(true); + this->exec(); +} + +void TTSFestivalGui::accept(void) +{ + //save settings in user config + QString newPath = QString("%1:%2").arg(ui.serverPath->text().trimmed()).arg(ui.clientPath->text().trimmed()); + qDebug() << "set\tpaths: " << newPath << "\n\tvoice: " << ui.voicesBox->currentText(); + settings->setTTSPath("festival", newPath); + settings->setTTSVoice("festival", ui.voicesBox->currentText()); + + settings->sync(); + + this->done(0); +} + +void TTSFestivalGui::reject(void) +{ + this->done(0); +} + +void TTSFestivalGui::onRefreshButton() +{ + /* Temporarily commit the settings so that we get the new path when we check for voices */ + QString newPath = QString("%1:%2").arg(ui.serverPath->text().trimmed()).arg(ui.clientPath->text().trimmed()); + QString oldPath = settings->ttsPath("festival"); + qDebug() << "new path: " << newPath << "\n" << "old path: " << oldPath << "\nuse new: " << (newPath != oldPath); + + if(newPath != oldPath) + { + qDebug() << "Using new paths for getVoiceList"; + settings->setTTSPath("festival", newPath); + settings->sync(); + } + + updateVoices(); + + if(newPath != oldPath) + { + settings->setTTSPath("festival", oldPath); + settings->sync(); + } +} + +void TTSFestivalGui::onShowDescription(int state) +{ + if(state == Qt::Unchecked) + ui.descriptionLabel->setText(""); + else + updateDescription(ui.voicesBox->currentText()); +} + +void TTSFestivalGui::updateVoices() +{ + ui.voicesBox->clear(); + ui.voicesBox->addItem(tr("Loading..")); + + QStringList voiceList = festival->getVoiceList(); + ui.voicesBox->clear(); + ui.voicesBox->addItems(voiceList); + + ui.voicesBox->setCurrentIndex(ui.voicesBox->findText(settings->ttsVoice("festival"))); + + updateDescription(settings->ttsVoice("festival")); +} + +void TTSFestivalGui::updateDescription(QString value) +{ + if(ui.showDescriptionCheckbox->checkState() == Qt::Checked) + { + ui.descriptionLabel->setText(tr("Querying festival")); + ui.descriptionLabel->setText(festival->getVoiceInfo(value)); + } +} diff --git a/rbutil/rbutilqt/ttsgui.h b/rbutil/rbutilqt/ttsgui.h index 68990b8..03b794e 100644 --- a/rbutil/rbutilqt/ttsgui.h +++ b/rbutil/rbutilqt/ttsgui.h @@ -26,9 +26,11 @@ #include "ui_ttsexescfgfrm.h" #include "ui_sapicfgfrm.h" +#include "ui_ttsfestivalcfgform.h" class RbSettings; class TTSSapi; +class TTSFestival; class TTSSapiGui : public QDialog { @@ -71,4 +73,30 @@ private: QString m_name; }; +class TTSFestivalGui : public QDialog +{ + Q_OBJECT +public: + TTSFestivalGui(TTSFestival* festival, QDialog* parent = NULL); + + void showCfg(); + void setCfg(RbSettings* sett){settings = sett;} + +public slots: + virtual void accept(void); + virtual void reject(void); + //virtual void reset(void); + + void onRefreshButton(); + void onShowDescription(int state); +private: + Ui::TTSFestivalCfgFrm ui; + RbSettings* settings; + TTSFestival* festival; + + void updateVoices(); +private slots: + void updateDescription(QString value); +}; + #endif diff --git a/rbutil/rbutilqt/voicefile.cpp b/rbutil/rbutilqt/voicefile.cpp index 1488ead..1b39a27 100644 --- a/rbutil/rbutilqt/voicefile.cpp +++ b/rbutil/rbutilqt/voicefile.cpp @@ -244,7 +244,10 @@ void VoiceFileCreator::downloadDone(bool error) m_logger->addItem(tr("creating ")+toSpeak,LOGINFO); QCoreApplication::processEvents(); - m_tts->voice(toSpeak,wavname); // generate wav + + // TODO: add support for aborting the operation + QString errStr; + m_tts->voice(toSpeak,wavname, &errStr); // generate wav } // todo strip