OSDN Git Service

Updated Monkey's Audio binaries to v10.36 (2023-12-17), compiled with ICL 2024.0...
[lamexp/LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
index fc245c4..0d5f68f 100644 (file)
@@ -1,12 +1,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 // LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2013 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, but always including the *additional*
-// restrictions defined in the "License.txt" file.
+// (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 "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())
+////////////////////////////////////////////////////////////
+// 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 int taskId, const QString &inputFile, const QString &templateFile, volatile bool *abortFlag)
+AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt &abortFlag)
 :
        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())
        {
@@ -81,15 +185,13 @@ void AnalyzeTask::run()
        }
        catch(const std::exception &error)
        {
-               fflush(stdout); fflush(stderr);
-               fprintf(stderr, "\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
-               lamexp_fatal_exit(L"Unhandeled C++ exception error, application will exit!");
+               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(...)
        {
-               fflush(stdout); fflush(stderr);
-               fprintf(stderr, "\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
-               lamexp_fatal_exit(L"Unhandeled C++ exception error, application will exit!");
+               MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
+               MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
        }
 }
 
@@ -97,11 +199,12 @@ void AnalyzeTask::run_ex(void)
 {
        int fileType = fileTypeNormal;
        QString currentFile = QDir::fromNativeSeparators(m_inputFile);
-       qDebug("Analyzing: %s", QUTF8(currentFile));
+       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;
@@ -116,7 +219,7 @@ void AnalyzeTask::run_ex(void)
                qWarning("Dummy CDDA file detected, skipping!");
                break;
        default:
-               if(file.metaInfo().title().isEmpty() || file.techInfo().containerType().isEmpty() || file.techInfo().audioType().isEmpty())
+               if(fileInfo.metaInfo().title().isEmpty() || fileInfo.techInfo().containerType().isEmpty() || fileInfo.techInfo().audioType().isEmpty())
                {
                        fileType = fileTypeUnknown;
                        if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
@@ -127,63 +230,69 @@ void AnalyzeTask::run_ex(void)
                        else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
                        {
                                qDebug("Found a potential Avisynth script, investigating...");
-                               if(analyzeAvisynthFile(currentFile, file))
+                               if(analyzeAvisynthFile(currentFile, fileInfo))
                                {
                                        fileType = fileTypeNormal;
                                }
                                else
                                {
-                                       qDebug("Rejected Avisynth file: %s", QUTF8(file.filePath()));
+                                       qDebug("Rejected Avisynth file: %s", MUTILS_UTF8(fileInfo.filePath()));
                                }
                        }
                        else
                        {
-                               qDebug("Rejected file of unknown type: %s", QUTF8(file.filePath()));
+                               qDebug("Rejected file of unknown type: %s", MUTILS_UTF8(fileInfo.filePath()));
                        }
                }
                break;
        }
 
        //Emit the file now!
-       emit fileAnalyzed(m_taskId, fileType, 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);
-
        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;
-       lamexp_init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
 
+       QProcess process;
+       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!");
@@ -195,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!");
@@ -206,262 +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", QUTF8(line));
-                               
-                               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.metaInfo().title().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.metaInfo().setTitle(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();
        }
 
-       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);
-       }
+#if MUTILS_DEBUG
+       qDebug("-----BEGIN MEDIAINFO-----\n%s\n-----END MEDIAINFO-----", data.constData());
+#endif //MUTILS_DEBUG
 
-       return audioFile;
+       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'", QUTF8(key), QUTF8(value));
-       
-       /*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.techInfo().setContainerType(QString());
-               audioFile.techInfo().setAudioType(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.techInfo().setContainerType(value);
-               }
-               else if(IS_KEY("Gen_Format_Profile"))
-               {
-                       audioFile.techInfo().setContainerProfile(value);
-               }
-               else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
-               {
-                       audioFile.metaInfo().setTitle(value);
-               }
-               else if(IS_KEY("Gen_Duration"))
-               {
-                       unsigned int tmp = parseDuration(value);
-                       if(tmp > 0) audioFile.techInfo().setDuration(tmp);
-               }
-               else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
-               {
-                       audioFile.metaInfo().setArtist(value);
-               }
-               else if(IS_KEY("Gen_Album"))
-               {
-                       audioFile.metaInfo().setAlbum(value);
-               }
-               else if(IS_KEY("Gen_Genre"))
-               {
-                       audioFile.metaInfo().setGenre(value);
-               }
-               else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
-               {
-                       unsigned int tmp = parseYear(value);
-                       if(tmp > 0) audioFile.metaInfo().setYear(tmp);
-               }
-               else if(IS_KEY("Gen_Comment"))
-               {
-                       audioFile.metaInfo().setComment(value);
-               }
-               else if(IS_KEY("Gen_Track/Position"))
+               const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes());
+               if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1))
                {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.metaInfo().setPosition(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!", QUTF8(key), QUTF8(value));
+                       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.techInfo().setAudioType(value);
-               }
-               else if(IS_KEY("Aud_Format_Profile"))
-               {
-                       audioFile.techInfo().setAudioProfile(value);
-               }
-               else if(IS_KEY("Aud_Format_Version"))
-               {
-                       audioFile.techInfo().setAudioVersion(value);
-               }
-               else if(IS_KEY("Aud_Channel(s)"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.techInfo().setAudioChannels(tmp);
-               }
-               else if(IS_KEY("Aud_SamplingRate"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.techInfo().setAudioSamplerate(tmp);
-               }
-               else if(IS_KEY("Aud_BitDepth"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.techInfo().setAudioBitdepth(tmp);
-               }
-               else if(IS_KEY("Aud_Duration"))
-               {
-                       unsigned int tmp = parseDuration(value);
-                       if(tmp > 0) audioFile.techInfo().setDuration(tmp);
-               }
-               else if(IS_KEY("Aud_BitRate"))
-               {
-                       bool ok = false;
-                       unsigned int tmp = value.toUInt(&ok);
-                       if(ok) audioFile.techInfo().setAudioBitrate(tmp/1000);
-               }
-               else if(IS_KEY("Aud_BitRate_Mode"))
-               {
-                       if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.techInfo().setAudioBitrateMode(AudioFileModel::BitrateModeConstant);
-                       if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.techInfo().setAudioBitrateMode(AudioFileModel::BitrateModeVariable);
-               }
-               else if(IS_KEY("Aud_Encoded_Library"))
+               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.techInfo().setAudioEncodeLib(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!", QUTF8(key), QUTF8(value));
+                       xmlStream.skipCurrentElement();
                }
-               return;
        }
+}
 
-       /*Section not recognized*/
-       qWarning("Unknown section: %s", QUTF8(key));
+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)
@@ -476,44 +534,32 @@ 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)
+       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())
        {
-       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()))
-       {
-               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.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;
-       lamexp_init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
+       MUtils::init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
 
        process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
 
@@ -530,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!");
@@ -548,44 +594,27 @@ 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)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int duration = val.toUInt(&ok);
-                                                       if(ok) info.techInfo().setDuration(duration);
-                                               }
-                                               if(key.compare("SamplesPerSec", 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 samplerate = val.toUInt(&ok);
-                                                       if(ok) info.techInfo().setAudioSamplerate (samplerate);
-                                               }
-                                               if(key.compare("Channels", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int channels = val.toUInt(&ok);
-                                                       if(ok) info.techInfo().setAudioChannels(channels);
-                                               }
-                                               if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int bitdepth = val.toUInt(&ok);
-                                                       if(ok) info.techInfo().setAudioBitdepth(bitdepth);
+                                                       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;
+                                                       }
                                                }
                                        }
                                }
@@ -627,43 +656,122 @@ 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))
+       if (STRICMP(encoding, QLatin1String("binary.base64")))
        {
-               QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
-               if(date.isValid())
-               {
-                       return date.year();
-               }
-               else
+               const QString decoded = QString::fromUtf8(QByteArray::fromBase64(str.toLatin1()));
+               return decoded;
+       }
+       return QString();
+}
+
+bool AnalyzeTask::parseUnsigned(const QString &str, quint32 &value)
+{
+       bool okay = false;
+       value = str.toUInt(&okay);
+       return okay;
+}
+bool AnalyzeTask::parseFloat(const QString &str, double &value)
+{
+       bool okay = false;
+       value = QLocale::c().toDouble(str, &okay);
+       return okay;
+}
+
+bool AnalyzeTask::parseYear(const QString &str, quint32 &value)
+{
+       if (str.startsWith(QLatin1String("UTC"), Qt::CaseInsensitive))
+       {
+               const QDate date = QDate::fromString(str.mid(3).trimmed().left(10), QLatin1String("yyyy-MM-dd"));
+               if (date.isValid())
                {
-                       return 0;
+                       value = date.year();
+                       return true;
                }
+               return false;
        }
        else
        {
-               bool ok = false;
-               int year = str.toInt(&ok);
-               if(ok && year > 0)
+               return parseUnsigned(str, value);
+       }
+}
+
+bool AnalyzeTask::parseRCMode(const QString &str, quint32 &value)
+{
+       if (STRICMP(str, QLatin1String("CBR")))
+       {
+               value = AudioFileModel::BitrateModeConstant;
+               return true;
+       }
+       if (STRICMP(str, QLatin1String("VBR")))
+       {
+               value = AudioFileModel::BitrateModeVariable;
+               return true;
+       }
+       return false;
+}
+
+QString AnalyzeTask::cleanAsciiStr(const QString &str)
+{
+       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();
+}
+
+bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
+{
+       while (xmlStream.readNextStartElement())
+       {
+               if (STRICMP(xmlStream.name(), name))
                {
-                       return year;
+                       return true;
                }
-               else
+               xmlStream.skipCurrentElement();
+       }
+       return false;
+}
+
+QString AnalyzeTask::findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes)
+{
+       for (QXmlStreamAttributes::ConstIterator iter = xmlAttributes.constBegin(); iter != xmlAttributes.constEnd(); ++iter)
+       {
+               if (STRICMP(iter->name(), name))
                {
-                       return 0;
+                       const QString value = iter->value().toString().simplified();
+                       if (!value.isEmpty())
+                       {
+                               return value; /*found*/
+                       }
                }
        }
+       return QString();
 }
 
-unsigned int AnalyzeTask::parseDuration(const QString &str)
+bool AnalyzeTask::checkVersionStr(const QString &str, const quint32 expectedMajor, const quint32 expectedMinor)
 {
-       bool ok = false;
-       unsigned int value = str.toUInt(&ok);
-       return ok ? (value/1000) : 0;
+       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
 ////////////////////////////////////////////////////////////