OSDN Git Service

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