Warning, file /education/marble/tools/speaker-files/main.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2011 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 /** 0007 * Takes an .sqlite database with metadata about voice guidance speakers 0008 * and associated .zip files containing 64.ogg files and produces four files 0009 * for each speaker: 0010 * - a .tar.gz to be used by KDE Marble via a GHNS dialog 0011 * - a .zip to be downloaded by Marble users via edu.kde.org 0012 * - a .zip to be downloaded by TomTom users via edu.kde.org 0013 * - an .ogg speaker preview file 0014 * The archives contain the speaker files and some additional stuff (license, authors, ...) 0015 * 0016 * The structure of the .sqlite database is expected as follows: 0017 * One table called speakers with the following layout: 0018 * - id PRIMARY KEY 0019 * - name, email, nickname, gender, language, description, token VARCHAR 0020 * - created DATETIME 0021 * Additionally, the field gender is expected to be either "male" or "female" and language 0022 * to have the format "NAME (langcode)" 0023 * 0024 * Also creates a knewstuff .xml file with the metadata. 0025 * 0026 * Some processing is done by calling other tools, namely tar, unzip, zip, vorbisgain, viftool. 0027 * Make sure they're found in $PATH 0028 * 0029 */ 0030 0031 #include <QCoreApplication> 0032 #include <QString> 0033 #include <QDebug> 0034 #include <QFileInfo> 0035 #include <QDir> 0036 #include <QTemporaryFile> 0037 #include <QProcess> 0038 0039 #include <QSqlDatabase> 0040 #include <QSqlQuery> 0041 #include <QSqlError> 0042 0043 QStringList tomTomFiles() 0044 { 0045 QStringList result; 0046 result << "100.ogg"; 0047 result << "200.ogg"; 0048 result << "2ndLeft.ogg"; 0049 result << "2ndRight.ogg"; 0050 result << "300.ogg"; 0051 result << "3rdLeft.ogg"; 0052 result << "3rdRight.ogg"; 0053 result << "400.ogg"; 0054 result << "500.ogg"; 0055 result << "50.ogg"; 0056 result << "600.ogg"; 0057 result << "700.ogg"; 0058 result << "800.ogg"; 0059 result << "80.ogg"; 0060 result << "After.ogg"; 0061 result << "AhExitLeft.ogg"; 0062 result << "AhExit.ogg"; 0063 result << "AhExitRight.ogg"; 0064 result << "AhFerry.ogg"; 0065 result << "AhKeepLeft.ogg"; 0066 result << "AhKeepRight.ogg"; 0067 result << "AhLeftTurn.ogg"; 0068 result << "AhRightTurn.ogg"; 0069 result << "AhUTurn.ogg"; 0070 result << "Arrive.ogg"; 0071 result << "BearLeft.ogg"; 0072 result << "BearRight.ogg"; 0073 result << "Charge.ogg"; 0074 result << "Depart.ogg"; 0075 result << "KeepLeft.ogg"; 0076 result << "KeepRight.ogg"; 0077 result << "LnLeft.ogg"; 0078 result << "LnRight.ogg"; 0079 result << "Meters.ogg"; 0080 result << "MwEnter.ogg"; 0081 result << "MwExitLeft.ogg"; 0082 result << "MwExit.ogg"; 0083 result << "MwExitRight.ogg"; 0084 result << "RbBack.ogg"; 0085 result << "RbCross.ogg"; 0086 result << "RbExit1.ogg"; 0087 result << "RbExit2.ogg"; 0088 result << "RbExit3.ogg"; 0089 result << "RbExit4.ogg"; 0090 result << "RbExit5.ogg"; 0091 result << "RbExit6.ogg"; 0092 result << "RbLeft.ogg"; 0093 result << "RbRight.ogg"; 0094 result << "RoadEnd.ogg"; 0095 result << "SharpLeft.ogg"; 0096 result << "SharpRight.ogg"; 0097 result << "Straight.ogg"; 0098 result << "TakeFerry.ogg"; 0099 result << "Then.ogg"; 0100 result << "TryUTurn.ogg"; 0101 result << "TurnLeft.ogg"; 0102 result << "TurnRight.ogg"; 0103 result << "UTurn.ogg"; 0104 result << "Yards.ogg"; 0105 return result; 0106 } 0107 0108 QStringList marbleFiles() 0109 { 0110 QStringList result; 0111 result << "Marble.ogg"; 0112 result << "RouteCalculated.ogg"; 0113 result << "RouteDeviated.ogg"; 0114 result << "GpsFound.ogg"; 0115 result << "GpsLost.ogg"; 0116 return result; 0117 } 0118 0119 void usage( const QString &application ) 0120 { 0121 qDebug() << "Usage: " << application << " /path/to/input/directory /path/to/output/directory /path/to/newstuff.xml"; 0122 } 0123 0124 void extract( const QString &zip, const QString &output ) 0125 { 0126 QProcess::execute( "unzip", QStringList() << "-q" << "-j" << "-d" << output << zip ); 0127 } 0128 0129 void normalize( const QString &output ) 0130 { 0131 QProcess vorbisgain; 0132 vorbisgain.setWorkingDirectory( output ); 0133 vorbisgain.start( "vorbisgain", QStringList() << "-a" << tomTomFiles() << marbleFiles() ); 0134 vorbisgain.waitForFinished(); 0135 } 0136 0137 void createLegalFiles( const QString &directory, const QString &name, const QString &email ) 0138 { 0139 QDir input( directory ); 0140 QFile authorsFile( input.filePath( "AUTHORS.txt" ) ); 0141 if ( authorsFile.open( QFile::WriteOnly | QFile::Truncate ) ) { 0142 QTextStream stream( &authorsFile ); 0143 stream << name << " <" << email << ">"; 0144 } 0145 authorsFile.close(); 0146 0147 QFile licenseFile( input.filePath( "LICENSE.txt" ) ); 0148 if ( licenseFile.open( QFile::WriteOnly | QFile::Truncate ) ) { 0149 QTextStream stream( &licenseFile ); 0150 stream << "The ogg files in this directory are licensed under the creative commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) license. "; 0151 stream << "See https://creativecommons.org/licenses/by-sa/3.0/ and the file CC-BY-SA-3.0 in this directory."; 0152 } 0153 licenseFile.close(); 0154 0155 QFile installFile( input.filePath( "INSTALL.txt" ) ); 0156 if ( installFile.open( QFile::WriteOnly | QFile::Truncate ) ) { 0157 QTextStream stream( &installFile ); 0158 stream << "To install this voice guidance speaker in Marble, copy the entire directory to the audio/speakers/ directory in Marble's data path.\n\n"; 0159 stream << "For example, if this directory is called 'MySpeaker' and you want to use it on the Nokia N900, copy the directory with all files to /home/user/MyDocs/.local/share/marble/audio/speakers/MySpeaker\n\n"; 0160 stream << "Afterwards start Marble on the N900 and press the routing info box (four icons on the bottom) for two seconds with the pen. Enter the configuration dialog and choose the 'MySpeaker' speaker.\n\n"; 0161 stream << "Check https://edu.kde.org/marble/speakers.php for updates and more speakers."; 0162 } 0163 installFile.close(); 0164 } 0165 0166 void convertToNewStuffFormat( const QString &input, const QString &output ) 0167 { 0168 QDir inputDirectory( input ); 0169 QStringList files; 0170 files << tomTomFiles() << marbleFiles(); 0171 files << "AUTHORS.txt" << "INSTALL.txt" << "LICENSE.txt"; 0172 QStringList arguments; 0173 arguments << "-czf" << output; 0174 for( const QString &file: files ) { 0175 arguments << inputDirectory.filePath( file ); 0176 } 0177 arguments << "/usr/share/common-licenses/CC-BY-SA-3.0"; 0178 0179 QProcess::execute( "tar", arguments ); 0180 } 0181 0182 void convertToMarbleFormat( const QString &input, const QString &output ) 0183 { 0184 QDir inputDirectory( input ); 0185 QStringList files; 0186 files << tomTomFiles() << marbleFiles(); 0187 files << "AUTHORS.txt" << "INSTALL.txt" << "LICENSE.txt"; 0188 QStringList arguments; 0189 arguments << "-q" << "-j" << output; 0190 for( const QString &file: files ) { 0191 arguments << inputDirectory.filePath( file ); 0192 } 0193 arguments << "/usr/share/common-licenses/CC-BY-SA-3.0"; 0194 0195 QProcess::execute( "zip", arguments ); 0196 } 0197 0198 void convertToTomTomFormat( const QString &input, const QString &output, const QString &nick, const QString &simpleNick, int index, bool male, const QString &lang ) 0199 { 0200 QStringList arguments; 0201 QString const prefix = input + QLatin1String("/data") + QString::number( index ); 0202 QString const vif = prefix + QLatin1String(".vif"); 0203 QString const chk = prefix + QLatin1String(".chk"); 0204 arguments << "join" << QString::number( index ) << nick << vif; 0205 QProcess viftool; 0206 viftool.setWorkingDirectory( input ); 0207 viftool.execute( "viftool", arguments ); 0208 0209 QFile vifFile( vif ); 0210 if ( vifFile.open( QFile::WriteOnly | QFile::Truncate ) ) { 0211 QTextStream stream( &vifFile ); 0212 stream << nick << "\n"; // Name 0213 stream << ( male ? 2 : 1 ) << "\n"; // gender index 0214 /** @todo: flag, language index */ 0215 stream << 2 << "\n"; // Language index 0216 stream << 114 << "\n"; // Flag index 0217 stream << "1\n"; // Version number 0218 } 0219 vifFile.close(); 0220 0221 QDir inputDirectory( input ); 0222 QStringList files; 0223 files << vif << chk; 0224 files << "AUTHORS.txt" << "LICENSE.txt"; 0225 QStringList zipArguments; 0226 zipArguments << "-q" << "-j" << ( output + QLatin1Char('/') + lang + QLatin1Char('-') + simpleNick + QLatin1String("-TomTom.zip") ); 0227 for( const QString &file: files ) { 0228 QString const filePath = inputDirectory.filePath( file ); 0229 zipArguments << filePath; 0230 } 0231 zipArguments << "/usr/share/common-licenses/CC-BY-SA-3.0"; 0232 0233 QProcess::execute( "zip", zipArguments ); 0234 } 0235 0236 int process( const QDir &input, const QDir &output, const QString &xml ) 0237 { 0238 QSqlDatabase database = QSqlDatabase::addDatabase( "QSQLITE" ); 0239 database.setDatabaseName( input.filePath( "speakers.db" ) ); 0240 if ( !database.open() ) { 0241 qDebug() << "Failed to connect to database " << input.filePath( "speakers.db" ); 0242 return 3; 0243 } 0244 0245 output.mkdir( "files.kde.org" ); 0246 QSqlQuery query( "SELECT * FROM speakers ORDER BY Id" ); 0247 0248 QFile xmlFile( xml ); 0249 if ( !xmlFile.open( QFile::WriteOnly | QFile::Truncate ) ) { 0250 qDebug() << "Failed to write to " << xmlFile.fileName(); 0251 return 3; 0252 } 0253 0254 QTextStream xmlOut( &xmlFile ); 0255 xmlOut << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 0256 xmlOut << "<!DOCTYPE knewstuff SYSTEM \"knewstuff.dtd\">\n"; 0257 xmlOut << "<?xml-stylesheet type=\"text/xsl\" href=\"speakers.xsl\" ?>\n"; 0258 xmlOut << "<knewstuff>\n"; 0259 0260 int index = 71; 0261 while (query.next()) { 0262 QString const name = query.value(1).toString(); 0263 QString const email = query.value(2).toString(); 0264 QString const nick = query.value(3).toString(); 0265 QString const gender = query.value(4).toString(); 0266 QString const language = query.value(5).toString(); 0267 QString const lang = language.mid(0, language.indexOf(QLatin1Char('(')) - 1).replace(QLatin1Char(' '), QLatin1Char('-')); 0268 QString const description = query.value(6).toString(); 0269 QString const token = query.value(7).toString(); 0270 QString const date = query.value(8).toString(); 0271 QString const zip = input.filePath( token ); 0272 QTemporaryFile tmpFile; 0273 tmpFile.open(); 0274 QString const extracted = tmpFile.fileName(); 0275 tmpFile.remove(); 0276 QDir::root().mkdir( extracted ); 0277 qDebug() << "Name: " << name; 0278 0279 QString const simpleNick = QString( nick ).replace( QLatin1Char(' '), QLatin1Char('-') ); 0280 QString const nickDir = output.filePath("files.kde.org") + QLatin1Char('/') + simpleNick; 0281 QDir::root().mkdir( nickDir ); 0282 extract( zip, extracted ); 0283 normalize( extracted ); 0284 createLegalFiles( extracted, name, email ); 0285 QFile::copy(extracted + QLatin1String("/Marble.ogg"), nickDir + QLatin1Char('/') + lang + QLatin1Char('-') + simpleNick + QLatin1String(".ogg")); 0286 convertToMarbleFormat(extracted, nickDir + QLatin1Char('/') + lang + QLatin1Char('-') + simpleNick + QLatin1String(".zip")); 0287 convertToTomTomFormat(extracted, nickDir, nick, simpleNick, index, gender == QLatin1String("male"), lang); 0288 convertToNewStuffFormat(extracted, nickDir + QLatin1Char('/') + lang + QLatin1Char('-') + simpleNick + QLatin1String(".tar.gz")); 0289 0290 xmlOut << " <stuff category=\"marble/data/audio\">\n"; 0291 xmlOut << " <name lang=\"en\">" << language << " - " << nick << " (" << gender << ")" << "</name>\n"; 0292 xmlOut << " <author>" << name << "</author>\n"; 0293 xmlOut << " <licence>CC-By-SA 3.0</licence>\n"; 0294 xmlOut << " <summary lang=\"en\">" << description << "</summary>\n"; 0295 xmlOut << " <version>0.1</version>\n"; 0296 xmlOut << " <releasedate>" << date << "</releasedate>\n"; 0297 xmlOut << " <preview lang=\"en\">http://edu.kde.org/marble/speaker-" << gender << ".png</preview>\n"; 0298 xmlOut << " <payload lang=\"en\">http://files.kde.org/marble/audio/speakers/" << simpleNick << "/" << lang << "-" << simpleNick << ".tar.gz</payload>\n"; 0299 xmlOut << " <payload lang=\"ogg\">http://files.kde.org/marble/audio/speakers/" << simpleNick << "/" << lang << "-" << simpleNick << ".ogg</payload>\n"; 0300 xmlOut << " <payload lang=\"zip\">http://files.kde.org/marble/audio/speakers/" << simpleNick << "/" << lang << "-" << simpleNick << ".zip</payload>\n"; 0301 xmlOut << " <payload lang=\"tomtom\">http://files.kde.org/marble/audio/speakers/" << simpleNick << "/" << lang << "-" << simpleNick << "-TomTom.zip</payload>\n"; 0302 xmlOut << " </stuff>\n"; 0303 0304 ++index; 0305 } 0306 0307 xmlOut << "</knewstuff>\n"; 0308 xmlFile.close(); 0309 return 0; 0310 } 0311 0312 int main(int argc, char *argv[]) 0313 { 0314 QCoreApplication a(argc, argv); 0315 0316 if ( argc < 4 ) { 0317 usage( argv[0] ); 0318 return 1; 0319 } 0320 0321 QFileInfo input( argv[1] ); 0322 if ( !input.exists() || !input.isDir() ) { 0323 qDebug() << "Incorrect input directory " << argv[1]; 0324 usage( argv[0] ); 0325 return 1; 0326 } 0327 0328 QFileInfo output( argv[2] ); 0329 if ( !output.exists() || !output.isWritable() ) { 0330 qDebug() << "Incorrect output directory " << argv[1]; 0331 usage( argv[0] ); 0332 return 1; 0333 } 0334 0335 QFileInfo xmlFile( argv[3] ); 0336 return process( QDir( input.absoluteFilePath() ), QDir( output.absoluteFilePath() ), xmlFile.absoluteFilePath() ); 0337 }