diff options
Diffstat (limited to 'rbutil/rbutilqt/tts.cpp')
-rw-r--r-- | rbutil/rbutilqt/tts.cpp | 707 |
1 files changed, 370 insertions, 337 deletions
diff --git a/rbutil/rbutilqt/tts.cpp b/rbutil/rbutilqt/tts.cpp index 277022b9a1..d55ba9e739 100644 --- a/rbutil/rbutilqt/tts.cpp +++ b/rbutil/rbutilqt/tts.cpp | |||
@@ -18,11 +18,16 @@ | |||
18 | ****************************************************************************/ | 18 | ****************************************************************************/ |
19 | 19 | ||
20 | #include "tts.h" | 20 | #include "tts.h" |
21 | #include "utils.h" | ||
22 | /********************************************************************* | ||
23 | * TTS Base | ||
24 | **********************************************************************/ | ||
25 | QMap<QString,QString> TTSBase::ttsList; | ||
21 | 26 | ||
27 | TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent) | ||
28 | { | ||
22 | 29 | ||
23 | // static variables | 30 | } |
24 | QMap<QString,QString> TTSBase::ttsList; | ||
25 | QMap<QString,TTSBase*> TTSBase::ttsCache; | ||
26 | 31 | ||
27 | // static functions | 32 | // static functions |
28 | void TTSBase::initTTSList() | 33 | void TTSBase::initTTSList() |
@@ -39,35 +44,29 @@ void TTSBase::initTTSList() | |||
39 | } | 44 | } |
40 | 45 | ||
41 | // function to get a specific encoder | 46 | // function to get a specific encoder |
42 | TTSBase* TTSBase::getTTS(QString ttsName) | 47 | TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName) |
43 | { | 48 | { |
44 | // check cache | 49 | |
45 | if(ttsCache.contains(ttsName)) | ||
46 | return ttsCache.value(ttsName); | ||
47 | |||
48 | TTSBase* tts; | 50 | TTSBase* tts; |
49 | #if defined(Q_OS_WIN) | 51 | #if defined(Q_OS_WIN) |
50 | if(ttsName == "sapi") | 52 | if(ttsName == "sapi") |
51 | { | 53 | { |
52 | tts = new TTSSapi(); | 54 | tts = new TTSSapi(parent); |
53 | ttsCache[ttsName] = tts; | ||
54 | return tts; | 55 | return tts; |
55 | } | 56 | } |
56 | else | 57 | else |
57 | #endif | 58 | #endif |
58 | #if defined(Q_OS_LINUX) | 59 | #if defined(Q_OS_LINUX) |
59 | if (ttsName == "festival") | 60 | if (ttsName == "festival") |
60 | { | 61 | { |
61 | tts = new TTSFestival(); | 62 | tts = new TTSFestival(parent); |
62 | ttsCache[ttsName] = tts; | 63 | return tts; |
63 | return tts; | ||
64 | } | 64 | } |
65 | else | 65 | else |
66 | #endif | 66 | #endif |
67 | if (true) // fix for OS other than WIN or LINUX | 67 | if (true) // fix for OS other than WIN or LINUX |
68 | { | 68 | { |
69 | tts = new TTSExes(ttsName); | 69 | tts = new TTSExes(ttsName,parent); |
70 | ttsCache[ttsName] = tts; | ||
71 | return tts; | 70 | return tts; |
72 | } | 71 | } |
73 | } | 72 | } |
@@ -90,69 +89,41 @@ QString TTSBase::getTTSName(QString tts) | |||
90 | return ttsList.value(tts); | 89 | return ttsList.value(tts); |
91 | } | 90 | } |
92 | 91 | ||
93 | /********************************************************************* | ||
94 | * TTS Base | ||
95 | **********************************************************************/ | ||
96 | TTSBase::TTSBase(): QObject() | ||
97 | { | ||
98 | |||
99 | } | ||
100 | 92 | ||
101 | /********************************************************************* | 93 | /********************************************************************* |
102 | * General TTS Exes | 94 | * General TTS Exes |
103 | **********************************************************************/ | 95 | **********************************************************************/ |
104 | TTSExes::TTSExes(QString name) : TTSBase() | 96 | TTSExes::TTSExes(QString name,QObject* parent) : TTSBase(parent) |
105 | { | 97 | { |
106 | m_name = name; | 98 | m_name = name; |
107 | 99 | ||
108 | m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; | 100 | m_TemplateMap["espeak"] = "\"%exe\" %options -w \"%wavfile\" \"%text\""; |
109 | m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; | 101 | m_TemplateMap["flite"] = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; |
110 | m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; | 102 | m_TemplateMap["swift"] = "\"%exe\" %options -o \"%wavfile\" \"%text\""; |
111 | 103 | ||
112 | } | 104 | } |
113 | 105 | ||
114 | void TTSExes::setCfg(RbSettings* sett) | 106 | void TTSExes::generateSettings() |
115 | { | 107 | { |
116 | // call function of base class | 108 | QString exepath =settings->subValue(m_name,RbSettings::TtsPath).toString(); |
117 | TTSBase::setCfg(sett); | 109 | if(exepath == "") exepath = findExecutable(m_name); |
118 | 110 | ||
119 | // if the config isnt OK, try to autodetect | 111 | insertSetting(eEXEPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to TTS engine:",exepath,EncTtsSetting::eBROWSEBTN)); |
120 | if(!configOk()) | 112 | insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,"TTS enginge options:",settings->subValue(m_name,RbSettings::TtsOptions))); |
121 | { | 113 | } |
122 | QString exepath; | ||
123 | //try autodetect tts | ||
124 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
125 | QStringList path = QString(getenv("PATH")).split(":", QString::SkipEmptyParts); | ||
126 | #elif defined(Q_OS_WIN) | ||
127 | QStringList path = QString(getenv("PATH")).split(";", QString::SkipEmptyParts); | ||
128 | #endif | ||
129 | qDebug() << path; | ||
130 | for(int i = 0; i < path.size(); i++) | ||
131 | { | ||
132 | QString executable = QDir::fromNativeSeparators(path.at(i)) + "/" + m_name; | ||
133 | #if defined(Q_OS_WIN) | ||
134 | executable += ".exe"; | ||
135 | QStringList ex = executable.split("\"", QString::SkipEmptyParts); | ||
136 | executable = ex.join(""); | ||
137 | #endif | ||
138 | qDebug() << executable; | ||
139 | if(QFileInfo(executable).isExecutable()) | ||
140 | { | ||
141 | exepath= QDir::toNativeSeparators(executable); | ||
142 | break; | ||
143 | } | ||
144 | } | ||
145 | settings->setSubValue(m_name, RbSettings::TtsPath, exepath); | ||
146 | settings->sync(); | ||
147 | } | ||
148 | 114 | ||
115 | void TTSExes::saveSettings() | ||
116 | { | ||
117 | settings->setSubValue(m_name,RbSettings::TtsPath,getSetting(eEXEPATH)->current().toString()); | ||
118 | settings->setSubValue(m_name,RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString()); | ||
119 | settings->sync(); | ||
149 | } | 120 | } |
150 | 121 | ||
151 | bool TTSExes::start(QString *errStr) | 122 | bool TTSExes::start(QString *errStr) |
152 | { | 123 | { |
153 | m_TTSexec = settings->subValue(m_name, RbSettings::TtsPath).toString(); | 124 | m_TTSexec = settings->subValue(m_name,RbSettings::TtsPath).toString(); |
154 | m_TTSOpts = settings->subValue(m_name, RbSettings::TtsOptions).toString(); | 125 | m_TTSOpts = settings->subValue(m_name,RbSettings::TtsOptions).toString(); |
155 | 126 | ||
156 | m_TTSTemplate = m_TemplateMap.value(m_name); | 127 | m_TTSTemplate = m_TemplateMap.value(m_name); |
157 | 128 | ||
158 | QFileInfo tts(m_TTSexec); | 129 | QFileInfo tts(m_TTSexec); |
@@ -169,7 +140,7 @@ bool TTSExes::start(QString *errStr) | |||
169 | 140 | ||
170 | TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) | 141 | TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) |
171 | { | 142 | { |
172 | (void) errStr; | 143 | (void) errStr; |
173 | QString execstring = m_TTSTemplate; | 144 | QString execstring = m_TTSTemplate; |
174 | 145 | ||
175 | execstring.replace("%exe",m_TTSexec); | 146 | execstring.replace("%exe",m_TTSexec); |
@@ -182,57 +153,84 @@ TTSStatus TTSExes::voice(QString text,QString wavfile, QString *errStr) | |||
182 | 153 | ||
183 | } | 154 | } |
184 | 155 | ||
185 | void TTSExes::showCfg() | ||
186 | { | ||
187 | #ifndef CONSOLE | ||
188 | TTSExesGui gui; | ||
189 | #else | ||
190 | TTSExesGuiCli gui; | ||
191 | #endif | ||
192 | gui.setCfg(settings); | ||
193 | gui.showCfg(m_name); | ||
194 | } | ||
195 | |||
196 | bool TTSExes::configOk() | 156 | bool TTSExes::configOk() |
197 | { | 157 | { |
198 | QString path = settings->subValue(m_name, RbSettings::TtsPath).toString(); | 158 | QString path = settings->subValue(m_name,RbSettings::TtsPath).toString(); |
199 | 159 | ||
200 | if (QFileInfo(path).exists()) | 160 | if (QFileInfo(path).exists()) |
201 | return true; | 161 | return true; |
202 | 162 | ||
203 | return false; | 163 | return false; |
204 | } | 164 | } |
205 | 165 | ||
206 | /********************************************************************* | 166 | /********************************************************************* |
207 | * TTS Sapi | 167 | * TTS Sapi |
208 | **********************************************************************/ | 168 | **********************************************************************/ |
209 | TTSSapi::TTSSapi() : TTSBase() | 169 | TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) |
210 | { | 170 | { |
211 | m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\""; | 171 | m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang /voice:\"%voice\" /speed:%speed \"%options\""; |
212 | defaultLanguage ="english"; | 172 | defaultLanguage ="english"; |
213 | m_sapi4 =false; | 173 | m_sapi4 =false; |
214 | } | 174 | } |
215 | 175 | ||
176 | void TTSSapi::generateSettings() | ||
177 | { | ||
178 | // language | ||
179 | QStringList languages = settings->languages(); | ||
180 | languages.sort(); | ||
181 | EncTtsSetting* setting =new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,"Language:",settings->subValue("sapi",RbSettings::TtsLanguage),languages); | ||
182 | connect(setting,SIGNAL(dataChanged()),this,SLOT(updateVoiceList())); | ||
183 | insertSetting(eLANGUAGE,setting); | ||
184 | // voice | ||
185 | setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,"Voice:",settings->subValue("sapi",RbSettings::TtsVoice),getVoiceList(settings->subValue("sapi",RbSettings::TtsLanguage).toString()),EncTtsSetting::eREFRESHBTN); | ||
186 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList())); | ||
187 | insertSetting(eVOICE,setting); | ||
188 | //speed | ||
189 | insertSetting(eSPEED,new EncTtsSetting(this,EncTtsSetting::eINT,"Speed:",settings->subValue("sapi",RbSettings::TtsSpeed),-10,10)); | ||
190 | // options | ||
191 | insertSetting(eOPTIONS,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Options:",settings->subValue("sapi",RbSettings::TtsOptions))); | ||
216 | 192 | ||
217 | bool TTSSapi::start(QString *errStr) | 193 | } |
194 | |||
195 | void TTSSapi::saveSettings() | ||
196 | { | ||
197 | //save settings in user config | ||
198 | settings->setSubValue("sapi",RbSettings::TtsLanguage,getSetting(eLANGUAGE)->current().toString()); | ||
199 | settings->setSubValue("sapi",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString()); | ||
200 | settings->setSubValue("sapi",RbSettings::TtsSpeed,getSetting(eSPEED)->current().toInt()); | ||
201 | settings->setSubValue("sapi",RbSettings::TtsOptions,getSetting(eOPTIONS)->current().toString()); | ||
202 | |||
203 | settings->sync(); | ||
204 | } | ||
205 | |||
206 | void TTSSapi::updateVoiceList() | ||
218 | { | 207 | { |
208 | qDebug() << "update voiceList"; | ||
209 | QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString()); | ||
210 | getSetting(eVOICE)->setList(voiceList); | ||
211 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); | ||
212 | else getSetting(eVOICE)->setCurrent(""); | ||
213 | } | ||
214 | |||
215 | bool TTSSapi::start(QString *errStr) | ||
216 | { | ||
219 | 217 | ||
220 | m_TTSOpts = settings->subValue("sapi", RbSettings::TtsOptions).toString(); | 218 | m_TTSOpts = settings->subValue("sapi",RbSettings::TtsOptions).toString(); |
221 | m_TTSLanguage =settings->subValue("sapi", RbSettings::TtsLanguage).toString(); | 219 | m_TTSLanguage =settings->subValue("sapi",RbSettings::TtsLanguage).toString(); |
222 | m_TTSVoice=settings->subValue("sapi", RbSettings::TtsVoice).toString(); | 220 | m_TTSVoice=settings->subValue("sapi",RbSettings::TtsVoice).toString(); |
223 | m_TTSSpeed=QString("%1").arg(settings->subValue("sapi", RbSettings::TtsSpeed).toInt()); | 221 | m_TTSSpeed=settings->subValue("sapi",RbSettings::TtsSpeed).toString(); |
224 | m_sapi4 = settings->value(RbSettings::TtsUseSapi4).toBool(); | 222 | m_sapi4 = settings->subValue("sapi",RbSettings::TtsUseSapi4).toBool(); |
225 | 223 | ||
226 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | 224 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); |
227 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | 225 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); |
228 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | 226 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; |
229 | 227 | ||
230 | QFileInfo tts(m_TTSexec); | 228 | QFileInfo tts(m_TTSexec); |
231 | if(!tts.exists()) | 229 | if(!tts.exists()) |
232 | { | 230 | { |
233 | *errStr = tr("Could not copy the Sapi-script"); | 231 | *errStr = tr("Could not copy the Sapi-script"); |
234 | return false; | 232 | return false; |
235 | } | 233 | } |
236 | // create the voice process | 234 | // create the voice process |
237 | QString execstring = m_TTSTemplate; | 235 | QString execstring = m_TTSTemplate; |
238 | execstring.replace("%exe",m_TTSexec); | 236 | execstring.replace("%exe",m_TTSexec); |
@@ -240,31 +238,31 @@ bool TTSSapi::start(QString *errStr) | |||
240 | execstring.replace("%lang",m_TTSLanguage); | 238 | execstring.replace("%lang",m_TTSLanguage); |
241 | execstring.replace("%voice",m_TTSVoice); | 239 | execstring.replace("%voice",m_TTSVoice); |
242 | execstring.replace("%speed",m_TTSSpeed); | 240 | execstring.replace("%speed",m_TTSSpeed); |
243 | 241 | ||
244 | if(m_sapi4) | 242 | if(m_sapi4) |
245 | execstring.append(" /sapi4 "); | 243 | execstring.append(" /sapi4 "); |
246 | 244 | ||
247 | qDebug() << "init" << execstring; | 245 | qDebug() << "init" << execstring; |
248 | voicescript = new QProcess(NULL); | 246 | voicescript = new QProcess(NULL); |
249 | //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error())); | 247 | //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error())); |
250 | 248 | ||
251 | voicescript->start(execstring); | 249 | voicescript->start(execstring); |
252 | if(!voicescript->waitForStarted()) | 250 | if(!voicescript->waitForStarted()) |
253 | { | 251 | { |
254 | *errStr = tr("Could not start the Sapi-script"); | 252 | *errStr = tr("Could not start the Sapi-script"); |
255 | return false; | 253 | return false; |
256 | } | 254 | } |
257 | 255 | ||
258 | if(!voicescript->waitForReadyRead(300)) | 256 | if(!voicescript->waitForReadyRead(300)) |
259 | { | 257 | { |
260 | *errStr = voicescript->readAllStandardError(); | 258 | *errStr = voicescript->readAllStandardError(); |
261 | if(*errStr != "") | 259 | if(*errStr != "") |
262 | return false; | 260 | return false; |
263 | } | 261 | } |
264 | 262 | ||
265 | voicestream = new QTextStream(voicescript); | 263 | voicestream = new QTextStream(voicescript); |
266 | voicestream->setCodec("UTF16-LE"); | 264 | voicestream->setCodec("UTF16-LE"); |
267 | 265 | ||
268 | return true; | 266 | return true; |
269 | } | 267 | } |
270 | 268 | ||
@@ -272,14 +270,14 @@ bool TTSSapi::start(QString *errStr) | |||
272 | QStringList TTSSapi::getVoiceList(QString language) | 270 | QStringList TTSSapi::getVoiceList(QString language) |
273 | { | 271 | { |
274 | QStringList result; | 272 | QStringList result; |
275 | 273 | ||
276 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | 274 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); |
277 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | 275 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; |
278 | 276 | ||
279 | QFileInfo tts(m_TTSexec); | 277 | QFileInfo tts(m_TTSexec); |
280 | if(!tts.exists()) | 278 | if(!tts.exists()) |
281 | return result; | 279 | return result; |
282 | 280 | ||
283 | // create the voice process | 281 | // create the voice process |
284 | QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices"; | 282 | QString execstring = "cscript //nologo \"%exe\" /language:%lang /listvoices"; |
285 | execstring.replace("%exe",m_TTSexec); | 283 | execstring.replace("%exe",m_TTSexec); |
@@ -287,34 +285,34 @@ QStringList TTSSapi::getVoiceList(QString language) | |||
287 | 285 | ||
288 | if(settings->value(RbSettings::TtsUseSapi4).toBool()) | 286 | if(settings->value(RbSettings::TtsUseSapi4).toBool()) |
289 | execstring.append(" /sapi4 "); | 287 | execstring.append(" /sapi4 "); |
290 | 288 | ||
291 | qDebug() << "init" << execstring; | 289 | qDebug() << "init" << execstring; |
292 | voicescript = new QProcess(NULL); | 290 | voicescript = new QProcess(NULL); |
293 | voicescript->start(execstring); | 291 | voicescript->start(execstring); |
294 | qDebug() << "wait for started"; | 292 | qDebug() << "wait for started"; |
295 | if(!voicescript->waitForStarted()) | 293 | if(!voicescript->waitForStarted()) |
296 | return result; | 294 | return result; |
297 | voicescript->closeWriteChannel(); | 295 | voicescript->closeWriteChannel(); |
298 | voicescript->waitForReadyRead(); | 296 | voicescript->waitForReadyRead(); |
299 | 297 | ||
300 | QString dataRaw = voicescript->readAllStandardError().data(); | 298 | QString dataRaw = voicescript->readAllStandardError().data(); |
301 | result = dataRaw.split(",",QString::SkipEmptyParts); | 299 | result = dataRaw.split(",",QString::SkipEmptyParts); |
302 | result.sort(); | 300 | if(result.size() > 0) |
303 | result.removeFirst(); | ||
304 | for(int i = 0; i< result.size();i++) | ||
305 | { | 301 | { |
306 | result[i] = result.at(i).simplified(); | 302 | result.sort(); |
307 | } | 303 | result.removeFirst(); |
308 | 304 | for(int i = 0; i< result.size();i++) | |
309 | 305 | { | |
306 | result[i] = result.at(i).simplified(); | ||
307 | } | ||
308 | } | ||
309 | |||
310 | delete voicescript; | 310 | delete voicescript; |
311 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", | 311 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner |
312 | QFile::ReadOwner |QFile::WriteOwner |QFile::ExeOwner | 312 | |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser |
313 | |QFile::ReadUser |QFile::WriteUser |QFile::ExeUser | 313 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup |
314 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup | 314 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); |
315 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther); | ||
316 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | 315 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); |
317 | |||
318 | return result; | 316 | return result; |
319 | } | 317 | } |
320 | 318 | ||
@@ -322,7 +320,7 @@ QStringList TTSSapi::getVoiceList(QString language) | |||
322 | 320 | ||
323 | TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) | 321 | TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) |
324 | { | 322 | { |
325 | (void) errStr; | 323 | (void) errStr; |
326 | QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; | 324 | QString query = "SPEAK\t"+wavfile+"\t"+text+"\r\n"; |
327 | qDebug() << "voicing" << query; | 325 | qDebug() << "voicing" << query; |
328 | *voicestream << query; | 326 | *voicestream << query; |
@@ -333,37 +331,24 @@ TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) | |||
333 | } | 331 | } |
334 | 332 | ||
335 | bool TTSSapi::stop() | 333 | bool TTSSapi::stop() |
336 | { | 334 | { |
337 | 335 | ||
338 | *voicestream << "QUIT\r\n"; | 336 | *voicestream << "QUIT\r\n"; |
339 | voicestream->flush(); | 337 | voicestream->flush(); |
340 | voicescript->waitForFinished(); | 338 | voicescript->waitForFinished(); |
341 | delete voicestream; | 339 | delete voicestream; |
342 | delete voicescript; | 340 | delete voicescript; |
343 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", | 341 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",QFile::ReadOwner |QFile::WriteOwner|QFile::ExeOwner |
344 | QFile::ReadOwner |QFile::WriteOwner |QFile::ExeOwner | 342 | |QFile::ReadUser| QFile::WriteUser| QFile::ExeUser |
345 | |QFile::ReadUser |QFile::WriteUser |QFile::ExeUser | 343 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup |
346 | |QFile::ReadGroup |QFile::WriteGroup |QFile::ExeGroup | 344 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther ); |
347 | |QFile::ReadOther |QFile::WriteOther |QFile::ExeOther); | ||
348 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | 345 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); |
349 | return true; | 346 | return true; |
350 | } | 347 | } |
351 | 348 | ||
352 | |||
353 | void TTSSapi::showCfg() | ||
354 | { | ||
355 | #ifndef CONSOLE | ||
356 | TTSSapiGui gui(this); | ||
357 | #else | ||
358 | TTSSapiGuiCli gui(this); | ||
359 | #endif | ||
360 | gui.setCfg(settings); | ||
361 | gui.showCfg(); | ||
362 | } | ||
363 | |||
364 | bool TTSSapi::configOk() | 349 | bool TTSSapi::configOk() |
365 | { | 350 | { |
366 | if(settings->subValue("sapi", RbSettings::TtsVoice).toString().isEmpty()) | 351 | if(settings->subValue("sapi",RbSettings::TtsVoice).toString().isEmpty()) |
367 | return false; | 352 | return false; |
368 | return true; | 353 | return true; |
369 | } | 354 | } |
@@ -372,252 +357,300 @@ bool TTSSapi::configOk() | |||
372 | **********************************************************************/ | 357 | **********************************************************************/ |
373 | TTSFestival::~TTSFestival() | 358 | TTSFestival::~TTSFestival() |
374 | { | 359 | { |
375 | stop(); | 360 | stop(); |
376 | } | 361 | } |
377 | 362 | ||
378 | void TTSFestival::startServer() | 363 | void TTSFestival::generateSettings() |
379 | { | 364 | { |
380 | if(!configOk()) | 365 | // server path |
381 | return; | 366 | QString exepath = settings->subValue("festival-server",RbSettings::TtsPath).toString(); |
367 | if(exepath == "" ) exepath = findExecutable("festival"); | ||
368 | insertSetting(eSERVERPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival server:",exepath,EncTtsSetting::eBROWSEBTN)); | ||
369 | |||
370 | // client path | ||
371 | QString clientpath = settings->subValue("festival-client",RbSettings::TtsPath).toString(); | ||
372 | if(clientpath == "" ) clientpath = findExecutable("festival_client"); | ||
373 | insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING,"Path to Festival client:",clientpath,EncTtsSetting::eBROWSEBTN)); | ||
374 | |||
375 | // voice | ||
376 | EncTtsSetting* setting = new EncTtsSetting(this,EncTtsSetting::eSTRINGLIST,"Voice:",settings->subValue("festival",RbSettings::TtsVoice),getVoiceList(exepath),EncTtsSetting::eREFRESHBTN); | ||
377 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceList())); | ||
378 | connect(setting,SIGNAL(dataChanged()),this,SLOT(clearVoiceDescription())); | ||
379 | insertSetting(eVOICE,setting); | ||
380 | |||
381 | //voice description | ||
382 | setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING,"Voice description:","",EncTtsSetting::eREFRESHBTN); | ||
383 | connect(setting,SIGNAL(refresh()),this,SLOT(updateVoiceDescription())); | ||
384 | insertSetting(eVOICEDESC,setting); | ||
385 | } | ||
382 | 386 | ||
383 | QStringList paths = settings->subValue("festival", RbSettings::TtsPath).toString().split(":"); | 387 | void TTSFestival::saveSettings() |
388 | { | ||
389 | //save settings in user config | ||
390 | settings->setSubValue("festival-server",RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString()); | ||
391 | settings->setSubValue("festival-client",RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString()); | ||
392 | settings->setSubValue("festival",RbSettings::TtsVoice,getSetting(eVOICE)->current().toString()); | ||
393 | |||
394 | settings->sync(); | ||
395 | } | ||
384 | 396 | ||
385 | serverProcess.start(QString("%1 --server").arg(paths[0])); | 397 | void TTSFestival::updateVoiceDescription() |
386 | serverProcess.waitForStarted(); | 398 | { |
399 | // get voice Info with current voice and path | ||
400 | QString info = getVoiceInfo(getSetting(eVOICE)->current().toString(),getSetting(eSERVERPATH)->current().toString()); | ||
401 | getSetting(eVOICEDESC)->setCurrent(info); | ||
402 | } | ||
387 | 403 | ||
388 | queryServer("(getpid)"); | 404 | void TTSFestival::clearVoiceDescription() |
389 | if(serverProcess.state() == QProcess::Running) | 405 | { |
390 | qDebug() << "Festival is up and running"; | 406 | getSetting(eVOICEDESC)->setCurrent(""); |
391 | else | ||
392 | qDebug() << "Festival failed to start"; | ||
393 | } | 407 | } |
394 | 408 | ||
395 | void TTSFestival::ensureServerRunning() | 409 | void TTSFestival::updateVoiceList() |
396 | { | 410 | { |
397 | if(serverProcess.state() != QProcess::Running) | 411 | QStringList voiceList = getVoiceList(getSetting(eSERVERPATH)->current().toString()); |
398 | { | 412 | getSetting(eVOICE)->setList(voiceList); |
399 | // least common denominator for all the server startup code paths | 413 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); |
400 | QProgressDialog progressDialog(tr(""), tr(""), 0, 0); | 414 | else getSetting(eVOICE)->setCurrent(""); |
401 | progressDialog.setWindowTitle(tr("Starting festival")); | 415 | } |
402 | progressDialog.setModal(true); | 416 | |
403 | progressDialog.setLabel(0); | 417 | void TTSFestival::startServer(QString path) |
404 | progressDialog.setCancelButton(0); | 418 | { |
405 | progressDialog.show(); | 419 | if(!configOk()) |
420 | return; | ||
421 | |||
422 | if(path == "") | ||
423 | path = settings->subValue("festival-server",RbSettings::TtsPath).toString(); | ||
424 | |||
425 | serverProcess.start(QString("%1 --server").arg(path)); | ||
426 | serverProcess.waitForStarted(); | ||
406 | 427 | ||
407 | QApplication::processEvents(); // actually show the dialog | 428 | queryServer("(getpid)",300,path); |
429 | if(serverProcess.state() == QProcess::Running) | ||
430 | qDebug() << "Festival is up and running"; | ||
431 | else | ||
432 | qDebug() << "Festival failed to start"; | ||
433 | } | ||
408 | 434 | ||
409 | startServer(); | 435 | void TTSFestival::ensureServerRunning(QString path) |
436 | { | ||
437 | if(serverProcess.state() != QProcess::Running) | ||
438 | { | ||
439 | startServer(path); | ||
410 | } | 440 | } |
411 | } | 441 | } |
412 | 442 | ||
413 | bool TTSFestival::start(QString* errStr) | 443 | bool TTSFestival::start(QString* errStr) |
414 | { | 444 | { |
415 | (void) errStr; | 445 | (void) errStr; |
416 | ensureServerRunning(); | 446 | ensureServerRunning(); |
417 | if (!settings->subValue("festival", RbSettings::TtsVoice).toString().isEmpty()) | 447 | if (!settings->subValue("festival",RbSettings::TtsVoice).toString().isEmpty()) |
418 | queryServer(QString("(voice.select '%1)") | 448 | queryServer(QString("(voice.select '%1)") |
419 | .arg(settings->subValue("festival", RbSettings::TtsVoice).toString())); | 449 | .arg(settings->subValue("festival", RbSettings::TtsVoice).toString())); |
420 | 450 | ||
421 | return true; | 451 | return true; |
422 | } | 452 | } |
423 | 453 | ||
424 | bool TTSFestival::stop() | 454 | bool TTSFestival::stop() |
425 | { | 455 | { |
426 | serverProcess.terminate(); | 456 | serverProcess.terminate(); |
427 | serverProcess.kill(); | 457 | serverProcess.kill(); |
428 | 458 | ||
429 | return true; | 459 | return true; |
430 | } | 460 | } |
431 | 461 | ||
432 | TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) | 462 | TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) |
433 | { | 463 | { |
434 | qDebug() << text << "->" << wavfile; | 464 | qDebug() << text << "->" << wavfile; |
435 | 465 | ||
436 | QStringList paths = settings->subValue("festival", RbSettings::TtsPath).toString().split(":"); | 466 | QString path = settings->subValue("festival-client",RbSettings::TtsPath).toString(); |
437 | QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(paths[1]).arg(wavfile); | 467 | QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp --output \"%2\" - ").arg(path).arg(wavfile); |
438 | qDebug() << cmd; | 468 | qDebug() << cmd; |
439 | 469 | ||
440 | QProcess clientProcess; | 470 | QProcess clientProcess; |
441 | clientProcess.start(cmd); | 471 | clientProcess.start(cmd); |
442 | clientProcess.write(QString("%1.\n").arg(text).toAscii()); | 472 | clientProcess.write(QString("%1.\n").arg(text).toAscii()); |
443 | clientProcess.waitForBytesWritten(); | 473 | clientProcess.waitForBytesWritten(); |
444 | clientProcess.closeWriteChannel(); | 474 | clientProcess.closeWriteChannel(); |
445 | clientProcess.waitForReadyRead(); | 475 | clientProcess.waitForReadyRead(); |
446 | QString response = clientProcess.readAll(); | 476 | QString response = clientProcess.readAll(); |
447 | response = response.trimmed(); | 477 | response = response.trimmed(); |
448 | if(!response.contains("Utterance")) | 478 | if(!response.contains("Utterance")) |
449 | { | 479 | { |
450 | qDebug() << "Could not voice string: " << response; | 480 | qDebug() << "Could not voice string: " << response; |
451 | *errStr = tr("engine could not voice string"); | 481 | *errStr = tr("engine could not voice string"); |
452 | return Warning; | 482 | return Warning; |
453 | /* do not stop the voicing process because of a single string | 483 | /* do not stop the voicing process because of a single string |
454 | TODO: needs proper settings */ | 484 | TODO: needs proper settings */ |
455 | } | 485 | } |
456 | clientProcess.closeReadChannel(QProcess::StandardError); | 486 | clientProcess.closeReadChannel(QProcess::StandardError); |
457 | clientProcess.closeReadChannel(QProcess::StandardOutput); | 487 | clientProcess.closeReadChannel(QProcess::StandardOutput); |
458 | clientProcess.terminate(); | 488 | clientProcess.terminate(); |
459 | clientProcess.kill(); | 489 | clientProcess.kill(); |
460 | 490 | ||
461 | return NoError; | 491 | return NoError; |
462 | } | 492 | } |
463 | 493 | ||
464 | bool TTSFestival::configOk() | 494 | bool TTSFestival::configOk() |
465 | { | 495 | { |
466 | QStringList paths = settings->subValue("festival", RbSettings::TtsPath).toString().split(":"); | 496 | QString serverPath = settings->subValue("festival-server",RbSettings::TtsPath).toString(); |
467 | if(paths.size() != 2) | 497 | QString clientPath = settings->subValue("festival-client",RbSettings::TtsVoice).toString(); |
468 | return false; | 498 | |
469 | bool ret = QFileInfo(paths[0]).isExecutable() && | 499 | bool ret = QFileInfo(serverPath).isExecutable() && |
470 | QFileInfo(paths[1]).isExecutable(); | 500 | QFileInfo(clientPath).isExecutable(); |
471 | if(settings->subValue("festival", RbSettings::TtsVoice).toString().size() > 0 | 501 | if(settings->subValue("festival",RbSettings::TtsVoice).toString().size() > 0 && voices.size() > 0) |
472 | && voices.size() > 0) | 502 | ret = ret && (voices.indexOf(settings->subValue("festival",RbSettings::TtsVoice).toString()) != -1); |
473 | ret = ret && (voices.indexOf(settings->subValue("festival", | 503 | return ret; |
474 | RbSettings::TtsVoice).toString()) != -1); | ||
475 | return ret; | ||
476 | } | ||
477 | |||
478 | void TTSFestival::showCfg() | ||
479 | { | ||
480 | #ifndef CONSOLE | ||
481 | TTSFestivalGui gui(this); | ||
482 | #endif | ||
483 | gui.setCfg(settings); | ||
484 | gui.showCfg(); | ||
485 | } | 504 | } |
486 | 505 | ||
487 | QStringList TTSFestival::getVoiceList() | 506 | QStringList TTSFestival::getVoiceList(QString path) |
488 | { | 507 | { |
489 | if(!configOk()) | 508 | if(!configOk()) |
490 | return QStringList(); | 509 | return QStringList(); |
491 | 510 | ||
492 | if(voices.size() > 0) | 511 | if(voices.size() > 0) |
493 | { | 512 | { |
494 | qDebug() << "Using voice cache"; | 513 | qDebug() << "Using voice cache"; |
495 | return voices; | 514 | return voices; |
496 | } | 515 | } |
497 | QString response = queryServer("(voice.list)"); | 516 | |
498 | 517 | QString response = queryServer("(voice.list)",3000,path); | |
499 | // get the 2nd line. It should be (<voice_name>, <voice_name>) | 518 | |
500 | response = response.mid(response.indexOf('\n') + 1, -1); | 519 | // get the 2nd line. It should be (<voice_name>, <voice_name>) |
501 | response = response.left(response.indexOf('\n')).trimmed(); | 520 | response = response.mid(response.indexOf('\n') + 1, -1); |
502 | 521 | response = response.left(response.indexOf('\n')).trimmed(); | |
503 | voices = response.mid(1, response.size()-2).split(' '); | 522 | |
504 | 523 | voices = response.mid(1, response.size()-2).split(' '); | |
505 | voices.sort(); | 524 | |
506 | if (voices.size() == 1 && voices[0].size() == 0) | 525 | voices.sort(); |
507 | voices.removeAt(0); | 526 | if (voices.size() == 1 && voices[0].size() == 0) |
508 | if (voices.size() > 0) | 527 | voices.removeAt(0); |
509 | qDebug() << "Voices: " << voices; | 528 | if (voices.size() > 0) |
510 | else | 529 | qDebug() << "Voices: " << voices; |
511 | qDebug() << "No voices."; | 530 | else |
512 | return voices; | 531 | qDebug() << "No voices."; |
532 | |||
533 | return voices; | ||
513 | } | 534 | } |
514 | 535 | ||
515 | QString TTSFestival::getVoiceInfo(QString voice) | 536 | QString TTSFestival::getVoiceInfo(QString voice,QString path) |
516 | { | 537 | { |
517 | if(!configOk()) | 538 | if(!configOk()) |
518 | return ""; | 539 | return ""; |
519 | 540 | ||
520 | if(!getVoiceList().contains(voice)) | 541 | if(!getVoiceList().contains(voice)) |
521 | return ""; | 542 | return ""; |
522 | 543 | ||
523 | if(voiceDescriptions.contains(voice)) | 544 | if(voiceDescriptions.contains(voice)) |
524 | return voiceDescriptions[voice]; | 545 | return voiceDescriptions[voice]; |
525 | 546 | ||
526 | QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000); | 547 | QString response = queryServer(QString("(voice.description '%1)").arg(voice), 3000,path); |
527 | 548 | ||
528 | if (response == "") | 549 | if (response == "") |
529 | { | 550 | { |
530 | voiceDescriptions[voice]=tr("No description available"); | 551 | voiceDescriptions[voice]=tr("No description available"); |
531 | } | 552 | } |
532 | else | 553 | else |
533 | { | 554 | { |
534 | response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard)); | 555 | response = response.remove(QRegExp("(description \"*\")", Qt::CaseInsensitive, QRegExp::Wildcard)); |
535 | qDebug() << "voiceInfo w/o descr: " << response; | 556 | qDebug() << "voiceInfo w/o descr: " << response; |
536 | response = response.remove(')'); | 557 | response = response.remove(')'); |
537 | QStringList responseLines = response.split('(', QString::SkipEmptyParts); | 558 | QStringList responseLines = response.split('(', QString::SkipEmptyParts); |
538 | responseLines.removeAt(0); // the voice name itself | 559 | responseLines.removeAt(0); // the voice name itself |
539 | 560 | ||
540 | QString description; | 561 | QString description; |
541 | foreach(QString line, responseLines) | 562 | foreach(QString line, responseLines) |
542 | { | 563 | { |
543 | line = line.remove('('); | 564 | line = line.remove('('); |
544 | line = line.simplified(); | 565 | line = line.simplified(); |
545 | 566 | ||
546 | line[0] = line[0].toUpper(); // capitalize the key | 567 | line[0] = line[0].toUpper(); // capitalize the key |
547 | 568 | ||
548 | int firstSpace = line.indexOf(' '); | 569 | int firstSpace = line.indexOf(' '); |
549 | if (firstSpace > 0) | 570 | if (firstSpace > 0) |
550 | { | 571 | { |
551 | line = line.insert(firstSpace, ':'); // add a colon between the key and the value | 572 | line = line.insert(firstSpace, ':'); // add a colon between the key and the value |
552 | line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value | 573 | line[firstSpace+2] = line[firstSpace+2].toUpper(); // capitalize the value |
553 | } | 574 | } |
554 | 575 | ||
555 | description += line + "\n"; | 576 | description += line + "\n"; |
556 | } | 577 | } |
557 | voiceDescriptions[voice] = description.trimmed(); | 578 | voiceDescriptions[voice] = description.trimmed(); |
558 | } | 579 | } |
559 | return voiceDescriptions[voice]; | 580 | |
581 | return voiceDescriptions[voice]; | ||
560 | } | 582 | } |
561 | 583 | ||
562 | QString TTSFestival::queryServer(QString query, int timeout) | 584 | QString TTSFestival::queryServer(QString query, int timeout,QString path) |
563 | { | 585 | { |
564 | if(!configOk()) | 586 | if(!configOk()) |
565 | return ""; | 587 | return ""; |
566 | 588 | ||
567 | ensureServerRunning(); | 589 | // this operation could take some time |
568 | 590 | emit busy(); | |
569 | qDebug() << "queryServer with " << query; | 591 | |
570 | QString response; | 592 | ensureServerRunning(path); |
571 | 593 | ||
572 | QDateTime endTime; | 594 | qDebug() << "queryServer with " << query; |
573 | if(timeout > 0) | 595 | QString response; |
574 | endTime = QDateTime::currentDateTime().addMSecs(timeout); | 596 | |
575 | 597 | QDateTime endTime; | |
576 | /* Festival is *extremely* unreliable. Although at this | 598 | if(timeout > 0) |
577 | * point we are sure that SIOD is accepting commands, | 599 | endTime = QDateTime::currentDateTime().addMSecs(timeout); |
578 | * we might end up with an empty response. Hence, the loop. | 600 | |
579 | */ | 601 | /* Festival is *extremely* unreliable. Although at this |
580 | while(true) | 602 | * point we are sure that SIOD is accepting commands, |
581 | { | 603 | * we might end up with an empty response. Hence, the loop. |
582 | QApplication::processEvents(QEventLoop::AllEvents, 50); | 604 | */ |
583 | QTcpSocket socket; | 605 | while(true) |
584 | 606 | { | |
585 | socket.connectToHost("localhost", 1314); | 607 | QCoreApplication::processEvents(QEventLoop::AllEvents, 50); |
586 | socket.waitForConnected(); | 608 | QTcpSocket socket; |
587 | 609 | ||
588 | if(socket.state() == QAbstractSocket::ConnectedState) | 610 | socket.connectToHost("localhost", 1314); |
611 | socket.waitForConnected(); | ||
612 | |||
613 | if(socket.state() == QAbstractSocket::ConnectedState) | ||
614 | { | ||
615 | socket.write(QString("%1\n").arg(query).toAscii()); | ||
616 | socket.waitForBytesWritten(); | ||
617 | socket.waitForReadyRead(); | ||
618 | |||
619 | response = socket.readAll().trimmed(); | ||
620 | |||
621 | if (response != "LP" && response != "") | ||
622 | break; | ||
623 | } | ||
624 | socket.abort(); | ||
625 | socket.disconnectFromHost(); | ||
626 | |||
627 | if(timeout > 0 && QDateTime::currentDateTime() >= endTime) | ||
589 | { | 628 | { |
590 | socket.write(QString("%1\n").arg(query).toAscii()); | 629 | emit busyEnd(); |
591 | socket.waitForBytesWritten(); | 630 | return ""; |
592 | socket.waitForReadyRead(); | ||
593 | |||
594 | response = socket.readAll().trimmed(); | ||
595 | |||
596 | if (response != "LP" && response != "") | ||
597 | break; | ||
598 | } | 631 | } |
599 | socket.abort(); | 632 | /* make sure we wait a little as we don't want to flood the server with requests */ |
600 | socket.disconnectFromHost(); | 633 | QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500); |
601 | 634 | while(QDateTime::currentDateTime() < tmpEndTime) | |
602 | if(timeout > 0 && QDateTime::currentDateTime() >= endTime) | 635 | QCoreApplication::processEvents(QEventLoop::AllEvents); |
603 | return ""; | 636 | } |
604 | 637 | if(response == "nil") | |
605 | /* make sure we wait a little as we don't want to flood the server with requests */ | ||
606 | QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500); | ||
607 | while(QDateTime::currentDateTime() < tmpEndTime) | ||
608 | QApplication::processEvents(QEventLoop::AllEvents); | ||
609 | } | ||
610 | if(response == "nil") | ||
611 | return ""; | ||
612 | |||
613 | QStringList lines = response.split('\n'); | ||
614 | if(lines.size() > 2) | ||
615 | { | 638 | { |
616 | lines.removeFirst(); | 639 | emit busyEnd(); |
617 | lines.removeLast(); | 640 | return ""; |
618 | } | 641 | } |
619 | else | 642 | |
620 | qDebug() << "Response too short: " << response; | 643 | QStringList lines = response.split('\n'); |
644 | if(lines.size() > 2) | ||
645 | { | ||
646 | lines.removeFirst(); | ||
647 | lines.removeLast(); | ||
648 | } | ||
649 | else | ||
650 | qDebug() << "Response too short: " << response; | ||
651 | |||
652 | emit busyEnd(); | ||
621 | return lines.join("\n"); | 653 | return lines.join("\n"); |
654 | |||
622 | } | 655 | } |
623 | 656 | ||