1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2015 LoRd_MuldeR <MuldeR2@GMX.de>
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
23 #include "PlaylistImporter.h"
29 #include <MUtils/Global.h>
33 #include <QStringList>
42 //Un-escape XML characters
48 g_xmlEscapeSequence
[] =
59 const char *PlaylistImporter::supportedExtensions
= "*.m3u *.m3u8 *.pls *.asx *.wpl";
62 ////////////////////////////////////////////////////////////
64 ////////////////////////////////////////////////////////////
66 bool PlaylistImporter::importPlaylist(QStringList
&fileList
, const QString
&playlistFile
)
68 QFileInfo
file(playlistFile
);
69 QDir
baseDir(file
.canonicalPath());
71 QDir
rootDir(baseDir
);
72 while(rootDir
.cdUp());
74 //Detect playlist type
75 playlist_t playlistType
= isPlaylist(file
.canonicalFilePath());
77 //Exit if not a playlist
78 if(playlistType
== notPlaylist
)
83 QFile
data(playlistFile
);
85 //Open file for reading
86 if(!data
.open(QIODevice::ReadOnly
))
91 //Skip very large files (parsing could take very long)
92 if((file
.size() < 3) || (file
.size() > 524288))
94 qWarning("File is very big. Probably not a Playlist. Rejecting...");
98 //Parse playlist depending on type
102 return parsePlaylist_m3u(data
, fileList
, baseDir
, rootDir
);
105 return parsePlaylist_pls(data
, fileList
, baseDir
, rootDir
);
108 return parsePlaylist_wpl(data
, fileList
, baseDir
, rootDir
);
116 ////////////////////////////////////////////////////////////
118 ////////////////////////////////////////////////////////////
120 PlaylistImporter::playlist_t
PlaylistImporter::isPlaylist(const QString
&fileName
)
122 QFileInfo
file (fileName
);
124 if(file
.suffix().compare("m3u", Qt::CaseInsensitive
) == 0 || file
.suffix().compare("m3u8", Qt::CaseInsensitive
) == 0)
128 else if(file
.suffix().compare("pls", Qt::CaseInsensitive
) == 0)
132 else if(file
.suffix().compare("asx", Qt::CaseInsensitive
) == 0 || file
.suffix().compare("wpl", Qt::CaseInsensitive
) == 0)
142 bool PlaylistImporter::parsePlaylist_m3u(QFile
&data
, QStringList
&fileList
, const QDir
&baseDir
, const QDir
&rootDir
)
144 const QTextCodec
*codec
= QTextCodec::codecForName("System");
145 const bool preferUTF8
= data
.fileName().endsWith(".m3u8", Qt::CaseInsensitive
);
146 bool foundAtLeastOneFile
= false;
153 QByteArray line
= data
.readLine();
157 filePath
[0] = QString(QDir::fromNativeSeparators(QString::fromUtf8(line
.constData(), line
.size()).trimmed()));
158 filePath
[1] = QString(QDir::fromNativeSeparators(codec
->toUnicode(line
.constData(), line
.size()).trimmed()));
159 filePath
[2] = QString(QDir::fromNativeSeparators(QString::fromLatin1(line
.constData(), line
.size()).trimmed()));
163 filePath
[0] = QString(QDir::fromNativeSeparators(codec
->toUnicode(line
.constData(), line
.size()).trimmed()));
164 filePath
[1] = QString(QDir::fromNativeSeparators(QString::fromLatin1(line
.constData(), line
.size()).trimmed()));
165 filePath
[2] = QString(QDir::fromNativeSeparators(QString::fromUtf8(line
.constData(), line
.size()).trimmed()));
168 for(size_t i
= 0; i
< 3; i
++)
170 if(!(filePath
[i
].isEmpty() || filePath
[i
].startsWith("#") || filePath
[i
].contains(QChar(QChar::ReplacementCharacter
))))
172 QFileInfo
filename(filePath
[i
]);
173 filename
.setCaching(false);
174 fixFilePath(filename
, baseDir
, rootDir
);
176 if(filename
.exists() && filename
.isFile())
178 qDebug("Found: \"%s\"", MUTILS_UTF8(filePath
[i
]));
179 if(isPlaylist(filename
.canonicalFilePath()) == notPlaylist
)
181 fileList
<< filename
.canonicalFilePath();
183 foundAtLeastOneFile
= true;
190 //If we did not find any files yet, try UTF-16 now
191 if(!foundAtLeastOneFile
)
193 const char* codecs
[2] = {"UTF-16LE", "UTF-16BE"};
195 for(size_t i
= 0; i
< 2; i
++)
197 QTextStream
stream(&data
);
198 stream
.setAutoDetectUnicode(false);
199 stream
.setCodec(codecs
[i
]);
202 while(!stream
.atEnd())
204 QString filePath
= stream
.readLine().trimmed();
206 if(!(filePath
.isEmpty() || filePath
.startsWith("#") || filePath
.contains(QChar(QChar::ReplacementCharacter
))))
208 QFileInfo
filename(filePath
);
209 filename
.setCaching(false);
210 fixFilePath(filename
, baseDir
, rootDir
);
212 if(filename
.exists() && filename
.isFile())
214 if(isPlaylist(filename
.canonicalFilePath()) == notPlaylist
)
216 fileList
<< filename
.canonicalFilePath();
218 foundAtLeastOneFile
= true;
223 if(foundAtLeastOneFile
) break;
227 return foundAtLeastOneFile
;
230 bool PlaylistImporter::parsePlaylist_pls(QFile
&data
, QStringList
&fileList
, const QDir
&baseDir
, const QDir
&rootDir
)
232 QRegExp
plsEntry("File(\\d+)=(.+)", Qt::CaseInsensitive
);
233 const QTextCodec
*codec
= QTextCodec::codecForName("System");
234 bool foundAtLeastOneFile
= false;
241 QByteArray line
= data
.readLine();
243 filePath
[0] = QString(QDir::fromNativeSeparators(codec
->toUnicode(line
.constData(), line
.size()).trimmed()));
244 filePath
[1] = QString(QDir::fromNativeSeparators(QString::fromLatin1(line
.constData(), line
.size()).trimmed()));
245 filePath
[2] = QString(QDir::fromNativeSeparators(QString::fromUtf8(line
.constData(), line
.size()).trimmed()));
247 for(size_t i
= 0; i
< 3; i
++)
249 if(!filePath
[i
].contains(QChar(QChar::ReplacementCharacter
)))
251 if(plsEntry
.indexIn(filePath
[i
]) >= 0)
253 QFileInfo
filename(QDir::fromNativeSeparators(plsEntry
.cap(2)).trimmed());
254 filename
.setCaching(false);
255 fixFilePath(filename
, baseDir
, rootDir
);
257 if(filename
.exists() && filename
.isFile())
259 if(isPlaylist(filename
.canonicalFilePath()) == notPlaylist
)
261 fileList
<< filename
.canonicalFilePath();
263 foundAtLeastOneFile
= true;
271 //If we did not find any files yet, try UTF-16 now
272 if(!foundAtLeastOneFile
)
274 const char* codecs
[2] = {"UTF-16LE", "UTF-16BE"};
276 for(size_t i
= 0; i
< 2; i
++)
278 QTextStream
stream(&data
);
279 stream
.setAutoDetectUnicode(false);
280 stream
.setCodec(codecs
[i
]);
283 while(!stream
.atEnd())
285 QString filePath
= stream
.readLine().trimmed();
287 if(!filePath
.contains(QChar(QChar::ReplacementCharacter
)))
289 if(plsEntry
.indexIn(filePath
) >= 0)
291 QFileInfo
filename(QDir::fromNativeSeparators(plsEntry
.cap(2)).trimmed());
292 filename
.setCaching(false);
293 fixFilePath(filename
, baseDir
, rootDir
);
295 if(filename
.exists() && filename
.isFile())
297 if(isPlaylist(filename
.canonicalFilePath()) == notPlaylist
)
299 fileList
<< filename
.canonicalFilePath();
301 foundAtLeastOneFile
= true;
307 if(foundAtLeastOneFile
) break;
311 return foundAtLeastOneFile
;
314 bool PlaylistImporter::parsePlaylist_wpl(QFile
&data
, QStringList
&fileList
, const QDir
&baseDir
, const QDir
&rootDir
)
316 bool foundAtLeastOneFile
= false;
318 QRegExp
skipData("<!--(.+)-->", Qt::CaseInsensitive
);
319 QRegExp
wplEntry("<(media|ref)[^<>]*(src|href)=\"([^\"]+)\"[^<>]*>", Qt::CaseInsensitive
);
321 skipData
.setMinimal(true);
323 QByteArray buffer
= data
.readAll();
324 QString line
= QString::fromUtf8(buffer
.constData(), buffer
.size()).simplified();
329 while((index
= skipData
.indexIn(line
)) >= 0)
331 line
.remove(index
, skipData
.matchedLength());
336 while((offset
= wplEntry
.indexIn(line
, offset
) + 1) > 0)
338 QFileInfo
filename(QDir::fromNativeSeparators(unescapeXml(wplEntry
.cap(3)).trimmed()));
339 filename
.setCaching(false);
340 fixFilePath(filename
, baseDir
, rootDir
);
342 if(filename
.exists() && filename
.isFile())
344 if(isPlaylist(filename
.canonicalFilePath()) == notPlaylist
)
346 fileList
<< filename
.canonicalFilePath();
347 foundAtLeastOneFile
= true;
352 return foundAtLeastOneFile
;
355 void PlaylistImporter::fixFilePath(QFileInfo
&filename
, const QDir
&baseDir
, const QDir
&rootDir
)
357 if(filename
.filePath().startsWith("/"))
359 while(filename
.filePath().startsWith("/"))
361 filename
.setFile(filename
.filePath().mid(1));
363 filename
.setFile(rootDir
.filePath(filename
.filePath()));
366 if(!filename
.isAbsolute())
368 filename
.setFile(baseDir
.filePath(filename
.filePath()));
372 QString
&PlaylistImporter::unescapeXml(QString
&str
)
374 for(int i
= 0; (g_xmlEscapeSequence
[i
].escape
&& g_xmlEscapeSequence
[i
].output
); i
++)
376 str
.replace(g_xmlEscapeSequence
[i
].escape
, g_xmlEscapeSequence
[i
].output
);