summaryrefslogtreecommitdiff
path: root/utils/rbutilqt/base/voicefile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rbutilqt/base/voicefile.cpp')
-rw-r--r--utils/rbutilqt/base/voicefile.cpp362
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
28VoiceFileCreator::VoiceFileCreator(QObject* parent) :QObject(parent)
29{
30 m_wavtrimThreshold=500;
31}
32
33void VoiceFileCreator::abort()
34{
35 m_abort = true;
36 emit aborted();
37}
38
39bool 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
178void 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
207void 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//!
345void 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