File indexing completed on 2024-05-19 04:55:59

0001 /**
0002  * \file importparser.cpp
0003  * Import parser.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 17 Sep 2003
0008  *
0009  * Copyright (C) 2003-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "importparser.h"
0028 #include <QCoreApplication>
0029 #include <QRegularExpression>
0030 #include "trackdata.h"
0031 #include "genres.h"
0032 
0033 /**
0034  * Constructor.
0035  */
0036 ImportParser::ImportParser() : m_trackIncrNr(0), m_trackIncrEnabled(false)
0037 {
0038 }
0039 
0040 /**
0041  * Get help text for format codes supported by setFormat().
0042  *
0043  * @param onlyRows if true only the tr elements are returned,
0044  *                 not the surrounding table
0045  *
0046  * @return help text.
0047  */
0048 QString ImportParser::getFormatToolTip(bool onlyRows)
0049 {
0050   QString str;
0051   if (!onlyRows) str += QLatin1String("<table>\n");
0052 
0053   str += QLatin1String("<tr><td>%s</td><td>%{title}</td><td>");
0054   str += QCoreApplication::translate("@default", "Title");
0055   str += QLatin1String("</td></tr>\n");
0056 
0057   str += QLatin1String("<tr><td>%l</td><td>%{album}</td><td>");
0058   str += QCoreApplication::translate("@default", "Album");
0059   str += QLatin1String("</td></tr>\n");
0060 
0061   str += QLatin1String("<tr><td>%a</td><td>%{artist}</td><td>");
0062   str += QCoreApplication::translate("@default", "Artist");
0063   str += QLatin1String("</td></tr>\n");
0064 
0065   str += QLatin1String("<tr><td>%c</td><td>%{comment}</td><td>");
0066   str += QCoreApplication::translate("@default", "Comment");
0067   str += QLatin1String("</td></tr>\n");
0068 
0069   str += QLatin1String("<tr><td>%y</td><td>%{year}</td><td>");
0070   const char* const yearStr = QT_TRANSLATE_NOOP("@default", "Year");
0071   str += QCoreApplication::translate("@default", yearStr);
0072   str += QLatin1String("</td></tr>\n");
0073 
0074   str += QLatin1String("<tr><td>%t</td><td>%{track}</td><td>");
0075   str += QCoreApplication::translate("@default", "Track");
0076   str += QLatin1String("</td></tr>\n");
0077 
0078   str += QLatin1String("<tr><td>%g</td><td>%{genre}</td><td>");
0079   str += QCoreApplication::translate("@default", "Genre");
0080   str += QLatin1String("</td></tr>\n");
0081 
0082   str += QLatin1String("<tr><td>%d</td><td>%{duration}</td><td>");
0083   const char* const lengthStr = QT_TRANSLATE_NOOP("@default", "Length");
0084   str += QCoreApplication::translate("@default", lengthStr);
0085   str += QLatin1String("</td></tr>\n");
0086 
0087   if (!onlyRows) str += QLatin1String("</table>\n");
0088   return str;
0089 }
0090 
0091 /**
0092  * Set import format.
0093  *
0094  * @param fmt format regexp
0095  * @param enableTrackIncr enable automatic track increment if no %t is found
0096  */
0097 void ImportParser::setFormat(const QString& fmt, bool enableTrackIncr)
0098 {
0099   static const struct {
0100     const char* from;
0101     const char* to;
0102   } codeToName[] = {
0103     { "%s", "%{title}" },
0104     { "%l", "%{album}" },
0105     { "%a", "%{artist}" },
0106     { "%c", "%{comment}" },
0107     { "%y", "%{date}" },
0108     { "%t", "%{track number}" },
0109     { "%g", "%{genre}" },
0110     { "%d", "%{__duration}" },
0111     { "%f", "%{file}" },
0112     { "%{year}", "%{date}" },
0113     { "%{track}", "%{track number}" },
0114     { "%{tracknumber}", "%{track number}" },
0115     { "%{duration}", "%{__duration}" },
0116   };
0117   int percentIdx = 0, nr = 1, lastIdx = fmt.length() - 1;
0118   m_pattern = fmt;
0119   for (const auto& [from, to] : codeToName) {
0120     m_pattern.replace(QString::fromLatin1(from), QString::fromLatin1(to));
0121   }
0122 
0123   m_codePos.clear();
0124   while ((percentIdx = m_pattern.indexOf(QLatin1String("%{"), percentIdx)) >= 0 &&
0125          percentIdx < lastIdx) {
0126     if (int closingBracePos = m_pattern.indexOf(QLatin1String("}("), percentIdx + 2);
0127         closingBracePos > percentIdx + 2) {
0128       QString code =
0129         m_pattern.mid(percentIdx + 2, closingBracePos - percentIdx - 2);
0130       m_codePos[code] = nr;
0131       percentIdx = closingBracePos + 2;
0132       ++nr;
0133     } else {
0134       percentIdx += 2;
0135     }
0136   }
0137 
0138   if (enableTrackIncr && !m_codePos.contains(QLatin1String("track number"))) {
0139     m_trackIncrEnabled = true;
0140     m_trackIncrNr = 1;
0141   } else {
0142     m_trackIncrEnabled = false;
0143     m_trackIncrNr = 0;
0144   }
0145 
0146   m_pattern.remove(QRegularExpression(QLatin1String("%\\{[^}]+\\}")));
0147   m_re.setPattern(m_pattern);
0148 }
0149 
0150 /**
0151  * Get next tags in text buffer.
0152  *
0153  * @param text text buffer containing data from file or clipboard
0154  * @param frames frames for output
0155  * @param pos  current position in buffer, will be updated to point
0156  *             behind current match (to be used for next call)
0157  * @return true if tags found (pos is index behind match).
0158  */
0159 bool ImportParser::getNextTags(const QString& text, TrackData& frames, int& pos)
0160 {
0161   QRegularExpressionMatch match;
0162   int idx, oldpos = pos;
0163   if (m_pattern.isEmpty()) {
0164     m_trackDuration.clear();
0165     return false;
0166   }
0167   if (!m_codePos.contains(QLatin1String("__duration"))) {
0168     m_trackDuration.clear();
0169   } else if (pos == 0) {
0170     m_trackDuration.clear();
0171     int dsp = 0; // "duration search pos"
0172     int lastDsp = dsp;
0173     while ((idx = (match = m_re.match(text, dsp)).capturedStart()) != -1) {
0174       QString durationStr = match.captured(m_codePos.value(QLatin1String("__duration")));
0175       int duration;
0176       QRegularExpression durationRe(QLatin1String("(\\d+):(\\d+)"));
0177       auto durationMatch = durationRe.match(durationStr);
0178       if (durationMatch.hasMatch()) {
0179         duration = durationMatch.captured(1).toInt() * 60 +
0180             durationMatch.captured(2).toInt();
0181       } else {
0182         duration = durationStr.toInt();
0183       }
0184       m_trackDuration.append(duration);
0185 
0186       dsp = idx + durationMatch.capturedLength();
0187       if (dsp > lastDsp) { /* avoid endless loop */
0188         lastDsp = dsp;
0189       } else {
0190         break;
0191       }
0192     }
0193   }
0194   if ((idx = (match = m_re.match(text, pos)).capturedStart()) != -1) {
0195     for (auto it = m_codePos.constBegin(); it != m_codePos.constEnd(); ++it) {
0196       const QString& name = it.key();
0197       QString str = match.captured(*it);
0198       if (name == QLatin1String("__return")) {
0199         m_returnValues.append(str);
0200       } else if (!str.isEmpty() && !name.startsWith(QLatin1String("__"))) {
0201         if (name == QLatin1String("file")) {
0202           if (TaggedFile* taggedFile = frames.getTaggedFile()) {
0203             frames.transformToFilename(str);
0204             taggedFile->setFilenameFormattedIfEnabled(str);
0205           }
0206         } else {
0207           frames.setValue(Frame::ExtendedType(name), str);
0208         }
0209       }
0210     }
0211     if (m_trackIncrEnabled) {
0212       frames.setTrack(m_trackIncrNr++);
0213     }
0214     pos = idx + match.capturedLength();
0215     if (pos > oldpos) { /* avoid endless loop */
0216       return true;
0217     }
0218   }
0219   return false;
0220 }