forked from len0rd/rockbox
Fix Festival tts engine.
Author: Delyan Kratunov Flyspray: FS#11155 part2 git-svn-id: svn://svn.rockbox.org/rockbox/trunk@25402 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
a79fee019e
commit
11c9be9c83
2 changed files with 107 additions and 55 deletions
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
TTSFestival::~TTSFestival()
|
TTSFestival::~TTSFestival()
|
||||||
{
|
{
|
||||||
|
qDebug() << "[Festival] Destroying instance";
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +49,7 @@ void TTSFestival::generateSettings()
|
||||||
EncTtsSetting* setting = new EncTtsSetting(this,
|
EncTtsSetting* setting = new EncTtsSetting(this,
|
||||||
EncTtsSetting::eSTRINGLIST, tr("Voice:"),
|
EncTtsSetting::eSTRINGLIST, tr("Voice:"),
|
||||||
RbSettings::subValue("festival", RbSettings::TtsVoice),
|
RbSettings::subValue("festival", RbSettings::TtsVoice),
|
||||||
getVoiceList(exepath), EncTtsSetting::eREFRESHBTN);
|
getVoiceList(), EncTtsSetting::eREFRESHBTN);
|
||||||
connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
|
connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
|
||||||
connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
|
connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
|
||||||
insertSetting(eVOICE,setting);
|
insertSetting(eVOICE,setting);
|
||||||
|
|
@ -76,8 +77,10 @@ void TTSFestival::saveSettings()
|
||||||
void TTSFestival::updateVoiceDescription()
|
void TTSFestival::updateVoiceDescription()
|
||||||
{
|
{
|
||||||
// get voice Info with current voice and path
|
// get voice Info with current voice and path
|
||||||
QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),
|
currentPath = getSetting(eSERVERPATH)->current().toString();
|
||||||
getSetting(eSERVERPATH)->current().toString());
|
QString info = getVoiceInfo(getSetting(eVOICE)->current().toString());
|
||||||
|
currentPath = "";
|
||||||
|
|
||||||
getSetting(eVOICEDESC)->setCurrent(info);
|
getSetting(eVOICEDESC)->setCurrent(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,47 +91,78 @@ void TTSFestival::clearVoiceDescription()
|
||||||
|
|
||||||
void TTSFestival::updateVoiceList()
|
void TTSFestival::updateVoiceList()
|
||||||
{
|
{
|
||||||
QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString());
|
currentPath = getSetting(eSERVERPATH)->current().toString();
|
||||||
|
QStringList voiceList = getVoiceList();
|
||||||
|
currentPath = "";
|
||||||
|
|
||||||
getSetting(eVOICE)->setList(voiceList);
|
getSetting(eVOICE)->setList(voiceList);
|
||||||
if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
|
if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
|
||||||
else getSetting(eVOICE)->setCurrent("");
|
else getSetting(eVOICE)->setCurrent("");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TTSFestival::startServer(QString path)
|
void TTSFestival::startServer()
|
||||||
{
|
{
|
||||||
if(!configOk())
|
if(!configOk())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(path == "")
|
if(serverProcess.state() != QProcess::Running)
|
||||||
path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
|
{
|
||||||
|
QString path;
|
||||||
|
/* currentPath is set by the GUI - if it's set, it is the currently set
|
||||||
|
path in the configuration GUI; if it's not set, use the saved path */
|
||||||
|
if (currentPath == "")
|
||||||
|
path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
|
||||||
|
else
|
||||||
|
path = currentPath;
|
||||||
|
|
||||||
serverProcess.start(QString("%1 --server").arg(path));
|
serverProcess.start(QString("%1 --server").arg(path));
|
||||||
serverProcess.waitForStarted();
|
serverProcess.waitForStarted();
|
||||||
|
|
||||||
queryServer("(getpid)",300,path);
|
/* A friendlier version of a spinlock */
|
||||||
if(serverProcess.state() == QProcess::Running)
|
while (serverProcess.pid() == 0 && serverProcess.state() != QProcess::Running)
|
||||||
qDebug() << "Festival is up and running";
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
|
||||||
else
|
|
||||||
qDebug() << "Festival failed to start";
|
if(serverProcess.state() == QProcess::Running)
|
||||||
|
qDebug() << "[Festival] Server is up and running";
|
||||||
|
else
|
||||||
|
qDebug() << "[Festival] Server failed to start, state: " << serverProcess.state();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TTSFestival::ensureServerRunning(QString path)
|
bool TTSFestival::ensureServerRunning()
|
||||||
{
|
{
|
||||||
if(serverProcess.state() != QProcess::Running)
|
if(serverProcess.state() != QProcess::Running)
|
||||||
{
|
{
|
||||||
startServer(path);
|
startServer();
|
||||||
}
|
}
|
||||||
|
return serverProcess.state() == QProcess::Running;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TTSFestival::start(QString* errStr)
|
bool TTSFestival::start(QString* errStr)
|
||||||
{
|
{
|
||||||
(void) errStr;
|
qDebug() << "[Festival] Starting server with voice " << RbSettings::subValue("festival", RbSettings::TtsVoice).toString();
|
||||||
ensureServerRunning();
|
|
||||||
if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
|
|
||||||
queryServer(QString("(voice.select '%1)")
|
|
||||||
.arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString()));
|
|
||||||
|
|
||||||
return true;
|
bool running = ensureServerRunning();
|
||||||
|
if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
|
||||||
|
{
|
||||||
|
/* There's no harm in using both methods to set the voice .. */
|
||||||
|
QString voiceSelect = QString("(voice.select '%1)\n")
|
||||||
|
.arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString());
|
||||||
|
queryServer(voiceSelect, 3000);
|
||||||
|
|
||||||
|
if(prologFile.open())
|
||||||
|
{
|
||||||
|
prologFile.write(voiceSelect.toAscii());
|
||||||
|
prologFile.close();
|
||||||
|
prologPath = QFileInfo(prologFile).absoluteFilePath();
|
||||||
|
qDebug() << "[Festival] Prolog created at " << prologPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!running)
|
||||||
|
(*errStr) = "Festival could not be started";
|
||||||
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TTSFestival::stop()
|
bool TTSFestival::stop()
|
||||||
|
|
@ -141,13 +175,13 @@ bool TTSFestival::stop()
|
||||||
|
|
||||||
TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
|
TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
|
||||||
{
|
{
|
||||||
qDebug() << text << "->" << wavfile;
|
qDebug() << "[Festival] Voicing " << text << "->" << wavfile;
|
||||||
|
|
||||||
QString path = RbSettings::subValue("festival-client",
|
QString path = RbSettings::subValue("festival-client",
|
||||||
RbSettings::TtsPath).toString();
|
RbSettings::TtsPath).toString();
|
||||||
QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp"
|
QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp"
|
||||||
" --output \"%2\" - ").arg(path).arg(wavfile);
|
" --output \"%2\" --prolog \"%3\" - ").arg(path).arg(wavfile).arg(prologPath);
|
||||||
qDebug() << cmd;
|
qDebug() << "[Festival] Client cmd: " << cmd;
|
||||||
|
|
||||||
QProcess clientProcess;
|
QProcess clientProcess;
|
||||||
clientProcess.start(cmd);
|
clientProcess.start(cmd);
|
||||||
|
|
@ -159,7 +193,7 @@ TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
|
||||||
response = response.trimmed();
|
response = response.trimmed();
|
||||||
if(!response.contains("Utterance"))
|
if(!response.contains("Utterance"))
|
||||||
{
|
{
|
||||||
qDebug() << "Could not voice string: " << response;
|
qDebug() << "[Festival] Could not voice string: " << response;
|
||||||
*errStr = tr("engine could not voice string");
|
*errStr = tr("engine could not voice string");
|
||||||
return Warning;
|
return Warning;
|
||||||
/* do not stop the voicing process because of a single string
|
/* do not stop the voicing process because of a single string
|
||||||
|
|
@ -175,32 +209,40 @@ TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
|
||||||
|
|
||||||
bool TTSFestival::configOk()
|
bool TTSFestival::configOk()
|
||||||
{
|
{
|
||||||
QString serverPath = RbSettings::subValue("festival-server",
|
bool ret;
|
||||||
RbSettings::TtsPath).toString();
|
if (currentPath == "")
|
||||||
QString clientPath = RbSettings::subValue("festival-client",
|
{
|
||||||
RbSettings::TtsPath).toString();
|
QString serverPath = RbSettings::subValue("festival-server",
|
||||||
|
RbSettings::TtsPath).toString();
|
||||||
|
QString clientPath = RbSettings::subValue("festival-client",
|
||||||
|
RbSettings::TtsPath).toString();
|
||||||
|
|
||||||
|
ret = QFileInfo(serverPath).isExecutable() &&
|
||||||
|
QFileInfo(clientPath).isExecutable();
|
||||||
|
if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0
|
||||||
|
&& voices.size() > 0)
|
||||||
|
ret = ret && (voices.indexOf(RbSettings::subValue("festival",
|
||||||
|
RbSettings::TtsVoice).toString()) != -1);
|
||||||
|
}
|
||||||
|
else /* If we're currently configuring the server, we need to know that
|
||||||
|
the entered path is valid */
|
||||||
|
ret = QFileInfo(currentPath).isExecutable();
|
||||||
|
|
||||||
bool ret = QFileInfo(serverPath).isExecutable() &&
|
|
||||||
QFileInfo(clientPath).isExecutable();
|
|
||||||
if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0
|
|
||||||
&& voices.size() > 0)
|
|
||||||
ret = ret && (voices.indexOf(RbSettings::subValue("festival",
|
|
||||||
RbSettings::TtsVoice).toString()) != -1);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList TTSFestival::getVoiceList(QString path)
|
QStringList TTSFestival::getVoiceList()
|
||||||
{
|
{
|
||||||
if(!configOk())
|
if(!configOk())
|
||||||
return QStringList();
|
return QStringList();
|
||||||
|
|
||||||
if(voices.size() > 0)
|
if(voices.size() > 0)
|
||||||
{
|
{
|
||||||
qDebug() << "Using voice cache";
|
qDebug() << "[Festival] Using voice cache";
|
||||||
return voices;
|
return voices;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString response = queryServer("(voice.list)",3000,path);
|
QString response = queryServer("(voice.list)", 10000);
|
||||||
|
|
||||||
// get the 2nd line. It should be (<voice_name>, <voice_name>)
|
// get the 2nd line. It should be (<voice_name>, <voice_name>)
|
||||||
response = response.mid(response.indexOf('\n') + 1, -1);
|
response = response.mid(response.indexOf('\n') + 1, -1);
|
||||||
|
|
@ -212,14 +254,14 @@ QStringList TTSFestival::getVoiceList(QString path)
|
||||||
if (voices.size() == 1 && voices[0].size() == 0)
|
if (voices.size() == 1 && voices[0].size() == 0)
|
||||||
voices.removeAt(0);
|
voices.removeAt(0);
|
||||||
if (voices.size() > 0)
|
if (voices.size() > 0)
|
||||||
qDebug() << "Voices: " << voices;
|
qDebug() << "[Festival] Voices: " << voices;
|
||||||
else
|
else
|
||||||
qDebug() << "No voices.";
|
qDebug() << "[Festival] No voices. Response was: " << response;
|
||||||
|
|
||||||
return voices;
|
return voices;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TTSFestival::getVoiceInfo(QString voice,QString path)
|
QString TTSFestival::getVoiceInfo(QString voice)
|
||||||
{
|
{
|
||||||
if(!configOk())
|
if(!configOk())
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -231,7 +273,7 @@ QString TTSFestival::getVoiceInfo(QString voice,QString path)
|
||||||
return voiceDescriptions[voice];
|
return voiceDescriptions[voice];
|
||||||
|
|
||||||
QString response = queryServer(QString("(voice.description '%1)").arg(voice),
|
QString response = queryServer(QString("(voice.description '%1)").arg(voice),
|
||||||
3000,path);
|
10000);
|
||||||
|
|
||||||
if (response == "")
|
if (response == "")
|
||||||
{
|
{
|
||||||
|
|
@ -241,7 +283,7 @@ QString TTSFestival::getVoiceInfo(QString voice,QString path)
|
||||||
{
|
{
|
||||||
response = response.remove(QRegExp("(description \"*\")",
|
response = response.remove(QRegExp("(description \"*\")",
|
||||||
Qt::CaseInsensitive, QRegExp::Wildcard));
|
Qt::CaseInsensitive, QRegExp::Wildcard));
|
||||||
qDebug() << "voiceInfo w/o descr: " << response;
|
qDebug() << "[Festival] voiceInfo w/o descr: " << response;
|
||||||
response = response.remove(')');
|
response = response.remove(')');
|
||||||
QStringList responseLines = response.split('(', QString::SkipEmptyParts);
|
QStringList responseLines = response.split('(', QString::SkipEmptyParts);
|
||||||
responseLines.removeAt(0); // the voice name itself
|
responseLines.removeAt(0); // the voice name itself
|
||||||
|
|
@ -271,7 +313,7 @@ QString TTSFestival::getVoiceInfo(QString voice,QString path)
|
||||||
return voiceDescriptions[voice];
|
return voiceDescriptions[voice];
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TTSFestival::queryServer(QString query, int timeout,QString path)
|
QString TTSFestival::queryServer(QString query, int timeout)
|
||||||
{
|
{
|
||||||
if(!configOk())
|
if(!configOk())
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -279,9 +321,15 @@ QString TTSFestival::queryServer(QString query, int timeout,QString path)
|
||||||
// this operation could take some time
|
// this operation could take some time
|
||||||
emit busy();
|
emit busy();
|
||||||
|
|
||||||
ensureServerRunning(path);
|
qDebug() << "[Festival] queryServer with " << query;
|
||||||
|
|
||||||
|
if (!ensureServerRunning())
|
||||||
|
{
|
||||||
|
qDebug() << "[Festival] queryServer: ensureServerRunning failed";
|
||||||
|
emit busyEnd();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "queryServer with " << query;
|
|
||||||
QString response;
|
QString response;
|
||||||
|
|
||||||
QDateTime endTime;
|
QDateTime endTime;
|
||||||
|
|
@ -334,11 +382,11 @@ QString TTSFestival::queryServer(QString query, int timeout,QString path)
|
||||||
QStringList lines = response.split('\n');
|
QStringList lines = response.split('\n');
|
||||||
if(lines.size() > 2)
|
if(lines.size() > 2)
|
||||||
{
|
{
|
||||||
lines.removeFirst();
|
lines.removeFirst(); /* should be LP */
|
||||||
lines.removeLast();
|
lines.removeLast(); /* should be ft_StUfF_keyOK */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
qDebug() << "Response too short: " << response;
|
qDebug() << "[Festival] Response too short: " << response;
|
||||||
|
|
||||||
emit busyEnd();
|
emit busyEnd();
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#ifndef TTSFESTIVAL_H
|
#ifndef TTSFESTIVAL_H
|
||||||
#define TTSFESTIVAL_H
|
#define TTSFESTIVAL_H
|
||||||
|
|
||||||
|
#include <QTemporaryFile>
|
||||||
#include "ttsbase.h"
|
#include "ttsbase.h"
|
||||||
|
|
||||||
class TTSFestival : public TTSBase
|
class TTSFestival : public TTSBase
|
||||||
|
|
@ -52,12 +53,15 @@ class TTSFestival : public TTSBase
|
||||||
void updateVoiceDescription();
|
void updateVoiceDescription();
|
||||||
void clearVoiceDescription();
|
void clearVoiceDescription();
|
||||||
private:
|
private:
|
||||||
QStringList getVoiceList(QString path ="");
|
QTemporaryFile prologFile;
|
||||||
QString getVoiceInfo(QString voice,QString path ="");
|
QString prologPath;
|
||||||
|
QString currentPath;
|
||||||
|
QStringList getVoiceList();
|
||||||
|
QString getVoiceInfo(QString voice);
|
||||||
|
|
||||||
inline void startServer(QString path="");
|
inline void startServer();
|
||||||
inline void ensureServerRunning(QString path="");
|
inline bool ensureServerRunning();
|
||||||
QString queryServer(QString query, int timeout = -1,QString path="");
|
QString queryServer(QString query, int timeout = -1);
|
||||||
QProcess serverProcess;
|
QProcess serverProcess;
|
||||||
QStringList voices;
|
QStringList voices;
|
||||||
QMap<QString, QString> voiceDescriptions;
|
QMap<QString, QString> voiceDescriptions;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue