Fix Sansa bootloader installation on OS X.
[kugel-rb.git] / rbutil / rbutilqt / base / ttsfestival.cpp
blob37d263a93239cddc3441fe34eed76b107745f024
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",RbSettings::TtsPath).toString();
33 if(exepath == "" ) exepath = findExecutable("festival");
34 insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN));
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));
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);
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);
56 void TTSFestival::saveSettings()
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());
63 RbSettings::sync();
66 void TTSFestival::updateVoiceDescription()
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);
73 void TTSFestival::clearVoiceDescription()
75 getSetting(eVOICEDESC)->setCurrent("");
78 void TTSFestival::updateVoiceList()
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("");
86 void TTSFestival::startServer(QString path)
88 if(!configOk())
89 return;
91 if(path == "")
92 path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
94 serverProcess.start(QString("%1 --server").arg(path));
95 serverProcess.waitForStarted();
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";
104 void TTSFestival::ensureServerRunning(QString path)
106 if(serverProcess.state() != QProcess::Running)
108 startServer(path);
112 bool TTSFestival::start(QString* errStr)
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()));
120 return true;
123 bool TTSFestival::stop()
125 serverProcess.terminate();
126 serverProcess.kill();
128 return true;
131 TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr)
133 qDebug() << text << "->" << wavfile;
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;
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"))
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 */
155 clientProcess.closeReadChannel(QProcess::StandardError);
156 clientProcess.closeReadChannel(QProcess::StandardOutput);
157 clientProcess.terminate();
158 clientProcess.kill();
160 return NoError;
163 bool TTSFestival::configOk()
165 QString serverPath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString();
166 QString clientPath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString();
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;
175 QStringList TTSFestival::getVoiceList(QString path)
177 if(!configOk())
178 return QStringList();
180 if(voices.size() > 0)
182 qDebug() << "Using voice cache";
183 return voices;
186 QString response = queryServer("(voice.list)",3000,path);
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();
192 voices = response.mid(1, response.size()-2).split(' ');
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.";
202 return voices;
205 QString TTSFestival::getVoiceInfo(QString voice,QString path)
207 if(!configOk())
208 return "";
210 if(!getVoiceList().contains(voice))
211 return "";
213 if(voiceDescriptions.contains(voice))
214 return voiceDescriptions[voice];
216 QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path);
218 if (response == "")
220 voiceDescriptions[voice]=tr("No description available");
222 else
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
230 QString description;
231 foreach(QString line, responseLines)
233 line = line.remove('(');
234 line = line.simplified();
236 line[0] = line[0].toUpper(); // capitalize the key
238 int firstSpace = line.indexOf(' ');
239 if (firstSpace > 0)
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
245 description += line + "\n";
247 voiceDescriptions[voice] = description.trimmed();
250 return voiceDescriptions[voice];
253 QString TTSFestival::queryServer(QString query, int timeout,QString path)
255 if(!configOk())
256 return "";
258 // this operation could take some time
259 emit busy();
261 ensureServerRunning(path);
263 qDebug() << "queryServer with " << query;
264 QString response;
266 QDateTime endTime;
267 if(timeout > 0)
268 endTime = QDateTime::currentDateTime().addMSecs(timeout);
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.
274 while(true)
276 QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
277 QTcpSocket socket;
279 socket.connectToHost("localhost", 1314);
280 socket.waitForConnected();
282 if(socket.state() == QAbstractSocket::ConnectedState)
284 socket.write(QString("%1\n").arg(query).toAscii());
285 socket.waitForBytesWritten();
286 socket.waitForReadyRead();
288 response = socket.readAll().trimmed();
290 if (response != "LP" && response != "")
291 break;
293 socket.abort();
294 socket.disconnectFromHost();
296 if(timeout > 0 && QDateTime::currentDateTime() >= endTime)
298 emit busyEnd();
299 return "";
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);
306 if(response == "nil")
308 emit busyEnd();
309 return "";
312 QStringList lines = response.split('\n');
313 if(lines.size() > 2)
315 lines.removeFirst();
316 lines.removeLast();
318 else
319 qDebug() << "Response too short: " << response;
321 emit busyEnd();
322 return lines.join("\n");