From c876d3bbefe0dc00c27ca0c12d29da5874946962 Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Wed, 15 Dec 2021 21:04:28 +0100 Subject: rbutil: Merge rbutil with utils folder. rbutil uses several components from the utils folder, and can be considered part of utils too. Having it in a separate folder is an arbitrary split that doesn't help anymore these days, so merge them. This also allows other utils to easily use libtools.make without the need to navigate to a different folder. Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21 --- utils/rbutilqt/base/voicefile.cpp | 362 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 utils/rbutilqt/base/voicefile.cpp (limited to 'utils/rbutilqt/base/voicefile.cpp') 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 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2007 by Dominik Wenger + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include "voicefile.h" +#include "utils.h" +#include "rockboxinfo.h" +#include "rbsettings.h" +#include "playerbuildinfo.h" +#include "ziputil.h" +#include "Logger.h" + +VoiceFileCreator::VoiceFileCreator(QObject* parent) :QObject(parent) +{ + m_wavtrimThreshold=500; +} + +void VoiceFileCreator::abort() +{ + m_abort = true; + emit aborted(); +} + +bool VoiceFileCreator::createVoiceFile() +{ + m_talkList.clear(); + m_abort = false; + emit logItem(tr("Starting Voicefile generation"),LOGINFO); + + // test if tempdir exists + if(!QDir(QDir::tempPath()+"/rbvoice/").exists()) + { + QDir(QDir::tempPath()).mkdir("rbvoice"); + } + m_path = QDir::tempPath() + "/rbvoice/"; + + // read rockbox-info.txt + RockboxInfo info(m_mountpoint); + if(!info.success()) + { + emit logItem(tr("could not find rockbox-info.txt"),LOGERROR); + emit done(true); + return false; + } + QString target = info.target(); + QString features = info.features(); + m_targetid = info.targetID().toInt(); + m_versionstring = info.version(); + m_voiceformat = info.voicefmt(); + QString version = m_versionstring.left(m_versionstring.indexOf("-")).remove("r"); + + // check if voicefile is present on target + QString fn = m_mountpoint + "/.rockbox/langs/voicestrings.zip"; + LOG_INFO() << "searching for zipped voicestrings at" << fn; + if(QFileInfo(fn).isFile()) { + // search for binary voice strings file in archive + ZipUtil z(this); + if(z.open(fn)) { + QStringList contents = z.files(); + int index; + for(index = 0; index < contents.size(); ++index) { + // strip any path, we don't know the structure in the zip + if(QFileInfo(contents.at(index)).baseName() == m_lang) { + break; + } + } + if(index < contents.size()) { + LOG_INFO() << "extracting strings file from zip"; + // extract strings + QTemporaryFile stringsfile; + stringsfile.open(); + QString sfn = stringsfile.fileName(); + // ZipUtil::extractArchive() only compares the filename. + if(z.extractArchive(sfn, QFileInfo(contents.at(index)).fileName())) { + emit logItem(tr("Extracted voice strings from installation"), LOGINFO); + + stringsfile.seek(0); + QByteArray data = stringsfile.readAll(); + const char* buf = data.constData(); + // check file header + // header (4 bytes): cookie = 9a, version = 06, targetid, options + // subheader for each user. Only "core" for now. + // subheader (6 bytes): count (2bytes), size (2bytes), offset (2bytes) + if(buf[0] != (char)0x9a || buf[1] != 0x06 || buf[2] != m_targetid) { + emit logItem(tr("Extracted voice strings incompatible"), LOGINFO); + } + else { + QMap voicestrings; + + /* skip header */ + int idx = 10; + do { + unsigned int id = ((unsigned char)buf[idx])<<8 + | ((unsigned char)buf[idx+1]); + // need to use strlen here, since QString::size() + // returns number of characters, not bytes. + int len = strlen(&buf[idx + 2]); + voicestrings[id] = QString::fromUtf8(&buf[idx+2]); + idx += 2 + len + 1; + + } while(idx < data.size()); + + stringsfile.close(); + + // create input file suitable for voicefont from strings. + QTemporaryFile voicefontlist; + voicefontlist.open(); + m_filename = voicefontlist.fileName(); + for(int i = 0; i < voicestrings.size(); ++i) { + QByteArray qba; + qba = QString("id: %1_%2\n") + .arg(voicestrings.keys().at(i) < 0x8000 ? "LANG" : "VOICE") + .arg(voicestrings.keys().at(i)).toUtf8(); + voicefontlist.write(qba); + qba = QString("voice: \"%1\"\n").arg( + voicestrings[voicestrings.keys().at(i)]).toUtf8(); + voicefontlist.write(qba); + } + voicefontlist.close(); + + // everything successful, now create the actual voice file. + create(); + return true; + } + + } + } + } + } + emit logItem(tr("Could not retrieve strings from installation, downloading"), LOGINFO); + // if either no zip with voice strings is found or something went wrong + // retrieving the necessary files we'll end up here, trying to get the + // genlang output as previously from the webserver. + + // prepare download url + QString genlang = PlayerBuildInfo::instance()->value( + PlayerBuildInfo::GenlangUrl).toString(); + genlang.replace("%LANG%", m_lang); + genlang.replace("%TARGET%", target); + genlang.replace("%REVISION%", version); + genlang.replace("%FEATURES%", features); + QUrl genlangUrl(genlang); + LOG_INFO() << "downloading" << genlangUrl; + + //download the correct genlang output + QTemporaryFile *downloadFile = new QTemporaryFile(this); + downloadFile->open(); + m_filename = downloadFile->fileName(); + downloadFile->close(); + // get the real file. + getter = new HttpGet(this); + getter->setFile(downloadFile); + + connect(getter, &HttpGet::done, this, &VoiceFileCreator::downloadDone); + connect(getter, &HttpGet::dataReadProgress, this, &VoiceFileCreator::logProgress); + connect(this, &VoiceFileCreator::aborted, getter, &HttpGet::abort); + emit logItem(tr("Downloading voice info..."),LOGINFO); + getter->getFile(genlangUrl); + return true; + } + + +void VoiceFileCreator::downloadDone(bool error) +{ + LOG_INFO() << "download done, error:" << error; + + // update progress bar + emit logProgress(1,1); + if(getter->httpResponse() != 200 && !getter->isCached()) { + emit logItem(tr("Download error: received HTTP error %1.") + .arg(getter->httpResponse()),LOGERROR); + emit done(true); + return; + } + + if(getter->isCached()) + emit logItem(tr("Cached file used."), LOGINFO); + if(error) + { + emit logItem(tr("Download error: %1").arg(getter->errorString()),LOGERROR); + emit done(true); + return; + } + else + emit logItem(tr("Download finished."),LOGINFO); + + QCoreApplication::processEvents(); + create(); +} + + +void VoiceFileCreator::create(void) +{ + //open downloaded file + QFile genlang(m_filename); + if(!genlang.open(QIODevice::ReadOnly)) + { + emit logItem(tr("failed to open downloaded file"),LOGERROR); + emit done(true); + return; + } + + //read in downloaded file + emit logItem(tr("Reading strings..."),LOGINFO); + QTextStream in(&genlang); +#if QT_VERSION < 0x060000 + in.setCodec("UTF-8"); +#else + in.setEncoding(QStringConverter::Utf8); +#endif + QString id, voice; + bool idfound = false; + bool voicefound=false; + bool useCorrection = RbSettings::value(RbSettings::UseTtsCorrections).toBool(); + while (!in.atEnd()) + { + QString line = in.readLine(); + if(line.contains("id:")) //ID found + { + id = line.remove("id:").remove('"').trimmed(); + idfound = true; + } + else if(line.contains("voice:")) // voice found + { + voice = line.remove("voice:").remove('"').trimmed(); + voice = voice.remove("<").remove(">"); + voicefound=true; + } + + if(idfound && voicefound) + { + TalkGenerator::TalkEntry entry; + entry.toSpeak = voice; + entry.wavfilename = m_path + "/" + id + ".wav"; + //voicefont wants them with .mp3 extension + entry.talkfilename = m_path + "/" + id + ".mp3"; + entry.voiced = false; + entry.encoded = false; + if(id == "VOICE_PAUSE") + { + QFile::copy(":/builtin/VOICE_PAUSE.wav",m_path + "/VOICE_PAUSE.wav"); + entry.wavfilename = m_path + "/VOICE_PAUSE.wav"; + entry.voiced = true; + m_talkList.append(entry); + } + else if(entry.toSpeak.isEmpty()) { + LOG_WARNING() << "Empty voice string for ID" << id; + } + else { + m_talkList.append(entry); + } + idfound=false; + voicefound=false; + } + } + genlang.close(); + + // check for empty list + if(m_talkList.size() == 0) + { + emit logItem(tr("The downloaded file was empty!"),LOGERROR); + emit done(true); + return; + } + + // generate files + { + TalkGenerator generator(this); + // set language for string correction. If not set no correction will be made. + if(useCorrection) + generator.setLang(m_lang); + connect(&generator, &TalkGenerator::done, this, &VoiceFileCreator::done); + connect(&generator, &TalkGenerator::logItem, this, &VoiceFileCreator::logItem); + connect(&generator, &TalkGenerator::logProgress, this, &VoiceFileCreator::logProgress); + connect(this, &VoiceFileCreator::aborted, &generator, &TalkGenerator::abort); + + if(generator.process(&m_talkList, m_wavtrimThreshold) == TalkGenerator::eERROR) + { + cleanup(); + emit logProgress(0,1); + emit done(true); + return; + } + } + + //make voicefile + emit logItem(tr("Creating voicefiles..."),LOGINFO); + FILE* ids2 = fopen(m_filename.toLocal8Bit(), "r"); + if (ids2 == nullptr) + { + cleanup(); + emit logItem(tr("Error opening downloaded file"),LOGERROR); + emit done(true); + return; + } + + FILE* output = fopen(QString(m_mountpoint + "/.rockbox/langs/" + m_lang + + ".voice").toLocal8Bit(), "wb"); + if (output == nullptr) + { + cleanup(); + fclose(ids2); + emit logItem(tr("Error opening output file"),LOGERROR); + emit done(true); + return; + } + + LOG_INFO() << "Running voicefont, format" << m_voiceformat; + voicefont(ids2,m_targetid,m_path.toLocal8Bit().data(), output, m_voiceformat); + // ids2 and output are closed by voicefont(). + + //cleanup + cleanup(); + + // Add Voice file to the install log + QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, nullptr); + installlog.beginGroup(QString("Voice (self created, %1)").arg(m_lang)); + installlog.setValue("/.rockbox/langs/" + m_lang + ".voice", m_versionstring); + installlog.endGroup(); + installlog.sync(); + + emit logProgress(1,1); + emit logItem(tr("successfully created."),LOGOK); + + emit done(false); +} + +//! \brief Cleans up Files potentially left in the temp dir +//! +void VoiceFileCreator::cleanup() +{ + emit logItem(tr("Cleaning up..."),LOGINFO); + + for(int i=0; i < m_talkList.size(); i++) + { + if(QFile::exists(m_talkList[i].wavfilename)) + QFile::remove(m_talkList[i].wavfilename); + if(QFile::exists(m_talkList[i].talkfilename)) + QFile::remove(m_talkList[i].talkfilename); + + QCoreApplication::processEvents(); + } + emit logItem(tr("Finished"),LOGINFO); + + return; +} + -- cgit v1.2.3