OSDN Git Service

Updated copyright year.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
index 2b40752..0d5f68f 100644 (file)
@@ -1,11 +1,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 // LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
+// Copyright (C) 2004-2023 LoRd_MuldeR <MuldeR2@GMX.de>
 //
 // This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
+// it under the terms of the GNU GENERAL PUBLIC LICENSE as published by
 // the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
+// (at your option) any later version; always including the non-optional
+// LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "License.txt" file!
 //
 // This program is distributed in the hope that it will be useful,
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 #include "Thread_FileAnalyzer_Task.h"
 
+//Internal
 #include "Global.h"
 #include "LockedFile.h"
 #include "Model_AudioFile.h"
-#include "PlaylistImporter.h"
+#include "MimeTypes.h"
 
+//MUtils
+#include <MUtils/Global.h>
+#include <MUtils/OSSupport.h>
+#include <MUtils/Lazy.h>
+#include <MUtils/Exception.h>
+
+//Qt
 #include <QDir>
 #include <QFileInfo>
 #include <QProcess>
 #include <QReadLocker>
 #include <QWriteLocker>
 #include <QThread>
+#include <QXmlSimpleReader>
+#include <QXmlInputSource>
+#include <QXmlStreamReader>
+#include <QStack>
 
+//CRT
 #include <math.h>
 #include <time.h>
 #include <assert.h>
 
-#define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
-#define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
-#define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
-
-/* static vars */
-QMutex AnalyzeTask::s_waitMutex;
-QWaitCondition AnalyzeTask::s_waitCond;
-QSet<unsigned int> AnalyzeTask::s_threadIdx_running;
-unsigned int AnalyzeTask::s_threadIdx_next = 0;
-QSemaphore AnalyzeTask::s_semaphore(0);
-
-/* more static vars */
-QReadWriteLock AnalyzeTask::s_lock;
-unsigned int AnalyzeTask::s_filesAccepted = 0;
-unsigned int AnalyzeTask::s_filesRejected = 0;
-unsigned int AnalyzeTask::s_filesDenied = 0;
-unsigned int AnalyzeTask::s_filesDummyCDDA = 0;
-unsigned int AnalyzeTask::s_filesCueSheet = 0;
-QStringList AnalyzeTask::s_additionalFiles;
-QSet<QString> AnalyzeTask::s_recentlyAdded;
-
-/*constants*/
-const int WAITCOND_TIMEOUT = 2500;
-const int MAX_RETRIES = 60000 / WAITCOND_TIMEOUT;
-const int MAX_QUEUE_SLOTS = 32;
+////////////////////////////////////////////////////////////
+// Helper Macros
+////////////////////////////////////////////////////////////
+
+#define ADD_PROPTERY_MAPPING_1(TYPE, NAME) do \
+{ \
+       ADD_PROPTERY_MAPPING_2(TYPE, NAME, NAME); \
+} \
+while(0)
+
+#define ADD_PROPTERY_MAPPING_2(TYPE, MI_NAME, LX_NAME) do \
+{ \
+       builder->insert(qMakePair(AnalyzeTask::trackType_##TYPE, QString::fromLatin1(#MI_NAME)), AnalyzeTask::propertyId_##LX_NAME); \
+} \
+while(0)
+
+#define SET_OPTIONAL(TYPE, IF_CMD, THEN_CMD) do \
+{ \
+       TYPE _tmp;\
+       if((IF_CMD)) { THEN_CMD; } \
+} \
+while(0)
+
+#define DIV_RND(A,B) (((A) + ((B) / 2U)) / (B))
+#define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
+
+////////////////////////////////////////////////////////////
+// Static initialization
+////////////////////////////////////////////////////////////
+
+static MUtils::Lazy<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>> s_mediaInfoIdx([]
+{
+       QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t> *const builder = new QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>();
+       ADD_PROPTERY_MAPPING_2(gen, format, container);
+       ADD_PROPTERY_MAPPING_2(gen, format_profile, container_profile);
+       ADD_PROPTERY_MAPPING_1(gen, duration);
+       ADD_PROPTERY_MAPPING_1(gen, title);
+       ADD_PROPTERY_MAPPING_2(gen, track, title);
+       ADD_PROPTERY_MAPPING_1(gen, artist);
+       ADD_PROPTERY_MAPPING_2(gen, performer, artist);
+       ADD_PROPTERY_MAPPING_1(gen, album);
+       ADD_PROPTERY_MAPPING_1(gen, genre);
+       ADD_PROPTERY_MAPPING_1(gen, released_date);
+       ADD_PROPTERY_MAPPING_2(gen, recorded_date, released_date);
+       ADD_PROPTERY_MAPPING_1(gen, track_position);
+       ADD_PROPTERY_MAPPING_1(gen, comment);
+       ADD_PROPTERY_MAPPING_1(aud, format);
+       ADD_PROPTERY_MAPPING_1(aud, format_version);
+       ADD_PROPTERY_MAPPING_1(aud, format_profile);
+       ADD_PROPTERY_MAPPING_2(aud, format_additionalfeatures, format_profile);
+       ADD_PROPTERY_MAPPING_1(aud, duration);
+       ADD_PROPTERY_MAPPING_1(aud, channel_s_);
+       ADD_PROPTERY_MAPPING_1(aud, samplingrate);
+       ADD_PROPTERY_MAPPING_1(aud, bitdepth);
+       ADD_PROPTERY_MAPPING_1(aud, bitrate);
+       ADD_PROPTERY_MAPPING_1(aud, bitrate_mode);
+       ADD_PROPTERY_MAPPING_1(aud, encoded_library);
+       ADD_PROPTERY_MAPPING_2(gen, cover_mime, cover_mime);
+       ADD_PROPTERY_MAPPING_2(gen, cover_data, cover_data);
+       return builder;
+});
+
+static MUtils::Lazy<const QMap<QString, AnalyzeTask::MI_propertyId_t>> s_avisynthIdx([]
+{
+       QMap<QString, AnalyzeTask::MI_propertyId_t> *const builder = new QMap<QString, AnalyzeTask::MI_propertyId_t>();
+       builder->insert(QLatin1String("totalseconds"),  AnalyzeTask::propertyId_duration);
+       builder->insert(QLatin1String("samplespersec"), AnalyzeTask::propertyId_samplingrate);
+       builder->insert(QLatin1String("channels"),      AnalyzeTask::propertyId_channel_s_);
+       builder->insert(QLatin1String("bitspersample"), AnalyzeTask::propertyId_bitdepth);
+       return builder;
+});
+
+static MUtils::Lazy<const QMap<QString, QString>> s_mimeTypes([]
+{
+       QMap<QString, QString> *const builder = new QMap<QString, QString>();
+       for (size_t i = 0U; MIME_TYPES[i].type; ++i)
+       {
+               builder->insert(QString::fromLatin1(MIME_TYPES[i].type), QString::fromLatin1(MIME_TYPES[i].ext[0]));
+       }
+       return builder;
+});
+
+static MUtils::Lazy<const QMap<QString, AnalyzeTask::MI_trackType_t>> s_trackTypes([]
+{
+       QMap<QString, AnalyzeTask::MI_trackType_t> *const builder = new QMap<QString, AnalyzeTask::MI_trackType_t>();
+       builder->insert("general", AnalyzeTask::trackType_gen);
+       builder->insert("audio",   AnalyzeTask::trackType_aud);
+       return builder;
+});
 
 ////////////////////////////////////////////////////////////
 // Constructor
 ////////////////////////////////////////////////////////////
 
-AnalyzeTask::AnalyzeTask(const QString &inputFile, const QString &templateFile, volatile bool *abortFlag)
+AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt &abortFlag)
 :
-       m_threadIdx(makeThreadIdx()),
+       m_taskId(taskId),
        m_inputFile(inputFile),
-       m_templateFile(templateFile),
-       m_mediaInfoBin(lamexp_lookup_tool("mediainfo.exe")),
-       m_avs2wavBin(lamexp_lookup_tool("avs2wav.exe")),
-       m_abortFlag(abortFlag)
+       m_mediaInfoBin(lamexp_tools_lookup("mediainfo.exe")),
+       m_mediaInfoVer(lamexp_tools_version("mediainfo.exe")),
+       m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")),
+       m_abortFlag(abortFlag),
+       m_mediaInfoIdx(*s_mediaInfoIdx),
+       m_avisynthIdx(*s_avisynthIdx),
+       m_mimeTypes(*s_mimeTypes),
+       m_trackTypes(*s_trackTypes)
 {
        if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
        {
@@ -88,13 +170,7 @@ AnalyzeTask::AnalyzeTask(const QString &inputFile, const QString &templateFile,
 
 AnalyzeTask::~AnalyzeTask(void)
 {
-       s_semaphore.release();
-       
-       s_waitMutex.lock();
-       s_threadIdx_running.remove(m_threadIdx);
-       s_waitMutex.unlock();
-
-       s_waitCond.wakeAll();
+       emit taskCompleted(m_taskId);
 }
 
 ////////////////////////////////////////////////////////////
@@ -107,153 +183,116 @@ void AnalyzeTask::run()
        {
                run_ex();
        }
+       catch(const std::exception &error)
+       {
+               MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
+               MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
+       }
        catch(...)
        {
-               qWarning("WARNING: Caught an in exception AnalyzeTask thread!");
+               MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
+               MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
        }
-
-       s_waitMutex.lock();
-       s_threadIdx_running.remove(m_threadIdx);
-       s_waitMutex.unlock();
-
-       s_waitCond.wakeAll();
 }
 
 void AnalyzeTask::run_ex(void)
 {
        int fileType = fileTypeNormal;
        QString currentFile = QDir::fromNativeSeparators(m_inputFile);
-       qDebug("Analyzing: %s", currentFile.toUtf8().constData());
-       
-       emit fileSelected(QFileInfo(currentFile).fileName());
-       emit progressValChanged(m_threadIdx + 1);
+       qDebug("Analyzing: %s", MUTILS_UTF8(currentFile));
        
-       AudioFileModel file = analyzeFile(currentFile, &fileType);
+       AudioFileModel fileInfo(currentFile);
+       analyzeFile(currentFile, fileInfo, &fileType);
 
-       if(*m_abortFlag)
+       if(MUTILS_BOOLIFY(m_abortFlag))
        {
                qWarning("Operation cancelled by user!");
                return;
        }
-       if(fileType == fileTypeSkip)
-       {
-               qWarning("File was recently added, skipping!");
-               return;
-       }
-       if(fileType == fileTypeDenied)
+
+       switch(fileType)
        {
-               QWriteLocker lock(&s_lock);
-               s_filesDenied++;
-               lock.unlock();
+       case fileTypeDenied:
                qWarning("Cannot access file for reading, skipping!");
-               return;
-       }
-       if(fileType == fileTypeCDDA)
-       {
-               QWriteLocker lock(&s_lock);
-               s_filesDummyCDDA++;
-               lock.unlock();
+               break;
+       case fileTypeCDDA:
                qWarning("Dummy CDDA file detected, skipping!");
-               return;
-       }
-
-       //Handle files with *incomplete* meida info
-       if(file.fileName().isEmpty() || file.formatContainerType().isEmpty() || file.formatAudioType().isEmpty())
-       {
-               QStringList fileList;
-               if(PlaylistImporter::importPlaylist(fileList, currentFile))
-               {
-                       qDebug("Imported playlist file.");
-                       QWriteLocker lock(&s_lock);
-                       s_additionalFiles << fileList;
-               }
-               else if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
-               {
-                       QWriteLocker lock(&s_lock);
-                       qWarning("Cue Sheet file detected, skipping!");
-                       s_filesCueSheet++;
-               }
-               else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
+               break;
+       default:
+               if(fileInfo.metaInfo().title().isEmpty() || fileInfo.techInfo().containerType().isEmpty() || fileInfo.techInfo().audioType().isEmpty())
                {
-                       qDebug("Found a potential Avisynth script, investigating...");
-                       if(analyzeAvisynthFile(currentFile, file))
+                       fileType = fileTypeUnknown;
+                       if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
                        {
-                               QWriteLocker lock(&s_lock);
-                               s_filesAccepted++;
-                               s_recentlyAdded.insert(file.filePath().toLower());
-                               lock.unlock();
-                               waitForPreviousThreads();
-                               emit fileAnalyzed(file);
+                               qWarning("Cue Sheet file detected, skipping!");
+                               fileType = fileTypeCueSheet;
+                       }
+                       else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
+                       {
+                               qDebug("Found a potential Avisynth script, investigating...");
+                               if(analyzeAvisynthFile(currentFile, fileInfo))
+                               {
+                                       fileType = fileTypeNormal;
+                               }
+                               else
+                               {
+                                       qDebug("Rejected Avisynth file: %s", MUTILS_UTF8(fileInfo.filePath()));
+                               }
                        }
                        else
                        {
-                               QWriteLocker lock(&s_lock);
-                               qDebug("Rejected Avisynth file: %s", file.filePath().toUtf8().constData());
-                               s_filesRejected++;
+                               qDebug("Rejected file of unknown type: %s", MUTILS_UTF8(fileInfo.filePath()));
                        }
                }
-               else
-               {
-                       QWriteLocker lock(&s_lock);
-                       qDebug("Rejected file of unknown type: %s", file.filePath().toUtf8().constData());
-                       s_filesRejected++;
-               }
-               return;
+               break;
        }
 
        //Emit the file now!
-       QWriteLocker lock(&s_lock);
-       s_filesAccepted++;
-       s_recentlyAdded.insert(file.filePath().toLower());
-       lock.unlock();
-       waitForPreviousThreads();
-       emit fileAnalyzed(file);
+       emit fileAnalyzed(m_taskId, fileType, fileInfo);
 }
 
 ////////////////////////////////////////////////////////////
 // Privtae Functions
 ////////////////////////////////////////////////////////////
 
-const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type)
+const AudioFileModel& AnalyzeTask::analyzeFile(const QString &filePath, AudioFileModel &audioFile, int *const type)
 {
        *type = fileTypeNormal;
-       AudioFileModel audioFile(filePath);
-
-       QReadLocker readLock(&s_lock);
-       if(s_recentlyAdded.contains(filePath.toLower()))
-       {
-               *type = fileTypeSkip;
-               return audioFile;
-       }
-       readLock.unlock();
-
        QFile readTest(filePath);
-       if(!readTest.open(QIODevice::ReadOnly))
+
+       if (!readTest.open(QIODevice::ReadOnly))
        {
                *type = fileTypeDenied;
                return audioFile;
        }
-       if(checkFile_CDDA(readTest))
+
+       if (checkFile_CDDA(readTest))
        {
                *type = fileTypeCDDA;
                return audioFile;
        }
+
        readTest.close();
+       return analyzeMediaFile(filePath, audioFile);
+}
 
-       bool skipNext = false;
-       unsigned int id_val[2] = {UINT_MAX, UINT_MAX};
-       cover_t coverType = coverNone;
+const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, AudioFileModel &audioFile)
+{
+       //bool skipNext = false;
+       QPair<quint32, quint32> id_val(UINT_MAX, UINT_MAX);
        QByteArray coverData;
 
        QStringList params;
-       params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile));
+       params << L1S("--Language=raw") << L1S("--Output=XML") << L1S("--Full") << L1S("--Cover_Data=base64");
        params << QDir::toNativeSeparators(filePath);
-       
+
        QProcess process;
-       process.setProcessChannelMode(QProcess::MergedChannels);
-       process.setReadChannel(QProcess::StandardOutput);
+       MUtils::init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
        process.start(m_mediaInfoBin, params);
-               
+
+       QByteArray data;
+       data.reserve(16384);
+
        if(!process.waitForStarted())
        {
                qWarning("MediaInfo process failed to create!");
@@ -265,7 +304,7 @@ const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type
 
        while(process.state() != QProcess::NotRunning)
        {
-               if(*m_abortFlag)
+               if(MUTILS_BOOLIFY(m_abortFlag))
                {
                        process.kill();
                        qWarning("Process was aborted on user request!");
@@ -276,257 +315,211 @@ const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type
                {
                        if(process.state() == QProcess::Running)
                        {
-                               qWarning("MediaInfo time out. Killing process and skipping file!");
+                               qWarning("MediaInfo time out. Killing the process now!");
                                process.kill();
                                process.waitForFinished(-1);
-                               return audioFile;
+                               break;
                        }
                }
 
-               QByteArray data;
-
-               while(process.canReadLine())
+               forever
                {
-                       QString line = QString::fromUtf8(process.readLine().constData()).simplified();
-                       if(!line.isEmpty())
-                       {
-                               //qDebug("Line:%s", line.toUtf8().constData());
-                               
-                               int index = line.indexOf('=');
-                               if(index > 0)
-                               {
-                                       QString key = line.left(index).trimmed();
-                                       QString val = line.mid(index+1).trimmed();
-                                       if(!key.isEmpty())
-                                       {
-                                               updateInfo(audioFile, &skipNext, id_val, &coverType, &coverData, key, val);
-                                       }
-                               }
+                       const QByteArray dataNext = process.readAll();
+                       if (dataNext.isEmpty()) {
+                               break; /*no more input data*/
                        }
+                       data += dataNext.trimmed();
                }
        }
 
-       if(audioFile.fileName().isEmpty())
-       {
-               QString baseName = QFileInfo(filePath).fileName();
-               int index = baseName.lastIndexOf(".");
-
-               if(index >= 0)
-               {
-                       baseName = baseName.left(index);
-               }
-
-               baseName = baseName.replace("_", " ").simplified();
-               index = baseName.lastIndexOf(" - ");
-
-               if(index >= 0)
-               {
-                       baseName = baseName.mid(index + 3).trimmed();
-               }
-
-               audioFile.setFileName(baseName);
-       }
-       
        process.waitForFinished();
-       if(process.state() != QProcess::NotRunning)
+       if (process.state() != QProcess::NotRunning)
        {
                process.kill();
                process.waitForFinished(-1);
        }
 
-       if((coverType != coverNone) && (!coverData.isEmpty()))
+       while (!process.atEnd())
        {
-               retrieveCover(audioFile, coverType, coverData);
+               const QByteArray dataNext = process.readAll();
+               if (dataNext.isEmpty()) {
+                       break; /*no more input data*/
+               }
+               data += dataNext.trimmed();
        }
 
-       return audioFile;
+#if MUTILS_DEBUG
+       qDebug("-----BEGIN MEDIAINFO-----\n%s\n-----END MEDIAINFO-----", data.constData());
+#endif //MUTILS_DEBUG
+
+       return parseMediaInfo(data, audioFile);
 }
 
-void AnalyzeTask::updateInfo(AudioFileModel &audioFile, bool *skipNext, unsigned int *id_val, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
+const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile)
 {
-       //qWarning("'%s' -> '%s'", key.toUtf8().constData(), value.toUtf8().constData());
-       
-       /*New Stream*/
-       if(IS_KEY("Gen_ID") || IS_KEY("Aud_ID"))
+       QXmlStreamReader xmlStream(data);
+       bool firstMediaFile = true;
+
+       if (findNextElement(QLatin1String("MediaInfo"), xmlStream))
        {
-               if(value.isEmpty())
+               const QString versionXml = findAttribute(QLatin1String("Version"),  xmlStream.attributes());
+               if (versionXml.isEmpty() || (!checkVersionStr(versionXml, 2U, 0U)))
                {
-                       *skipNext = false;
+                       qWarning("Invalid file format version property: \"%s\"", MUTILS_UTF8(versionXml));
+                       return audioFile;
                }
-               else
+               if (findNextElement(QLatin1String("CreatingLibrary"), xmlStream))
                {
-                       //We ignore all ID's, except for the lowest one!
-                       bool ok = false;
-                       unsigned int id = value.toUInt(&ok);
-                       if(ok)
+                       const QString versionLib = findAttribute(QLatin1String("Version"), xmlStream.attributes());
+                       const QString identifier = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
+                       if (!STRICMP(identifier, QLatin1String("MediaInfoLib")))
+                       {
+                               qWarning("Invalid library identiofier property: \"%s\"", MUTILS_UTF8(identifier));
+                               return audioFile;
+                       }
+                       if (!versionLib.isEmpty())
                        {
-                               if(IS_KEY("Gen_ID")) { id_val[0] = qMin(id_val[0], id); *skipNext = (id > id_val[0]); }
-                               if(IS_KEY("Aud_ID")) { id_val[1] = qMin(id_val[1], id); *skipNext = (id > id_val[1]); }
+                               if (m_mediaInfoVer != UINT_MAX)
+                               {
+                                       const quint32 mediaInfoVer = (m_mediaInfoVer > 9999U) ? m_mediaInfoVer / 10U : m_mediaInfoVer;
+                                       if (!checkVersionStr(versionLib, mediaInfoVer / 100U, mediaInfoVer % 100U))
+                                       {
+                                               qWarning("Invalid library version property: \"%s\"", MUTILS_UTF8(versionLib));
+                                               return audioFile;
+                                       }
+                               }
                        }
                        else
                        {
-                               *skipNext = true;
+                               qWarning("Library version property not found!");
+                               return audioFile;
+                       }
+                       while (findNextElement(QLatin1String("Media"), xmlStream))
+                       {
+                               if (firstMediaFile || audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty())
+                               {
+                                       firstMediaFile = false;
+                                       parseFileInfo(xmlStream, audioFile);
+                               }
+                               else
+                               {
+                                       qWarning("Skipping non-primary file!");
+                                       xmlStream.skipCurrentElement();
+                               }
                        }
                }
-               if(*skipNext)
-               {
-                       qWarning("Skipping info for non-primary stream!");
-               }
-               return;
        }
 
-       /*Skip or empty?*/
-       if((*skipNext) || value.isEmpty())
+       if (!(audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty()))
        {
-               return;
+               if (audioFile.metaInfo().title().isEmpty())
+               {
+                       QString baseName = QFileInfo(audioFile.filePath()).fileName();
+                       int index;
+                       if ((index = baseName.lastIndexOf(".")) >= 0)
+                       {
+                               baseName = baseName.left(index);
+                       }
+                       baseName = baseName.replace("_", " ").simplified();
+                       if ((index = baseName.lastIndexOf(" - ")) >= 0)
+                       {
+                               baseName = baseName.mid(index + 3).trimmed();
+                       }
+                       audioFile.metaInfo().setTitle(baseName);
+               }
+               if ((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
+               {
+                       if (audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
+               }
        }
-
-       /*Playlist file?*/
-       if(IS_KEY("Aud_Source"))
+       else
        {
-               *skipNext = true;
-               audioFile.setFormatContainerType(QString());
-               audioFile.setFormatAudioType(QString());
-               qWarning("Skipping info for playlist file!");
-               return;
+               qWarning("Audio file format could *not* be recognized!");
        }
 
-       /*General Section*/
-       if(IS_SEC("Gen"))
+       return audioFile;
+}
+
+void AnalyzeTask::parseFileInfo(QXmlStreamReader &xmlStream, AudioFileModel &audioFile)
+{
+       QSet<MI_trackType_t> tracksProcessed;
+       MI_trackType_t trackType;
+       while (findNextElement(QLatin1String("Track"), xmlStream))
        {
-               if(IS_KEY("Gen_Format"))
-               {
-                       audioFile.setFormatContainerType(value);
-               }
-               else if(IS_KEY("Gen_Format_Profile"))
-               {
-                       audioFile.setFormatContainerProfile(value);
-               }
-               else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
-               {
-                       audioFile.setFileName(value);
-               }
-               else if(IS_KEY("Gen_Duration"))
-               {
-                       unsigned int tmp = parseDuration(value);
-                       if(tmp > 0) audioFile.setFileDuration(tmp);
-               }
-               else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
-               {
-                       audioFile.setFileArtist(value);
-               }
-               else if(IS_KEY("Gen_Album"))
-               {
-                       audioFile.setFileAlbum(value);
-               }
-               else if(IS_KEY("Gen_Genre"))
-               {
-                       audioFile.setFileGenre(value);
-               }
-               else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
-               {
-                       unsigned int tmp = parseYear(value);
-                       if(tmp > 0) audioFile.setFileYear(tmp);
-               }
-               else if(IS_KEY("Gen_Comment"))
+               const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes());
+               if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1))
                {
-                       audioFile.setFileComment(value);
-               }
-               else if(IS_KEY("Gen_Track/Position"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.setFilePosition(tmp);
-               }
-               else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
-               {
-                       if(*coverType == coverNone)
+                       if (!tracksProcessed.contains(trackType))
                        {
-                               *coverType = coverJpeg;
+                               tracksProcessed << trackType;
+                               parseTrackInfo(xmlStream, trackType, audioFile);
+                       }
+                       else
+                       {
+                               qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeString));
+                               xmlStream.skipCurrentElement();
                        }
-               }
-               else if(IS_KEY("Gen_Cover_Mime"))
-               {
-                       QString temp = FIRST_TOK(value);
-                       if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
-                       else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
-                       else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
-               }
-               else if(IS_KEY("Gen_Cover_Data"))
-               {
-                       if(!coverData->isEmpty()) coverData->clear();
-                       coverData->append(QByteArray::fromBase64(FIRST_TOK(value).toLatin1()));
                }
                else
                {
-                       qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
+                       qWarning("Skipping unsupported '%s' track!", MUTILS_UTF8(typeString));
+                       xmlStream.skipCurrentElement();
                }
-               return;
        }
+}
 
-       /*Audio Section*/
-       if(IS_SEC("Aud"))
+void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile)
+{
+       QString coverMimeType;
+       while (xmlStream.readNextStartElement())
        {
-
-               if(IS_KEY("Aud_Format"))
-               {
-                       audioFile.setFormatAudioType(value);
-               }
-               else if(IS_KEY("Aud_Format_Profile"))
+               const MI_propertyId_t idx = m_mediaInfoIdx.value(qMakePair(trackType, xmlStream.name().toString().simplified().toLower()), MI_propertyId_t(-1));
+               if (idx != MI_propertyId_t(-1))
                {
-                       audioFile.setFormatAudioProfile(value);
-               }
-               else if(IS_KEY("Aud_Format_Version"))
-               {
-                       audioFile.setFormatAudioVersion(value);
-               }
-               else if(IS_KEY("Aud_Channel(s)"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.setFormatAudioChannels(tmp);
-               }
-               else if(IS_KEY("Aud_SamplingRate"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.setFormatAudioSamplerate(tmp);
-               }
-               else if(IS_KEY("Aud_BitDepth"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.setFormatAudioBitdepth(tmp);
-               }
-               else if(IS_KEY("Aud_Duration"))
-               {
-                       unsigned int tmp = parseDuration(value);
-                       if(tmp > 0) audioFile.setFileDuration(tmp);
-               }
-               else if(IS_KEY("Aud_BitRate"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.setFormatAudioBitrate(tmp/1000);
-               }
-               else if(IS_KEY("Aud_BitRate_Mode"))
-               {
-                       if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeConstant);
-                       if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeVariable);
-               }
-               else if(IS_KEY("Aud_Encoded_Library"))
-               {
-                       audioFile.setFormatAudioEncodeLib(value);
+                       const QString encoding = findAttribute(QLatin1String("dt"), xmlStream.attributes());
+                       const QString value = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
+                       if (!value.isEmpty())
+                       {
+                               parseProperty(encoding.isEmpty() ? value : decodeStr(value, encoding), idx, audioFile, coverMimeType);
+                       }
                }
                else
                {
-                       qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
+                       xmlStream.skipCurrentElement();
                }
-               return;
        }
+}
 
-       /*Section not recognized*/
-       qWarning("Unknown section: %s", key.toUtf8().constData());
+void AnalyzeTask::parseProperty(const QString &value, const MI_propertyId_t propertyIdx, AudioFileModel &audioFile, QString &coverMimeType)
+{
+#if MUTILS_DEBUG
+       qDebug("Property #%d = \"%s\"", propertyIdx, MUTILS_UTF8(value.left(24)));
+#endif
+       switch (propertyIdx)
+       {
+               case propertyId_container:         audioFile.techInfo().setContainerType(value);                                                                  return;
+               case propertyId_container_profile: audioFile.techInfo().setContainerProfile(value);                                                               return;
+               case propertyId_duration:          SET_OPTIONAL(double, parseFloat(value, _tmp), audioFile.techInfo().setDuration(qRound(_tmp)));                 return;
+               case propertyId_title:             audioFile.metaInfo().setTitle(value);                                                                          return;
+               case propertyId_artist:            audioFile.metaInfo().setArtist(value);                                                                         return;
+               case propertyId_album:             audioFile.metaInfo().setAlbum(value);                                                                          return;
+               case propertyId_genre:             audioFile.metaInfo().setGenre(value);                                                                          return;
+               case propertyId_released_date:     SET_OPTIONAL(quint32, parseYear(value, _tmp), audioFile.metaInfo().setYear(_tmp));                             return;
+               case propertyId_track_position:    SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.metaInfo().setPosition(_tmp));                     return;
+               case propertyId_comment:           audioFile.metaInfo().setComment(value);                                                                        return;
+               case propertyId_format:            audioFile.techInfo().setAudioType(value);                                                                      return;
+               case propertyId_format_version:    audioFile.techInfo().setAudioVersion(value);                                                                   return;
+               case propertyId_format_profile:    audioFile.techInfo().setAudioProfile(value);                                                                   return;
+               case propertyId_channel_s_:        SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioChannels(_tmp));                return;
+               case propertyId_samplingrate:      SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioSamplerate(_tmp));              return;
+               case propertyId_bitdepth:          SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioBitdepth(_tmp));                return;
+               case propertyId_bitrate:           SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioBitrate(DIV_RND(_tmp, 1000U))); return;
+               case propertyId_bitrate_mode:      SET_OPTIONAL(quint32, parseRCMode(value, _tmp), audioFile.techInfo().setAudioBitrateMode(_tmp));               return;
+               case propertyId_encoded_library:   audioFile.techInfo().setAudioEncodeLib(cleanAsciiStr(value));                                                  return;
+               case propertyId_cover_mime:        coverMimeType = value;                                                                                         return;
+               case propertyId_cover_data:        retrieveCover(audioFile, coverMimeType, value);                                                                return;
+               default: MUTILS_THROW_FMT("Invalid property ID: %d", propertyIdx);
+       }
 }
 
 bool AnalyzeTask::checkFile_CDDA(QFile &file)
@@ -541,45 +534,33 @@ bool AnalyzeTask::checkFile_CDDA(QFile &file)
        return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
 }
 
-void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
+void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, const QString &coverType, const QString &coverData)
 {
-       qDebug("Retrieving cover!");
-       QString extension;
-
-       switch(coverType)
-       {
-       case coverPng:
-               extension = QString::fromLatin1("png");
-               break;
-       case coverGif:
-               extension = QString::fromLatin1("gif");
-               break;
-       default:
-               extension = QString::fromLatin1("jpg");
-               break;
-       }
-       
-       if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
+       const QByteArray content = QByteArray::fromBase64(coverData.toLatin1());
+       const QString type = m_mimeTypes.value(coverType.toLower());
+       qDebug("Retrieving cover! (mime=\"%s\", type=\"%s\", len=%d)", MUTILS_L1STR(coverType), MUTILS_L1STR(type), content.size());
+       if(!QImage::fromData(content, type.isEmpty() ? NULL : MUTILS_L1STR(type.toUpper())).isNull())
        {
-               QFile coverFile(QString("%1/%2.%3").arg(lamexp_temp_folder2(), lamexp_rand_str(), extension));
+               QFile coverFile(QString("%1/%2.%3").arg(MUtils::temp_folder(), MUtils::next_rand_str(), type.isEmpty() ? QLatin1String("jpg") : type));
                if(coverFile.open(QIODevice::WriteOnly))
                {
-                       coverFile.write(coverData);
+                       coverFile.write(content);
                        coverFile.close();
-                       audioFile.setFileCover(coverFile.fileName(), true);
+                       audioFile.metaInfo().setCover(coverFile.fileName(), true);
                }
        }
        else
        {
-               qWarning("Image data seems to be invalid :-(");
+               qWarning("Image data seems to be invalid! [Header:%s]", content.left(32).toHex().constData());
        }
 }
 
+
 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
 {
        QProcess process;
-       process.setProcessChannelMode(QProcess::MergedChannels);
-       process.setReadChannel(QProcess::StandardOutput);
+       MUtils::init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
+
        process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
 
        if(!process.waitForStarted())
@@ -595,7 +576,7 @@ bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &i
 
        while(process.state() != QProcess::NotRunning)
        {
-               if(*m_abortFlag)
+               if(MUTILS_BOOLIFY(m_abortFlag))
                {
                        process.kill();
                        qWarning("Process was aborted on user request!");
@@ -613,53 +594,36 @@ bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &i
                        }
                }
 
-               QByteArray data;
-
                while(process.canReadLine())
                {
-                       QString line = QString::fromUtf8(process.readLine().constData()).simplified();
+                       const QString line = QString::fromUtf8(process.readLine().constData()).simplified();
                        if(!line.isEmpty())
                        {
-                               int index = line.indexOf(':');
-                               if(index > 0)
+                               if(bInfoHeaderFound)
                                {
-                                       QString key = line.left(index).trimmed();
-                                       QString val = line.mid(index+1).trimmed();
-
-                                       if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
+                                       const qint32 index = line.indexOf(':');
+                                       if (index > 0)
                                        {
-                                               if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
+                                               const QString key = line.left(index).trimmed();
+                                               const QString val = line.mid(index + 1).trimmed();
+                                               if (!(key.isEmpty() || val.isEmpty()))
                                                {
-                                                       bool ok = false;
-                                                       unsigned int duration = val.toUInt(&ok);
-                                                       if(ok) info.setFileDuration(duration);
+                                                       switch (m_avisynthIdx.value(key.toLower(), MI_propertyId_t(-1)))
+                                                       {
+                                                               case propertyId_duration:     SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setDuration(_tmp));        break;
+                                                               case propertyId_samplingrate: SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioSamplerate(_tmp)); break;
+                                                               case propertyId_channel_s_:   SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioChannels(_tmp));   break;
+                                                               case propertyId_bitdepth:     SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioBitdepth(_tmp));   break;
+                                                       }
                                                }
-                                               if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int samplerate = val.toUInt(&ok);
-                                                       if(ok) info.setFormatAudioSamplerate (samplerate);
-                                               }
-                                               if(key.compare("Channels", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int channels = val.toUInt(&ok);
-                                                       if(ok) info.setFormatAudioChannels(channels);
-                                               }
-                                               if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int bitdepth = val.toUInt(&ok);
-                                                       if(ok) info.setFormatAudioBitdepth(bitdepth);
-                                               }                                       
                                        }
                                }
                                else
                                {
                                        if(line.contains("[Audio Info]", Qt::CaseInsensitive))
                                        {
-                                               info.setFormatAudioType("Avisynth");
-                                               info.setFormatContainerType("Avisynth");
+                                               info.techInfo().setAudioType("Avisynth");
+                                               info.techInfo().setContainerType("Avisynth");
                                                bInfoHeaderFound = true;
                                        }
                                }
@@ -692,177 +656,129 @@ bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &i
        }
 }
 
-unsigned int AnalyzeTask::parseYear(const QString &str)
+// ---------------------------------------------------------
+// Utility Functions
+// ---------------------------------------------------------
+
+QString AnalyzeTask::decodeStr(const QString &str, const QString &encoding)
 {
-       if(str.startsWith("UTC", Qt::CaseInsensitive))
-       {
-               QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
-               if(date.isValid())
-               {
-                       return date.year();
-               }
-               else
-               {
-                       return 0;
-               }
-       }
-       else
+       if (STRICMP(encoding, QLatin1String("binary.base64")))
        {
-               bool ok = false;
-               int year = str.toInt(&ok);
-               if(ok && year > 0)
-               {
-                       return year;
-               }
-               else
-               {
-                       return 0;
-               }
+               const QString decoded = QString::fromUtf8(QByteArray::fromBase64(str.toLatin1()));
+               return decoded;
        }
+       return QString();
 }
 
-unsigned int AnalyzeTask::parseDuration(const QString &str)
+bool AnalyzeTask::parseUnsigned(const QString &str, quint32 &value)
 {
-       bool ok = false;
-       unsigned int value = str.toUInt(&ok);
-       return ok ? (value/1000) : 0;
+       bool okay = false;
+       value = str.toUInt(&okay);
+       return okay;
 }
-
-unsigned __int64 AnalyzeTask::makeThreadIdx(void)
+bool AnalyzeTask::parseFloat(const QString &str, double &value)
 {
-       s_waitMutex.lock();
-       unsigned int idx = s_threadIdx_next++;
-       s_threadIdx_running.insert(idx);
-       s_waitMutex.unlock();
-
-       return idx;
+       bool okay = false;
+       value = QLocale::c().toDouble(str, &okay);
+       return okay;
 }
 
-void AnalyzeTask::waitForPreviousThreads(void)
+bool AnalyzeTask::parseYear(const QString &str, quint32 &value)
 {
-       //This function will block until all threads with a *lower* index have terminated.
-       //Required to make sure that the files will be added in the "correct" order!
-
-       s_waitMutex.lock();
-       int retryCount = 0;
-       
-       forever
+       if (str.startsWith(QLatin1String("UTC"), Qt::CaseInsensitive))
        {
-               bool bWaitFlag = false;
-               QSet<unsigned int>::const_iterator i;
-
-               for(i = s_threadIdx_running.begin(); i != s_threadIdx_running.end(); ++i)
-               {
-                       if(*i < m_threadIdx) { bWaitFlag = true; break; }
-               }
-
-               if((!bWaitFlag) || *m_abortFlag)
+               const QDate date = QDate::fromString(str.mid(3).trimmed().left(10), QLatin1String("yyyy-MM-dd"));
+               if (date.isValid())
                {
-                       s_waitMutex.unlock();
-                       return;
-               }
-
-               if(!s_waitCond.wait(&s_waitMutex, WAITCOND_TIMEOUT))
-               {
-                       if(++retryCount > MAX_RETRIES)
-                       {
-                               qWarning("AnalyzeTask::waitForPreviousThreads encountered timeout !!!");
-                               s_threadIdx_running.clear();
-                       }
+                       value = date.year();
+                       return true;
                }
+               return false;
+       }
+       else
+       {
+               return parseUnsigned(str, value);
        }
 }
 
-////////////////////////////////////////////////////////////
-// Public Functions
-////////////////////////////////////////////////////////////
-
-unsigned int AnalyzeTask::filesAccepted(void)
-{
-       QReadLocker lock(&s_lock);
-       return s_filesAccepted;
-}
-
-unsigned int AnalyzeTask::filesRejected(void)
-{
-       QReadLocker lock(&s_lock);
-       return s_filesRejected;
-}
-
-unsigned int AnalyzeTask::filesDenied(void)
-{
-       QReadLocker lock(&s_lock);
-       return s_filesDenied;
-}
-
-unsigned int AnalyzeTask::filesDummyCDDA(void)
+bool AnalyzeTask::parseRCMode(const QString &str, quint32 &value)
 {
-       QReadLocker lock(&s_lock);
-       return s_filesDummyCDDA;
+       if (STRICMP(str, QLatin1String("CBR")))
+       {
+               value = AudioFileModel::BitrateModeConstant;
+               return true;
+       }
+       if (STRICMP(str, QLatin1String("VBR")))
+       {
+               value = AudioFileModel::BitrateModeVariable;
+               return true;
+       }
+       return false;
 }
 
-unsigned int AnalyzeTask::filesCueSheet(void)
+QString AnalyzeTask::cleanAsciiStr(const QString &str)
 {
-       QReadLocker lock(&s_lock);
-       return s_filesCueSheet;
+       QByteArray ascii = str.toLatin1();
+       for (QByteArray::Iterator iter = ascii.begin(); iter != ascii.end(); ++iter)
+       {
+               if ((*iter < 0x20) || (*iter >= 0x7F)) *iter = 0x3F;
+       }
+       return QString::fromLatin1(ascii).remove(QLatin1Char('?')).simplified();
 }
 
-int AnalyzeTask::getAdditionalFiles(QStringList &fileList)
+bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
 {
-       QReadLocker readLock(&s_lock);
-       int count = s_additionalFiles.count();
-       readLock.unlock();
-
-       if(count > 0)
+       while (xmlStream.readNextStartElement())
        {
-               QWriteLocker lock(&s_lock);
-               count = s_additionalFiles.count();
-               fileList << s_additionalFiles;
-               s_additionalFiles.clear();
-               return count;
+               if (STRICMP(xmlStream.name(), name))
+               {
+                       return true;
+               }
+               xmlStream.skipCurrentElement();
        }
-
-       return 0;
+       return false;
 }
 
-bool AnalyzeTask::waitForFreeSlot(volatile bool *abortFlag)
+QString AnalyzeTask::findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes)
 {
-       bool ret = false;
-
-       for(int i = 0; i < MAX_RETRIES; i++)
+       for (QXmlStreamAttributes::ConstIterator iter = xmlAttributes.constBegin(); iter != xmlAttributes.constEnd(); ++iter)
        {
-               ret = s_semaphore.tryAcquire(1, WAITCOND_TIMEOUT);
-               if(ret || (*abortFlag)) break;
+               if (STRICMP(iter->name(), name))
+               {
+                       const QString value = iter->value().toString().simplified();
+                       if (!value.isEmpty())
+                       {
+                               return value; /*found*/
+                       }
+               }
        }
-
-       return ret;
+       return QString();
 }
 
-void AnalyzeTask::reset(void)
+bool AnalyzeTask::checkVersionStr(const QString &str, const quint32 expectedMajor, const quint32 expectedMinor)
 {
-       QWriteLocker lock(&s_lock);
-       s_filesAccepted = 0;
-       s_filesRejected = 0;
-       s_filesDenied = 0;
-       s_filesDummyCDDA = 0;
-       s_filesCueSheet = 0;
-       s_additionalFiles.clear();
-       s_recentlyAdded.clear();
-       lock.unlock();
-
-       s_waitMutex.lock();
-       s_threadIdx_next = 0;
-       s_threadIdx_running.clear();
-       s_waitMutex.unlock();
-
-       int freeSlots = s_semaphore.available();
-       if(freeSlots < MAX_QUEUE_SLOTS)
-       {
-               s_semaphore.release(MAX_QUEUE_SLOTS - freeSlots);
+       QRegExp version("^(\\d+)\\.(\\d+)($|\\.)");
+       if (version.indexIn(str) >= 0)
+       {
+               quint32 actual[2];
+               if (MUtils::regexp_parse_uint32(version, actual, 2))
+               {
+                       if ((actual[0] == expectedMajor) && (actual[1] >= expectedMinor))
+                       {
+                               return true;
+                       }
+               }
        }
+       return false;
 }
 
 ////////////////////////////////////////////////////////////
+// Public Functions
+////////////////////////////////////////////////////////////
+
+/*NONE*/
+
+////////////////////////////////////////////////////////////
 // EVENTS
 ////////////////////////////////////////////////////////////