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",RbSettings::TtsPath
).toString();
33 if(exepath
== "" ) exepath
= findExecutable("festival");
34 insertSetting(eSERVERPATH
,new EncTtsSetting(this,EncTtsSetting::eSTRING
,"Path to Festival server:",exepath
,EncTtsSetting::eBROWSEBTN
));
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
));
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
);
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());
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
)
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";
101 qDebug() << "Festival failed to start";
104 void TTSFestival::ensureServerRunning(QString path
)
106 if(serverProcess
.state() != QProcess::Running
)
112 bool TTSFestival::start(QString
* 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()));
123 bool TTSFestival::stop()
125 serverProcess
.terminate();
126 serverProcess
.kill();
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
);
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");
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();
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);
175 QStringList
TTSFestival::getVoiceList(QString path
)
178 return QStringList();
180 if(voices
.size() > 0)
182 qDebug() << "Using voice cache";
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(' ');
195 if (voices
.size() == 1 && voices
[0].size() == 0)
197 if (voices
.size() > 0)
198 qDebug() << "Voices: " << voices
;
200 qDebug() << "No voices.";
205 QString
TTSFestival::getVoiceInfo(QString voice
,QString path
)
210 if(!getVoiceList().contains(voice
))
213 if(voiceDescriptions
.contains(voice
))
214 return voiceDescriptions
[voice
];
216 QString response
= queryServer(QString("(voice.description '%1)").arg(voice
), 3000,path
);
220 voiceDescriptions
[voice
]=tr("No description available");
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
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(' ');
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
)
258 // this operation could take some time
261 ensureServerRunning(path
);
263 qDebug() << "queryServer with " << query
;
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.
276 QCoreApplication::processEvents(QEventLoop::AllEvents
, 50);
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
!= "")
294 socket
.disconnectFromHost();
296 if(timeout
> 0 && QDateTime::currentDateTime() >= endTime
)
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")
312 QStringList lines
= response
.split('\n');
319 qDebug() << "Response too short: " << response
;
322 return lines
.join("\n");