OSDN Git Service

Updated license info.
[lamexp/LameXP.git] / src / PlaylistImporter.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2013 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
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.
10 //
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.
15 //
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.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "PlaylistImporter.h"
24
25 #include "Global.h"
26
27 #include <QString>
28 #include <QStringList>
29 #include <QDir>
30 #include <QFileInfo>
31 #include <QProcess>
32 #include <QDate>
33 #include <QTime>
34 #include <QDebug>
35 #include <QTextCodec>
36
37 //Un-escape XML characters
38 static const struct
39 {
40         char *escape;
41         char *output;
42 }
43 g_xmlEscapeSequence[] =
44 {
45         {"&amp;", "&"},
46         {"&lt;", "<"},
47         {"&gt;", ">"},
48         {"&apos;", "'"},
49         {"&nbsp;", " "},
50         {"&quot;", "\""},
51         {NULL, NULL}
52 };
53
54 const char *PlaylistImporter::supportedExtensions = "*.m3u *.m3u8 *.pls *.asx *.wpl";
55
56
57 ////////////////////////////////////////////////////////////
58 // Public Functions
59 ////////////////////////////////////////////////////////////
60
61 bool PlaylistImporter::importPlaylist(QStringList &fileList, const QString &playlistFile)
62 {
63         QFileInfo file(playlistFile);
64         QDir baseDir(file.canonicalPath());
65
66         QDir rootDir(baseDir);
67         while(rootDir.cdUp());
68         
69         //Detect playlist type
70         playlist_t playlistType = isPlaylist(file.canonicalFilePath());
71
72         //Exit if not a playlist
73         if(playlistType == notPlaylist)
74         {
75                 return false;
76         }
77         
78         QFile data(playlistFile);
79
80         //Open file for reading
81         if(!data.open(QIODevice::ReadOnly))
82         {
83                 return false;
84         }
85
86         //Skip very large files (parsing could take very long)
87         if((file.size() < 3) || (file.size() > 524288))
88         {
89                 qWarning("File is very big. Probably not a Playlist. Rejecting...");
90                 return false;
91         }
92
93         //Parse playlist depending on type
94         switch(playlistType)
95         {
96         case m3uPlaylist:
97                 return parsePlaylist_m3u(data, fileList, baseDir, rootDir);
98                 break;
99         case plsPlaylist:
100                 return parsePlaylist_pls(data, fileList, baseDir, rootDir);
101                 break;
102         case wplPlaylist:
103                 return parsePlaylist_wpl(data, fileList, baseDir, rootDir);
104                 break;
105         default:
106                 return false;
107                 break;
108         }
109 }
110
111 ////////////////////////////////////////////////////////////
112 // Private Functions
113 ////////////////////////////////////////////////////////////
114
115 PlaylistImporter::playlist_t PlaylistImporter::isPlaylist(const QString &fileName)
116 {
117         QFileInfo file (fileName);
118         
119         if(file.suffix().compare("m3u", Qt::CaseInsensitive) == 0 || file.suffix().compare("m3u8", Qt::CaseInsensitive) == 0)
120         {
121                 return m3uPlaylist;
122         }
123         else if(file.suffix().compare("pls", Qt::CaseInsensitive) == 0)
124         {
125                 return  plsPlaylist;
126         }
127         else if(file.suffix().compare("asx", Qt::CaseInsensitive) == 0 || file.suffix().compare("wpl", Qt::CaseInsensitive) == 0)
128         {
129                 return  wplPlaylist;
130         }
131         else
132         {
133                 return notPlaylist;
134         }
135 }
136
137 bool PlaylistImporter::parsePlaylist_m3u(QFile &data, QStringList &fileList, const QDir &baseDir, const QDir &rootDir)
138 {
139         const QTextCodec *codec = QTextCodec::codecForName("System");
140         const bool preferUTF8 = data.fileName().endsWith(".m3u8", Qt::CaseInsensitive);
141         bool foundAtLeastOneFile = false;
142         
143         data.reset();
144
145         while(!data.atEnd())
146         {
147                 QString filePath[3];
148                 QByteArray line = data.readLine();
149
150                 if(preferUTF8)
151                 {
152                         filePath[0] = QString(QDir::fromNativeSeparators(QString::fromUtf8(line.constData(), line.size()).trimmed()));
153                         filePath[1] = QString(QDir::fromNativeSeparators(codec->toUnicode(line.constData(), line.size()).trimmed()));
154                         filePath[2] = QString(QDir::fromNativeSeparators(QString::fromLatin1(line.constData(), line.size()).trimmed()));
155                 }
156                 else
157                 {
158                         filePath[0] = QString(QDir::fromNativeSeparators(codec->toUnicode(line.constData(), line.size()).trimmed()));
159                         filePath[1] = QString(QDir::fromNativeSeparators(QString::fromLatin1(line.constData(), line.size()).trimmed()));
160                         filePath[2] = QString(QDir::fromNativeSeparators(QString::fromUtf8(line.constData(), line.size()).trimmed()));
161                 }
162
163                 for(size_t i = 0; i < 3; i++)
164                 {
165                         if(!(filePath[i].isEmpty() || filePath[i].startsWith("#") || filePath[i].contains(QChar(QChar::ReplacementCharacter))))
166                         {
167                                 QFileInfo filename(filePath[i]);
168                                 filename.setCaching(false);
169                                 fixFilePath(filename, baseDir, rootDir);
170
171                                 if(filename.exists() && filename.isFile())
172                                 {
173                                         qDebug("Found: \"%s\"", QUTF8(filePath[i]));
174                                         if(isPlaylist(filename.canonicalFilePath()) == notPlaylist)
175                                         {
176                                                 fileList << filename.canonicalFilePath();
177                                         }
178                                         foundAtLeastOneFile = true;
179                                         break;
180                                 }
181                         }
182                 }
183         }
184
185         //If we did not find any files yet, try UTF-16 now
186         if(!foundAtLeastOneFile)
187         {
188                 const char* codecs[2] = {"UTF-16LE", "UTF-16BE"};
189
190                 for(size_t i = 0; i < 2; i++)
191                 {
192                         QTextStream stream(&data);
193                         stream.setAutoDetectUnicode(false);
194                         stream.setCodec(codecs[i]);
195                         stream.seek(0i64);
196
197                         while(!stream.atEnd())
198                         {
199                                 QString filePath = stream.readLine().trimmed();
200
201                                 if(!(filePath.isEmpty() || filePath.startsWith("#") || filePath.contains(QChar(QChar::ReplacementCharacter))))
202                                 {
203                                         QFileInfo filename(filePath);
204                                         filename.setCaching(false);
205                                         fixFilePath(filename, baseDir, rootDir);
206
207                                         if(filename.exists() && filename.isFile())
208                                         {
209                                                 if(isPlaylist(filename.canonicalFilePath()) == notPlaylist)
210                                                 {
211                                                         fileList << filename.canonicalFilePath();
212                                                 }
213                                                 foundAtLeastOneFile = true;
214                                         }
215                                 }
216                         }
217
218                         if(foundAtLeastOneFile) break;
219                 }
220         }
221
222         return foundAtLeastOneFile;
223 }
224
225 bool PlaylistImporter::parsePlaylist_pls(QFile &data, QStringList &fileList, const QDir &baseDir, const QDir &rootDir)
226 {
227         QRegExp plsEntry("File(\\d+)=(.+)", Qt::CaseInsensitive);
228         const QTextCodec *codec = QTextCodec::codecForName("System");
229         bool foundAtLeastOneFile = false;
230
231         data.reset();
232
233         while(!data.atEnd())
234         {
235                 QString filePath[3];
236                 QByteArray line = data.readLine();
237
238                 filePath[0] = QString(QDir::fromNativeSeparators(codec->toUnicode(line.constData(), line.size()).trimmed()));
239                 filePath[1] = QString(QDir::fromNativeSeparators(QString::fromLatin1(line.constData(), line.size()).trimmed()));
240                 filePath[2] = QString(QDir::fromNativeSeparators(QString::fromUtf8(line.constData(), line.size()).trimmed()));
241                 
242                 for(size_t i = 0; i < 3; i++)
243                 {
244                         if(!filePath[i].contains(QChar(QChar::ReplacementCharacter)))
245                         {
246                                 if(plsEntry.indexIn(filePath[i]) >= 0)
247                                 {
248                                         QFileInfo filename(QDir::fromNativeSeparators(plsEntry.cap(2)).trimmed());
249                                         filename.setCaching(false);
250                                         fixFilePath(filename, baseDir, rootDir);
251
252                                         if(filename.exists() && filename.isFile())
253                                         {
254                                                 if(isPlaylist(filename.canonicalFilePath()) == notPlaylist)
255                                                 {
256                                                         fileList << filename.canonicalFilePath();
257                                                 }
258                                                 foundAtLeastOneFile = true;
259                                                 break;
260                                         }
261                                 }
262                         }
263                 }
264         }
265
266         //If we did not find any files yet, try UTF-16 now
267         if(!foundAtLeastOneFile)
268         {
269                 const char* codecs[2] = {"UTF-16LE", "UTF-16BE"};
270
271                 for(size_t i = 0; i < 2; i++)
272                 {
273                         QTextStream stream(&data);
274                         stream.setAutoDetectUnicode(false);
275                         stream.setCodec(codecs[i]);
276                         stream.seek(0i64);
277
278                         while(!stream.atEnd())
279                         {
280                                 QString filePath = stream.readLine().trimmed();
281
282                                 if(!filePath.contains(QChar(QChar::ReplacementCharacter)))
283                                 {
284                                         if(plsEntry.indexIn(filePath) >= 0)
285                                         {
286                                                 QFileInfo filename(QDir::fromNativeSeparators(plsEntry.cap(2)).trimmed());
287                                                 filename.setCaching(false);
288                                                 fixFilePath(filename, baseDir, rootDir);
289
290                                                 if(filename.exists() && filename.isFile())
291                                                 {
292                                                         if(isPlaylist(filename.canonicalFilePath()) == notPlaylist)
293                                                         {
294                                                                 fileList << filename.canonicalFilePath();
295                                                         }
296                                                         foundAtLeastOneFile = true;
297                                                 }
298                                         }
299                                 }
300                         }
301
302                         if(foundAtLeastOneFile) break;
303                 }
304         }
305
306         return foundAtLeastOneFile;
307 }
308
309 bool PlaylistImporter::parsePlaylist_wpl(QFile &data, QStringList &fileList, const QDir &baseDir, const QDir &rootDir)
310 {
311         bool foundAtLeastOneFile = false;
312
313         QRegExp skipData("<!--(.+)-->", Qt::CaseInsensitive);
314         QRegExp wplEntry("<(media|ref)[^<>]*(src|href)=\"([^\"]+)\"[^<>]*>", Qt::CaseInsensitive);
315         
316         skipData.setMinimal(true);
317
318         QByteArray buffer = data.readAll();
319         QString line = QString::fromUtf8(buffer.constData(), buffer.size()).simplified();
320         buffer.clear();
321
322         int index = 0;
323
324         while((index = skipData.indexIn(line)) >= 0)
325         {
326                 line.remove(index, skipData.matchedLength());
327         }
328
329         int offset = 0;
330
331         while((offset = wplEntry.indexIn(line, offset) + 1) > 0)
332         {
333                 QFileInfo filename(QDir::fromNativeSeparators(unescapeXml(wplEntry.cap(3)).trimmed()));
334                 filename.setCaching(false);
335                 fixFilePath(filename, baseDir, rootDir);
336
337                 if(filename.exists() && filename.isFile())
338                 {
339                         if(isPlaylist(filename.canonicalFilePath()) == notPlaylist)
340                         {
341                                 fileList << filename.canonicalFilePath();
342                                 foundAtLeastOneFile = true;
343                         }
344                 }
345         }
346
347         return foundAtLeastOneFile;
348 }
349
350 void PlaylistImporter::fixFilePath(QFileInfo &filename, const QDir &baseDir, const QDir &rootDir)
351 {
352         if(filename.filePath().startsWith("/"))
353         {
354                 while(filename.filePath().startsWith("/"))
355                 {
356                         filename.setFile(filename.filePath().mid(1));
357                 }
358                 filename.setFile(rootDir.filePath(filename.filePath()));
359         }
360         
361         if(!filename.isAbsolute())
362         {
363                 filename.setFile(baseDir.filePath(filename.filePath()));
364         }
365 }
366
367 QString &PlaylistImporter::unescapeXml(QString &str)
368 {
369         for(int i = 0; (g_xmlEscapeSequence[i].escape && g_xmlEscapeSequence[i].output); i++)
370         {
371                 str.replace(g_xmlEscapeSequence[i].escape, g_xmlEscapeSequence[i].output);
372         }
373         
374         return str;
375 }