File indexing completed on 2024-04-28 16:06:19

0001 /* AUDEX CDDA EXTRACTOR
0002  * SPDX-FileCopyrightText: Copyright (C) 2007 Marco Nelles
0003  * <https://userbase.kde.org/Audex>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "schemeparser.h"
0009 
0010 #include <QDebug>
0011 #include <QStandardPaths>
0012 
0013 #if QT_VERSION >= 0x060000
0014 #define IS_TRUE(val) (val.typeId() == QMetaType::Bool && val.toBool())
0015 #define IS_INT(val)                                                                                                                                            \
0016     (val.typeId() == QMetaType::Int || val.typeId() == QMetaType::UInt || val.typeId() == QMetaType::LongLong || val.typeId() == QMetaType::ULongLong)
0017 #define IS_DATETIME(val) (val.typeId() == QMetaType::QDateTime || val.typeId() == QMetaType::QDate || val.typeId() == QMetaType::QTime)
0018 #endif
0019 
0020 #if QT_VERSION < 0x060000
0021 #define IS_TRUE(val) (val.type() == QVariant::Bool && val.toBool())
0022 #define IS_INT(val) (val.type() == QVariant::Int || val.type() == QVariant::UInt || val.type() == QVariant::LongLong || val.type() == QVariant::ULongLong)
0023 #define IS_DATETIME(val) (val.type() == QVariant::DateTime || val.type() == QVariant::Date || val.type() == QVariant::Time)
0024 #endif
0025 
0026 SchemeParser::SchemeParser(QObject *parent)
0027     : QObject(parent)
0028 {
0029     Q_UNUSED(parent);
0030 }
0031 
0032 SchemeParser::~SchemeParser()
0033 {
0034 }
0035 
0036 const QString SchemeParser::parseScheme(const QString &scheme, const Placeholders &placeholders, PlaceholdersParameters *placeholders_parameters)
0037 {
0038     p_error_string.clear();
0039 
0040     QString result;
0041 
0042     bool region_placeholdername = false;
0043     int index_placeholdername = 0;
0044     QString placeholdername;
0045 
0046     bool region_in_braces = false;
0047 
0048     bool region_placeholdername_in_braces = false;
0049     bool region_parameters_in_braces = false;
0050 
0051     QString parameters_string;
0052 
0053     int index = 0;
0054     while (index < scheme.length()) {
0055         QChar c = scheme[index];
0056 
0057         if (region_in_braces) {
0058             if (region_placeholdername_in_braces) {
0059                 if (c.isLetterOrNumber() || c == QChar('_')) {
0060                     placeholdername.append(c);
0061                 } else if (c == '}') {
0062                     if (placeholders.contains(placeholdername)) {
0063                         result.append(placeholders[placeholdername].toString());
0064                     }
0065                     if (placeholders_parameters)
0066                         placeholders_parameters->insert(placeholdername, Parameters());
0067                     region_placeholdername_in_braces = false;
0068                     region_in_braces = false;
0069                     placeholdername.clear();
0070                 } else if (c.isSpace()) {
0071                     if (!placeholdername.isEmpty()) {
0072                         region_placeholdername_in_braces = false;
0073                         region_parameters_in_braces = true;
0074                     }
0075                 } else {
0076                     p_error_string = i18n("Illegal character found at index %1: %2").arg(index).arg(c);
0077                     return QString();
0078                 }
0079 
0080             } else if (region_parameters_in_braces) {
0081                 if (c == '}') {
0082                     Parameters parameters(parameters_string, QChar(' '));
0083 
0084                     if (parameters.error()) {
0085                         p_error_string = parameters.errorString();
0086                         return QString();
0087                     }
0088 
0089                     if (placeholders_parameters)
0090                         placeholders_parameters->insert(placeholdername, parameters);
0091 
0092                     if (placeholders.contains(placeholdername)) {
0093                         QVariant value = placeholders[placeholdername];
0094 
0095                         KeyList keylist = parameters.keys();
0096                         for (int i = 0; i < keylist.size(); ++i) {
0097                             const QString key = keylist.at(i);
0098 
0099                             if (key == "uppercase" && IS_TRUE(parameters.value("uppercase")))
0100                                 value = value.toString().toUpper();
0101 
0102                             if (key == "lowercase" && IS_TRUE(parameters.value("lowercase")))
0103                                 value = value.toString().toLower();
0104 
0105                             if (key == "left" && IS_INT(parameters.value("left"))) {
0106                                 int left = parameters.value("left").toInt();
0107                                 if (left > 0)
0108                                     value = value.toString().left(left);
0109                             }
0110 
0111                             if (key == "underscores" && IS_TRUE(parameters.value("underscores")))
0112                                 value = replace_spaces_with_underscores(value.toString());
0113 
0114                             if (key == "fat32compatible" && IS_TRUE(parameters.value("fat32compatible")))
0115                                 value = make_fat32_compatible(value.toString());
0116 
0117                             if ((key == "replace_char_list" && IS_TRUE(parameters.value("replace_char_list")))
0118                                 || (key == "replace_chars" && IS_TRUE(parameters.value("replace_chars")))) {
0119                                 if (parameters.contains("replace_char_list_from") && parameters.contains("replace_char_list_to")
0120                                     && parameters.value("replace_char_list_from").toString().length()
0121                                         == parameters.value("replace_char_list_to").toString().length())
0122                                     value = QVariant(replace_char_list(parameters.value("replace_char_list_from").toString(),
0123                                                                        parameters.value("replace_char_list_to").toString(),
0124                                                                        value.toString()));
0125                                 else
0126                                     p_error_string = i18n(
0127                                                          "replace_char_list parameter needs two more parameters replace_char_list_from and "
0128                                                          "replace_char_list_to with equal size.")
0129                                                          .arg(index)
0130                                                          .arg(c);
0131                             }
0132 
0133                             if (key == "length" && IS_INT(value) && IS_INT(parameters.value("length"))) {
0134                                 int number = value.toInt();
0135                                 int l = parameters.value("length").toInt();
0136                                 QChar fc = '0';
0137                                 if (parameters.contains("fillchar") && !parameters.value("fillchar").toString().isEmpty())
0138                                     fc = parameters.value("fillchar").toString().at(0);
0139                                 value = QString("%1").arg(number, l, 10, fc);
0140                             }
0141 
0142                             if (key == "iec" && IS_INT(value)) {
0143                                 QChar iec;
0144                                 if (!parameters.value("iec").toString().isEmpty())
0145                                     iec = parameters.value("iec").toString().at(0).toLower();
0146                                 if ((iec != 'b') && (iec != 'k') && (iec != 'm') && (iec != 'g'))
0147                                     iec = 'm';
0148                                 int p = 2;
0149                                 if (parameters.contains("precision"))
0150                                     p = parameters.value("precision").toInt();
0151                                 if (p < 1)
0152                                     p = 2;
0153                                 if (iec == 'b')
0154                                     value = QString("%1").arg(value.toLongLong() / 1.0f, 0, 'f', p);
0155                                 else if (iec == 'k')
0156                                     value = QString("%1").arg(value.toLongLong() / 1024.0f, 0, 'f', p);
0157                                 else if (iec == 'm')
0158                                     value = QString("%1").arg(value.toLongLong() / (1024.0f * 1024.0f), 0, 'f', p);
0159                                 else if (iec == 'g')
0160                                     value = QString("%1").arg(value.toLongLong() / (1024.0f * 1024.0f * 1024.0f), 0, 'f', p);
0161                             }
0162 
0163                             if (key == "format_datetime" && IS_DATETIME(value)) {
0164                                 QString locale_name;
0165                                 if (!parameters.value("locale").toString().isEmpty())
0166                                     locale_name = parameters.value("locale").toString();
0167                                 QString format;
0168                                 if (!parameters.value("format_datetime").toString().isEmpty())
0169                                     format = parameters.value("format_datetime").toString();
0170                                 if (!parameters.value("format").toString().isEmpty())
0171                                     format = parameters.value("format").toString();
0172                                 if (format.isEmpty()) {
0173                                     if (!locale_name.isEmpty()) {
0174                                         QLocale locale(locale_name);
0175                                         value = locale.toString(value.toDateTime());
0176                                     } else {
0177                                         value = value.toDateTime().toString();
0178                                     }
0179                                 } else {
0180                                     if (!locale_name.isEmpty()) {
0181                                         QLocale locale(locale_name);
0182                                         value = locale.toString(value.toDateTime(), format);
0183                                     } else {
0184                                         value = value.toDateTime().toString(format);
0185                                     }
0186                                 }
0187                             }
0188 
0189                             if (key == "pre" || key == "preparam") {
0190                                 QString pre;
0191                                 if (key == "preparam")
0192                                     pre = parameters.value("preparam").toString();
0193                                 else
0194                                     pre = parameters.value("pre").toString();
0195                                 value = QString("%1%2").arg(pre).arg(value.toString());
0196                             }
0197 
0198                             if (key == "post" || key == "postparam") {
0199                                 QString post;
0200                                 if (key == "postparam")
0201                                     post = parameters.value("postparam").toString();
0202                                 else
0203                                     post = parameters.value("post").toString();
0204                                 value = QString("%1%2").arg(value.toString()).arg(post);
0205                             }
0206                         }
0207 
0208                         result.append(value.toString());
0209 
0210                         region_parameters_in_braces = false;
0211                         region_in_braces = false;
0212                         placeholdername.clear();
0213 
0214                         parameters_string.clear();
0215                     }
0216 
0217                 } else {
0218                     parameters_string.append(c);
0219                 }
0220             }
0221 
0222         } else if (region_placeholdername) {
0223             if (c == '{' && index_placeholdername == 0) {
0224                 region_in_braces = true;
0225                 region_placeholdername_in_braces = true;
0226                 region_placeholdername = false;
0227                 placeholdername.clear();
0228                 index_placeholdername = 0;
0229             } else {
0230                 if (!c.isLetterOrNumber() && c != QChar('_')) {
0231                     if (placeholders.contains(placeholdername)) {
0232                         result.append(placeholders[placeholdername].toString());
0233                     }
0234                     if (placeholders_parameters)
0235                         placeholders_parameters->insert(placeholdername, Parameters());
0236                     region_placeholdername = false;
0237                     placeholdername.clear();
0238                     index_placeholdername = 0;
0239                     result.append(c);
0240                 } else {
0241                     placeholdername.append(c);
0242                     ++index_placeholdername;
0243                 }
0244             }
0245 
0246         } else {
0247             switch (c.unicode()) {
0248             case '$':
0249                 region_placeholdername = true;
0250                 break;
0251             case '\\':
0252                 if (index < scheme.length() - 1 && scheme[index + 1] == '$') {
0253                     result.append('$');
0254                     ++index;
0255                 }
0256                 break;
0257             default:
0258                 result.append(c);
0259             }
0260         }
0261 
0262         ++index;
0263     }
0264 
0265     if (placeholders.contains(placeholdername)) {
0266         result.append(placeholders[placeholdername].toString());
0267     }
0268 
0269     if (placeholders_parameters && !placeholdername.isEmpty())
0270         placeholders_parameters->insert(placeholdername, Parameters());
0271 
0272     if (region_in_braces) {
0273         p_error_string = i18n("Unclosed brace.");
0274         return QString();
0275     }
0276 
0277     return result;
0278 }
0279 
0280 const QString SchemeParser::parsePerTrackFilenameScheme(const QString &scheme,
0281                                                         const int trackno,
0282                                                         const int cdno,
0283                                                         const int trackoffset,
0284                                                         const int nooftracks,
0285                                                         const QString &artist,
0286                                                         const QString &title,
0287                                                         const QString &tartist,
0288                                                         const QString &ttitle,
0289                                                         const QString &date,
0290                                                         const QString &genre,
0291                                                         const QString &isrc,
0292                                                         const QString &suffix,
0293                                                         const bool fat32compatible,
0294                                                         const bool replacespaceswithunderscores,
0295                                                         const bool twodigitstracknum)
0296 {
0297     Placeholders placeholders;
0298 
0299     placeholders.insert(VAR_ALBUM_ARTIST, customize_placeholder_value(artist, fat32compatible, true, replacespaceswithunderscores));
0300     placeholders.insert(VAR_ALBUM_TITLE, customize_placeholder_value(title, fat32compatible, true, replacespaceswithunderscores));
0301     placeholders.insert(VAR_TRACK_ARTIST, customize_placeholder_value(tartist, fat32compatible, true, replacespaceswithunderscores));
0302     placeholders.insert(VAR_TRACK_TITLE, customize_placeholder_value(ttitle, fat32compatible, true, replacespaceswithunderscores));
0303     placeholders.insert(VAR_DATE, customize_placeholder_value(date, fat32compatible, true, replacespaceswithunderscores));
0304     placeholders.insert(VAR_GENRE, customize_placeholder_value(genre, fat32compatible, true, replacespaceswithunderscores));
0305     placeholders.insert(VAR_ISRC, customize_placeholder_value(isrc, fat32compatible, true, replacespaceswithunderscores));
0306 
0307     placeholders.insert(VAR_SUFFIX, suffix);
0308 
0309     int tn = trackno;
0310     if (trackoffset > 1)
0311         tn += trackoffset;
0312 
0313     if (twodigitstracknum)
0314         placeholders.insert(VAR_TRACK_NO, QString("%1").arg(tn, 2, 10, QChar('0')));
0315     else
0316         placeholders.insert(VAR_TRACK_NO, tn);
0317 
0318     placeholders.insert(VAR_CD_NO, cdno);
0319     placeholders.insert(VAR_NO_OF_TRACKS, nooftracks);
0320 
0321     return parseScheme(scheme, placeholders);
0322 }
0323 
0324 const QString SchemeParser::parsePerTrackCommandScheme(const QString &scheme,
0325                                                        const QString &input,
0326                                                        const QString &output,
0327                                                        const int trackno,
0328                                                        const int cdno,
0329                                                        const int trackoffset,
0330                                                        const int nooftracks,
0331                                                        const QString &artist,
0332                                                        const QString &title,
0333                                                        const QString &tartist,
0334                                                        const QString &ttitle,
0335                                                        const QString &date,
0336                                                        const QString &genre,
0337                                                        const QString &isrc,
0338                                                        const QString &suffix,
0339                                                        const QImage &cover,
0340                                                        const QString &tmppath,
0341                                                        const QString &encoder,
0342                                                        const bool demomode)
0343 {
0344     Placeholders placeholders;
0345 
0346     placeholders.insert(VAR_INPUT_FILE, input);
0347     placeholders.insert(VAR_OUTPUT_FILE, output);
0348 
0349     placeholders.insert(VAR_ALBUM_ARTIST, customize_placeholder_value(artist));
0350     placeholders.insert(VAR_ALBUM_TITLE, customize_placeholder_value(title));
0351     placeholders.insert(VAR_TRACK_ARTIST, customize_placeholder_value(tartist));
0352     placeholders.insert(VAR_TRACK_TITLE, customize_placeholder_value(ttitle));
0353     placeholders.insert(VAR_DATE, customize_placeholder_value(date));
0354     placeholders.insert(VAR_GENRE, customize_placeholder_value(genre));
0355     placeholders.insert(VAR_ISRC, customize_placeholder_value(isrc));
0356     placeholders.insert(VAR_ENCODER, customize_placeholder_value(encoder));
0357 
0358     placeholders.insert(VAR_AUDEX, customize_placeholder_value(AUDEX_VERSION));
0359 
0360     placeholders.insert(VAR_SUFFIX, suffix);
0361 
0362     int tn = trackno;
0363     if (trackoffset > 1)
0364         tn += trackoffset;
0365     placeholders.insert(VAR_TRACK_NO, tn);
0366 
0367     placeholders.insert(VAR_CD_NO, cdno);
0368     placeholders.insert(VAR_NO_OF_TRACKS, nooftracks);
0369 
0370     PlaceholdersParameters placeholders_found;
0371 
0372     // Just parse now only to check if cover placeholder is used and retrieve cover parameters - parse again later on:
0373     QString result = parseScheme(scheme, placeholders, &placeholders_found);
0374 
0375     if (placeholders_found.contains(VAR_COVER_FILE)) {
0376         Parameters cover_parameters = placeholders_found.value(VAR_COVER_FILE, Parameters());
0377 
0378         QString format = STANDARD_EMBED_COVER_FORMAT;
0379         if (cover_parameters.contains("formatimage") && !cover_parameters.value("formatimage").toString().isEmpty())
0380             format = cover_parameters.value("formatimage").toString();
0381         if (cover_parameters.contains("format") && !cover_parameters.value("format").toString().isEmpty())
0382             format = cover_parameters.value("format").toString();
0383 
0384         if (!QImageReader::supportedImageFormats().contains(format.toLatin1().toUpper()))
0385             format = STANDARD_EMBED_COVER_FORMAT;
0386 
0387         QString cover_filepath;
0388         bool success = false;
0389         if (demomode) {
0390             cover_filepath = tmppath + "/cover-embedded." + format.toLower();
0391             success = true;
0392 
0393         } else {
0394             int x = -1;
0395             int y = -1;
0396             bool ok;
0397             if (cover_parameters.contains("x")) {
0398                 // when *ok is false, QString::toInt() often return 0
0399                 x = cover_parameters.value("x").toInt(&ok);
0400                 if (!ok)
0401                     x = -1;
0402             }
0403             if (cover_parameters.contains("y")) {
0404                 // when *ok is false, QString::toInt() often return 0
0405                 x = cover_parameters.value("y").toInt(&ok);
0406                 if (!ok)
0407                     y = -1;
0408             }
0409 
0410             cover_filepath = tmppath + "/cover-embedded." + format.toLower();
0411 
0412             if (!QFileInfo::exists(cover_filepath)) {
0413                 if (cover.isNull()) {
0414                     success = QImage(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("audex/images/cdcase_wo_latches.png")))
0415                                   .scaledToWidth(STANDARD_EMBED_COVER_WIDTH, Qt::SmoothTransformation)
0416                                   .save(cover_filepath, format.toLatin1());
0417                 } else {
0418                     if (x != -1 && y != -1)
0419                         success = cover.scaled(QSize(x, y), Qt::IgnoreAspectRatio, Qt::SmoothTransformation).save(cover_filepath);
0420                     else
0421                         success = cover.scaledToWidth(STANDARD_EMBED_COVER_WIDTH, Qt::SmoothTransformation).save(cover_filepath);
0422                 }
0423 
0424                 if (success) {
0425                     qDebug() << "Successfully created temporary cover file" << cover_filepath << "(" << QFileInfo(cover_filepath).size() / 1024 << "KiB)";
0426                 } else {
0427                     p_error_string = i18n("Could not create temporary cover file: %1").arg(cover_filepath);
0428                     qDebug() << "WARNING! Could not create temporary cover file" << cover_filepath;
0429                 }
0430             } else {
0431                 success = true;
0432             }
0433         }
0434 
0435         if (success)
0436             placeholders.insert(VAR_COVER_FILE, cover_filepath);
0437     }
0438 
0439     return parseScheme(scheme, placeholders);
0440 }
0441 
0442 const QString SchemeParser::parseFilenameScheme(const QString &text,
0443                                                 const int cdno,
0444                                                 const int nooftracks,
0445                                                 const QString &artist,
0446                                                 const QString &title,
0447                                                 const QString &date,
0448                                                 const QString &genre,
0449                                                 const QString &suffix,
0450                                                 const bool fat32compatible)
0451 {
0452     Placeholders placeholders;
0453 
0454     placeholders.insert(VAR_ALBUM_ARTIST, customize_placeholder_value(artist, fat32compatible, false));
0455     placeholders.insert(VAR_ALBUM_TITLE, customize_placeholder_value(title, fat32compatible, false));
0456     placeholders.insert(VAR_DATE, customize_placeholder_value(date, fat32compatible, false));
0457     placeholders.insert(VAR_GENRE, customize_placeholder_value(genre, fat32compatible, false));
0458 
0459     placeholders.insert(VAR_SUFFIX, suffix);
0460 
0461     placeholders.insert(VAR_CD_NO, cdno);
0462     placeholders.insert(VAR_NO_OF_TRACKS, nooftracks);
0463 
0464     return parseScheme(text, placeholders);
0465 }
0466 
0467 void SchemeParser::parseInfoTextScheme(QStringList &text,
0468                                        const QString &artist,
0469                                        const QString &title,
0470                                        const QString &date,
0471                                        const QString &genre,
0472                                        const QString &mcn,
0473                                        const quint32 discid,
0474                                        const qreal size,
0475                                        const int length,
0476                                        const int nooftracks)
0477 {
0478     Placeholders placeholders;
0479 
0480     placeholders.insert(VAR_ALBUM_ARTIST, artist);
0481     placeholders.insert(VAR_ALBUM_TITLE, title);
0482     placeholders.insert(VAR_DATE, date);
0483     placeholders.insert(VAR_GENRE, genre);
0484     placeholders.insert(VAR_MCN, mcn);
0485     placeholders.insert(VAR_AUDEX, AUDEX_VERSION);
0486 
0487     placeholders.insert(VAR_DISCID, QString("%1").arg(discid, 0, 16));
0488     placeholders.insert(VAR_CD_LENGTH, QString("%1:%2").arg(length / 60, 2, 10, QChar('0')).arg(length % 60, 2, 10, QChar('0')));
0489     placeholders.insert(VAR_CD_SIZE, size);
0490     placeholders.insert(VAR_NO_OF_TRACKS, nooftracks);
0491     placeholders.insert(VAR_TODAY, QDateTime::currentDateTime());
0492     placeholders.insert(VAR_NOW, QDateTime::currentDateTime());
0493     placeholders.insert(VAR_LINEBREAK, QString("\n"));
0494 
0495     text = parseScheme(text.join('\n'), placeholders).split('\n');
0496 }
0497 
0498 const QString SchemeParser::make_compatible(const QString &string)
0499 {
0500     QString s = string;
0501     for (int i = 0; i < s.size(); i++) {
0502         switch (s[i].unicode()) {
0503         case '/':
0504         case '\\':
0505             s[i] = '_';
0506             break;
0507 
0508         case '"':
0509             s[i] = '\'';
0510             break;
0511 
0512         default:
0513             break;
0514         }
0515     }
0516     return s;
0517 }
0518 
0519 const QString SchemeParser::make_compatible_2(const QString &string)
0520 {
0521     QString s = string;
0522     s = s.replace('"', "\"");
0523     return s;
0524 }
0525 
0526 // remove \ / : * ? " < > |
0527 const QString SchemeParser::make_fat32_compatible(const QString &string)
0528 {
0529     QString s = string;
0530     for (int i = 0; i < s.size(); i++) {
0531         switch (s[i].unicode()) {
0532         case '\\':
0533         case '/':
0534         case ':':
0535         case '*':
0536         case '?':
0537         case '"':
0538         case '<':
0539         case '>':
0540         case '|':
0541             s[i] = '_';
0542             break;
0543 
0544         default:
0545             break;
0546         }
0547     }
0548     return s;
0549 }
0550 
0551 const QString SchemeParser::replace_spaces_with_underscores(const QString &string)
0552 {
0553     QString s = string;
0554     s.replace(' ', '_');
0555     return s;
0556 }
0557 
0558 const QString SchemeParser::replace_char_list(const QString &from, const QString &to, const QString &string)
0559 {
0560     qDebug() << "starting replacement for:" << string;
0561 
0562     if (from.count() != to.count()) {
0563         qDebug() << "Could not replace if list length are not equal";
0564         return string;
0565     }
0566 
0567     QString result = string;
0568     for (int i = 0; i < from.count(); i++) {
0569         result = result.replace(from.at(i), to.at(i));
0570     }
0571 
0572     qDebug() << "finished:" << result;
0573 
0574     return result;
0575 }