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 }