summaryrefslogtreecommitdiff
path: root/rbutil/rbutilqt/base/ttsfestival.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'rbutil/rbutilqt/base/ttsfestival.cpp')
-rw-r--r--rbutil/rbutilqt/base/ttsfestival.cpp325
1 files changed, 325 insertions, 0 deletions
diff --git a/rbutil/rbutilqt/base/ttsfestival.cpp b/rbutil/rbutilqt/base/ttsfestival.cpp
new file mode 100644
index 0000000000..37d263a932
--- /dev/null
+++ b/rbutil/rbutilqt/base/ttsfestival.cpp
@@ -0,0 +1,325 @@
1/***************************************************************************
2* __________ __ ___.
3* Open \______ \ ____ ____ | | _\_ |__ _______ ___
4* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7* \/ \/ \/ \/ \/
8*
9* Copyright (C) 2007 by Dominik Wenger
10* $Id$
11*
12* All files in this archive are subject to the GNU General Public License.
13* See the file COPYING in the source tree root for full license agreement.
14*
15* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16* KIND, either express or implied.
17*
18****************************************************************************/
19
20#include "ttsfestival.h"
21#include "utils.h"
22#include "rbsettings.h"
23
24TTSFestival::~TTSFestival()
25{
26 stop();
27}
28
29void TTSFestival::generateSettings()
30{
31 // server path
32 QString exepath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
33 if(exepath == "" ) exepath = findExecutable("festival");
34 insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN));
35
36 // client path
37 QString clientpath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
38 if(clientpath == "" ) clientpath = findExecutable("festival_client");
39 insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,
40 tr("Path to Festival client:"),clientpath,EncTtsSetting::eBROWSEBTN));
41
42 // voice
43 EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,
44 tr("Voice:"),RbSettings::subValue("festival",RbSettings::TtsVoice),getVoiceList(exepath),EncTtsSetting::eREFRESHBTN);
45 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
46 connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
47 insertSetting(eVOICE,setting);
48
49 //voice description
50 setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,
51 tr("Voice description:"),"",EncTtsSetting::eREFRESHBTN);
52 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription()));
53 insertSetting(eVOICEDESC,setting);
54}
55
56void TTSFestival::saveSettings()
57{
58 //save settings in user config
59 RbSettings::setSubValue("festival-server",RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString());
60 RbSettings::setSubValue("festival-client",RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString());
61 RbSettings::setSubValue("festival",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
62
63 RbSettings::sync();
64}
65
66void TTSFestival::updateVoiceDescription()
67{
68 // get voice Info with current voice and path
69 QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),getSetting(eSERVERPATH)->current().toString());
70 getSetting(eVOICEDESC)->setCurrent(info);
71}
72
73void TTSFestival::clearVoiceDescription()
74{
75 getSetting(eVOICEDESC)->setCurrent("");
76}
77
78void TTSFestival::updateVoiceList()
79{
80 QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString());
81 getSetting(eVOICE)->setList(voiceList);
82 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
83 else getSetting(eVOICE)->setCurrent("");
84}
85
86void TTSFestival::startServer(QString path)
87{
88 if(!configOk())
89 return;
90
91 if(path == "")
92 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
93
94 serverProcess.start(QString("%1 --server").arg(path));
95 serverProcess.waitForStarted();
96
97 queryServer("(getpid)",300,path);
98 if(serverProcess.state() == QProcess::Running)
99 qDebug() << "Festival is up and running";
100 else
101 qDebug() << "Festival failed to start";
102}
103
104void TTSFestival::ensureServerRunning(QString path)
105{
106 if(serverProcess.state() != QProcess::Running)
107 {
108 startServer(path);
109 }
110}
111
112bool TTSFestival::start(QString* errStr)
113{
114 (void) errStr;
115 ensureServerRunning();
116 if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
117 queryServer(QString("(voice.select '%1)")
118 .arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString()));
119
120 return true;
121}
122
123bool TTSFestival::stop()
124{
125 serverProcess.terminate();
126 serverProcess.kill();
127
128 return true;
129}
130
131TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
132{
133 qDebug() << text << "->" << wavfile;
134
135 QString path = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
136 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path).arg(wavfile);
137 qDebug() << cmd;
138
139 QProcess clientProcess;
140 clientProcess.start(cmd);
141 clientProcess.write(QString("%1.\n").arg(text).toAscii());
142 clientProcess.waitForBytesWritten();
143 clientProcess.closeWriteChannel();
144 clientProcess.waitForReadyRead();
145 QString response = clientProcess.readAll();
146 response = response.trimmed();
147 if(!response.contains("Utterance"))
148 {
149 qDebug() << "Could not voice string: " << response;
150 *errStr = tr("engine could not voice string");
151 return Warning;
152 /* do not stop the voicing process because of a single string
153 TODO: needs proper settings */
154 }
155 clientProcess.closeReadChannel(QProcess::StandardError);
156 clientProcess.closeReadChannel(QProcess::StandardOutput);
157 clientProcess.terminate();
158 clientProcess.kill();
159
160 return NoError;
161}
162
163bool TTSFestival::configOk()
164{
165 QString serverPath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
166 QString clientPath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
167
168 bool ret = QFileInfo(serverPath).isExecutable() &&
169 QFileInfo(clientPath).isExecutable();
170 if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0 && voices.size() > 0)
171 ret = ret && (voices.indexOf(RbSettings::subValue("festival",RbSettings::TtsVoice).toString()) != -1);
172 return ret;
173}
174
175QStringList TTSFestival::getVoiceList(QString path)
176{
177 if(!configOk())
178 return QStringList();
179
180 if(voices.size() > 0)
181 {
182 qDebug() << "Using voice cache";
183 return voices;
184 }
185
186 QString response = queryServer("(voice.list)",3000,path);
187
188 // get the 2nd line. It should be (<voice_name>, <voice_name>)
189 response = response.mid(response.indexOf('\n') + 1, -1);
190 response = response.left(response.indexOf('\n')).trimmed();
191
192 voices = response.mid(1, response.size()-2).split(' ');
193
194 voices.sort();
195 if (voices.size() == 1 && voices[0].size() == 0)
196 voices.removeAt(0);
197 if (voices.size() > 0)
198 qDebug() << "Voices: " << voices;
199 else
200 qDebug() << "No voices.";
201
202 return voices;
203}
204
205QString TTSFestival::getVoiceInfo(QString voice,QString path)
206{
207 if(!configOk())
208 return "";
209
210 if(!getVoiceList().contains(voice))
211 return "";
212
213 if(voiceDescriptions.contains(voice))
214 return voiceDescriptions[voice];
215
216 QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path);
217
218 if (response == "")
219 {
220 voiceDescriptions[voice]=tr("No description available");
221 }
222 else
223 {
224 response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard));
225 qDebug() << "voiceInfo w/o descr: " << response;
226 response = response.remove(')');
227 QStringList responseLines = response.split('(', QString::SkipEmptyParts);
228 responseLines.removeAt(0); // the voice name itself
229
230 QString description;
231 foreach(QString line, responseLines)
232 {
233 line = line.remove('(');
234 line = line.simplified();
235
236 line[0] = line[0].toUpper(); // capitalize the key
237
238 int firstSpace = line.indexOf(' ');
239 if (firstSpace > 0)
240 {
241 line = line.insert(firstSpace, ':'); // add a colon between the key and the value
242 line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value
243 }
244
245 description += line + "\n";
246 }
247 voiceDescriptions[voice] = description.trimmed();
248 }
249
250 return voiceDescriptions[voice];
251}
252
253QString TTSFestival::queryServer(QString query, int timeout,QString path)
254{
255 if(!configOk())
256 return "";
257
258 // this operation could take some time
259 emit busy();
260
261 ensureServerRunning(path);
262
263 qDebug() << "queryServer with " << query;
264 QString response;
265
266 QDateTime endTime;
267 if(timeout > 0)
268 endTime = QDateTime::currentDateTime().addMSecs(timeout);
269
270 /* Festival is *extremely* unreliable. Although at this
271 * point we are sure that SIOD is accepting commands,
272 * we might end up with an empty response. Hence, the loop.
273 */
274 while(true)
275 {
276 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
277 QTcpSocket socket;
278
279 socket.connectToHost("localhost", 1314);
280 socket.waitForConnected();
281
282 if(socket.state() == QAbstractSocket::ConnectedState)
283 {
284 socket.write(QString("%1\n").arg(query).toAscii());
285 socket.waitForBytesWritten();
286 socket.waitForReadyRead();
287
288 response = socket.readAll().trimmed();
289
290 if (response != "LP" && response != "")
291 break;
292 }
293 socket.abort();
294 socket.disconnectFromHost();
295
296 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
297 {
298 emit busyEnd();
299 return "";
300 }
301 /* make sure we wait a little as we don't want to flood the server with requests */
302 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
303 while(QDateTime::currentDateTime() < tmpEndTime)
304 QCoreApplication::processEvents(QEventLoop::AllEvents);
305 }
306 if(response == "nil")
307 {
308 emit busyEnd();
309 return "";
310 }
311
312 QStringList lines = response.split('\n');
313 if(lines.size() > 2)
314 {
315 lines.removeFirst();
316 lines.removeLast();
317 }
318 else
319 qDebug() << "Response too short: " << response;
320
321 emit busyEnd();
322 return lines.join("\n");
323
324}
325