Packard Bell Vibe 500: correct the path to a proper one in rbutil (proper directory...
[kugel-rb.git] / rbutil / rbutilqt / base / ttsfestival.cpp
blob06cf0ef0cce13122633945ee6b7554fa14fa1885
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2007 by Dominik Wenger
10 * $Id$
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.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 #include "ttsfestival.h"
21 #include "utils.h"
22 #include "rbsettings.h"
24 TTSFestival::~TTSFestival()
26 stop();
29 void TTSFestival::generateSettings()
31 // server path
32 QString exepath = RbSettings::subValue("festival-server",
33 RbSettings::TtsPath).toString();
34 if(exepath == "" ) exepath = findExecutable("festival");
35 insertSetting(eSERVERPATH,new EncTtsSetting(this,
36 EncTtsSetting::eSTRING, "Path to Festival server:",
37 exepath,EncTtsSetting::eBROWSEBTN));
39 // client path
40 QString clientpath = RbSettings::subValue("festival-client",
41 RbSettings::TtsPath).toString();
42 if(clientpath == "" ) clientpath = findExecutable("festival_client");
43 insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,
44 tr("Path to Festival client:"),
45 clientpath,EncTtsSetting::eBROWSEBTN));
47 // voice
48 EncTtsSetting* setting = new EncTtsSetting(this,
49 EncTtsSetting::eSTRINGLIST, tr("Voice:"),
50 RbSettings::subValue("festival", RbSettings::TtsVoice),
51 getVoiceList(exepath), EncTtsSetting::eREFRESHBTN);
52 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList()));
53 connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription()));
54 insertSetting(eVOICE,setting);
56 //voice description
57 setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,
58 tr("Voice description:"),"",EncTtsSetting::eREFRESHBTN);
59 connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription()));
60 insertSetting(eVOICEDESC,setting);
63 void TTSFestival::saveSettings()
65 //save settings in user config
66 RbSettings::setSubValue("festival-server",
67 RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString());
68 RbSettings::setSubValue("festival-client",
69 RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString());
70 RbSettings::setSubValue("festival",
71 RbSettings::TtsVoice,getSetting(eVOICE)->current().toString());
73 RbSettings::sync();
76 void TTSFestival::updateVoiceDescription()
78 // get voice Info with current voice and path
79 QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),
80 getSetting(eSERVERPATH)->current().toString());
81 getSetting(eVOICEDESC)->setCurrent(info);
84 void TTSFestival::clearVoiceDescription()
86 getSetting(eVOICEDESC)->setCurrent("");
89 void TTSFestival::updateVoiceList()
91 QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString());
92 getSetting(eVOICE)->setList(voiceList);
93 if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
94 else getSetting(eVOICE)->setCurrent("");
97 void TTSFestival::startServer(QString path)
99 if(!configOk())
100 return;
102 if(path == "")
103 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
105 serverProcess.start(QString("%1 --server").arg(path));
106 serverProcess.waitForStarted();
108 queryServer("(getpid)",300,path);
109 if(serverProcess.state() == QProcess::Running)
110 qDebug() << "Festival is up and running";
111 else
112 qDebug() << "Festival failed to start";
115 void TTSFestival::ensureServerRunning(QString path)
117 if(serverProcess.state() != QProcess::Running)
119 startServer(path);
123 bool TTSFestival::start(QString* errStr)
125 (void) errStr;
126 ensureServerRunning();
127 if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty())
128 queryServer(QString("(voice.select '%1)")
129 .arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString()));
131 return true;
134 bool TTSFestival::stop()
136 serverProcess.terminate();
137 serverProcess.kill();
139 return true;
142 TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
144 qDebug() << text << "->" << wavfile;
146 QString path = RbSettings::subValue("festival-client",
147 RbSettings::TtsPath).toString();
148 QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp"
149 " --output \"%2\" - ").arg(path).arg(wavfile);
150 qDebug() << cmd;
152 QProcess clientProcess;
153 clientProcess.start(cmd);
154 clientProcess.write(QString("%1.\n").arg(text).toAscii());
155 clientProcess.waitForBytesWritten();
156 clientProcess.closeWriteChannel();
157 clientProcess.waitForReadyRead();
158 QString response = clientProcess.readAll();
159 response = response.trimmed();
160 if(!response.contains("Utterance"))
162 qDebug() << "Could not voice string: " << response;
163 *errStr = tr("engine could not voice string");
164 return Warning;
165 /* do not stop the voicing process because of a single string
166 TODO: needs proper settings */
168 clientProcess.closeReadChannel(QProcess::StandardError);
169 clientProcess.closeReadChannel(QProcess::StandardOutput);
170 clientProcess.terminate();
171 clientProcess.kill();
173 return NoError;
176 bool TTSFestival::configOk()
178 QString serverPath = RbSettings::subValue("festival-server",
179 RbSettings::TtsPath).toString();
180 QString clientPath = RbSettings::subValue("festival-client",
181 RbSettings::TtsPath).toString();
183 bool ret = QFileInfo(serverPath).isExecutable() &&
184 QFileInfo(clientPath).isExecutable();
185 if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0
186 && voices.size() > 0)
187 ret = ret && (voices.indexOf(RbSettings::subValue("festival",
188 RbSettings::TtsVoice).toString()) != -1);
189 return ret;
192 QStringList TTSFestival::getVoiceList(QString path)
194 if(!configOk())
195 return QStringList();
197 if(voices.size() > 0)
199 qDebug() << "Using voice cache";
200 return voices;
203 QString response = queryServer("(voice.list)",3000,path);
205 // get the 2nd line. It should be (<voice_name>, <voice_name>)
206 response = response.mid(response.indexOf('\n') + 1, -1);
207 response = response.left(response.indexOf('\n')).trimmed();
209 voices = response.mid(1, response.size()-2).split(' ');
211 voices.sort();
212 if (voices.size() == 1 && voices[0].size() == 0)
213 voices.removeAt(0);
214 if (voices.size() > 0)
215 qDebug() << "Voices: " << voices;
216 else
217 qDebug() << "No voices.";
219 return voices;
222 QString TTSFestival::getVoiceInfo(QString voice,QString path)
224 if(!configOk())
225 return "";
227 if(!getVoiceList().contains(voice))
228 return "";
230 if(voiceDescriptions.contains(voice))
231 return voiceDescriptions[voice];
233 QString response = queryServer(QString("(voice.description '%1)").arg(voice),
234 3000,path);
236 if (response == "")
238 voiceDescriptions[voice]=tr("No description available");
240 else
242 response = response.remove(QRegExp("(description \"*\")",
243 Qt::CaseInsensitive, QRegExp::Wildcard));
244 qDebug() << "voiceInfo w/o descr: " << response;
245 response = response.remove(')');
246 QStringList responseLines = response.split('(', QString::SkipEmptyParts);
247 responseLines.removeAt(0); // the voice name itself
249 QString description;
250 foreach(QString line, responseLines)
252 line = line.remove('(');
253 line = line.simplified();
255 line[0] = line[0].toUpper(); // capitalize the key
257 int firstSpace = line.indexOf(' ');
258 if (firstSpace > 0)
260 // add a colon between the key and the value
261 line = line.insert(firstSpace, ':');
262 // capitalize the value
263 line[firstSpace+2] = line[firstSpace+2].toUpper();
266 description += line + "\n";
268 voiceDescriptions[voice] = description.trimmed();
271 return voiceDescriptions[voice];
274 QString TTSFestival::queryServer(QString query, int timeout,QString path)
276 if(!configOk())
277 return "";
279 // this operation could take some time
280 emit busy();
282 ensureServerRunning(path);
284 qDebug() << "queryServer with " << query;
285 QString response;
287 QDateTime endTime;
288 if(timeout > 0)
289 endTime = QDateTime::currentDateTime().addMSecs(timeout);
291 /* Festival is *extremely* unreliable. Although at this
292 * point we are sure that SIOD is accepting commands,
293 * we might end up with an empty response. Hence, the loop.
295 while(true)
297 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
298 QTcpSocket socket;
300 socket.connectToHost("localhost", 1314);
301 socket.waitForConnected();
303 if(socket.state() == QAbstractSocket::ConnectedState)
305 socket.write(QString("%1\n").arg(query).toAscii());
306 socket.waitForBytesWritten();
307 socket.waitForReadyRead();
309 response = socket.readAll().trimmed();
311 if (response != "LP" && response != "")
312 break;
314 socket.abort();
315 socket.disconnectFromHost();
317 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
319 emit busyEnd();
320 return "";
322 /* make sure we wait a little as we don't want to flood the server
323 * with requests */
324 QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500);
325 while(QDateTime::currentDateTime() < tmpEndTime)
326 QCoreApplication::processEvents(QEventLoop::AllEvents);
328 if(response == "nil")
330 emit busyEnd();
331 return "";
334 QStringList lines = response.split('\n');
335 if(lines.size() > 2)
337 lines.removeFirst();
338 lines.removeLast();
340 else
341 qDebug() << "Response too short: " << response;
343 emit busyEnd();
344 return lines.join("\n");