diff options
author | Dominik Wenger <domonoky@googlemail.com> | 2009-10-13 19:54:27 +0000 |
---|---|---|
committer | Dominik Wenger <domonoky@googlemail.com> | 2009-10-13 19:54:27 +0000 |
commit | 3215c09462fe90c35dc6d9e1979e970280b052b1 (patch) | |
tree | 1fc7be334470d6d21b746a40bc7dbe440a722d35 | |
parent | 31c18116de2105deed8aa8dab84bf08a930f2112 (diff) | |
download | rockbox-3215c09462fe90c35dc6d9e1979e970280b052b1.tar.gz rockbox-3215c09462fe90c35dc6d9e1979e970280b052b1.zip |
rbutil: split tts.cpp/h into individual files.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@23158 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r-- | rbutil/rbutilqt/base/talkgenerator.h | 2 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/tts.cpp | 666 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/tts.h | 180 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttsbase.cpp | 92 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttsbase.h | 74 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttsexes.cpp | 94 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttsexes.h | 55 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttsfestival.cpp | 325 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttsfestival.h | 67 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttssapi.cpp | 213 | ||||
-rw-r--r-- | rbutil/rbutilqt/base/ttssapi.h | 72 | ||||
-rw-r--r-- | rbutil/rbutilqt/configure.cpp | 2 | ||||
-rw-r--r-- | rbutil/rbutilqt/rbutilqt.pro | 10 |
13 files changed, 1002 insertions, 850 deletions
diff --git a/rbutil/rbutilqt/base/talkgenerator.h b/rbutil/rbutilqt/base/talkgenerator.h index eb08df8d51..b139c1879b 100644 --- a/rbutil/rbutilqt/base/talkgenerator.h +++ b/rbutil/rbutilqt/base/talkgenerator.h | |||
@@ -27,7 +27,7 @@ | |||
27 | #include "progressloggerinterface.h" | 27 | #include "progressloggerinterface.h" |
28 | 28 | ||
29 | #include "encoders.h" | 29 | #include "encoders.h" |
30 | #include "tts.h" | 30 | #include "ttsbase.h" |
31 | 31 | ||
32 | //! \brief Talk generator, generates .wav and .talk files out of a list. | 32 | //! \brief Talk generator, generates .wav and .talk files out of a list. |
33 | class TalkGenerator :public QObject | 33 | class TalkGenerator :public QObject |
diff --git a/rbutil/rbutilqt/base/tts.cpp b/rbutil/rbutilqt/base/tts.cpp deleted file mode 100644 index 852edc33d0..0000000000 --- a/rbutil/rbutilqt/base/tts.cpp +++ /dev/null | |||
@@ -1,666 +0,0 @@ | |||
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 "tts.h" | ||
21 | #include "utils.h" | ||
22 | #include "rbsettings.h" | ||
23 | /********************************************************************* | ||
24 | * TTS Base | ||
25 | **********************************************************************/ | ||
26 | QMap<QString,QString> TTSBase::ttsList; | ||
27 | |||
28 | TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent) | ||
29 | { | ||
30 | |||
31 | } | ||
32 | |||
33 | // static functions | ||
34 | void TTSBase::initTTSList() | ||
35 | { | ||
36 | ttsList["espeak"] = "Espeak TTS Engine"; | ||
37 | ttsList["flite"] = "Flite TTS Engine"; | ||
38 | ttsList["swift"] = "Swift TTS Engine"; | ||
39 | #if defined(Q_OS_WIN) | ||
40 | ttsList["sapi"] = "Sapi TTS Engine"; | ||
41 | #endif | ||
42 | #if defined(Q_OS_LINUX) | ||
43 | ttsList["festival"] = "Festival TTS Engine"; | ||
44 | #endif | ||
45 | } | ||
46 | |||
47 | // function to get a specific encoder | ||
48 | TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName) | ||
49 | { | ||
50 | |||
51 | TTSBase* tts; | ||
52 | #if defined(Q_OS_WIN) | ||
53 | if(ttsName == "sapi") | ||
54 | { | ||
55 | tts = new TTSSapi(parent); | ||
56 | return tts; | ||
57 | } | ||
58 | else | ||
59 | #endif | ||
60 | #if defined(Q_OS_LINUX) | ||
61 | if (ttsName == "festival") | ||
62 | { | ||
63 | tts = new TTSFestival(parent); | ||
64 | return tts; | ||
65 | } | ||
66 | else | ||
67 | #endif | ||
68 | if (true) // fix for OS other than WIN or LINUX | ||
69 | { | ||
70 | tts = new TTSExes(ttsName,parent); | ||
71 | return tts; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | // get the list of encoders, nice names | ||
76 | QStringList TTSBase::getTTSList() | ||
77 | { | ||
78 | // init list if its empty | ||
79 | if(ttsList.count() == 0) | ||
80 | initTTSList(); | ||
81 | |||
82 | return ttsList.keys(); | ||
83 | } | ||
84 | |||
85 | // get nice name of a specific tts | ||
86 | QString TTSBase::getTTSName(QString tts) | ||
87 | { | ||
88 | if(ttsList.isEmpty()) | ||
89 | initTTSList(); | ||
90 | return ttsList.value(tts); | ||
91 | } | ||
92 | |||
93 | |||
94 | /********************************************************************* | ||
95 | * General TTS Exes | ||
96 | **********************************************************************/ | ||
97 | TTSExes::TTSExes(QString name,QObject* parent) : TTSBase(parent) | ||
98 | { | ||
99 | m_name = name; | ||
100 | |||
101 | m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; | ||
102 | m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; | ||
103 | m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; | ||
104 | |||
105 | } | ||
106 | |||
107 | void TTSExes::generateSettings() | ||
108 | { | ||
109 | QString exepath =RbSettings::subValue(m_name,RbSettings::TtsPath).toString(); | ||
110 | if(exepath == "") exepath = findExecutable(m_name); | ||
111 | |||
112 | insertSetting(eEXEPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
113 | tr("Path to TTS engine:"),exepath,EncTtsSetting::eBROWSEBTN)); | ||
114 | insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
115 | tr("TTS engine options:"),RbSettings::subValue(m_name,RbSettings::TtsOptions))); | ||
116 | } | ||
117 | |||
118 | void TTSExes::saveSettings() | ||
119 | { | ||
120 | RbSettings::setSubValue(m_name,RbSettings::TtsPath,getSetting(eEXEPATH)->current().toString()); | ||
121 | RbSettings::setSubValue(m_name,RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString()); | ||
122 | RbSettings::sync(); | ||
123 | } | ||
124 | |||
125 | bool TTSExes::start(QString *errStr) | ||
126 | { | ||
127 | m_TTSexec = RbSettings::subValue(m_name,RbSettings::TtsPath).toString(); | ||
128 | m_TTSOpts = RbSettings::subValue(m_name,RbSettings::TtsOptions).toString(); | ||
129 | |||
130 | m_TTSTemplate = m_TemplateMap.value(m_name); | ||
131 | |||
132 | QFileInfo tts(m_TTSexec); | ||
133 | if(tts.exists()) | ||
134 | { | ||
135 | return true; | ||
136 | } | ||
137 | else | ||
138 | { | ||
139 | *errStr = tr("TTS executable not found"); | ||
140 | return false; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) | ||
145 | { | ||
146 | (void) errStr; | ||
147 | QString execstring = m_TTSTemplate; | ||
148 | |||
149 | execstring.replace("%exe",m_TTSexec); | ||
150 | execstring.replace("%options",m_TTSOpts); | ||
151 | execstring.replace("%wavfile",wavfile); | ||
152 | execstring.replace("%text",text); | ||
153 | //qDebug() << "voicing" << execstring; | ||
154 | QProcess::execute(execstring); | ||
155 | return NoError; | ||
156 | |||
157 | } | ||
158 | |||
159 | bool TTSExes::configOk() | ||
160 | { | ||
161 | QString path = RbSettings::subValue(m_name,RbSettings::TtsPath).toString(); | ||
162 | |||
163 | if (QFileInfo(path).exists()) | ||
164 | return true; | ||
165 | |||
166 | return false; | ||
167 | } | ||
168 | |||
169 | /********************************************************************* | ||
170 | * TTS Sapi | ||
171 | **********************************************************************/ | ||
172 | TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) | ||
173 | { | ||
174 | m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\""; | ||
175 | defaultLanguage ="english"; | ||
176 | m_sapi4 =false; | ||
177 | } | ||
178 | |||
179 | void TTSSapi::generateSettings() | ||
180 | { | ||
181 | // language | ||
182 | QStringList languages = RbSettings::languages(); | ||
183 | languages.sort(); | ||
184 | EncTtsSetting* setting =new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, | ||
185 | tr("Language:"),RbSettings::subValue("sapi",RbSettings::TtsLanguage),languages); | ||
186 | connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceList())); | ||
187 | insertSetting(eLANGUAGE,setting); | ||
188 | // voice | ||
189 | setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, | ||
190 | tr("Voice:"),RbSettings::subValue("sapi",RbSettings::TtsVoice),getVoiceList(RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString()),EncTtsSetting::eREFRESHBTN); | ||
191 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList())); | ||
192 | insertSetting(eVOICE,setting); | ||
193 | //speed | ||
194 | insertSetting(eSPEED,new EncTtsSetting(this,EncTtsSetting::eINT, | ||
195 | tr("Speed:"),RbSettings::subValue("sapi",RbSettings::TtsSpeed),-10,10)); | ||
196 | // options | ||
197 | insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
198 | tr("Options:"),RbSettings::subValue("sapi",RbSettings::TtsOptions))); | ||
199 | |||
200 | } | ||
201 | |||
202 | void TTSSapi::saveSettings() | ||
203 | { | ||
204 | //save settings in user config | ||
205 | RbSettings::setSubValue("sapi",RbSettings::TtsLanguage,getSetting(eLANGUAGE)->current().toString()); | ||
206 | RbSettings::setSubValue("sapi",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString()); | ||
207 | RbSettings::setSubValue("sapi",RbSettings::TtsSpeed,getSetting(eSPEED)->current().toInt()); | ||
208 | RbSettings::setSubValue("sapi",RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString()); | ||
209 | |||
210 | RbSettings::sync(); | ||
211 | } | ||
212 | |||
213 | void TTSSapi::updateVoiceList() | ||
214 | { | ||
215 | qDebug() << "update voiceList"; | ||
216 | QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString()); | ||
217 | getSetting(eVOICE)->setList(voiceList); | ||
218 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); | ||
219 | else getSetting(eVOICE)->setCurrent(""); | ||
220 | } | ||
221 | |||
222 | bool TTSSapi::start(QString *errStr) | ||
223 | { | ||
224 | |||
225 | m_TTSOpts = RbSettings::subValue("sapi",RbSettings::TtsOptions).toString(); | ||
226 | m_TTSLanguage =RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString(); | ||
227 | m_TTSVoice=RbSettings::subValue("sapi",RbSettings::TtsVoice).toString(); | ||
228 | m_TTSSpeed=RbSettings::subValue("sapi",RbSettings::TtsSpeed).toString(); | ||
229 | m_sapi4 = RbSettings::subValue("sapi",RbSettings::TtsUseSapi4).toBool(); | ||
230 | |||
231 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
232 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | ||
233 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | ||
234 | |||
235 | QFileInfo tts(m_TTSexec); | ||
236 | if(!tts.exists()) | ||
237 | { | ||
238 | *errStr = tr("Could not copy the Sapi-script"); | ||
239 | return false; | ||
240 | } | ||
241 | // create the voice process | ||
242 | QString execstring = m_TTSTemplate; | ||
243 | execstring.replace("%exe",m_TTSexec); | ||
244 | execstring.replace("%options",m_TTSOpts); | ||
245 | execstring.replace("%lang",m_TTSLanguage); | ||
246 | execstring.replace("%voice",m_TTSVoice); | ||
247 | execstring.replace("%speed",m_TTSSpeed); | ||
248 | |||
249 | if(m_sapi4) | ||
250 | execstring.append(" /sapi4 "); | ||
251 | |||
252 | qDebug() << "init" << execstring; | ||
253 | voicescript = new QProcess(NULL); | ||
254 | //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error())); | ||
255 | |||
256 | voicescript->start(execstring); | ||
257 | if(!voicescript->waitForStarted()) | ||
258 | { | ||
259 | *errStr = tr("Could not start the Sapi-script"); | ||
260 | return false; | ||
261 | } | ||
262 | |||
263 | if(!voicescript->waitForReadyRead(300)) | ||
264 | { | ||
265 | *errStr = voicescript->readAllStandardError(); | ||
266 | if(*errStr != "") | ||
267 | return false; | ||
268 | } | ||
269 | |||
270 | voicestream = new QTextStream(voicescript); | ||
271 | voicestream->setCodec("UTF16-LE"); | ||
272 | |||
273 | return true; | ||
274 | } | ||
275 | |||
276 | |||
277 | QStringList TTSSapi::getVoiceList(QString language) | ||
278 | { | ||
279 | QStringList result; | ||
280 | |||
281 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | ||
282 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | ||
283 | |||
284 | QFileInfo tts(m_TTSexec); | ||
285 | if(!tts.exists()) | ||
286 | return result; | ||
287 | |||
288 | // create the voice process | ||
289 | QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices"; | ||
290 | execstring.replace("%exe",m_TTSexec); | ||
291 | execstring.replace("%lang",language); | ||
292 | |||
293 | if(RbSettings::value(RbSettings::TtsUseSapi4).toBool()) | ||
294 | execstring.append(" /sapi4 "); | ||
295 | |||
296 | qDebug() << "init" << execstring; | ||
297 | voicescript = new QProcess(NULL); | ||
298 | voicescript->start(execstring); | ||
299 | qDebug() << "wait for started"; | ||
300 | if(!voicescript->waitForStarted()) | ||
301 | return result; | ||
302 | voicescript->closeWriteChannel(); | ||
303 | voicescript->waitForReadyRead(); | ||
304 | |||
305 | QString dataRaw = voicescript->readAllStandardError().data(); | ||
306 | result = dataRaw.split(",",QString::SkipEmptyParts); | ||
307 | if(result.size() > 0) | ||
308 | { | ||
309 | result.sort(); | ||
310 | result.removeFirst(); | ||
311 | for(int i = 0; i< result.size();i++) | ||
312 | { | ||
313 | result[i] = result.at(i).simplified(); | ||
314 | } | ||
315 | } | ||
316 | |||
317 | delete voicescript; | ||
318 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner | ||
319 | |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser | ||
320 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup | ||
321 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); | ||
322 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
323 | return result; | ||
324 | } | ||
325 | |||
326 | |||
327 | |||
328 | TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) | ||
329 | { | ||
330 | (void) errStr; | ||
331 | QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; | ||
332 | qDebug() << "voicing" << query; | ||
333 | *voicestream << query; | ||
334 | *voicestream << "SYNC\tbla\r\n"; | ||
335 | voicestream->flush(); | ||
336 | voicescript->waitForReadyRead(); | ||
337 | return NoError; | ||
338 | } | ||
339 | |||
340 | bool TTSSapi::stop() | ||
341 | { | ||
342 | |||
343 | *voicestream << "QUIT\r\n"; | ||
344 | voicestream->flush(); | ||
345 | voicescript->waitForFinished(); | ||
346 | delete voicestream; | ||
347 | delete voicescript; | ||
348 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner | ||
349 | |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser | ||
350 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup | ||
351 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); | ||
352 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
353 | return true; | ||
354 | } | ||
355 | |||
356 | bool TTSSapi::configOk() | ||
357 | { | ||
358 | if(RbSettings::subValue("sapi",RbSettings::TtsVoice).toString().isEmpty()) | ||
359 | return false; | ||
360 | return true; | ||
361 | } | ||
362 | /********************************************************************** | ||
363 | * TSSFestival - client-server wrapper | ||
364 | **********************************************************************/ | ||
365 | TTSFestival::~TTSFestival() | ||
366 | { | ||
367 | stop(); | ||
368 | } | ||
369 | |||
370 | void TTSFestival::generateSettings() | ||
371 | { | ||
372 | // server path | ||
373 | QString exepath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString(); | ||
374 | if(exepath == "" ) exepath = findExecutable("festival"); | ||
375 | insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN)); | ||
376 | |||
377 | // client path | ||
378 | QString clientpath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString(); | ||
379 | if(clientpath == "" ) clientpath = findExecutable("festival_client"); | ||
380 | insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
381 | tr("Path to Festival client:"),clientpath,EncTtsSetting::eBROWSEBTN)); | ||
382 | |||
383 | // voice | ||
384 | EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, | ||
385 | tr("Voice:"),RbSettings::subValue("festival",RbSettings::TtsVoice),getVoiceList(exepath),EncTtsSetting::eREFRESHBTN); | ||
386 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList())); | ||
387 | connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription())); | ||
388 | insertSetting(eVOICE,setting); | ||
389 | |||
390 | //voice description | ||
391 | setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING, | ||
392 | tr("Voice description:"),"",EncTtsSetting::eREFRESHBTN); | ||
393 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription())); | ||
394 | insertSetting(eVOICEDESC,setting); | ||
395 | } | ||
396 | |||
397 | void TTSFestival::saveSettings() | ||
398 | { | ||
399 | //save settings in user config | ||
400 | RbSettings::setSubValue("festival-server",RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString()); | ||
401 | RbSettings::setSubValue("festival-client",RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString()); | ||
402 | RbSettings::setSubValue("festival",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString()); | ||
403 | |||
404 | RbSettings::sync(); | ||
405 | } | ||
406 | |||
407 | void TTSFestival::updateVoiceDescription() | ||
408 | { | ||
409 | // get voice Info with current voice and path | ||
410 | QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),getSetting(eSERVERPATH)->current().toString()); | ||
411 | getSetting(eVOICEDESC)->setCurrent(info); | ||
412 | } | ||
413 | |||
414 | void TTSFestival::clearVoiceDescription() | ||
415 | { | ||
416 | getSetting(eVOICEDESC)->setCurrent(""); | ||
417 | } | ||
418 | |||
419 | void TTSFestival::updateVoiceList() | ||
420 | { | ||
421 | QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString()); | ||
422 | getSetting(eVOICE)->setList(voiceList); | ||
423 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); | ||
424 | else getSetting(eVOICE)->setCurrent(""); | ||
425 | } | ||
426 | |||
427 | void TTSFestival::startServer(QString path) | ||
428 | { | ||
429 | if(!configOk()) | ||
430 | return; | ||
431 | |||
432 | if(path == "") | ||
433 | path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString(); | ||
434 | |||
435 | serverProcess.start(QString("%1 --server").arg(path)); | ||
436 | serverProcess.waitForStarted(); | ||
437 | |||
438 | queryServer("(getpid)",300,path); | ||
439 | if(serverProcess.state() == QProcess::Running) | ||
440 | qDebug() << "Festival is up and running"; | ||
441 | else | ||
442 | qDebug() << "Festival failed to start"; | ||
443 | } | ||
444 | |||
445 | void TTSFestival::ensureServerRunning(QString path) | ||
446 | { | ||
447 | if(serverProcess.state() != QProcess::Running) | ||
448 | { | ||
449 | startServer(path); | ||
450 | } | ||
451 | } | ||
452 | |||
453 | bool TTSFestival::start(QString* errStr) | ||
454 | { | ||
455 | (void) errStr; | ||
456 | ensureServerRunning(); | ||
457 | if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty()) | ||
458 | queryServer(QString("(voice.select '%1)") | ||
459 | .arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString())); | ||
460 | |||
461 | return true; | ||
462 | } | ||
463 | |||
464 | bool TTSFestival::stop() | ||
465 | { | ||
466 | serverProcess.terminate(); | ||
467 | serverProcess.kill(); | ||
468 | |||
469 | return true; | ||
470 | } | ||
471 | |||
472 | TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) | ||
473 | { | ||
474 | qDebug() << text << "->" << wavfile; | ||
475 | |||
476 | QString path = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString(); | ||
477 | QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path).arg(wavfile); | ||
478 | qDebug() << cmd; | ||
479 | |||
480 | QProcess clientProcess; | ||
481 | clientProcess.start(cmd); | ||
482 | clientProcess.write(QString("%1.\n").arg(text).toAscii()); | ||
483 | clientProcess.waitForBytesWritten(); | ||
484 | clientProcess.closeWriteChannel(); | ||
485 | clientProcess.waitForReadyRead(); | ||
486 | QString response = clientProcess.readAll(); | ||
487 | response = response.trimmed(); | ||
488 | if(!response.contains("Utterance")) | ||
489 | { | ||
490 | qDebug() << "Could not voice string: " << response; | ||
491 | *errStr = tr("engine could not voice string"); | ||
492 | return Warning; | ||
493 | /* do not stop the voicing process because of a single string | ||
494 | TODO: needs proper settings */ | ||
495 | } | ||
496 | clientProcess.closeReadChannel(QProcess::StandardError); | ||
497 | clientProcess.closeReadChannel(QProcess::StandardOutput); | ||
498 | clientProcess.terminate(); | ||
499 | clientProcess.kill(); | ||
500 | |||
501 | return NoError; | ||
502 | } | ||
503 | |||
504 | bool TTSFestival::configOk() | ||
505 | { | ||
506 | QString serverPath = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString(); | ||
507 | QString clientPath = RbSettings::subValue("festival-client",RbSettings::TtsPath).toString(); | ||
508 | |||
509 | bool ret = QFileInfo(serverPath).isExecutable() && | ||
510 | QFileInfo(clientPath).isExecutable(); | ||
511 | if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0 && voices.size() > 0) | ||
512 | ret = ret && (voices.indexOf(RbSettings::subValue("festival",RbSettings::TtsVoice).toString()) != -1); | ||
513 | return ret; | ||
514 | } | ||
515 | |||
516 | QStringList TTSFestival::getVoiceList(QString path) | ||
517 | { | ||
518 | if(!configOk()) | ||
519 | return QStringList(); | ||
520 | |||
521 | if(voices.size() > 0) | ||
522 | { | ||
523 | qDebug() << "Using voice cache"; | ||
524 | return voices; | ||
525 | } | ||
526 | |||
527 | QString response = queryServer("(voice.list)",3000,path); | ||
528 | |||
529 | // get the 2nd line. It should be (<voice_name>, <voice_name>) | ||
530 | response = response.mid(response.indexOf('\n') + 1, -1); | ||
531 | response = response.left(response.indexOf('\n')).trimmed(); | ||
532 | |||
533 | voices = response.mid(1, response.size()-2).split(' '); | ||
534 | |||
535 | voices.sort(); | ||
536 | if (voices.size() == 1 && voices[0].size() == 0) | ||
537 | voices.removeAt(0); | ||
538 | if (voices.size() > 0) | ||
539 | qDebug() << "Voices: " << voices; | ||
540 | else | ||
541 | qDebug() << "No voices."; | ||
542 | |||
543 | return voices; | ||
544 | } | ||
545 | |||
546 | QString TTSFestival::getVoiceInfo(QString voice,QString path) | ||
547 | { | ||
548 | if(!configOk()) | ||
549 | return ""; | ||
550 | |||
551 | if(!getVoiceList().contains(voice)) | ||
552 | return ""; | ||
553 | |||
554 | if(voiceDescriptions.contains(voice)) | ||
555 | return voiceDescriptions[voice]; | ||
556 | |||
557 | QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path); | ||
558 | |||
559 | if (response == "") | ||
560 | { | ||
561 | voiceDescriptions[voice]=tr("No description available"); | ||
562 | } | ||
563 | else | ||
564 | { | ||
565 | response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard)); | ||
566 | qDebug() << "voiceInfo w/o descr: " << response; | ||
567 | response = response.remove(')'); | ||
568 | QStringList responseLines = response.split('(', QString::SkipEmptyParts); | ||
569 | responseLines.removeAt(0); // the voice name itself | ||
570 | |||
571 | QString description; | ||
572 | foreach(QString line, responseLines) | ||
573 | { | ||
574 | line = line.remove('('); | ||
575 | line = line.simplified(); | ||
576 | |||
577 | line[0] = line[0].toUpper(); // capitalize the key | ||
578 | |||
579 | int firstSpace = line.indexOf(' '); | ||
580 | if (firstSpace > 0) | ||
581 | { | ||
582 | line = line.insert(firstSpace, ':'); // add a colon between the key and the value | ||
583 | line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value | ||
584 | } | ||
585 | |||
586 | description += line + "\n"; | ||
587 | } | ||
588 | voiceDescriptions[voice] = description.trimmed(); | ||
589 | } | ||
590 | |||
591 | return voiceDescriptions[voice]; | ||
592 | } | ||
593 | |||
594 | QString TTSFestival::queryServer(QString query, int timeout,QString path) | ||
595 | { | ||
596 | if(!configOk()) | ||
597 | return ""; | ||
598 | |||
599 | // this operation could take some time | ||
600 | emit busy(); | ||
601 | |||
602 | ensureServerRunning(path); | ||
603 | |||
604 | qDebug() << "queryServer with " << query; | ||
605 | QString response; | ||
606 | |||
607 | QDateTime endTime; | ||
608 | if(timeout > 0) | ||
609 | endTime = QDateTime::currentDateTime().addMSecs(timeout); | ||
610 | |||
611 | /* Festival is *extremely* unreliable. Although at this | ||
612 | * point we are sure that SIOD is accepting commands, | ||
613 | * we might end up with an empty response. Hence, the loop. | ||
614 | */ | ||
615 | while(true) | ||
616 | { | ||
617 | QCoreApplication::processEvents(QEventLoop::AllEvents, 50); | ||
618 | QTcpSocket socket; | ||
619 | |||
620 | socket.connectToHost("localhost", 1314); | ||
621 | socket.waitForConnected(); | ||
622 | |||
623 | if(socket.state() == QAbstractSocket::ConnectedState) | ||
624 | { | ||
625 | socket.write(QString("%1\n").arg(query).toAscii()); | ||
626 | socket.waitForBytesWritten(); | ||
627 | socket.waitForReadyRead(); | ||
628 | |||
629 | response = socket.readAll().trimmed(); | ||
630 | |||
631 | if (response != "LP" && response != "") | ||
632 | break; | ||
633 | } | ||
634 | socket.abort(); | ||
635 | socket.disconnectFromHost(); | ||
636 | |||
637 | if(timeout > 0 && QDateTime::currentDateTime() >= endTime) | ||
638 | { | ||
639 | emit busyEnd(); | ||
640 | return ""; | ||
641 | } | ||
642 | /* make sure we wait a little as we don't want to flood the server with requests */ | ||
643 | QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500); | ||
644 | while(QDateTime::currentDateTime() < tmpEndTime) | ||
645 | QCoreApplication::processEvents(QEventLoop::AllEvents); | ||
646 | } | ||
647 | if(response == "nil") | ||
648 | { | ||
649 | emit busyEnd(); | ||
650 | return ""; | ||
651 | } | ||
652 | |||
653 | QStringList lines = response.split('\n'); | ||
654 | if(lines.size() > 2) | ||
655 | { | ||
656 | lines.removeFirst(); | ||
657 | lines.removeLast(); | ||
658 | } | ||
659 | else | ||
660 | qDebug() << "Response too short: " << response; | ||
661 | |||
662 | emit busyEnd(); | ||
663 | return lines.join("\n"); | ||
664 | |||
665 | } | ||
666 | |||
diff --git a/rbutil/rbutilqt/base/tts.h b/rbutil/rbutilqt/base/tts.h deleted file mode 100644 index f665ed2865..0000000000 --- a/rbutil/rbutilqt/base/tts.h +++ /dev/null | |||
@@ -1,180 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * $Id$ | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | |||
23 | #ifndef TTS_H | ||
24 | #define TTS_H | ||
25 | |||
26 | #include <QtCore> | ||
27 | #include <QProcess> | ||
28 | #include <QDateTime> | ||
29 | #include <QRegExp> | ||
30 | #include <QTcpSocket> | ||
31 | |||
32 | #include "encttssettings.h" | ||
33 | |||
34 | enum TTSStatus{ FatalError, NoError, Warning }; | ||
35 | |||
36 | class TTSBase : public EncTtsSettingInterface | ||
37 | { | ||
38 | Q_OBJECT | ||
39 | public: | ||
40 | TTSBase(QObject *parent); | ||
41 | //! Child class should generate a clip | ||
42 | virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) =0; | ||
43 | //! Child class should do startup | ||
44 | virtual bool start(QString *errStr) =0; | ||
45 | //! child class should stop | ||
46 | virtual bool stop() =0; | ||
47 | |||
48 | // configuration | ||
49 | //! Child class should return true, when configuration is good | ||
50 | virtual bool configOk()=0; | ||
51 | //! Child class should generate and insertSetting(..) its settings | ||
52 | virtual void generateSettings() = 0; | ||
53 | //! Chlid class should commit the Settings to permanent storage | ||
54 | virtual void saveSettings() = 0; | ||
55 | |||
56 | // static functions | ||
57 | static TTSBase* getTTS(QObject* parent,QString ttsname); | ||
58 | static QStringList getTTSList(); | ||
59 | static QString getTTSName(QString tts); | ||
60 | |||
61 | private: | ||
62 | //inits the tts List | ||
63 | static void initTTSList(); | ||
64 | |||
65 | protected: | ||
66 | static QMap<QString,QString> ttsList; | ||
67 | }; | ||
68 | |||
69 | class TTSSapi : public TTSBase | ||
70 | { | ||
71 | //! Enum to identify the settings | ||
72 | enum ESettings | ||
73 | { | ||
74 | eLANGUAGE, | ||
75 | eVOICE, | ||
76 | eSPEED, | ||
77 | eOPTIONS | ||
78 | }; | ||
79 | |||
80 | Q_OBJECT | ||
81 | public: | ||
82 | TTSSapi(QObject* parent=NULL); | ||
83 | |||
84 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
85 | bool start(QString *errStr); | ||
86 | bool stop(); | ||
87 | |||
88 | // for settings | ||
89 | bool configOk(); | ||
90 | void generateSettings(); | ||
91 | void saveSettings(); | ||
92 | |||
93 | private slots: | ||
94 | void updateVoiceList(); | ||
95 | |||
96 | private: | ||
97 | QStringList getVoiceList(QString language); | ||
98 | |||
99 | QProcess* voicescript; | ||
100 | QTextStream* voicestream; | ||
101 | QString defaultLanguage; | ||
102 | |||
103 | QString m_TTSexec; | ||
104 | QString m_TTSOpts; | ||
105 | QString m_TTSTemplate; | ||
106 | QString m_TTSLanguage; | ||
107 | QString m_TTSVoice; | ||
108 | QString m_TTSSpeed; | ||
109 | bool m_sapi4; | ||
110 | }; | ||
111 | |||
112 | |||
113 | class TTSExes : public TTSBase | ||
114 | { | ||
115 | enum ESettings | ||
116 | { | ||
117 | eEXEPATH, | ||
118 | eOPTIONS | ||
119 | }; | ||
120 | |||
121 | Q_OBJECT | ||
122 | public: | ||
123 | TTSExes(QString name,QObject* parent=NULL); | ||
124 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
125 | bool start(QString *errStr); | ||
126 | bool stop() {return true;} | ||
127 | |||
128 | // for settings | ||
129 | void generateSettings(); | ||
130 | void saveSettings(); | ||
131 | bool configOk(); | ||
132 | |||
133 | private: | ||
134 | QString m_name; | ||
135 | QString m_TTSexec; | ||
136 | QString m_TTSOpts; | ||
137 | QString m_TTSTemplate; | ||
138 | QMap<QString,QString> m_TemplateMap; | ||
139 | }; | ||
140 | |||
141 | class TTSFestival : public TTSBase | ||
142 | { | ||
143 | enum ESettings | ||
144 | { | ||
145 | eSERVERPATH, | ||
146 | eCLIENTPATH, | ||
147 | eVOICE, | ||
148 | eVOICEDESC | ||
149 | }; | ||
150 | |||
151 | Q_OBJECT | ||
152 | public: | ||
153 | TTSFestival(QObject* parent=NULL) :TTSBase(parent) {} | ||
154 | ~TTSFestival(); | ||
155 | bool start(QString *errStr); | ||
156 | bool stop(); | ||
157 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
158 | |||
159 | // for settings | ||
160 | bool configOk(); | ||
161 | void generateSettings(); | ||
162 | void saveSettings(); | ||
163 | |||
164 | private slots: | ||
165 | void updateVoiceList(); | ||
166 | void updateVoiceDescription(); | ||
167 | void clearVoiceDescription(); | ||
168 | private: | ||
169 | QStringList getVoiceList(QString path =""); | ||
170 | QString getVoiceInfo(QString voice,QString path =""); | ||
171 | |||
172 | inline void startServer(QString path=""); | ||
173 | inline void ensureServerRunning(QString path=""); | ||
174 | QString queryServer(QString query, int timeout = -1,QString path=""); | ||
175 | QProcess serverProcess; | ||
176 | QStringList voices; | ||
177 | QMap<QString, QString> voiceDescriptions; | ||
178 | }; | ||
179 | |||
180 | #endif | ||
diff --git a/rbutil/rbutilqt/base/ttsbase.cpp b/rbutil/rbutilqt/base/ttsbase.cpp new file mode 100644 index 0000000000..1f4060fc72 --- /dev/null +++ b/rbutil/rbutilqt/base/ttsbase.cpp | |||
@@ -0,0 +1,92 @@ | |||
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 "ttsbase.h" | ||
21 | |||
22 | #include "ttsfestival.h" | ||
23 | #include "ttssapi.h" | ||
24 | #include "ttsexes.h" | ||
25 | |||
26 | // list of tts names and identifiers | ||
27 | QMap<QString,QString> TTSBase::ttsList; | ||
28 | |||
29 | TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent) | ||
30 | { | ||
31 | |||
32 | } | ||
33 | |||
34 | // static functions | ||
35 | void TTSBase::initTTSList() | ||
36 | { | ||
37 | ttsList["espeak"] = "Espeak TTS Engine"; | ||
38 | ttsList["flite"] = "Flite TTS Engine"; | ||
39 | ttsList["swift"] = "Swift TTS Engine"; | ||
40 | #if defined(Q_OS_WIN) | ||
41 | ttsList["sapi"] = "Sapi TTS Engine"; | ||
42 | #endif | ||
43 | #if defined(Q_OS_LINUX) | ||
44 | ttsList["festival"] = "Festival TTS Engine"; | ||
45 | #endif | ||
46 | } | ||
47 | |||
48 | // function to get a specific encoder | ||
49 | TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName) | ||
50 | { | ||
51 | |||
52 | TTSBase* tts; | ||
53 | #if defined(Q_OS_WIN) | ||
54 | if(ttsName == "sapi") | ||
55 | { | ||
56 | tts = new TTSSapi(parent); | ||
57 | return tts; | ||
58 | } | ||
59 | else | ||
60 | #endif | ||
61 | #if defined(Q_OS_LINUX) | ||
62 | if (ttsName == "festival") | ||
63 | { | ||
64 | tts = new TTSFestival(parent); | ||
65 | return tts; | ||
66 | } | ||
67 | else | ||
68 | #endif | ||
69 | if (true) // fix for OS other than WIN or LINUX | ||
70 | { | ||
71 | tts = new TTSExes(ttsName,parent); | ||
72 | return tts; | ||
73 | } | ||
74 | } | ||
75 | |||
76 | // get the list of encoders, nice names | ||
77 | QStringList TTSBase::getTTSList() | ||
78 | { | ||
79 | // init list if its empty | ||
80 | if(ttsList.count() == 0) | ||
81 | initTTSList(); | ||
82 | |||
83 | return ttsList.keys(); | ||
84 | } | ||
85 | |||
86 | // get nice name of a specific tts | ||
87 | QString TTSBase::getTTSName(QString tts) | ||
88 | { | ||
89 | if(ttsList.isEmpty()) | ||
90 | initTTSList(); | ||
91 | return ttsList.value(tts); | ||
92 | } | ||
diff --git a/rbutil/rbutilqt/base/ttsbase.h b/rbutil/rbutilqt/base/ttsbase.h new file mode 100644 index 0000000000..7c5932401f --- /dev/null +++ b/rbutil/rbutilqt/base/ttsbase.h | |||
@@ -0,0 +1,74 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * $Id$ | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | |||
23 | #ifndef TTSBASE_H | ||
24 | #define TTSBASE_H | ||
25 | |||
26 | #include <QtCore> | ||
27 | #include <QProcess> | ||
28 | #include <QDateTime> | ||
29 | #include <QRegExp> | ||
30 | #include <QTcpSocket> | ||
31 | |||
32 | #include "encttssettings.h" | ||
33 | |||
34 | enum TTSStatus{ FatalError, NoError, Warning }; | ||
35 | |||
36 | class TTSBase : public EncTtsSettingInterface | ||
37 | { | ||
38 | Q_OBJECT | ||
39 | public: | ||
40 | TTSBase(QObject *parent); | ||
41 | //! Child class should generate a clip | ||
42 | virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) =0; | ||
43 | //! Child class should do startup | ||
44 | virtual bool start(QString *errStr) =0; | ||
45 | //! child class should stop | ||
46 | virtual bool stop() =0; | ||
47 | |||
48 | // configuration | ||
49 | //! Child class should return true, when configuration is good | ||
50 | virtual bool configOk()=0; | ||
51 | //! Child class should generate and insertSetting(..) its settings | ||
52 | virtual void generateSettings() = 0; | ||
53 | //! Chlid class should commit the Settings to permanent storage | ||
54 | virtual void saveSettings() = 0; | ||
55 | |||
56 | // static functions | ||
57 | static TTSBase* getTTS(QObject* parent,QString ttsname); | ||
58 | static QStringList getTTSList(); | ||
59 | static QString getTTSName(QString tts); | ||
60 | |||
61 | private: | ||
62 | //inits the tts List | ||
63 | static void initTTSList(); | ||
64 | |||
65 | protected: | ||
66 | static QMap<QString,QString> ttsList; | ||
67 | }; | ||
68 | |||
69 | |||
70 | |||
71 | |||
72 | |||
73 | |||
74 | #endif | ||
diff --git a/rbutil/rbutilqt/base/ttsexes.cpp b/rbutil/rbutilqt/base/ttsexes.cpp new file mode 100644 index 0000000000..d116290161 --- /dev/null +++ b/rbutil/rbutilqt/base/ttsexes.cpp | |||
@@ -0,0 +1,94 @@ | |||
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 "ttsexes.h" | ||
21 | #include "utils.h" | ||
22 | #include "rbsettings.h" | ||
23 | |||
24 | TTSExes::TTSExes(QString name,QObject* parent) : TTSBase(parent) | ||
25 | { | ||
26 | m_name = name; | ||
27 | |||
28 | m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; | ||
29 | m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; | ||
30 | m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; | ||
31 | |||
32 | } | ||
33 | |||
34 | void TTSExes::generateSettings() | ||
35 | { | ||
36 | QString exepath =RbSettings::subValue(m_name,RbSettings::TtsPath).toString(); | ||
37 | if(exepath == "") exepath = findExecutable(m_name); | ||
38 | |||
39 | insertSetting(eEXEPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
40 | tr("Path to TTS engine:"),exepath,EncTtsSetting::eBROWSEBTN)); | ||
41 | insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
42 | tr("TTS engine options:"),RbSettings::subValue(m_name,RbSettings::TtsOptions))); | ||
43 | } | ||
44 | |||
45 | void TTSExes::saveSettings() | ||
46 | { | ||
47 | RbSettings::setSubValue(m_name,RbSettings::TtsPath,getSetting(eEXEPATH)->current().toString()); | ||
48 | RbSettings::setSubValue(m_name,RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString()); | ||
49 | RbSettings::sync(); | ||
50 | } | ||
51 | |||
52 | bool TTSExes::start(QString *errStr) | ||
53 | { | ||
54 | m_TTSexec = RbSettings::subValue(m_name,RbSettings::TtsPath).toString(); | ||
55 | m_TTSOpts = RbSettings::subValue(m_name,RbSettings::TtsOptions).toString(); | ||
56 | |||
57 | m_TTSTemplate = m_TemplateMap.value(m_name); | ||
58 | |||
59 | QFileInfo tts(m_TTSexec); | ||
60 | if(tts.exists()) | ||
61 | { | ||
62 | return true; | ||
63 | } | ||
64 | else | ||
65 | { | ||
66 | *errStr = tr("TTS executable not found"); | ||
67 | return false; | ||
68 | } | ||
69 | } | ||
70 | |||
71 | TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) | ||
72 | { | ||
73 | (void) errStr; | ||
74 | QString execstring = m_TTSTemplate; | ||
75 | |||
76 | execstring.replace("%exe",m_TTSexec); | ||
77 | execstring.replace("%options",m_TTSOpts); | ||
78 | execstring.replace("%wavfile",wavfile); | ||
79 | execstring.replace("%text",text); | ||
80 | //qDebug() << "voicing" << execstring; | ||
81 | QProcess::execute(execstring); | ||
82 | return NoError; | ||
83 | |||
84 | } | ||
85 | |||
86 | bool TTSExes::configOk() | ||
87 | { | ||
88 | QString path = RbSettings::subValue(m_name,RbSettings::TtsPath).toString(); | ||
89 | |||
90 | if (QFileInfo(path).exists()) | ||
91 | return true; | ||
92 | |||
93 | return false; | ||
94 | } | ||
diff --git a/rbutil/rbutilqt/base/ttsexes.h b/rbutil/rbutilqt/base/ttsexes.h new file mode 100644 index 0000000000..ab966f5a7e --- /dev/null +++ b/rbutil/rbutilqt/base/ttsexes.h | |||
@@ -0,0 +1,55 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Dominik Wenger | ||
10 | * $Id$ | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #ifndef TTSEXES_H | ||
23 | #define TTSEXES_H | ||
24 | |||
25 | #include "ttsbase.h" | ||
26 | |||
27 | class TTSExes : public TTSBase | ||
28 | { | ||
29 | enum ESettings | ||
30 | { | ||
31 | eEXEPATH, | ||
32 | eOPTIONS | ||
33 | }; | ||
34 | |||
35 | Q_OBJECT | ||
36 | public: | ||
37 | TTSExes(QString name,QObject* parent=NULL); | ||
38 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
39 | bool start(QString *errStr); | ||
40 | bool stop() {return true;} | ||
41 | |||
42 | // for settings | ||
43 | void generateSettings(); | ||
44 | void saveSettings(); | ||
45 | bool configOk(); | ||
46 | |||
47 | private: | ||
48 | QString m_name; | ||
49 | QString m_TTSexec; | ||
50 | QString m_TTSOpts; | ||
51 | QString m_TTSTemplate; | ||
52 | QMap<QString,QString> m_TemplateMap; | ||
53 | }; | ||
54 | |||
55 | #endif | ||
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 | |||
24 | TTSFestival::~TTSFestival() | ||
25 | { | ||
26 | stop(); | ||
27 | } | ||
28 | |||
29 | void 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 | |||
56 | void 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 | |||
66 | void 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 | |||
73 | void TTSFestival::clearVoiceDescription() | ||
74 | { | ||
75 | getSetting(eVOICEDESC)->setCurrent(""); | ||
76 | } | ||
77 | |||
78 | void 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 | |||
86 | void 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 | |||
104 | void TTSFestival::ensureServerRunning(QString path) | ||
105 | { | ||
106 | if(serverProcess.state() != QProcess::Running) | ||
107 | { | ||
108 | startServer(path); | ||
109 | } | ||
110 | } | ||
111 | |||
112 | bool 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 | |||
123 | bool TTSFestival::stop() | ||
124 | { | ||
125 | serverProcess.terminate(); | ||
126 | serverProcess.kill(); | ||
127 | |||
128 | return true; | ||
129 | } | ||
130 | |||
131 | TTSStatus 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 | |||
163 | bool 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 | |||
175 | QStringList 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 | |||
205 | QString 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 | |||
253 | QString 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 | |||
diff --git a/rbutil/rbutilqt/base/ttsfestival.h b/rbutil/rbutilqt/base/ttsfestival.h new file mode 100644 index 0000000000..9d644c1857 --- /dev/null +++ b/rbutil/rbutilqt/base/ttsfestival.h | |||
@@ -0,0 +1,67 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Dominik Wenger | ||
10 | * $Id$ | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #ifndef TTSFESTIVAL_H | ||
23 | #define TTSFESTIVAL_H | ||
24 | |||
25 | #include "ttsbase.h" | ||
26 | |||
27 | class TTSFestival : public TTSBase | ||
28 | { | ||
29 | enum ESettings | ||
30 | { | ||
31 | eSERVERPATH, | ||
32 | eCLIENTPATH, | ||
33 | eVOICE, | ||
34 | eVOICEDESC | ||
35 | }; | ||
36 | |||
37 | Q_OBJECT | ||
38 | public: | ||
39 | TTSFestival(QObject* parent=NULL) :TTSBase(parent) {} | ||
40 | ~TTSFestival(); | ||
41 | bool start(QString *errStr); | ||
42 | bool stop(); | ||
43 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
44 | |||
45 | // for settings | ||
46 | bool configOk(); | ||
47 | void generateSettings(); | ||
48 | void saveSettings(); | ||
49 | |||
50 | private slots: | ||
51 | void updateVoiceList(); | ||
52 | void updateVoiceDescription(); | ||
53 | void clearVoiceDescription(); | ||
54 | private: | ||
55 | QStringList getVoiceList(QString path =""); | ||
56 | QString getVoiceInfo(QString voice,QString path =""); | ||
57 | |||
58 | inline void startServer(QString path=""); | ||
59 | inline void ensureServerRunning(QString path=""); | ||
60 | QString queryServer(QString query, int timeout = -1,QString path=""); | ||
61 | QProcess serverProcess; | ||
62 | QStringList voices; | ||
63 | QMap<QString, QString> voiceDescriptions; | ||
64 | }; | ||
65 | |||
66 | |||
67 | #endif | ||
diff --git a/rbutil/rbutilqt/base/ttssapi.cpp b/rbutil/rbutilqt/base/ttssapi.cpp new file mode 100644 index 0000000000..e356d613de --- /dev/null +++ b/rbutil/rbutilqt/base/ttssapi.cpp | |||
@@ -0,0 +1,213 @@ | |||
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 "ttssapi.h" | ||
21 | #include "utils.h" | ||
22 | #include "rbsettings.h" | ||
23 | |||
24 | TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) | ||
25 | { | ||
26 | m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\""; | ||
27 | defaultLanguage ="english"; | ||
28 | m_sapi4 =false; | ||
29 | } | ||
30 | |||
31 | void TTSSapi::generateSettings() | ||
32 | { | ||
33 | // language | ||
34 | QStringList languages = RbSettings::languages(); | ||
35 | languages.sort(); | ||
36 | EncTtsSetting* setting =new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, | ||
37 | tr("Language:"),RbSettings::subValue("sapi",RbSettings::TtsLanguage),languages); | ||
38 | connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceList())); | ||
39 | insertSetting(eLANGUAGE,setting); | ||
40 | // voice | ||
41 | setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST, | ||
42 | tr("Voice:"),RbSettings::subValue("sapi",RbSettings::TtsVoice),getVoiceList(RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString()),EncTtsSetting::eREFRESHBTN); | ||
43 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList())); | ||
44 | insertSetting(eVOICE,setting); | ||
45 | //speed | ||
46 | insertSetting(eSPEED,new EncTtsSetting(this,EncTtsSetting::eINT, | ||
47 | tr("Speed:"),RbSettings::subValue("sapi",RbSettings::TtsSpeed),-10,10)); | ||
48 | // options | ||
49 | insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
50 | tr("Options:"),RbSettings::subValue("sapi",RbSettings::TtsOptions))); | ||
51 | |||
52 | } | ||
53 | |||
54 | void TTSSapi::saveSettings() | ||
55 | { | ||
56 | //save settings in user config | ||
57 | RbSettings::setSubValue("sapi",RbSettings::TtsLanguage,getSetting(eLANGUAGE)->current().toString()); | ||
58 | RbSettings::setSubValue("sapi",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString()); | ||
59 | RbSettings::setSubValue("sapi",RbSettings::TtsSpeed,getSetting(eSPEED)->current().toInt()); | ||
60 | RbSettings::setSubValue("sapi",RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString()); | ||
61 | |||
62 | RbSettings::sync(); | ||
63 | } | ||
64 | |||
65 | void TTSSapi::updateVoiceList() | ||
66 | { | ||
67 | qDebug() << "update voiceList"; | ||
68 | QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString()); | ||
69 | getSetting(eVOICE)->setList(voiceList); | ||
70 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); | ||
71 | else getSetting(eVOICE)->setCurrent(""); | ||
72 | } | ||
73 | |||
74 | bool TTSSapi::start(QString *errStr) | ||
75 | { | ||
76 | |||
77 | m_TTSOpts = RbSettings::subValue("sapi",RbSettings::TtsOptions).toString(); | ||
78 | m_TTSLanguage =RbSettings::subValue("sapi",RbSettings::TtsLanguage).toString(); | ||
79 | m_TTSVoice=RbSettings::subValue("sapi",RbSettings::TtsVoice).toString(); | ||
80 | m_TTSSpeed=RbSettings::subValue("sapi",RbSettings::TtsSpeed).toString(); | ||
81 | m_sapi4 = RbSettings::subValue("sapi",RbSettings::TtsUseSapi4).toBool(); | ||
82 | |||
83 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
84 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | ||
85 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | ||
86 | |||
87 | QFileInfo tts(m_TTSexec); | ||
88 | if(!tts.exists()) | ||
89 | { | ||
90 | *errStr = tr("Could not copy the Sapi-script"); | ||
91 | return false; | ||
92 | } | ||
93 | // create the voice process | ||
94 | QString execstring = m_TTSTemplate; | ||
95 | execstring.replace("%exe",m_TTSexec); | ||
96 | execstring.replace("%options",m_TTSOpts); | ||
97 | execstring.replace("%lang",m_TTSLanguage); | ||
98 | execstring.replace("%voice",m_TTSVoice); | ||
99 | execstring.replace("%speed",m_TTSSpeed); | ||
100 | |||
101 | if(m_sapi4) | ||
102 | execstring.append(" /sapi4 "); | ||
103 | |||
104 | qDebug() << "init" << execstring; | ||
105 | voicescript = new QProcess(NULL); | ||
106 | //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error())); | ||
107 | |||
108 | voicescript->start(execstring); | ||
109 | if(!voicescript->waitForStarted()) | ||
110 | { | ||
111 | *errStr = tr("Could not start the Sapi-script"); | ||
112 | return false; | ||
113 | } | ||
114 | |||
115 | if(!voicescript->waitForReadyRead(300)) | ||
116 | { | ||
117 | *errStr = voicescript->readAllStandardError(); | ||
118 | if(*errStr != "") | ||
119 | return false; | ||
120 | } | ||
121 | |||
122 | voicestream = new QTextStream(voicescript); | ||
123 | voicestream->setCodec("UTF16-LE"); | ||
124 | |||
125 | return true; | ||
126 | } | ||
127 | |||
128 | |||
129 | QStringList TTSSapi::getVoiceList(QString language) | ||
130 | { | ||
131 | QStringList result; | ||
132 | |||
133 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | ||
134 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | ||
135 | |||
136 | QFileInfo tts(m_TTSexec); | ||
137 | if(!tts.exists()) | ||
138 | return result; | ||
139 | |||
140 | // create the voice process | ||
141 | QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices"; | ||
142 | execstring.replace("%exe",m_TTSexec); | ||
143 | execstring.replace("%lang",language); | ||
144 | |||
145 | if(RbSettings::value(RbSettings::TtsUseSapi4).toBool()) | ||
146 | execstring.append(" /sapi4 "); | ||
147 | |||
148 | qDebug() << "init" << execstring; | ||
149 | voicescript = new QProcess(NULL); | ||
150 | voicescript->start(execstring); | ||
151 | qDebug() << "wait for started"; | ||
152 | if(!voicescript->waitForStarted()) | ||
153 | return result; | ||
154 | voicescript->closeWriteChannel(); | ||
155 | voicescript->waitForReadyRead(); | ||
156 | |||
157 | QString dataRaw = voicescript->readAllStandardError().data(); | ||
158 | result = dataRaw.split(",",QString::SkipEmptyParts); | ||
159 | if(result.size() > 0) | ||
160 | { | ||
161 | result.sort(); | ||
162 | result.removeFirst(); | ||
163 | for(int i = 0; i< result.size();i++) | ||
164 | { | ||
165 | result[i] = result.at(i).simplified(); | ||
166 | } | ||
167 | } | ||
168 | |||
169 | delete voicescript; | ||
170 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner | ||
171 | |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser | ||
172 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup | ||
173 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); | ||
174 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
175 | return result; | ||
176 | } | ||
177 | |||
178 | |||
179 | |||
180 | TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) | ||
181 | { | ||
182 | (void) errStr; | ||
183 | QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; | ||
184 | qDebug() << "voicing" << query; | ||
185 | *voicestream << query; | ||
186 | *voicestream << "SYNC\tbla\r\n"; | ||
187 | voicestream->flush(); | ||
188 | voicescript->waitForReadyRead(); | ||
189 | return NoError; | ||
190 | } | ||
191 | |||
192 | bool TTSSapi::stop() | ||
193 | { | ||
194 | |||
195 | *voicestream << "QUIT\r\n"; | ||
196 | voicestream->flush(); | ||
197 | voicescript->waitForFinished(); | ||
198 | delete voicestream; | ||
199 | delete voicescript; | ||
200 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner | ||
201 | |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser | ||
202 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup | ||
203 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); | ||
204 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
205 | return true; | ||
206 | } | ||
207 | |||
208 | bool TTSSapi::configOk() | ||
209 | { | ||
210 | if(RbSettings::subValue("sapi",RbSettings::TtsVoice).toString().isEmpty()) | ||
211 | return false; | ||
212 | return true; | ||
213 | } | ||
diff --git a/rbutil/rbutilqt/base/ttssapi.h b/rbutil/rbutilqt/base/ttssapi.h new file mode 100644 index 0000000000..5e81575585 --- /dev/null +++ b/rbutil/rbutilqt/base/ttssapi.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Dominik Wenger | ||
10 | * $Id$ | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #ifndef TTSSAPI_H | ||
23 | #define TTSSAPI_H | ||
24 | |||
25 | #include "ttsbase.h" | ||
26 | |||
27 | class TTSSapi : public TTSBase | ||
28 | { | ||
29 | //! Enum to identify the settings | ||
30 | enum ESettings | ||
31 | { | ||
32 | eLANGUAGE, | ||
33 | eVOICE, | ||
34 | eSPEED, | ||
35 | eOPTIONS | ||
36 | }; | ||
37 | |||
38 | Q_OBJECT | ||
39 | public: | ||
40 | TTSSapi(QObject* parent=NULL); | ||
41 | |||
42 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
43 | bool start(QString *errStr); | ||
44 | bool stop(); | ||
45 | |||
46 | // for settings | ||
47 | bool configOk(); | ||
48 | void generateSettings(); | ||
49 | void saveSettings(); | ||
50 | |||
51 | private slots: | ||
52 | void updateVoiceList(); | ||
53 | |||
54 | private: | ||
55 | QStringList getVoiceList(QString language); | ||
56 | |||
57 | QProcess* voicescript; | ||
58 | QTextStream* voicestream; | ||
59 | QString defaultLanguage; | ||
60 | |||
61 | QString m_TTSexec; | ||
62 | QString m_TTSOpts; | ||
63 | QString m_TTSTemplate; | ||
64 | QString m_TTSLanguage; | ||
65 | QString m_TTSVoice; | ||
66 | QString m_TTSSpeed; | ||
67 | bool m_sapi4; | ||
68 | }; | ||
69 | |||
70 | |||
71 | |||
72 | #endif | ||
diff --git a/rbutil/rbutilqt/configure.cpp b/rbutil/rbutilqt/configure.cpp index 9552aa0614..3e7b5f729e 100644 --- a/rbutil/rbutilqt/configure.cpp +++ b/rbutil/rbutilqt/configure.cpp | |||
@@ -25,7 +25,7 @@ | |||
25 | #include "ui_configurefrm.h" | 25 | #include "ui_configurefrm.h" |
26 | #include "browsedirtree.h" | 26 | #include "browsedirtree.h" |
27 | #include "encoders.h" | 27 | #include "encoders.h" |
28 | #include "tts.h" | 28 | #include "ttsbase.h" |
29 | #include "system.h" | 29 | #include "system.h" |
30 | #include "encttscfggui.h" | 30 | #include "encttscfggui.h" |
31 | #include "rbsettings.h" | 31 | #include "rbsettings.h" |
diff --git a/rbutil/rbutilqt/rbutilqt.pro b/rbutil/rbutilqt/rbutilqt.pro index 576d59961d..4b170cd01e 100644 --- a/rbutil/rbutilqt/rbutilqt.pro +++ b/rbutil/rbutilqt/rbutilqt.pro | |||
@@ -98,7 +98,10 @@ SOURCES += rbutilqt.cpp \ | |||
98 | base/encoders.cpp \ | 98 | base/encoders.cpp \ |
99 | encttscfggui.cpp \ | 99 | encttscfggui.cpp \ |
100 | base/encttssettings.cpp \ | 100 | base/encttssettings.cpp \ |
101 | base/tts.cpp \ | 101 | base/ttsbase.cpp \ |
102 | base/ttsexes.cpp \ | ||
103 | base/ttssapi.cpp \ | ||
104 | base/ttsfestival.cpp \ | ||
102 | ../../tools/wavtrim.c \ | 105 | ../../tools/wavtrim.c \ |
103 | ../../tools/voicefont.c \ | 106 | ../../tools/voicefont.c \ |
104 | base/voicefile.cpp \ | 107 | base/voicefile.cpp \ |
@@ -155,7 +158,10 @@ HEADERS += rbutilqt.h \ | |||
155 | base/encoders.h \ | 158 | base/encoders.h \ |
156 | encttscfggui.h \ | 159 | encttscfggui.h \ |
157 | base/encttssettings.h \ | 160 | base/encttssettings.h \ |
158 | base/tts.h \ | 161 | base/ttsbase.h \ |
162 | base/ttsexes.h \ | ||
163 | base/ttsfestival.h \ | ||
164 | base/ttssapi.h \ | ||
159 | ../../tools/wavtrim.h \ | 165 | ../../tools/wavtrim.h \ |
160 | ../../tools/voicefont.h \ | 166 | ../../tools/voicefont.h \ |
161 | base/voicefile.h \ | 167 | base/voicefile.h \ |