1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
9 * Copyright (C) 2007 by Dominik Wenger
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"
22 #include "rbsettings.h"
24 TTSFestival::~TTSFestival()
29 void TTSFestival::generateSettings()
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
));
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
));
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
);
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());
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
)
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";
112 qDebug() << "Festival failed to start";
115 void TTSFestival::ensureServerRunning(QString path
)
117 if(serverProcess
.state() != QProcess::Running
)
123 bool TTSFestival::start(QString
* 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()));
134 bool TTSFestival::stop()
136 serverProcess
.terminate();
137 serverProcess
.kill();
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
);
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");
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();
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);
192 QStringList
TTSFestival::getVoiceList(QString path
)
195 return QStringList();
197 if(voices
.size() > 0)
199 qDebug() << "Using voice cache";
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(' ');
212 if (voices
.size() == 1 && voices
[0].size() == 0)
214 if (voices
.size() > 0)
215 qDebug() << "Voices: " << voices
;
217 qDebug() << "No voices.";
222 QString
TTSFestival::getVoiceInfo(QString voice
,QString path
)
227 if(!getVoiceList().contains(voice
))
230 if(voiceDescriptions
.contains(voice
))
231 return voiceDescriptions
[voice
];
233 QString response
= queryServer(QString("(voice.description '%1)").arg(voice
),
238 voiceDescriptions
[voice
]=tr("No description available");
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
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(' ');
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
)
279 // this operation could take some time
282 ensureServerRunning(path
);
284 qDebug() << "queryServer with " << query
;
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.
297 QCoreApplication::processEvents(QEventLoop::AllEvents
, 50);
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
!= "")
315 socket
.disconnectFromHost();
317 if(timeout
> 0 && QDateTime::currentDateTime() >= endTime
)
322 /* make sure we wait a little as we don't want to flood the server
324 QDateTime tmpEndTime
= QDateTime::currentDateTime().addMSecs(500);
325 while(QDateTime::currentDateTime() < tmpEndTime
)
326 QCoreApplication::processEvents(QEventLoop::AllEvents
);
328 if(response
== "nil")
334 QStringList lines
= response
.split('\n');
341 qDebug() << "Response too short: " << response
;
344 return lines
.join("\n");