OSDN Git Service

Workaround for a recent change in MediaInfo's behavior for M3U playlist files.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer.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 "Thread_FileAnalyzer.h"
23
24 #include "Global.h"
25 #include "LockedFile.h"
26 #include "Model_AudioFile.h"
27 #include "PlaylistImporter.h"
28
29 #include <QDir>
30 #include <QFileInfo>
31 #include <QProcess>
32 #include <QDate>
33 #include <QTime>
34 #include <QDebug>
35 #include <QImage>
36
37 #include <math.h>
38
39 #define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
40 #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
41 #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
42
43 ////////////////////////////////////////////////////////////
44 // Constructor
45 ////////////////////////////////////////////////////////////
46
47 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
48 :
49         m_inputFiles(inputFiles),
50         m_mediaInfoBin(lamexp_lookup_tool("mediainfo.exe")),
51         m_avs2wavBin(lamexp_lookup_tool("avs2wav.exe")),
52         m_templateFile(NULL),
53         m_abortFlag(false)
54 {
55         m_bSuccess = false;
56         m_bAborted = false;
57                 
58         if(m_mediaInfoBin.isEmpty())
59         {
60                 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
61         }
62
63         m_filesAccepted = 0;
64         m_filesRejected = 0;
65         m_filesDenied = 0;
66         m_filesDummyCDDA = 0;
67         m_filesCueSheet = 0;
68 }
69
70 FileAnalyzer::~FileAnalyzer(void)
71 {
72         if(m_templateFile)
73         {
74                 QString templatePath = m_templateFile->filePath();
75                 LAMEXP_DELETE(m_templateFile);
76                 if(QFile::exists(templatePath)) QFile::remove(templatePath);
77         }
78 }
79
80 ////////////////////////////////////////////////////////////
81 // Static data
82 ////////////////////////////////////////////////////////////
83
84 const char *FileAnalyzer::g_tags_gen[] =
85 {
86         "ID",
87         "Format",
88         "Format_Profile",
89         "Format_Version",
90         "Duration",
91         "Title", "Track",
92         "Track/Position",
93         "Artist", "Performer",
94         "Album",
95         "Genre",
96         "Released_Date", "Recorded_Date",
97         "Comment",
98         "Cover",
99         "Cover_Type",
100         "Cover_Mime",
101         "Cover_Data",
102         NULL
103 };
104
105 const char *FileAnalyzer::g_tags_aud[] =
106 {
107         "ID",
108         "Format",
109         "Format_Profile",
110         "Format_Version",
111         "Channel(s)",
112         "SamplingRate",
113         "BitDepth",
114         "BitRate",
115         "BitRate_Mode",
116         NULL
117 };
118
119 ////////////////////////////////////////////////////////////
120 // Thread Main
121 ////////////////////////////////////////////////////////////
122
123 void FileAnalyzer::run()
124 {
125         m_bSuccess = false;
126         m_bAborted = false;
127
128         m_filesAccepted = 0;
129         m_filesRejected = 0;
130         m_filesDenied = 0;
131         m_filesDummyCDDA = 0;
132         m_filesCueSheet = 0;
133
134         m_inputFiles.sort();
135         m_recentlyAdded.clear();
136         m_abortFlag = false;
137
138         if(!m_templateFile)
139         {
140                 if(!createTemplate())
141                 {
142                         qWarning("Failed to create template file!");
143                         return;
144                 }
145         }
146
147         while(!m_inputFiles.isEmpty())
148         {
149                 int fileType = fileTypeNormal;
150                 QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
151                 qDebug("Analyzing: %s", currentFile.toUtf8().constData());
152                 emit fileSelected(QFileInfo(currentFile).fileName());
153                 AudioFileModel file = analyzeFile(currentFile, &fileType);
154                 
155                 if(m_abortFlag)
156                 {
157                         MessageBeep(MB_ICONERROR);
158                         m_bAborted = true;
159                         qWarning("Operation cancelled by user!");
160                         return;
161                 }
162                 if(fileType == fileTypeSkip)
163                 {
164                         qWarning("File was recently added, skipping!");
165                         continue;
166                 }
167                 if(fileType == fileTypeDenied)
168                 {
169                         m_filesDenied++;
170                         qWarning("Cannot access file for reading, skipping!");
171                         continue;
172                 }
173                 if(fileType == fileTypeCDDA)
174                 {
175                         m_filesDummyCDDA++;
176                         qWarning("Dummy CDDA file detected, skipping!");
177                         continue;
178                 }
179                 
180                 if(file.fileName().isEmpty() || file.formatContainerType().isEmpty() || file.formatAudioType().isEmpty())
181                 {
182                         if(PlaylistImporter::importPlaylist(m_inputFiles, currentFile))
183                         {
184                                 qDebug("Imported playlist file.");
185                         }
186                         else if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
187                         {
188                                 qWarning("Cue Sheet file detected, skipping!");
189                                 m_filesCueSheet++;
190                         }
191                         else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
192                         {
193                                 qDebug("Found a potential Avisynth script, investigating...");
194                                 if(analyzeAvisynthFile(currentFile, file))
195                                 {
196                                         m_filesAccepted++;
197                                         emit fileAnalyzed(file);
198                                 }
199                                 else
200                                 {
201                                         qDebug("Rejected Avisynth file: %s", file.filePath().toUtf8().constData());
202                                         m_filesRejected++;
203                                 }
204                         }
205                         else
206                         {
207                                 qDebug("Rejected file of unknown type: %s", file.filePath().toUtf8().constData());
208                                 m_filesRejected++;
209                         }
210                         continue;
211                 }
212
213                 m_filesAccepted++;
214                 m_recentlyAdded.append(file.filePath());
215                 emit fileAnalyzed(file);
216         }
217
218         qDebug("All files added.\n");
219         m_bSuccess = true;
220 }
221
222 ////////////////////////////////////////////////////////////
223 // Privtae Functions
224 ////////////////////////////////////////////////////////////
225
226 const AudioFileModel FileAnalyzer::analyzeFile(const QString &filePath, int *type)
227 {
228         *type = fileTypeNormal;
229         
230         AudioFileModel audioFile(filePath);
231
232         if(m_recentlyAdded.contains(filePath, Qt::CaseInsensitive))
233         {
234                 *type = fileTypeSkip;
235                 return audioFile;
236         }
237
238         QFile readTest(filePath);
239         if(!readTest.open(QIODevice::ReadOnly))
240         {
241                 *type = fileTypeDenied;
242                 return audioFile;
243         }
244         if(checkFile_CDDA(readTest))
245         {
246                 *type = fileTypeCDDA;
247                 return audioFile;
248         }
249         readTest.close();
250
251         bool skipNext = false;
252         unsigned int id_val[2] = {UINT_MAX, UINT_MAX};
253         cover_t coverType = coverNone;
254         QByteArray coverData;
255
256         QStringList params;
257         params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile->filePath()));
258         params << QDir::toNativeSeparators(filePath);
259         
260         QProcess process;
261         process.setProcessChannelMode(QProcess::MergedChannels);
262         process.setReadChannel(QProcess::StandardOutput);
263         process.start(m_mediaInfoBin, params);
264                 
265         if(!process.waitForStarted())
266         {
267                 qWarning("MediaInfo process failed to create!");
268                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
269                 process.kill();
270                 process.waitForFinished(-1);
271                 return audioFile;
272         }
273
274         while(process.state() != QProcess::NotRunning)
275         {
276                 if(m_abortFlag)
277                 {
278                         process.kill();
279                         qWarning("Process was aborted on user request!");
280                         break;
281                 }
282                 
283                 if(!process.waitForReadyRead())
284                 {
285                         if(process.state() == QProcess::Running)
286                         {
287                                 qWarning("MediaInfo time out. Killing process and skipping file!");
288                                 process.kill();
289                                 process.waitForFinished(-1);
290                                 return audioFile;
291                         }
292                 }
293
294                 QByteArray data;
295
296                 while(process.canReadLine())
297                 {
298                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
299                         if(!line.isEmpty())
300                         {
301                                 //qDebug("Line:%s", line.toUtf8().constData());
302                                 
303                                 int index = line.indexOf('=');
304                                 if(index > 0)
305                                 {
306                                         QString key = line.left(index).trimmed();
307                                         QString val = line.mid(index+1).trimmed();
308                                         if(!key.isEmpty())
309                                         {
310                                                 updateInfo(audioFile, &skipNext, id_val, &coverType, &coverData, key, val);
311                                         }
312                                 }
313                         }
314                 }
315         }
316
317         if(audioFile.fileName().isEmpty())
318         {
319                 QString baseName = QFileInfo(filePath).fileName();
320                 int index = baseName.lastIndexOf(".");
321
322                 if(index >= 0)
323                 {
324                         baseName = baseName.left(index);
325                 }
326
327                 baseName = baseName.replace("_", " ").simplified();
328                 index = baseName.lastIndexOf(" - ");
329
330                 if(index >= 0)
331                 {
332                         baseName = baseName.mid(index + 3).trimmed();
333                 }
334
335                 audioFile.setFileName(baseName);
336         }
337         
338         process.waitForFinished();
339         if(process.state() != QProcess::NotRunning)
340         {
341                 process.kill();
342                 process.waitForFinished(-1);
343         }
344
345         if((coverType != coverNone) && (!coverData.isEmpty()))
346         {
347                 retrieveCover(audioFile, coverType, coverData);
348         }
349
350         return audioFile;
351 }
352
353 void FileAnalyzer::updateInfo(AudioFileModel &audioFile, bool *skipNext, unsigned int *id_val, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
354 {
355         //qWarning("'%s' -> '%s'", key.toUtf8().constData(), value.toUtf8().constData());
356         
357         /*New Stream*/
358         if(IS_KEY("Gen_ID") || IS_KEY("Aud_ID"))
359         {
360                 if(value.isEmpty())
361                 {
362                         *skipNext = false;
363                 }
364                 else
365                 {
366                         //We ignore all ID's, except for the lowest one!
367                         bool ok = false;
368                         unsigned int id = value.toUInt(&ok);
369                         if(ok)
370                         {
371                                 if(IS_KEY("Gen_ID")) { id_val[0] = qMin(id_val[0], id); *skipNext = (id > id_val[0]); }
372                                 if(IS_KEY("Aud_ID")) { id_val[1] = qMin(id_val[1], id); *skipNext = (id > id_val[1]); }
373                         }
374                         else
375                         {
376                                 *skipNext = true;
377                         }
378                 }
379                 if(*skipNext)
380                 {
381                         qWarning("Skipping info for non-primary stream!");
382                 }
383                 return;
384         }
385
386         /*Skip?*/
387         if((*skipNext) || value.isEmpty())
388         {
389                 return;
390         }
391
392         /*General Section*/
393         if(IS_SEC("Gen"))
394         {
395                 if(IS_KEY("Gen_Format"))
396                 {
397                         if(value.compare("HLS", Qt::CaseInsensitive)) //MediaInfo detects "HLS" for .m3u files, we'll ignore that
398                         {
399                                 audioFile.setFormatContainerType(value);
400                         }
401                 }
402                 else if(IS_KEY("Gen_Format_Profile"))
403                 {
404                         audioFile.setFormatContainerProfile(value);
405                 }
406                 else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
407                 {
408                         audioFile.setFileName(value);
409                 }
410                 else if(IS_KEY("Gen_Duration"))
411                 {
412                         unsigned int tmp = parseDuration(value);
413                         if(tmp > 0) audioFile.setFileDuration(tmp);
414                 }
415                 else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
416                 {
417                         audioFile.setFileArtist(value);
418                 }
419                 else if(IS_KEY("Gen_Album"))
420                 {
421                         audioFile.setFileAlbum(value);
422                 }
423                 else if(IS_KEY("Gen_Genre"))
424                 {
425                         audioFile.setFileGenre(value);
426                 }
427                 else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
428                 {
429                         unsigned int tmp = parseYear(value);
430                         if(tmp > 0) audioFile.setFileYear(tmp);
431                 }
432                 else if(IS_KEY("Gen_Comment"))
433                 {
434                         audioFile.setFileComment(value);
435                 }
436                 else if(IS_KEY("Gen_Track/Position"))
437                 {
438                         bool ok = false;
439                         unsigned int tmp = value.toUInt(&ok);
440                         if(ok) audioFile.setFilePosition(tmp);
441                 }
442                 else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
443                 {
444                         if(*coverType == coverNone)
445                         {
446                                 *coverType = coverJpeg;
447                         }
448                 }
449                 else if(IS_KEY("Gen_Cover_Mime"))
450                 {
451                         QString temp = FIRST_TOK(value);
452                         if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
453                         else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
454                         else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
455                 }
456                 else if(IS_KEY("Gen_Cover_Data"))
457                 {
458                         if(!coverData->isEmpty()) coverData->clear();
459                         coverData->append(QByteArray::fromBase64(FIRST_TOK(value).toLatin1()));
460                 }
461                 else
462                 {
463                         qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
464                 }
465                 return;
466         }
467
468         /*Audio Section*/
469         if(IS_SEC("Aud"))
470         {
471
472                 if(IS_KEY("Aud_Format"))
473                 {
474                         audioFile.setFormatAudioType(value);
475                 }
476                 else if(IS_KEY("Aud_Format_Profile"))
477                 {
478                         audioFile.setFormatAudioProfile(value);
479                 }
480                 else if(IS_KEY("Aud_Format_Version"))
481                 {
482                         audioFile.setFormatAudioVersion(value);
483                 }
484                 else if(IS_KEY("Aud_Channel(s)"))
485                 {
486                         bool ok = false;
487                         unsigned int tmp = value.toUInt(&ok);
488                         if(ok) audioFile.setFormatAudioChannels(tmp);
489                 }
490                 else if(IS_KEY("Aud_SamplingRate"))
491                 {
492                         bool ok = false;
493                         unsigned int tmp = value.toUInt(&ok);
494                         if(ok) audioFile.setFormatAudioSamplerate(tmp);
495                 }
496                 else if(IS_KEY("Aud_BitDepth"))
497                 {
498                         bool ok = false;
499                         unsigned int tmp = value.toUInt(&ok);
500                         if(ok) audioFile.setFormatAudioBitdepth(tmp);
501                 }
502                 else if(IS_KEY("Aud_Duration"))
503                 {
504                         unsigned int tmp = parseDuration(value);
505                         if(tmp > 0) audioFile.setFileDuration(tmp);
506                 }
507                 else if(IS_KEY("Aud_BitRate"))
508                 {
509                         bool ok = false;
510                         unsigned int tmp = value.toUInt(&ok);
511                         if(ok) audioFile.setFormatAudioBitrate(tmp/1000);
512                 }
513                 else if(IS_KEY("Aud_BitRate_Mode"))
514                 {
515                         if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeConstant);
516                         if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeVariable);
517                 }
518                 else
519                 {
520                         qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
521                 }
522                 return;
523         }
524
525         /*Section not recognized*/
526         qWarning("Unknown section: %s", key.toUtf8().constData());
527 }
528
529 bool FileAnalyzer::checkFile_CDDA(QFile &file)
530 {
531         file.reset();
532         QByteArray data = file.read(128);
533         
534         int i = data.indexOf("RIFF");
535         int j = data.indexOf("CDDA");
536         int k = data.indexOf("fmt ");
537
538         return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
539 }
540
541 void FileAnalyzer::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
542 {
543         qDebug("Retrieving cover!");
544         QString extension;
545
546         switch(coverType)
547         {
548         case coverPng:
549                 extension = QString::fromLatin1("png");
550                 break;
551         case coverGif:
552                 extension = QString::fromLatin1("gif");
553                 break;
554         default:
555                 extension = QString::fromLatin1("jpg");
556                 break;
557         }
558         
559         if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
560         {
561                 QFile coverFile(QString("%1/%2.%3").arg(lamexp_temp_folder2(), lamexp_rand_str(), extension));
562                 if(coverFile.open(QIODevice::WriteOnly))
563                 {
564                         coverFile.write(coverData);
565                         coverFile.close();
566                         audioFile.setFileCover(coverFile.fileName(), true);
567                 }
568         }
569         else
570         {
571                 qWarning("Image data seems to be invalid :-(");
572         }
573 }
574
575 bool FileAnalyzer::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
576 {
577         QProcess process;
578         process.setProcessChannelMode(QProcess::MergedChannels);
579         process.setReadChannel(QProcess::StandardOutput);
580         process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
581
582         if(!process.waitForStarted())
583         {
584                 qWarning("AVS2WAV process failed to create!");
585                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
586                 process.kill();
587                 process.waitForFinished(-1);
588                 return false;
589         }
590
591         bool bInfoHeaderFound = false;
592
593         while(process.state() != QProcess::NotRunning)
594         {
595                 if(m_abortFlag)
596                 {
597                         process.kill();
598                         qWarning("Process was aborted on user request!");
599                         break;
600                 }
601                 
602                 if(!process.waitForReadyRead())
603                 {
604                         if(process.state() == QProcess::Running)
605                         {
606                                 qWarning("AVS2WAV time out. Killing process and skipping file!");
607                                 process.kill();
608                                 process.waitForFinished(-1);
609                                 return false;
610                         }
611                 }
612
613                 QByteArray data;
614
615                 while(process.canReadLine())
616                 {
617                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
618                         if(!line.isEmpty())
619                         {
620                                 int index = line.indexOf(':');
621                                 if(index > 0)
622                                 {
623                                         QString key = line.left(index).trimmed();
624                                         QString val = line.mid(index+1).trimmed();
625
626                                         if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
627                                         {
628                                                 if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
629                                                 {
630                                                         bool ok = false;
631                                                         unsigned int duration = val.toUInt(&ok);
632                                                         if(ok) info.setFileDuration(duration);
633                                                 }
634                                                 if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
635                                                 {
636                                                         bool ok = false;
637                                                         unsigned int samplerate = val.toUInt(&ok);
638                                                         if(ok) info.setFormatAudioSamplerate (samplerate);
639                                                 }
640                                                 if(key.compare("Channels", Qt::CaseInsensitive) == 0)
641                                                 {
642                                                         bool ok = false;
643                                                         unsigned int channels = val.toUInt(&ok);
644                                                         if(ok) info.setFormatAudioChannels(channels);
645                                                 }
646                                                 if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
647                                                 {
648                                                         bool ok = false;
649                                                         unsigned int bitdepth = val.toUInt(&ok);
650                                                         if(ok) info.setFormatAudioBitdepth(bitdepth);
651                                                 }                                       
652                                         }
653                                 }
654                                 else
655                                 {
656                                         if(line.contains("[Audio Info]", Qt::CaseInsensitive))
657                                         {
658                                                 info.setFormatAudioType("Avisynth");
659                                                 info.setFormatContainerType("Avisynth");
660                                                 bInfoHeaderFound = true;
661                                         }
662                                 }
663                         }
664                 }
665         }
666         
667         process.waitForFinished();
668         if(process.state() != QProcess::NotRunning)
669         {
670                 process.kill();
671                 process.waitForFinished(-1);
672         }
673
674         //Check exit code
675         switch(process.exitCode())
676         {
677         case 0:
678                 qDebug("Avisynth script was analyzed successfully.");
679                 return true;
680                 break;
681         case -5:
682                 qWarning("It appears that Avisynth is not installed on the system!");
683                 return false;
684                 break;
685         default:
686                 qWarning("Failed to open the Avisynth script, bad AVS file?");
687                 return false;
688                 break;
689         }
690 }
691
692 bool FileAnalyzer::createTemplate(void)
693 {
694         if(m_templateFile)
695         {
696                 qWarning("Template file already exists!");
697                 return true;
698         }
699         
700         QString templatePath = QString("%1/%2.txt").arg(lamexp_temp_folder2(), lamexp_rand_str());
701
702         QFile templateFile(templatePath);
703         if(!templateFile.open(QIODevice::WriteOnly))
704         {
705                 return false;
706         }
707
708         templateFile.write("General;");
709         for(size_t i = 0; g_tags_gen[i]; i++)
710         {
711                 templateFile.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen[i]).toLatin1().constData());
712         }
713         templateFile.write("\\n\r\n");
714
715         templateFile.write("Audio;");
716         for(size_t i = 0; g_tags_aud[i]; i++)
717         {
718                 templateFile.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud[i]).toLatin1().constData());
719         }
720         templateFile.write("\\n\r\n");
721
722         bool success = (templateFile.error() == QFile::NoError);
723         templateFile.close();
724         
725         if(!success)
726         {
727                 QFile::remove(templatePath);
728                 return false;
729         }
730
731         try
732         {
733                 m_templateFile = new LockedFile(templatePath);
734         }
735         catch(...)
736         {
737                 qWarning("Failed to lock template file!");
738                 return false;
739         }
740
741         return true;
742 }
743
744 unsigned int FileAnalyzer::parseYear(const QString &str)
745 {
746         if(str.startsWith("UTC", Qt::CaseInsensitive))
747         {
748                 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
749                 if(date.isValid())
750                 {
751                         return date.year();
752                 }
753                 else
754                 {
755                         return 0;
756                 }
757         }
758         else
759         {
760                 bool ok = false;
761                 int year = str.toInt(&ok);
762                 if(ok && year > 0)
763                 {
764                         return year;
765                 }
766                 else
767                 {
768                         return 0;
769                 }
770         }
771 }
772
773 unsigned int FileAnalyzer::parseDuration(const QString &str)
774 {
775         bool ok = false;
776         unsigned int value = str.toUInt(&ok);
777         return ok ? (value/1000) : 0;
778 }
779
780 ////////////////////////////////////////////////////////////
781 // Public Functions
782 ////////////////////////////////////////////////////////////
783
784 unsigned int FileAnalyzer::filesAccepted(void)
785 {
786         return m_filesAccepted;
787 }
788
789 unsigned int FileAnalyzer::filesRejected(void)
790 {
791         return m_filesRejected;
792 }
793
794 unsigned int FileAnalyzer::filesDenied(void)
795 {
796         return m_filesDenied;
797 }
798
799 unsigned int FileAnalyzer::filesDummyCDDA(void)
800 {
801         return m_filesDummyCDDA;
802 }
803
804 unsigned int FileAnalyzer::filesCueSheet(void)
805 {
806         return m_filesCueSheet;
807 }
808
809 ////////////////////////////////////////////////////////////
810 // EVENTS
811 ////////////////////////////////////////////////////////////
812
813 /*NONE*/