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