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 23f4c55..0d5f68f 100644 (file)
@@ -1,12 +1,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 // LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2017 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
@@ -31,6 +31,7 @@
 //MUtils
 #include <MUtils/Global.h>
 #include <MUtils/OSSupport.h>
+#include <MUtils/Lazy.h>
 #include <MUtils/Exception.h>
 
 //Qt
 #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())
-
-#define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
+////////////////////////////////////////////////////////////
+// Helper Macros
+////////////////////////////////////////////////////////////
 
-#define ADD_PROPTERY_MAPPING(TYPE, NAME) do \
+#define ADD_PROPTERY_MAPPING_1(TYPE, NAME) do \
 { \
-       builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#NAME)), propertyId_##TYPE##_##NAME); \
+       ADD_PROPTERY_MAPPING_2(TYPE, NAME, NAME); \
 } \
 while(0)
 
-////////////////////////////////////////////////////////////
-// Static Data
-////////////////////////////////////////////////////////////
+#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)
 
-typedef enum
-{
-       propertyId_gen_format,
-       propertyId_gen_format_profile,
-       propertyId_gen_duration,
-       propertyId_aud_format,
-       propertyId_aud_format_version,
-       propertyId_aud_format_profile,
-       propertyId_aud_channel_s_,
-       propertyId_aud_samplingrate
-}
-MI_propertyId_t;
+#define SET_OPTIONAL(TYPE, IF_CMD, THEN_CMD) do \
+{ \
+       TYPE _tmp;\
+       if((IF_CMD)) { THEN_CMD; } \
+} \
+while(0)
 
-static QReadWriteLock g_properties_lock;
-static QScopedPointer<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, MI_propertyId_t>> g_properties_data;
+#define DIV_RND(A,B) (((A) + ((B) / 2U)) / (B))
+#define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
 
 ////////////////////////////////////////////////////////////
-// XML Content Handler
+// Static initialization
 ////////////////////////////////////////////////////////////
 
-class AnalyzeTask_XmlHandler : public QXmlDefaultHandler
+static MUtils::Lazy<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>> s_mediaInfoIdx([]
 {
-public:
-       AnalyzeTask_XmlHandler(AudioFileModel &audioFile, const quint32 &version) :
-               m_audioFile(audioFile), m_version(version), m_trackType(trackType_non), m_trackIdx(0), m_properties(initializeProperties()) {}
-
-protected:
-       virtual bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts);
-       virtual bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName);
-       virtual bool characters(const QString& ch);
-
-private:
-       typedef enum
-       {
-               trackType_non = 0,
-               trackType_gen = 1,
-               trackType_aud = 2,
-       }
-       trackType_t;
-
-       typedef enum
+       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)
        {
-               propertyId_gen_format,
-               propertyId_gen_format_profile,
-               propertyId_gen_duration,
-               propertyId_aud_format,
-               propertyId_aud_format_version,
-               propertyId_aud_format_profile,
-               propertyId_aud_channel_s_,
-               propertyId_aud_samplingrate
+               builder->insert(QString::fromLatin1(MIME_TYPES[i].type), QString::fromLatin1(MIME_TYPES[i].ext[0]));
        }
-       propertyId_t;
-
-       const quint32 m_version;
-       const QMap<QPair<trackType_t, QString>, propertyId_t> &m_properties;
-
-       QStack<QString> m_stack;
-       AudioFileModel &m_audioFile;
-       trackType_t m_trackType;
-       quint32 m_trackIdx;
-       propertyId_t m_currentProperty;
+       return builder;
+});
 
-       static QReadWriteLock s_propertiesMutex;
-       static QScopedPointer<const QMap<QPair<trackType_t, QString>, propertyId_t>> s_propertiesMap;
-
-       bool updatePropertry(const propertyId_t &idx, const QString &value);
-
-       static const QMap<QPair<trackType_t, QString>, propertyId_t> &initializeProperties();
-       static bool parseUnsigned(const QString &str, quint32 &value);
-       static quint32 decodeTime(quint32 &value);
-};
+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
@@ -153,32 +156,16 @@ AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt
        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_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())
        {
                qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
        }
-
-       QReadLocker rdLocker(&g_properties_lock);
-       if (g_properties_data.isNull())
-       {
-               rdLocker.unlock();
-               QWriteLocker wrLocker(&g_properties_lock);
-               if (g_properties_data.isNull())
-               {
-                       QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t> *const builder = new QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t>();
-                       ADD_PROPTERY_MAPPING(gen, format);
-                       ADD_PROPTERY_MAPPING(gen, format_profile);
-                       ADD_PROPTERY_MAPPING(gen, duration);
-                       ADD_PROPTERY_MAPPING(aud, format);
-                       ADD_PROPTERY_MAPPING(aud, format_version);
-                       ADD_PROPTERY_MAPPING(aud, format_profile);
-                       ADD_PROPTERY_MAPPING(aud, channel_s_);
-                       ADD_PROPTERY_MAPPING(aud, samplingrate);
-                       g_properties_data.reset(builder);
-               }
-       }
 }
 
 AnalyzeTask::~AnalyzeTask(void)
@@ -293,13 +280,10 @@ const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, Aud
 {
        //bool skipNext = false;
        QPair<quint32, quint32> id_val(UINT_MAX, UINT_MAX);
-       quint32 coverType = UINT_MAX;
        QByteArray coverData;
 
        QStringList params;
-       params << QString("--Language=raw");
-       params << QString("-f");
-       params << QString("--Output=XML");
+       params << L1S("--Language=raw") << L1S("--Output=XML") << L1S("--Full") << L1S("--Cover_Data=base64");
        params << QDir::toNativeSeparators(filePath);
 
        QProcess process;
@@ -344,7 +328,7 @@ const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, Aud
                        if (dataNext.isEmpty()) {
                                break; /*no more input data*/
                        }
-                       data += dataNext;
+                       data += dataNext.trimmed();
                }
        }
 
@@ -361,92 +345,67 @@ const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, Aud
                if (dataNext.isEmpty()) {
                        break; /*no more input data*/
                }
-               data += dataNext;
-       }
-       
-       qDebug("!!!--START-->>>\n%s\n<<<--END--!!!", data.constData());
-       return parseMediaInfo(data, audioFile);
-
-       /* 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);
-       }
-
-       if((coverType != UINT_MAX) && (!coverData.isEmpty()))
-       {
-               retrieveCover(audioFile, coverType, coverData);
+               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);
 }
 
 const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile)
 {
-       QMap<QString, MI_trackType_t> trackTypes;
-       trackTypes.insert("general", trackType_gen);
-       trackTypes.insert("audio", trackType_aud);
-
        QXmlStreamReader xmlStream(data);
-       bool firstFile = true;
-       QSet<MI_trackType_t> tracksFound;
+       bool firstMediaFile = true;
 
        if (findNextElement(QLatin1String("MediaInfo"), xmlStream))
        {
-               const QStringRef version = xmlStream.attributes().value(QLatin1String("version"));
-               if (version.isEmpty() || (!STRICMP(version, QString().sprintf("0.%u.%02u", m_mediaInfoVer / 100U, m_mediaInfoVer % 100))))
+               const QString versionXml = findAttribute(QLatin1String("Version"),  xmlStream.attributes());
+               if (versionXml.isEmpty() || (!checkVersionStr(versionXml, 2U, 0U)))
                {
-                       qWarning("Invalid version property \"%s\" was detected!", MUTILS_UTF8(version));
+                       qWarning("Invalid file format version property: \"%s\"", MUTILS_UTF8(versionXml));
                        return audioFile;
                }
-               while (findNextElement(QLatin1String("File"), xmlStream))
+               if (findNextElement(QLatin1String("CreatingLibrary"), xmlStream))
                {
-                       if (firstFile)
+                       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())
                        {
-                               firstFile = false;
-                               while (findNextElement(QLatin1String("Track"), xmlStream))
+                               if (m_mediaInfoVer != UINT_MAX)
                                {
-                                       const QString typeAttr = xmlStream.attributes().value(QLatin1String("type")).toString().simplified().toLower();
-                                       const MI_trackType_t trackType = trackTypes.value(typeAttr, MI_trackType_t(-1));
-                                       if (trackType != MI_trackType_t(-1))
+                                       const quint32 mediaInfoVer = (m_mediaInfoVer > 9999U) ? m_mediaInfoVer / 10U : m_mediaInfoVer;
+                                       if (!checkVersionStr(versionLib, mediaInfoVer / 100U, mediaInfoVer % 100U))
                                        {
-                                               if (!tracksFound.contains(trackType))
-                                               {
-                                                       tracksFound << trackType;
-                                                       parseTrackInfo(xmlStream, trackType, audioFile);
-                                               }
-                                               else
-                                               {
-                                                       qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeAttr));
-                                                       xmlStream.skipCurrentElement();
-                                               }
+                                               qWarning("Invalid library version property: \"%s\"", MUTILS_UTF8(versionLib));
+                                               return audioFile;
                                        }
                                }
                        }
                        else
                        {
-                               qWarning("Skipping non-primary file!");
-                               xmlStream.skipCurrentElement();
+                               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();
+                               }
                        }
                }
        }
@@ -481,18 +440,47 @@ const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioF
        return audioFile;
 }
 
+void AnalyzeTask::parseFileInfo(QXmlStreamReader &xmlStream, AudioFileModel &audioFile)
+{
+       QSet<MI_trackType_t> tracksProcessed;
+       MI_trackType_t trackType;
+       while (findNextElement(QLatin1String("Track"), xmlStream))
+       {
+               const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes());
+               if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1))
+               {
+                       if (!tracksProcessed.contains(trackType))
+                       {
+                               tracksProcessed << trackType;
+                               parseTrackInfo(xmlStream, trackType, audioFile);
+                       }
+                       else
+                       {
+                               qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeString));
+                               xmlStream.skipCurrentElement();
+                       }
+               }
+               else
+               {
+                       qWarning("Skipping unsupported '%s' track!", MUTILS_UTF8(typeString));
+                       xmlStream.skipCurrentElement();
+               }
+       }
+}
+
 void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile)
 {
+       QString coverMimeType;
        while (xmlStream.readNextStartElement())
        {
-               qWarning("%d::%s", trackType, MUTILS_UTF8(xmlStream.name()));
-               const MI_propertyId_t idx = g_properties_data->value(qMakePair(trackType, xmlStream.name().toString().simplified().toLower()), MI_propertyId_t(-1));
+               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))
                {
+                       const QString encoding = findAttribute(QLatin1String("dt"), xmlStream.attributes());
                        const QString value = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
                        if (!value.isEmpty())
                        {
-                               qWarning("--> %d: \"%s\"", idx, MUTILS_UTF8(value));
+                               parseProperty(encoding.isEmpty() ? value : decodeStr(value, encoding), idx, audioFile, coverMimeType);
                        }
                }
                else
@@ -500,7 +488,38 @@ void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType
                        xmlStream.skipCurrentElement();
                }
        }
+}
 
+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)
@@ -515,27 +534,28 @@ bool AnalyzeTask::checkFile_CDDA(QFile &file)
        return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
 }
 
-void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, const quint32 coverType, const QByteArray &coverData)
+void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, const QString &coverType, const QString &coverData)
 {
-       qDebug("Retrieving cover! (MIME_TYPES_MAX=%u)", MIME_TYPES_MAX);
-       
-       static const QString ext = QString::fromLatin1(MIME_TYPES[qBound(0U, coverType, MIME_TYPES_MAX)].ext[0]);
-       if(!(QImage::fromData(coverData, ext.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(MUtils::temp_folder(), MUtils::next_rand_str(), ext));
+               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;
@@ -574,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)
-                                               {
-                                                       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)
+                                               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 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;
+                                                       }
                                                }
                                        }
                                }
@@ -657,215 +660,116 @@ bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &i
 // Utility Functions
 // ---------------------------------------------------------
 
-quint32 AnalyzeTask::parseYear(const QString &str)
+QString AnalyzeTask::decodeStr(const QString &str, const QString &encoding)
+{
+       if (STRICMP(encoding, QLatin1String("binary.base64")))
+       {
+               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("UTC", Qt::CaseInsensitive))
+       if (str.startsWith(QLatin1String("UTC"), Qt::CaseInsensitive))
        {
-               QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
+               const QDate date = QDate::fromString(str.mid(3).trimmed().left(10), QLatin1String("yyyy-MM-dd"));
                if (date.isValid())
                {
-                       return date.year();
-               }
-               else
-               {
-                       return 0;
+                       value = date.year();
+                       return true;
                }
+               return false;
        }
        else
        {
-               bool ok = false;
-               int year = str.toInt(&ok);
-               if (ok && year > 0)
-               {
-                       return year;
-               }
-               else
-               {
-                       return 0;
-               }
+               return parseUnsigned(str, value);
        }
 }
 
-bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
+bool AnalyzeTask::parseRCMode(const QString &str, quint32 &value)
 {
-       while (xmlStream.readNextStartElement())
+       if (STRICMP(str, QLatin1String("CBR")))
        {
-               if (STRICMP(xmlStream.name(), name))
-               {
-                       return true;
-               }
-               xmlStream.skipCurrentElement();
+               value = AudioFileModel::BitrateModeConstant;
+               return true;
+       }
+       if (STRICMP(str, QLatin1String("VBR")))
+       {
+               value = AudioFileModel::BitrateModeVariable;
+               return true;
        }
        return false;
 }
 
-// ---------------------------------------------------------
-// XML Content Handler Implementation
-// ---------------------------------------------------------
-
-#define DEFINE_PROPTERY_MAPPING(TYPE, NAME) do \
-{ \
-       builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#NAME)), propertyId_##TYPE##_##NAME); \
-} \
-while(0)
-
-#define SET_OPTIONAL(TYPE, IF_CMD, THEN_CMD) do \
-{ \
-       TYPE _tmp;\
-       if((IF_CMD)) { THEN_CMD; } \
-} \
-while(0)
-
-QReadWriteLock AnalyzeTask_XmlHandler::s_propertiesMutex;
-QScopedPointer<const QMap<QPair<AnalyzeTask_XmlHandler::trackType_t, QString>, AnalyzeTask_XmlHandler::propertyId_t>> AnalyzeTask_XmlHandler::s_propertiesMap;
-
-const QMap<QPair<AnalyzeTask_XmlHandler::trackType_t, QString>, AnalyzeTask_XmlHandler::propertyId_t> &AnalyzeTask_XmlHandler::initializeProperties(void)
+QString AnalyzeTask::cleanAsciiStr(const QString &str)
 {
-       QReadLocker rdLocker(&s_propertiesMutex);
-       if (!s_propertiesMap.isNull())
+       QByteArray ascii = str.toLatin1();
+       for (QByteArray::Iterator iter = ascii.begin(); iter != ascii.end(); ++iter)
        {
-               return *s_propertiesMap.data();
+               if ((*iter < 0x20) || (*iter >= 0x7F)) *iter = 0x3F;
        }
-
-       rdLocker.unlock();
-       QWriteLocker wrLocker(&s_propertiesMutex);
-
-       if (s_propertiesMap.isNull())
-       {
-               QMap<QPair<trackType_t, QString>, propertyId_t> *const builder = new QMap<QPair<trackType_t, QString>, propertyId_t>();
-               DEFINE_PROPTERY_MAPPING(gen, format);
-               DEFINE_PROPTERY_MAPPING(gen, format_profile);
-               DEFINE_PROPTERY_MAPPING(gen, duration);
-               DEFINE_PROPTERY_MAPPING(aud, format);
-               DEFINE_PROPTERY_MAPPING(aud, format_version);
-               DEFINE_PROPTERY_MAPPING(aud, format_profile);
-               DEFINE_PROPTERY_MAPPING(aud, channel_s_);
-               DEFINE_PROPTERY_MAPPING(aud, samplingrate);
-               s_propertiesMap.reset(builder);
-       }
-
-       return *s_propertiesMap.data();
+       return QString::fromLatin1(ascii).remove(QLatin1Char('?')).simplified();
 }
 
-bool AnalyzeTask_XmlHandler::startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts)
+bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
 {
-       m_stack.push(qName);
-       switch (m_stack.size())
+       while (xmlStream.readNextStartElement())
        {
-       case 1:
-               if (!STRICMP(qName, "mediaInfo"))
-               {
-                       qWarning("Invalid XML structure was detected! (1)");
-                       return false;
-               }
-               if (!STRICMP(atts.value("version"), QString().sprintf("0.%u.%02u", m_version / 100U, m_version % 100)))
-               {
-                       qWarning("Invalid version property was detected!");
-                       return false;
-               }
-               return true;
-       case 2:
-               if (!STRICMP(qName, "file"))
-               {
-                       qWarning("Invalid XML structure was detected! (2)");
-                       return false;
-               }
-               return true;
-       case 3:
-               if (!STRICMP(qName, "track"))
-               {
-                       qWarning("Invalid XML structure was detected! (3)");
-                       return false;
-               }
-               else
-               {
-                       const QString value = atts.value("type").trimmed();
-                       if (STRICMP(value, "general"))
-                       {
-                               m_trackType = trackType_gen;
-                       }
-                       else if (STRICMP(value, "audio"))
-                       {
-                               if (m_trackIdx++)
-                               {
-                                       qWarning("Skipping non-primary audio track!");
-                                       m_trackType = trackType_non;
-                               }
-                               else
-                               {
-                                       m_trackType = trackType_aud;
-                               }
-                       }
-                       else /*e.g. video*/
-                       {
-                               qWarning("Skipping a non-audio track!");
-                               m_trackType = trackType_non;
-                       }
-                       return true;
-               }
-       case 4:
-               switch (m_trackType)
+               if (STRICMP(xmlStream.name(), name))
                {
-               case trackType_gen:
-                       m_currentProperty = m_properties.value(qMakePair(trackType_gen, qName.simplified().toLower()), propertyId_t(-1));
-                       return true;
-               case trackType_aud:
-                       m_currentProperty = m_properties.value(qMakePair(trackType_aud, qName.simplified().toLower()), propertyId_t(-1));
-                       return true;
-               default:
-                       m_currentProperty = propertyId_t(-1);
                        return true;
                }
-       default:
-               return true;
+               xmlStream.skipCurrentElement();
        }
+       return false;
 }
 
-bool AnalyzeTask_XmlHandler::endElement(const QString &namespaceURI, const QString &localName, const QString &qName)
-{
-       m_stack.pop();
-       return true;
-}
-
-bool AnalyzeTask_XmlHandler::characters(const QString& ch)
+QString AnalyzeTask::findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes)
 {
-       if ((m_currentProperty != propertyId_t(-1)) && (m_stack.size() == 4))
+       for (QXmlStreamAttributes::ConstIterator iter = xmlAttributes.constBegin(); iter != xmlAttributes.constEnd(); ++iter)
        {
-               const QString value = ch.simplified();
-               if (!value.isEmpty())
+               if (STRICMP(iter->name(), name))
                {
-                       updatePropertry(m_currentProperty, value);
+                       const QString value = iter->value().toString().simplified();
+                       if (!value.isEmpty())
+                       {
+                               return value; /*found*/
+                       }
                }
        }
-       return true;
+       return QString();
 }
 
-bool AnalyzeTask_XmlHandler::updatePropertry(const propertyId_t &idx, const QString &value)
+bool AnalyzeTask::checkVersionStr(const QString &str, const quint32 expectedMajor, const quint32 expectedMinor)
 {
-       switch (idx)
-       {
-               case propertyId_gen_format:         m_audioFile.techInfo().setContainerType(value);                                                          return true;
-               case propertyId_gen_format_profile: m_audioFile.techInfo().setContainerProfile(value);                                                       return true;
-               case propertyId_gen_duration:       SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), m_audioFile.techInfo().setDuration(decodeTime(_tmp))); return true;
-               case propertyId_aud_format:         m_audioFile.techInfo().setAudioType(value);                                                              return true;
-               case propertyId_aud_format_version: m_audioFile.techInfo().setAudioVersion(value);                                                           return true;
-               case propertyId_aud_format_profile: m_audioFile.techInfo().setAudioProfile(value);                                                           return true;
-               case propertyId_aud_channel_s_:     SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), m_audioFile.techInfo().setAudioChannels(_tmp));        return true;
-               case propertyId_aud_samplingrate:   SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), m_audioFile.techInfo().setAudioSamplerate(_tmp));      return true;
-               default: MUTILS_THROW_FMT("Invalid property ID: %d", idx);
+       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;
+                       }
+               }
        }
-}
-
-bool AnalyzeTask_XmlHandler::parseUnsigned(const QString &str, quint32 &value)
-{
-       bool okay = false;
-       value = str.toUInt(&okay);
-       return okay;
-}
-
-quint32 AnalyzeTask_XmlHandler::decodeTime(quint32 &value)
-{
-       return (value + 500U) / 1000U;
+       return false;
 }
 
 ////////////////////////////////////////////////////////////