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 "playlist.h" 0009 #include <QRegularExpression> 0010 0011 Playlist::Playlist() 0012 { 0013 } 0014 0015 Playlist::Playlist(const QByteArray &playlist) 0016 { 0017 addPlaylist(playlist); 0018 } 0019 0020 Playlist::~Playlist() 0021 { 0022 } 0023 0024 void Playlist::addPlaylist(const QByteArray &playlist) 0025 { 0026 QString format = p_playlist_format(playlist); 0027 0028 if (format == "m3u") { 0029 p_add_M3U(playlist); 0030 } else if (format == "pls") { 0031 p_add_PLS(playlist); 0032 } else if (format == "xspf") { 0033 p_add_XSPF(playlist); 0034 } 0035 } 0036 0037 void Playlist::clear() 0038 { 0039 p_playlist.clear(); 0040 } 0041 0042 void Playlist::appendItem(const PlaylistItem &item) 0043 { 0044 p_playlist.append(item); 0045 } 0046 0047 QByteArray Playlist::toM3U(const QString &playlistPath, const bool utf8) const 0048 { 0049 QStringList playlist; 0050 playlist.append("#EXTM3U"); 0051 0052 for (int i = 0; i < p_playlist.count(); ++i) { 0053 PlaylistItem pi = p_playlist[i]; 0054 if (pi.filename().isEmpty()) 0055 continue; 0056 0057 if (!pi.artist().isEmpty()) { 0058 playlist.append(QString("#EXTINF:%1,%2 - %3").arg(QString::number(pi.length()), pi.artist(), pi.title())); 0059 } else { 0060 playlist.append(QString("#EXTINF:%1,%2").arg(QString::number(pi.length()), pi.title())); 0061 } 0062 if (!playlistPath.isEmpty()) { 0063 QDir dir(playlistPath); 0064 playlist.append(dir.relativeFilePath(pi.filename())); 0065 } else { 0066 playlist.append(pi.filename()); 0067 } 0068 } 0069 0070 if (utf8) 0071 return playlist.join("\n").append("\n").toUtf8(); 0072 else 0073 return playlist.join("\n").append("\n").toLatin1(); 0074 } 0075 0076 QByteArray Playlist::toPLS(const QString &playlistPath, const bool utf8) const 0077 { 0078 QStringList playlist; 0079 playlist.append("[Playlist]"); 0080 0081 int j = 0; 0082 for (int i = 0; i < p_playlist.count(); ++i) { 0083 PlaylistItem pi = p_playlist[i]; 0084 0085 if (pi.filename().isEmpty()) 0086 continue; 0087 ++j; 0088 0089 if (!playlistPath.isEmpty()) { 0090 QDir dir(playlistPath); 0091 playlist.append(QString("File%1=%2").arg(i + 1).arg(dir.relativeFilePath(pi.filename()))); 0092 } else { 0093 playlist.append(QString("File%1=%2").arg(i + 1).arg(pi.filename())); 0094 } 0095 0096 if (!pi.artist().isEmpty()) { 0097 playlist.append(QString("Title%1=%2 - %3").arg(QString::number(i + 1), pi.artist(), pi.title())); 0098 } else { 0099 playlist.append(QString("Title%1=%2").arg(i + 1).arg(pi.title())); 0100 } 0101 0102 playlist.append(QString("Length%1=%2").arg(i + 1).arg(pi.length())); 0103 } 0104 0105 playlist.append(QString("NumberOfEntries=%1").arg(j)); 0106 playlist.append(QString("Version=2")); 0107 0108 if (utf8) 0109 return playlist.join("\n").append("\n").toUtf8(); 0110 else 0111 return playlist.join("\n").append("\n").toLatin1(); 0112 } 0113 0114 QByteArray Playlist::toXSPF() const 0115 { 0116 QDomDocument doc; 0117 QDomElement root = doc.createElement("playlist"); 0118 root.setAttribute("version", "1"); 0119 root.setAttribute("xmlns", "http://xspf.org/ns/0"); 0120 0121 QDomElement creator = doc.createElement("creator"); 0122 QDomText text = doc.createTextNode("audex"); 0123 creator.appendChild(text); 0124 root.appendChild(creator); 0125 0126 QDomElement tracklist = doc.createElement("trackList"); 0127 0128 int j = 0; 0129 for (int i = 0; i < p_playlist.count(); ++i) { 0130 PlaylistItem pi = p_playlist[i]; 0131 0132 if (pi.filename().isEmpty()) 0133 continue; 0134 ++j; 0135 0136 QDomElement track = doc.createElement("track"); 0137 0138 QDomElement ch = doc.createElement("location"); 0139 QDomText text = doc.createTextNode(pi.filename()); 0140 0141 ch.appendChild(text); 0142 track.appendChild(ch); 0143 0144 if (!pi.artist().isEmpty()) { 0145 ch = doc.createElement("creator"); 0146 text = doc.createTextNode(pi.artist()); 0147 ch.appendChild(text); 0148 track.appendChild(ch); 0149 } 0150 0151 ch = doc.createElement("title"); 0152 text = doc.createTextNode(pi.title()); 0153 ch.appendChild(text); 0154 track.appendChild(ch); 0155 0156 ch = doc.createElement("trackNum"); 0157 text = doc.createTextNode(QString::number(j)); 0158 ch.appendChild(text); 0159 track.appendChild(ch); 0160 0161 if (pi.length() > 0) { 0162 ch = doc.createElement("duration"); 0163 text = doc.createTextNode(QString::number(pi.length() * 1000)); 0164 ch.appendChild(text); 0165 track.appendChild(ch); 0166 } 0167 0168 tracklist.appendChild(track); 0169 } 0170 0171 root.appendChild(tracklist); 0172 doc.appendChild(root); 0173 QByteArray xml_header("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 0174 return doc.toByteArray().prepend(xml_header); 0175 } 0176 0177 const QString Playlist::p_playlist_format(const QByteArray &playlist) 0178 { 0179 if (playlist.contains("#EXTM3U") || playlist.contains("#EXTINF")) 0180 return "m3u"; 0181 if (playlist.toLower().contains("[playlist]")) 0182 return "pls"; 0183 if (playlist.contains("<playlist") && playlist.contains("<trackList")) 0184 return "xspf"; 0185 0186 return QString(); 0187 } 0188 0189 void Playlist::p_add_M3U(const QByteArray &playlist) 0190 { 0191 QTextStream stream(playlist, QIODevice::ReadOnly); 0192 0193 QString line = stream.readLine().trimmed(); 0194 0195 bool extended = false; 0196 if (line.startsWith(QLatin1String("#EXTM3U"))) { 0197 extended = true; 0198 line = stream.readLine().trimmed(); 0199 } 0200 0201 PlaylistItem pi; 0202 while (!line.isNull()) { 0203 if (line.startsWith('#')) { 0204 if (extended && line.startsWith(QLatin1String("#EXT"))) { 0205 pi = p_parse_m3u_metadata_line(line); 0206 } 0207 } else if (!line.isEmpty()) { 0208 pi.setFilename(line); 0209 if (!pi.filename().isEmpty()) { 0210 p_playlist.append(pi); 0211 pi.clear(); 0212 } 0213 } 0214 0215 line = stream.readLine().trimmed(); 0216 } 0217 } 0218 0219 void Playlist::p_add_PLS(const QByteArray &playlist) 0220 { 0221 QTextStream stream(playlist, QIODevice::ReadOnly); 0222 0223 QString line = stream.readLine().trimmed(); 0224 0225 QMap<int, PlaylistItem> items; 0226 while (!line.isNull()) { 0227 int equals = line.indexOf('='); 0228 QString key = line.left(equals).toLower(); 0229 QString value = line.mid(equals + 1); 0230 0231 static QRegularExpression n_re("\\d+$"); 0232 auto match = n_re.match(key); 0233 int n = match.captured(0).toInt(); 0234 0235 if (key.startsWith(QLatin1String("file"))) { 0236 items[n].setFilename(value); 0237 } else if (key.startsWith(QLatin1String("title"))) { 0238 items[n].setTitle(value); 0239 } else if (key.startsWith(QLatin1String("length"))) { 0240 bool ok; 0241 int seconds = value.toInt(&ok); 0242 if (ok) 0243 items[n].setLength(seconds); 0244 } 0245 0246 line = stream.readLine().trimmed(); 0247 } 0248 0249 QMap<int, PlaylistItem>::const_iterator i = items.constBegin(); 0250 while (i != items.constEnd()) { 0251 p_playlist.append(i.value()); 0252 ++i; 0253 } 0254 } 0255 0256 void Playlist::p_add_XSPF(const QByteArray &playlist) 0257 { 0258 QDomDocument doc; 0259 QString errorMsg; 0260 int errorCol; 0261 int errorRow; 0262 0263 if (!doc.setContent(QString(playlist), &errorMsg, &errorRow, &errorCol)) 0264 return; 0265 0266 QDomElement rootElement = doc.firstChildElement("playlist"); 0267 if (rootElement.isNull()) 0268 return; 0269 0270 QDomElement tracklistElement = rootElement.firstChildElement("trackList"); 0271 if (tracklistElement.isNull()) 0272 return; 0273 0274 QMap<int, PlaylistItem> items; 0275 QMap<int, PlaylistItem> items_no_tracknum; 0276 QDomElement child = tracklistElement.firstChildElement("track"); 0277 int m = 0; 0278 while (!child.isNull()) { 0279 bool ok; 0280 int n = child.firstChildElement("trackNum").text().toInt(&ok); 0281 if (!ok) { 0282 items_no_tracknum[m].setFilename(child.firstChildElement("location").text()); 0283 items_no_tracknum[m].setArtist(child.firstChildElement("creator").text()); 0284 items_no_tracknum[m].setTitle(child.firstChildElement("title").text()); 0285 items_no_tracknum[m].setLength(child.firstChildElement("duration").text().toInt() / 1000); 0286 ++m; 0287 } else { 0288 items[n].setFilename(child.firstChildElement("location").text()); 0289 items[n].setArtist(child.firstChildElement("creator").text()); 0290 items[n].setTitle(child.firstChildElement("title").text()); 0291 items[n].setLength(child.firstChildElement("duration").text().toInt() / 1000); 0292 } 0293 0294 child = child.nextSiblingElement(); 0295 } 0296 0297 QMap<int, PlaylistItem>::const_iterator i = items.constBegin(); 0298 while (i != items.constEnd()) { 0299 p_playlist.append(i.value()); 0300 ++i; 0301 } 0302 QMap<int, PlaylistItem>::const_iterator j = items_no_tracknum.constBegin(); 0303 while (j != items_no_tracknum.constEnd()) { 0304 p_playlist.append(j.value()); 0305 ++j; 0306 } 0307 } 0308 0309 const PlaylistItem Playlist::p_parse_m3u_metadata_line(const QString &line) 0310 { 0311 PlaylistItem pi; 0312 0313 QString info = line.section(':', 1); 0314 QString l = info.section(',', 0, 0); 0315 bool ok; 0316 int length = l.toInt(&ok); 0317 if (!ok) 0318 return pi; 0319 pi.setLength(length); 0320 0321 QString track_info = info.section(',', 1); 0322 QStringList list = track_info.split('-'); 0323 if (list.length() <= 1) { 0324 pi.setTitle(track_info); 0325 return pi; 0326 } 0327 pi.setArtist(list[0].trimmed()); 0328 pi.setTitle(list[1].trimmed()); 0329 return pi; 0330 }