diff options
Diffstat (limited to 'utils/rbutilqt/base/voicefile.cpp')
-rw-r--r-- | utils/rbutilqt/base/voicefile.cpp | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/utils/rbutilqt/base/voicefile.cpp b/utils/rbutilqt/base/voicefile.cpp new file mode 100644 index 0000000000..eabf7a721a --- /dev/null +++ b/utils/rbutilqt/base/voicefile.cpp | |||
@@ -0,0 +1,362 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "voicefile.h" | ||
21 | #include "utils.h" | ||
22 | #include "rockboxinfo.h" | ||
23 | #include "rbsettings.h" | ||
24 | #include "playerbuildinfo.h" | ||
25 | #include "ziputil.h" | ||
26 | #include "Logger.h" | ||
27 | |||
28 | VoiceFileCreator::VoiceFileCreator(QObject* parent) :QObject(parent) | ||
29 | { | ||
30 | m_wavtrimThreshold=500; | ||
31 | } | ||
32 | |||
33 | void VoiceFileCreator::abort() | ||
34 | { | ||
35 | m_abort = true; | ||
36 | emit aborted(); | ||
37 | } | ||
38 | |||
39 | bool VoiceFileCreator::createVoiceFile() | ||
40 | { | ||
41 | m_talkList.clear(); | ||
42 | m_abort = false; | ||
43 | emit logItem(tr("Starting Voicefile generation"),LOGINFO); | ||
44 | |||
45 | // test if tempdir exists | ||
46 | if(!QDir(QDir::tempPath()+"/rbvoice/").exists()) | ||
47 | { | ||
48 | QDir(QDir::tempPath()).mkdir("rbvoice"); | ||
49 | } | ||
50 | m_path = QDir::tempPath() + "/rbvoice/"; | ||
51 | |||
52 | // read rockbox-info.txt | ||
53 | RockboxInfo info(m_mountpoint); | ||
54 | if(!info.success()) | ||
55 | { | ||
56 | emit logItem(tr("could not find rockbox-info.txt"),LOGERROR); | ||
57 | emit done(true); | ||
58 | return false; | ||
59 | } | ||
60 | QString target = info.target(); | ||
61 | QString features = info.features(); | ||
62 | m_targetid = info.targetID().toInt(); | ||
63 | m_versionstring = info.version(); | ||
64 | m_voiceformat = info.voicefmt(); | ||
65 | QString version = m_versionstring.left(m_versionstring.indexOf("-")).remove("r"); | ||
66 | |||
67 | // check if voicefile is present on target | ||
68 | QString fn = m_mountpoint + "/.rockbox/langs/voicestrings.zip"; | ||
69 | LOG_INFO() << "searching for zipped voicestrings at" << fn; | ||
70 | if(QFileInfo(fn).isFile()) { | ||
71 | // search for binary voice strings file in archive | ||
72 | ZipUtil z(this); | ||
73 | if(z.open(fn)) { | ||
74 | QStringList contents = z.files(); | ||
75 | int index; | ||
76 | for(index = 0; index < contents.size(); ++index) { | ||
77 | // strip any path, we don't know the structure in the zip | ||
78 | if(QFileInfo(contents.at(index)).baseName() == m_lang) { | ||
79 | break; | ||
80 | } | ||
81 | } | ||
82 | if(index < contents.size()) { | ||
83 | LOG_INFO() << "extracting strings file from zip"; | ||
84 | // extract strings | ||
85 | QTemporaryFile stringsfile; | ||
86 | stringsfile.open(); | ||
87 | QString sfn = stringsfile.fileName(); | ||
88 | // ZipUtil::extractArchive() only compares the filename. | ||
89 | if(z.extractArchive(sfn, QFileInfo(contents.at(index)).fileName())) { | ||
90 | emit logItem(tr("Extracted voice strings from installation"), LOGINFO); | ||
91 | |||
92 | stringsfile.seek(0); | ||
93 | QByteArray data = stringsfile.readAll(); | ||
94 | const char* buf = data.constData(); | ||
95 | // check file header | ||
96 | // header (4 bytes): cookie = 9a, version = 06, targetid, options | ||
97 | // subheader for each user. Only "core" for now. | ||
98 | // subheader (6 bytes): count (2bytes), size (2bytes), offset (2bytes) | ||
99 | if(buf[0] != (char)0x9a || buf[1] != 0x06 || buf[2] != m_targetid) { | ||
100 | emit logItem(tr("Extracted voice strings incompatible"), LOGINFO); | ||
101 | } | ||
102 | else { | ||
103 | QMap<int, QString> voicestrings; | ||
104 | |||
105 | /* skip header */ | ||
106 | int idx = 10; | ||
107 | do { | ||
108 | unsigned int id = ((unsigned char)buf[idx])<<8 | ||
109 | | ((unsigned char)buf[idx+1]); | ||
110 | // need to use strlen here, since QString::size() | ||
111 | // returns number of characters, not bytes. | ||
112 | int len = strlen(&buf[idx + 2]); | ||
113 | voicestrings[id] = QString::fromUtf8(&buf[idx+2]); | ||
114 | idx += 2 + len + 1; | ||
115 | |||
116 | } while(idx < data.size()); | ||
117 | |||
118 | stringsfile.close(); | ||
119 | |||
120 | // create input file suitable for voicefont from strings. | ||
121 | QTemporaryFile voicefontlist; | ||
122 | voicefontlist.open(); | ||
123 | m_filename = voicefontlist.fileName(); | ||
124 | for(int i = 0; i < voicestrings.size(); ++i) { | ||
125 | QByteArray qba; | ||
126 | qba = QString("id: %1_%2\n") | ||
127 | .arg(voicestrings.keys().at(i) < 0x8000 ? "LANG" : "VOICE") | ||
128 | .arg(voicestrings.keys().at(i)).toUtf8(); | ||
129 | voicefontlist.write(qba); | ||
130 | qba = QString("voice: \"%1\"\n").arg( | ||
131 | voicestrings[voicestrings.keys().at(i)]).toUtf8(); | ||
132 | voicefontlist.write(qba); | ||
133 | } | ||
134 | voicefontlist.close(); | ||
135 | |||
136 | // everything successful, now create the actual voice file. | ||
137 | create(); | ||
138 | return true; | ||
139 | } | ||
140 | |||
141 | } | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | emit logItem(tr("Could not retrieve strings from installation, downloading"), LOGINFO); | ||
146 | // if either no zip with voice strings is found or something went wrong | ||
147 | // retrieving the necessary files we'll end up here, trying to get the | ||
148 | // genlang output as previously from the webserver. | ||
149 | |||
150 | // prepare download url | ||
151 | QString genlang = PlayerBuildInfo::instance()->value( | ||
152 | PlayerBuildInfo::GenlangUrl).toString(); | ||
153 | genlang.replace("%LANG%", m_lang); | ||
154 | genlang.replace("%TARGET%", target); | ||
155 | genlang.replace("%REVISION%", version); | ||
156 | genlang.replace("%FEATURES%", features); | ||
157 | QUrl genlangUrl(genlang); | ||
158 | LOG_INFO() << "downloading" << genlangUrl; | ||
159 | |||
160 | //download the correct genlang output | ||
161 | QTemporaryFile *downloadFile = new QTemporaryFile(this); | ||
162 | downloadFile->open(); | ||
163 | m_filename = downloadFile->fileName(); | ||
164 | downloadFile->close(); | ||
165 | // get the real file. | ||
166 | getter = new HttpGet(this); | ||
167 | getter->setFile(downloadFile); | ||
168 | |||
169 | connect(getter, &HttpGet::done, this, &VoiceFileCreator::downloadDone); | ||
170 | connect(getter, &HttpGet::dataReadProgress, this, &VoiceFileCreator::logProgress); | ||
171 | connect(this, &VoiceFileCreator::aborted, getter, &HttpGet::abort); | ||
172 | emit logItem(tr("Downloading voice info..."),LOGINFO); | ||
173 | getter->getFile(genlangUrl); | ||
174 | return true; | ||
175 | } | ||
176 | |||
177 | |||
178 | void VoiceFileCreator::downloadDone(bool error) | ||
179 | { | ||
180 | LOG_INFO() << "download done, error:" << error; | ||
181 | |||
182 | // update progress bar | ||
183 | emit logProgress(1,1); | ||
184 | if(getter->httpResponse() != 200 && !getter->isCached()) { | ||
185 | emit logItem(tr("Download error: received HTTP error %1.") | ||
186 | .arg(getter->httpResponse()),LOGERROR); | ||
187 | emit done(true); | ||
188 | return; | ||
189 | } | ||
190 | |||
191 | if(getter->isCached()) | ||
192 | emit logItem(tr("Cached file used."), LOGINFO); | ||
193 | if(error) | ||
194 | { | ||
195 | emit logItem(tr("Download error: %1").arg(getter->errorString()),LOGERROR); | ||
196 | emit done(true); | ||
197 | return; | ||
198 | } | ||
199 | else | ||
200 | emit logItem(tr("Download finished."),LOGINFO); | ||
201 | |||
202 | QCoreApplication::processEvents(); | ||
203 | create(); | ||
204 | } | ||
205 | |||
206 | |||
207 | void VoiceFileCreator::create(void) | ||
208 | { | ||
209 | //open downloaded file | ||
210 | QFile genlang(m_filename); | ||
211 | if(!genlang.open(QIODevice::ReadOnly)) | ||
212 | { | ||
213 | emit logItem(tr("failed to open downloaded file"),LOGERROR); | ||
214 | emit done(true); | ||
215 | return; | ||
216 | } | ||
217 | |||
218 | //read in downloaded file | ||
219 | emit logItem(tr("Reading strings..."),LOGINFO); | ||
220 | QTextStream in(&genlang); | ||
221 | #if QT_VERSION < 0x060000 | ||
222 | in.setCodec("UTF-8"); | ||
223 | #else | ||
224 | in.setEncoding(QStringConverter::Utf8); | ||
225 | #endif | ||
226 | QString id, voice; | ||
227 | bool idfound = false; | ||
228 | bool voicefound=false; | ||
229 | bool useCorrection = RbSettings::value(RbSettings::UseTtsCorrections).toBool(); | ||
230 | while (!in.atEnd()) | ||
231 | { | ||
232 | QString line = in.readLine(); | ||
233 | if(line.contains("id:")) //ID found | ||
234 | { | ||
235 | id = line.remove("id:").remove('"').trimmed(); | ||
236 | idfound = true; | ||
237 | } | ||
238 | else if(line.contains("voice:")) // voice found | ||
239 | { | ||
240 | voice = line.remove("voice:").remove('"').trimmed(); | ||
241 | voice = voice.remove("<").remove(">"); | ||
242 | voicefound=true; | ||
243 | } | ||
244 | |||
245 | if(idfound && voicefound) | ||
246 | { | ||
247 | TalkGenerator::TalkEntry entry; | ||
248 | entry.toSpeak = voice; | ||
249 | entry.wavfilename = m_path + "/" + id + ".wav"; | ||
250 | //voicefont wants them with .mp3 extension | ||
251 | entry.talkfilename = m_path + "/" + id + ".mp3"; | ||
252 | entry.voiced = false; | ||
253 | entry.encoded = false; | ||
254 | if(id == "VOICE_PAUSE") | ||
255 | { | ||
256 | QFile::copy(":/builtin/VOICE_PAUSE.wav",m_path + "/VOICE_PAUSE.wav"); | ||
257 | entry.wavfilename = m_path + "/VOICE_PAUSE.wav"; | ||
258 | entry.voiced = true; | ||
259 | m_talkList.append(entry); | ||
260 | } | ||
261 | else if(entry.toSpeak.isEmpty()) { | ||
262 | LOG_WARNING() << "Empty voice string for ID" << id; | ||
263 | } | ||
264 | else { | ||
265 | m_talkList.append(entry); | ||
266 | } | ||
267 | idfound=false; | ||
268 | voicefound=false; | ||
269 | } | ||
270 | } | ||
271 | genlang.close(); | ||
272 | |||
273 | // check for empty list | ||
274 | if(m_talkList.size() == 0) | ||
275 | { | ||
276 | emit logItem(tr("The downloaded file was empty!"),LOGERROR); | ||
277 | emit done(true); | ||
278 | return; | ||
279 | } | ||
280 | |||
281 | // generate files | ||
282 | { | ||
283 | TalkGenerator generator(this); | ||
284 | // set language for string correction. If not set no correction will be made. | ||
285 | if(useCorrection) | ||
286 | generator.setLang(m_lang); | ||
287 | connect(&generator, &TalkGenerator::done, this, &VoiceFileCreator::done); | ||
288 | connect(&generator, &TalkGenerator::logItem, this, &VoiceFileCreator::logItem); | ||
289 | connect(&generator, &TalkGenerator::logProgress, this, &VoiceFileCreator::logProgress); | ||
290 | connect(this, &VoiceFileCreator::aborted, &generator, &TalkGenerator::abort); | ||
291 | |||
292 | if(generator.process(&m_talkList, m_wavtrimThreshold) == TalkGenerator::eERROR) | ||
293 | { | ||
294 | cleanup(); | ||
295 | emit logProgress(0,1); | ||
296 | emit done(true); | ||
297 | return; | ||
298 | } | ||
299 | } | ||
300 | |||
301 | //make voicefile | ||
302 | emit logItem(tr("Creating voicefiles..."),LOGINFO); | ||
303 | FILE* ids2 = fopen(m_filename.toLocal8Bit(), "r"); | ||
304 | if (ids2 == nullptr) | ||
305 | { | ||
306 | cleanup(); | ||
307 | emit logItem(tr("Error opening downloaded file"),LOGERROR); | ||
308 | emit done(true); | ||
309 | return; | ||
310 | } | ||
311 | |||
312 | FILE* output = fopen(QString(m_mountpoint + "/.rockbox/langs/" + m_lang | ||
313 | + ".voice").toLocal8Bit(), "wb"); | ||
314 | if (output == nullptr) | ||
315 | { | ||
316 | cleanup(); | ||
317 | fclose(ids2); | ||
318 | emit logItem(tr("Error opening output file"),LOGERROR); | ||
319 | emit done(true); | ||
320 | return; | ||
321 | } | ||
322 | |||
323 | LOG_INFO() << "Running voicefont, format" << m_voiceformat; | ||
324 | voicefont(ids2,m_targetid,m_path.toLocal8Bit().data(), output, m_voiceformat); | ||
325 | // ids2 and output are closed by voicefont(). | ||
326 | |||
327 | //cleanup | ||
328 | cleanup(); | ||
329 | |||
330 | // Add Voice file to the install log | ||
331 | QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, nullptr); | ||
332 | installlog.beginGroup(QString("Voice (self created, %1)").arg(m_lang)); | ||
333 | installlog.setValue("/.rockbox/langs/" + m_lang + ".voice", m_versionstring); | ||
334 | installlog.endGroup(); | ||
335 | installlog.sync(); | ||
336 | |||
337 | emit logProgress(1,1); | ||
338 | emit logItem(tr("successfully created."),LOGOK); | ||
339 | |||
340 | emit done(false); | ||
341 | } | ||
342 | |||
343 | //! \brief Cleans up Files potentially left in the temp dir | ||
344 | //! | ||
345 | void VoiceFileCreator::cleanup() | ||
346 | { | ||
347 | emit logItem(tr("Cleaning up..."),LOGINFO); | ||
348 | |||
349 | for(int i=0; i < m_talkList.size(); i++) | ||
350 | { | ||
351 | if(QFile::exists(m_talkList[i].wavfilename)) | ||
352 | QFile::remove(m_talkList[i].wavfilename); | ||
353 | if(QFile::exists(m_talkList[i].talkfilename)) | ||
354 | QFile::remove(m_talkList[i].talkfilename); | ||
355 | |||
356 | QCoreApplication::processEvents(); | ||
357 | } | ||
358 | emit logItem(tr("Finished"),LOGINFO); | ||
359 | |||
360 | return; | ||
361 | } | ||
362 | |||