OSDN Git Service

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