Index: rbutil/rbutilqt/ttsgui.cpp =================================================================== --- rbutil/rbutilqt/ttsgui.cpp (revision 20307) +++ rbutil/rbutilqt/ttsgui.cpp (working copy) @@ -181,3 +181,58 @@ } } +TTSFestivalGui::TTSFestivalGui(TTSFestival* api, QDialog* parent) : + QDialog(parent), festival(api) +{ + ui.setupUi(this); + this->hide(); + + connect(ui.refreshButton, SIGNAL(clicked()), this, SLOT(updateVoices())); + connect(ui.voicesBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateDescription(QString))); +} + +void TTSFestivalGui::showCfg() +{ + // 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]); + } + //show dialog + this->exec(); +} + +void TTSFestivalGui::accept(void) +{ + //save settings in user config + settings->setTTSPath("festival", QString("%1:%2").arg(ui.serverPath->text().trimmed()).arg(ui.clientPath->text().trimmed())); + settings->setTTSVoice("festival", ui.voicesBox->currentText()); + + settings->sync(); + + this->done(0); +} + +void TTSFestivalGui::reject(void) +{ + this->done(0); +} + +void TTSFestivalGui::updateVoices() +{ + ui.voicesBox->clear(); + ui.voicesBox->addItems(festival->getVoiceList()); + ui.voicesBox->setCurrentIndex(ui.voicesBox->findText(settings->ttsVoice("festival"))); +} + +void TTSFestivalGui::updateDescription(QString value) +{ + ui.descriptionLabel->setText(tr("Querying festival")); + ui.descriptionLabel->setText(festival->getVoiceInfo(value)); +} Index: rbutil/rbutilqt/ttsfestivalcfgform.ui =================================================================== --- rbutil/rbutilqt/ttsfestivalcfgform.ui (revision 0) +++ rbutil/rbutilqt/ttsfestivalcfgform.ui (revision 0) @@ -0,0 +1,315 @@ + + TTSFestivalCfgFrm + + + + 0 + 0 + 340 + 314 + + + + 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 + + + + + + + + + + + + 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 + + + + + Index: rbutil/rbutilqt/tts.cpp =================================================================== --- rbutil/rbutilqt/tts.cpp (revision 20307) +++ rbutil/rbutilqt/tts.cpp (working copy) @@ -33,7 +33,9 @@ #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 @@ return ttsCache.value(ttsName); TTSBase* tts; +#if defined(Q_OS_WIN) if(ttsName == "sapi") { tts = new TTSSapi(); @@ -51,7 +54,18 @@ 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; return tts; @@ -92,7 +106,7 @@ 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,7 +167,7 @@ } } -bool TTSExes::voice(QString text,QString wavfile) +TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr, bool ignoreNonAscii) { QString execstring = m_TTSTemplate; @@ -163,7 +177,7 @@ execstring.replace("%text",text); //qDebug() << "voicing" << execstring; QProcess::execute(execstring); - return true; + return NoError; } @@ -304,7 +318,7 @@ -bool TTSSapi::voice(QString text,QString wavfile) +TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr, bool ignoreNonAscii) { QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; qDebug() << "voicing" << query; @@ -312,7 +326,7 @@ *voicestream << "SYNC\tbla\r\n"; voicestream->flush(); voicescript->waitForReadyRead(); - return true; + return NoError; } bool TTSSapi::stop() @@ -349,5 +363,325 @@ 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(); + + QProgressDialog progressDialog(tr(""), tr(""), 0, 0); + progressDialog.setWindowTitle(tr("Starting festival")); + progressDialog.setModal(true); + progressDialog.setLabel(0); + progressDialog.setCancelButton(0); + progressDialog.show(); + + /* This loop tries to connect to festival in order to determine + * if festival has started. After it, festival has either crashed + * (state == NotRunning) or is ready to be used (state == Running) + */ + /*while(serverProcess.state() == QProcess::Running) + { + + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + QTcpSocket socket; + socket.connectToHost("localhost", 1314); + socket.waitForConnected(); + + if (socket.state() == QAbstractSocket::ConnectedState) + { + // a quick way to see if Scheme is running + socket.write("(getpid)\n"); + socket.waitForBytesWritten(); + socket.waitForReadyRead(); + QString response = socket.readAll().trimmed(); + if(response != "LP") + break; + } + + socket.abort(); + socket.disconnectFromHost(); + }*/ + + 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) + startServer(); +} + +bool TTSFestival::start(QString* errStr) +{ + ensureServerRunning(); + if (!settings->ttsVoice("festival").isEmpty()) + { + QTcpSocket setVoiceSocket; + setVoiceSocket.connectToHost("localhost", 1314); + setVoiceSocket.waitForConnected(); + setVoiceSocket.write(QString("(voice.select '%1)\n").arg(settings->ttsVoice("festival")).toAscii()); + setVoiceSocket.waitForBytesWritten(); + setVoiceSocket.waitForReadyRead(); + qDebug() << setVoiceSocket.readAll(); + setVoiceSocket.disconnectFromHost(); + } + + return true; +} + +bool TTSFestival::stop() +{ + serverProcess.terminate(); + serverProcess.kill(); + + return true; +} + +TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr, bool ignoreNonAscii) +{ + qDebug() << text << "->" << wavfile; + if(ignoreNonAscii && !isASCII(text)) + { + *errStr = tr("ignored Non-ASCII text"); + return Warning; + } + 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 FatalError; + } + 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)"); + + qDebug() << "voiceList: " << response; + // 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 ""; + + qDebug() << "voiceInfo for " << voice; + QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000); + + if (response == "") + return tr("No description available"); + + 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 name of the voice + + QString ret; + foreach(QString line, responseLines) + { + line = line.remove('(').trimmed(); + + QStringList tokens = line.split(' ', QString::SkipEmptyParts); + tokens[0] = tokens[0].toLower(); + tokens[0][0] = tokens[0][0].toUpper(); // capitalize the key name + + ret += tokens[0]; + + tokens.removeAt(0); + line = tokens.join(" ").toLower(); + line[0] = line[0].toUpper(); + + ret += ": " + line + "\n"; + } + return ret.trimmed(); +} + +void TTSFestival::setCfg(RbSettings* sett) +{ + // call function of base class + TTSBase::setCfg(sett); + + // if the config isnt OK, try to autodetect + if(!configOk()) + { + QString exepath; + //try autodetect tts +#if defined(Q_OS_LINUX) || defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) + QStringList path = QString(getenv("PATH")).split(":", QString::SkipEmptyParts); +#elif defined(Q_OS_WIN) + QStringList path = QString(getenv("PATH")).split(";", QString::SkipEmptyParts); +#endif + qDebug() << path; + for(int i = 0; i < path.size(); i++) + { + QString server = QDir::fromNativeSeparators(path.at(i)) + "/" + "festival"; + QString client = QDir::fromNativeSeparators(path.at(i)) + "/" + "festival_client"; +#if defined(Q_OS_WIN) + server += ".exe"; + client += ".exe"; + QStringList ex = server.split("\"", QString::SkipEmptyParts); + server = ex.join(""); + ex = client.split("\"", QString::SkipEmptyParts); + client = ex.join(""); +#endif + qDebug() << server << client; + if(QFileInfo(server).isExecutable() && QFileInfo(client).isExecutable()) + { + exepath= QDir::toNativeSeparators(server) + ":" + QDir::toNativeSeparators(client); + break; + } + } + settings->setTTSPath("festival",exepath); + settings->sync(); + } + +} +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, (timeout > 0) ? timeout : 500); + 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") + break; + else + { + QDateTime endTime = QDateTime::currentDateTime().addMSecs(200); + while(QDateTime::currentDateTime() <= endTime) + QApplication::processEvents(QEventLoop::AllEvents, 200); + } + } + socket.abort(); + socket.disconnectFromHost(); + + if(timeout > 0 && QDateTime::currentDateTime() >= endTime) + return ""; + } + if(response == "nil") + return ""; + + QStringList lines = response.split('\n'); + lines.removeFirst(); + lines.removeLast(); + return lines.join("\n"); +} +bool TTSBase::isASCII(const QString str) +{ + QByteArray data = str.toUtf8(); + for(int i=0; i 127 + return false; + } + return true; +} Index: rbutil/rbutilqt/rbutilqt.pro =================================================================== --- rbutil/rbutilqt/rbutilqt.pro (revision 20307) +++ rbutil/rbutilqt/rbutilqt.pro (working copy) @@ -2,11 +2,12 @@ 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 @@ 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 @@ base/bootloaderinstallfile.cpp \ ../../tools/mkboot.c \ ../../tools/iriver.c - HEADERS += rbutilqt.h \ install.h \ base/httpget.h \ @@ -138,26 +143,36 @@ ../../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 @@ 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 @@ 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 +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 -} - +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 } - - - Index: rbutil/rbutilqt/talkfile.h =================================================================== --- rbutil/rbutilqt/talkfile.h (revision 20307) +++ rbutil/rbutilqt/talkfile.h (working copy) @@ -43,6 +43,7 @@ void setDir(QDir dir){m_dir = dir; } void setMountPoint(QString mountpoint) {m_mountpoint =mountpoint; } + void setIgnoreNonAscii(bool ov) {m_ignoreNonAscii = ov;} void setOverwriteTalk(bool ov) {m_overwriteTalk = ov;} void setRecursive(bool ov) {m_recursive = ov;} void setStripExtensions(bool ov) {m_stripExtensions = ov;} @@ -58,8 +59,8 @@ 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); @@ -76,6 +77,7 @@ bool m_stripExtensions; bool m_talkFolders; bool m_talkFiles; + bool m_ignoreNonAscii; ProgressloggerInterface* m_logger; Index: rbutil/rbutilqt/voicefile.cpp =================================================================== --- rbutil/rbutilqt/voicefile.cpp (revision 20307) +++ rbutil/rbutilqt/voicefile.cpp (working copy) @@ -244,7 +244,10 @@ 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 Index: rbutil/rbutilqt/installtalkwindow.cpp =================================================================== --- rbutil/rbutilqt/installtalkwindow.cpp (revision 20307) +++ rbutil/rbutilqt/installtalkwindow.cpp (working copy) @@ -96,6 +96,7 @@ talkcreator->setDir(QDir(folderToTalk)); talkcreator->setMountPoint(settings->mountpoint()); + talkcreator->setIgnoreNonAscii(ui.IgnoreNonAscii->isChecked()); talkcreator->setOverwriteTalk(ui.OverwriteTalk->isChecked()); talkcreator->setRecursive(ui.recursive->isChecked()); talkcreator->setStripExtensions(ui.StripExtensions->isChecked()); Index: rbutil/rbutilqt/tts.h =================================================================== --- rbutil/rbutilqt/tts.h (revision 20307) +++ rbutil/rbutilqt/tts.h (working copy) @@ -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, bool ignoreNonAscii = false) + { (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(){} @@ -65,6 +73,7 @@ RbSettings* settings; static QMap ttsList; static QMap ttsCache; + bool isASCII(const QString str); }; class TTSSapi : public TTSBase @@ -72,7 +81,7 @@ Q_OBJECT public: TTSSapi(); - virtual bool voice(QString text,QString wavfile); + virtual TTSStatus voice(QString text,QString wavfile, QString *errStr, bool ignoreNonAscii = false); virtual bool start(QString *errStr); virtual bool stop(); virtual void showCfg(); @@ -99,7 +108,7 @@ Q_OBJECT public: TTSExes(QString name); - virtual bool voice(QString text,QString wavfile); + virtual TTSStatus voice(QString text,QString wavfile, QString *errStr, bool ignoreNonAscii = false); virtual bool start(QString *errStr); virtual bool stop() {return true;} virtual void showCfg(); @@ -115,4 +124,26 @@ 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 void setCfg(RbSettings* sett); + virtual TTSStatus voice(QString text,QString wavfile, QString *errStr, bool ignoreNonAscii = false); + + QStringList getVoiceList(); + QString getVoiceInfo(QString voice); +private: + void startServer(); + void ensureServerRunning(); + QString queryServer(QString query, int timeout = -1); + QProcess serverProcess; + QStringList voices; +}; + #endif Index: rbutil/rbutilqt/installtalkfrm.ui =================================================================== --- rbutil/rbutilqt/installtalkfrm.ui (revision 20307) +++ rbutil/rbutilqt/installtalkfrm.ui (working copy) @@ -9,7 +9,7 @@ 0 0 600 - 450 + 468 @@ -125,7 +125,7 @@ - + Overwrite Talkfiles @@ -158,14 +158,14 @@ + + + + Ignore filenames with non-ASCII characters + + + - recursive - StripExtensions - OverwriteTalk - talkFolders - talkFiles - label_3 - fileFilter @@ -230,6 +230,7 @@ talkFolders recursive StripExtensions + IgnoreNonAscii OverwriteTalk buttonOk buttonCancel Index: rbutil/rbutilqt/ttsgui.h =================================================================== --- rbutil/rbutilqt/ttsgui.h (revision 20307) +++ rbutil/rbutilqt/ttsgui.h (working copy) @@ -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,26 @@ 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 updateVoices(); +private: + Ui::TTSFestivalCfgFrm ui; + RbSettings* settings; + TTSFestival* festival; +private slots: + void updateDescription(QString value); +}; + #endif Index: rbutil/rbutilqt/talkfile.cpp =================================================================== --- rbutil/rbutilqt/talkfile.cpp (revision 20307) +++ rbutil/rbutilqt/talkfile.cpp (working copy) @@ -34,7 +34,7 @@ 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 @@ } } - // 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 @@ //! \param toSpeak QStringList with the Entries to voice. //! \param errString pointer to where the Error cause is written //! \returns true on success, false on error or user abort -bool TalkFileCreator::voiceList(QStringList toSpeak,QString* errString) +TTSStatus TalkFileCreator::voiceList(QStringList toSpeak,QStringList& voicedEntries) { resetProgress(toSpeak.size()); + QStringList errors; + bool warnings = false; for(int i=0; i < toSpeak.size(); i++) { if(m_abort) { - *errString = tr("Talk file creation aborted"); - return false; + m_logger->addItem(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, m_ignoreNonAscii); + 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 @@ //! \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 @@ 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 @@ } 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 @@ 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);