summaryrefslogtreecommitdiff
path: root/utils/rbutilqt/base/ttsfestival.cpp
diff options
context:
space:
mode:
authorDominik Riebeling <Dominik.Riebeling@gmail.com>2022-03-20 09:57:48 +0100
committerDominik Riebeling <Dominik.Riebeling@gmail.com>2022-03-20 09:59:17 +0100
commitc21d10cb33e0d123d4b53acdc28e73002d240634 (patch)
tree96fda070400835cb0cf6d0bf0d393f4090083f72 /utils/rbutilqt/base/ttsfestival.cpp
parente21f80f397bb63a46ae364fa6ef8d5b64a96ffce (diff)
downloadrockbox-c21d10cb33e0d123d4b53acdc28e73002d240634.tar.gz
rockbox-c21d10cb33e0d123d4b53acdc28e73002d240634.zip
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
Diffstat (limited to 'utils/rbutilqt/base/ttsfestival.cpp')
-rw-r--r--utils/rbutilqt/base/ttsfestival.cpp88
1 files changed, 48 insertions, 40 deletions
diff --git a/utils/rbutilqt/base/ttsfestival.cpp b/utils/rbutilqt/base/ttsfestival.cpp
index 4c718de824..fbb8166a9a 100644
--- a/utils/rbutilqt/base/ttsfestival.cpp
+++ b/utils/rbutilqt/base/ttsfestival.cpp
@@ -121,18 +121,14 @@ void TTSFestival::startServer()
121 QString path; 121 QString path;
122 /* currentPath is set by the GUI - if it's set, it is the currently set 122 /* currentPath is set by the GUI - if it's set, it is the currently set
123 path in the configuration GUI; if it's not set, use the saved path */ 123 path in the configuration GUI; if it's not set, use the saved path */
124 if (currentPath == "") 124 if (currentPath.isEmpty())
125 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString(); 125 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
126 else 126 else
127 path = currentPath; 127 path = currentPath;
128 128
129 serverProcess.start(QString("%1 --server").arg(path)); 129 serverProcess.start(path, QStringList("--server"));
130 serverProcess.waitForStarted(); 130 serverProcess.waitForStarted();
131 131
132 /* A friendlier version of a spinlock */
133 while (serverProcess.processId() == 0 && serverProcess.state() != QProcess::Running)
134 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
135
136 if(serverProcess.state() == QProcess::Running) 132 if(serverProcess.state() == QProcess::Running)
137 LOG_INFO() << "Server is up and running"; 133 LOG_INFO() << "Server is up and running";
138 else 134 else
@@ -174,7 +170,7 @@ bool TTSFestival::start(QString* errStr)
174 } 170 }
175 171
176 if (!running) 172 if (!running)
177 (*errStr) = "Festival could not be started"; 173 (*errStr) = tr("Festival could not be started");
178 return running; 174 return running;
179} 175}
180 176
@@ -192,12 +188,13 @@ TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
192 188
193 QString path = RbSettings::subValue("festival-client", 189 QString path = RbSettings::subValue("festival-client",
194 RbSettings::TtsPath).toString(); 190 RbSettings::TtsPath).toString();
195 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp" 191 QStringList cmd;
196 " --output \"%2\" --prolog \"%3\" - ").arg(path, wavfile, prologPath); 192 cmd << "--server" << "localhost" << "--otype" << "riff" << "--ttw"
197 LOG_INFO() << "Client cmd:" << cmd; 193 << "--withlisp" << "--output" << wavfile << "--prolog" << prologPath << "-";
194 LOG_INFO() << "Client cmd:" << path << cmd;
198 195
199 QProcess clientProcess; 196 QProcess clientProcess;
200 clientProcess.start(cmd); 197 clientProcess.start(path, cmd);
201 clientProcess.write(QString("%1.\n").arg(text).toLatin1()); 198 clientProcess.write(QString("%1.\n").arg(text).toLatin1());
202 clientProcess.waitForBytesWritten(); 199 clientProcess.waitForBytesWritten();
203 clientProcess.closeWriteChannel(); 200 clientProcess.closeWriteChannel();
@@ -332,6 +329,9 @@ QString TTSFestival::getVoiceInfo(QString voice)
332 329
333QString TTSFestival::queryServer(QString query, int timeout) 330QString TTSFestival::queryServer(QString query, int timeout)
334{ 331{
332 // make sure we always abort at some point.
333 if(timeout == 0)
334 timeout = 60000;
335 if(!configOk()) 335 if(!configOk())
336 return ""; 336 return "";
337 337
@@ -347,66 +347,74 @@ QString TTSFestival::queryServer(QString query, int timeout)
347 return ""; 347 return "";
348 } 348 }
349 349
350 QString response;
351 350
352 QDateTime endTime; 351 QDateTime endTime = QDateTime::currentDateTime().addMSecs(timeout);
353 if(timeout > 0)
354 endTime = QDateTime::currentDateTime().addMSecs(timeout);
355 352
356 /* Festival is *extremely* unreliable. Although at this 353 /* Festival is *extremely* unreliable. Although at this
357 * point we are sure that SIOD is accepting commands, 354 * point we are sure that SIOD is accepting commands,
358 * we might end up with an empty response. Hence, the loop. 355 * we might end up with an empty response. Hence, the loop.
359 */ 356 */
360 while(true) 357 QTcpSocket socket;
358 QString response;
359 while(QDateTime::currentDateTime() < endTime)
361 { 360 {
362 QCoreApplication::processEvents(QEventLoop::AllEvents, 50); 361 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
363 QTcpSocket socket;
364 362
365 socket.connectToHost("localhost", 1314); 363 if(socket.state() != QAbstractSocket::ConnectedState)
366 socket.waitForConnected();
367
368 if(socket.state() == QAbstractSocket::ConnectedState)
369 { 364 {
365 LOG_INFO() << "socket not (yet) connected, trying again.";
366 socket.connectToHost("localhost", 1314);
367 // appears we need to recheck the state still.
368 socket.waitForConnected();
369 }
370 else
371 {
372 // seems to be necessary to resend the request at times.
370 socket.write(QString("%1\n").arg(query).toLatin1()); 373 socket.write(QString("%1\n").arg(query).toLatin1());
371 socket.waitForBytesWritten(); 374 socket.waitForBytesWritten();
372 socket.waitForReadyRead(); 375 socket.waitForReadyRead();
373 376
374 response = socket.readAll().trimmed(); 377 // we might not get the complete response on the first read.
378 // Concatenate until we got a full response.
379 response += socket.readAll();
375 380
376 if (response != "LP" && response != "") 381 // The query response ends with this.
382 if (response.contains("ft_StUfF_keyOK"))
383 {
377 break; 384 break;
385 }
378 } 386 }
379 socket.abort();
380 socket.disconnectFromHost();
381 387
382 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
383 {
384 emit busyEnd();
385 return "";
386 }
387 /* make sure we wait a little as we don't want to flood the server 388 /* make sure we wait a little as we don't want to flood the server
388 * with requests */ 389 * with requests */
389 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500); 390 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
390 while(QDateTime::currentDateTime() < tmpEndTime) 391 while(QDateTime::currentDateTime() < tmpEndTime)
391 QCoreApplication::processEvents(QEventLoop::AllEvents); 392 QCoreApplication::processEvents(QEventLoop::AllEvents);
392 } 393 }
394 emit busyEnd();
395 socket.disconnectFromHost();
396
393 if(response == "nil") 397 if(response == "nil")
394 { 398 {
395 emit busyEnd();
396 return ""; 399 return "";
397 } 400 }
398 401
399 QStringList lines = response.split('\n'); 402 /* The response starts with "LP\n", and ends with "ft_StUfF_keyOK", but we
400 if(lines.size() > 2) 403 * could get trailing data -- we might have sent the request more than
404 * once. Use a regex to get the actual response part.
405 */
406 QRegularExpression regex("LP\\n(.*?)\\nft_StUfF_keyOK",
407 QRegularExpression::MultilineOption
408 | QRegularExpression::DotMatchesEverythingOption);
409 QRegularExpressionMatch match = regex.match(response);
410 if(match.hasMatch())
401 { 411 {
402 lines.removeFirst(); /* should be LP */ 412 response = match.captured(1);
403 lines.removeLast(); /* should be ft_StUfF_keyOK */ 413 }
414 else {
415 LOG_WARNING() << "Invalid Festival response." << response;
404 } 416 }
405 else
406 LOG_ERROR() << "Response too short:" << response;
407
408 emit busyEnd();
409 return lines.join("\n");
410 417
418 return response.trimmed();
411} 419}
412 420