rbutil: Rework Festival TTS integration.
When communicating with Festival via socket don't assume readAll() would read all data we expect. We can only read the data that has been sent by the server so far, and this is not necessarily complete. This notably improves the configuration dialog response and reliably. Change-Id: I9a812f03df785fb3ad32783a8573a2c86dc317ed
This commit is contained in:
parent
e21f80f397
commit
c21d10cb33
1 changed files with 48 additions and 40 deletions
|
@ -121,18 +121,14 @@ void TTSFestival::startServer()
|
||||||
QString path;
|
QString path;
|
||||||
/* currentPath is set by the GUI - if it's set, it is the currently set
|
/* 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 */
|
path in the configuration GUI; if it's not set, use the saved path */
|
||||||
if (currentPath == "")
|
if (currentPath.isEmpty())
|
||||||
path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
|
path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
|
||||||
else
|
else
|
||||||
path = currentPath;
|
path = currentPath;
|
||||||
|
|
||||||
serverProcess.start(QString("%1 --server").arg(path));
|
serverProcess.start(path, QStringList("--server"));
|
||||||
serverProcess.waitForStarted();
|
serverProcess.waitForStarted();
|
||||||
|
|
||||||
/* A friendlier version of a spinlock */
|
|
||||||
while (serverProcess.processId() == 0 && serverProcess.state() != QProcess::Running)
|
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
|
|
||||||
|
|
||||||
if(serverProcess.state() == QProcess::Running)
|
if(serverProcess.state() == QProcess::Running)
|
||||||
LOG_INFO() << "Server is up and running";
|
LOG_INFO() << "Server is up and running";
|
||||||
else
|
else
|
||||||
|
@ -174,7 +170,7 @@ bool TTSFestival::start(QString* errStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!running)
|
if (!running)
|
||||||
(*errStr) = "Festival could not be started";
|
(*errStr) = tr("Festival could not be started");
|
||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,12 +188,13 @@ TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
|
||||||
|
|
||||||
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"
|
QStringList cmd;
|
||||||
" --output \"%2\" --prolog \"%3\" - ").arg(path, wavfile, prologPath);
|
cmd << "--server" << "localhost" << "--otype" << "riff" << "--ttw"
|
||||||
LOG_INFO() << "Client cmd:" << cmd;
|
<< "--withlisp" << "--output" << wavfile << "--prolog" << prologPath << "-";
|
||||||
|
LOG_INFO() << "Client cmd:" << path << cmd;
|
||||||
|
|
||||||
QProcess clientProcess;
|
QProcess clientProcess;
|
||||||
clientProcess.start(cmd);
|
clientProcess.start(path, cmd);
|
||||||
clientProcess.write(QString("%1.\n").arg(text).toLatin1());
|
clientProcess.write(QString("%1.\n").arg(text).toLatin1());
|
||||||
clientProcess.waitForBytesWritten();
|
clientProcess.waitForBytesWritten();
|
||||||
clientProcess.closeWriteChannel();
|
clientProcess.closeWriteChannel();
|
||||||
|
@ -332,6 +329,9 @@ QString TTSFestival::getVoiceInfo(QString voice)
|
||||||
|
|
||||||
QString TTSFestival::queryServer(QString query, int timeout)
|
QString TTSFestival::queryServer(QString query, int timeout)
|
||||||
{
|
{
|
||||||
|
// make sure we always abort at some point.
|
||||||
|
if(timeout == 0)
|
||||||
|
timeout = 60000;
|
||||||
if(!configOk())
|
if(!configOk())
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
|
@ -347,66 +347,74 @@ QString TTSFestival::queryServer(QString query, int timeout)
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
QString response;
|
|
||||||
|
|
||||||
QDateTime endTime;
|
QDateTime endTime = QDateTime::currentDateTime().addMSecs(timeout);
|
||||||
if(timeout > 0)
|
|
||||||
endTime = QDateTime::currentDateTime().addMSecs(timeout);
|
|
||||||
|
|
||||||
/* Festival is *extremely* unreliable. Although at this
|
/* Festival is *extremely* unreliable. Although at this
|
||||||
* point we are sure that SIOD is accepting commands,
|
* point we are sure that SIOD is accepting commands,
|
||||||
* we might end up with an empty response. Hence, the loop.
|
* we might end up with an empty response. Hence, the loop.
|
||||||
*/
|
*/
|
||||||
while(true)
|
QTcpSocket socket;
|
||||||
|
QString response;
|
||||||
|
while(QDateTime::currentDateTime() < endTime)
|
||||||
{
|
{
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
|
||||||
QTcpSocket socket;
|
|
||||||
|
|
||||||
socket.connectToHost("localhost", 1314);
|
if(socket.state() != QAbstractSocket::ConnectedState)
|
||||||
socket.waitForConnected();
|
|
||||||
|
|
||||||
if(socket.state() == QAbstractSocket::ConnectedState)
|
|
||||||
{
|
{
|
||||||
|
LOG_INFO() << "socket not (yet) connected, trying again.";
|
||||||
|
socket.connectToHost("localhost", 1314);
|
||||||
|
// appears we need to recheck the state still.
|
||||||
|
socket.waitForConnected();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// seems to be necessary to resend the request at times.
|
||||||
socket.write(QString("%1\n").arg(query).toLatin1());
|
socket.write(QString("%1\n").arg(query).toLatin1());
|
||||||
socket.waitForBytesWritten();
|
socket.waitForBytesWritten();
|
||||||
socket.waitForReadyRead();
|
socket.waitForReadyRead();
|
||||||
|
|
||||||
response = socket.readAll().trimmed();
|
// we might not get the complete response on the first read.
|
||||||
|
// Concatenate until we got a full response.
|
||||||
|
response += socket.readAll();
|
||||||
|
|
||||||
if (response != "LP" && response != "")
|
// The query response ends with this.
|
||||||
|
if (response.contains("ft_StUfF_keyOK"))
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
socket.abort();
|
|
||||||
socket.disconnectFromHost();
|
|
||||||
|
|
||||||
if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
|
|
||||||
{
|
|
||||||
emit busyEnd();
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
/* make sure we wait a little as we don't want to flood the server
|
/* make sure we wait a little as we don't want to flood the server
|
||||||
* with requests */
|
* with requests */
|
||||||
QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
|
QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
|
||||||
while(QDateTime::currentDateTime() < tmpEndTime)
|
while(QDateTime::currentDateTime() < tmpEndTime)
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
||||||
}
|
}
|
||||||
|
emit busyEnd();
|
||||||
|
socket.disconnectFromHost();
|
||||||
|
|
||||||
if(response == "nil")
|
if(response == "nil")
|
||||||
{
|
{
|
||||||
emit busyEnd();
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList lines = response.split('\n');
|
/* The response starts with "LP\n", and ends with "ft_StUfF_keyOK", but we
|
||||||
if(lines.size() > 2)
|
* could get trailing data -- we might have sent the request more than
|
||||||
|
* once. Use a regex to get the actual response part.
|
||||||
|
*/
|
||||||
|
QRegularExpression regex("LP\\n(.*?)\\nft_StUfF_keyOK",
|
||||||
|
QRegularExpression::MultilineOption
|
||||||
|
| QRegularExpression::DotMatchesEverythingOption);
|
||||||
|
QRegularExpressionMatch match = regex.match(response);
|
||||||
|
if(match.hasMatch())
|
||||||
{
|
{
|
||||||
lines.removeFirst(); /* should be LP */
|
response = match.captured(1);
|
||||||
lines.removeLast(); /* should be ft_StUfF_keyOK */
|
}
|
||||||
|
else {
|
||||||
|
LOG_WARNING() << "Invalid Festival response." << response;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
LOG_ERROR() << "Response too short:" << response;
|
|
||||||
|
|
||||||
emit busyEnd();
|
|
||||||
return lines.join("\n");
|
|
||||||
|
|
||||||
|
return response.trimmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue