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 }