From 5aff7b65472036a9e1c431fa4b7f50670514a2dc Mon Sep 17 00:00:00 2001 From: LoRd_MuldeR Date: Tue, 31 Oct 2017 13:00:28 +0100 Subject: [PATCH] Switch to using QXmlStreamReader instead of SAX parser (part #1). --- src/Config.h | 2 +- src/Thread_FileAnalyzer_Task.cpp | 192 +++++++++++++++++++++++++------ src/Thread_FileAnalyzer_Task.h | 22 +++- 3 files changed, 176 insertions(+), 40 deletions(-) diff --git a/src/Config.h b/src/Config.h index b6733da3..ffc4b290 100644 --- a/src/Config.h +++ b/src/Config.h @@ -35,7 +35,7 @@ #define VER_LAMEXP_MINOR_LO 6 #define VER_LAMEXP_TYPE Alpha #define VER_LAMEXP_PATCH 7 -#define VER_LAMEXP_BUILD 2037 +#define VER_LAMEXP_BUILD 2040 #define VER_LAMEXP_CONFG 2002 /////////////////////////////////////////////////////////////////////////////// diff --git a/src/Thread_FileAnalyzer_Task.cpp b/src/Thread_FileAnalyzer_Task.cpp index 2eb82716..23f4c55e 100644 --- a/src/Thread_FileAnalyzer_Task.cpp +++ b/src/Thread_FileAnalyzer_Task.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include //CRT @@ -57,7 +58,33 @@ #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive)) #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first()) -#define STR_EQ(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0) +#define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0) + +#define ADD_PROPTERY_MAPPING(TYPE, NAME) do \ +{ \ + builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#NAME)), propertyId_##TYPE##_##NAME); \ +} \ +while(0) + +//////////////////////////////////////////////////////////// +// Static Data +//////////////////////////////////////////////////////////// + +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; + +static QReadWriteLock g_properties_lock; +static QScopedPointer, MI_propertyId_t>> g_properties_data; //////////////////////////////////////////////////////////// // XML Content Handler @@ -132,6 +159,26 @@ AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt { 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, MI_propertyId_t> *const builder = new QMap, 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) @@ -317,7 +364,7 @@ const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, Aud data += dataNext; } - //qDebug("!!!--START-->>>\n%s\n<<<--END--!!!", data.constData()); + qDebug("!!!--START-->>>\n%s\n<<<--END--!!!", data.constData()); return parseMediaInfo(data, audioFile); /* if(audioFile.metaInfo().title().isEmpty()) @@ -356,44 +403,106 @@ const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, Aud const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile) { - QXmlInputSource xmlSource; - xmlSource.setData(data); + QMap trackTypes; + trackTypes.insert("general", trackType_gen); + trackTypes.insert("audio", trackType_aud); - QScopedPointer xmlHandler(new AnalyzeTask_XmlHandler(audioFile, m_mediaInfoVer)); + QXmlStreamReader xmlStream(data); + bool firstFile = true; + QSet tracksFound; - QXmlSimpleReader xmlReader; - xmlReader.setContentHandler(xmlHandler.data()); - xmlReader.setErrorHandler(xmlHandler.data()); - - if (xmlReader.parse(xmlSource)) + if (findNextElement(QLatin1String("MediaInfo"), xmlStream)) { - while (xmlReader.parseContinue()) {/*continue*/} + const QStringRef version = xmlStream.attributes().value(QLatin1String("version")); + if (version.isEmpty() || (!STRICMP(version, QString().sprintf("0.%u.%02u", m_mediaInfoVer / 100U, m_mediaInfoVer % 100)))) + { + qWarning("Invalid version property \"%s\" was detected!", MUTILS_UTF8(version)); + return audioFile; + } + while (findNextElement(QLatin1String("File"), xmlStream)) + { + if (firstFile) + { + firstFile = false; + while (findNextElement(QLatin1String("Track"), xmlStream)) + { + 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)) + { + if (!tracksFound.contains(trackType)) + { + tracksFound << trackType; + parseTrackInfo(xmlStream, trackType, audioFile); + } + else + { + qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeAttr)); + xmlStream.skipCurrentElement(); + } + } + } + } + else + { + qWarning("Skipping non-primary file!"); + xmlStream.skipCurrentElement(); + } + } } - if(audioFile.metaInfo().title().isEmpty()) + if (!(audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty())) { - QString baseName = QFileInfo(audioFile.filePath()).fileName(); - int index; - if((index = baseName.lastIndexOf("."))>= 0) + if (audioFile.metaInfo().title().isEmpty()) { - baseName = baseName.left(index); + 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); } - baseName = baseName.replace("_", " ").simplified(); - if((index = baseName.lastIndexOf(" - ")) >= 0) + if ((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0)) { - baseName = baseName.mid(index + 3).trimmed(); + if (audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32); } - audioFile.metaInfo().setTitle(baseName); } - - if ((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0)) + else { - if (audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32); + qWarning("Audio file format could *not* be recognized!"); } return audioFile; } +void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile) +{ + 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)); + if (idx != MI_propertyId_t(-1)) + { + const QString value = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified(); + if (!value.isEmpty()) + { + qWarning("--> %d: \"%s\"", idx, MUTILS_UTF8(value)); + } + } + else + { + xmlStream.skipCurrentElement(); + } + } + +} + bool AnalyzeTask::checkFile_CDDA(QFile &file) { file.reset(); @@ -544,12 +653,16 @@ bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &i } } -unsigned int AnalyzeTask::parseYear(const QString &str) +// --------------------------------------------------------- +// Utility Functions +// --------------------------------------------------------- + +quint32 AnalyzeTask::parseYear(const QString &str) { - if(str.startsWith("UTC", Qt::CaseInsensitive)) + if (str.startsWith("UTC", Qt::CaseInsensitive)) { QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd"); - if(date.isValid()) + if (date.isValid()) { return date.year(); } @@ -562,7 +675,7 @@ unsigned int AnalyzeTask::parseYear(const QString &str) { bool ok = false; int year = str.toInt(&ok); - if(ok && year > 0) + if (ok && year > 0) { return year; } @@ -573,6 +686,19 @@ unsigned int AnalyzeTask::parseYear(const QString &str) } } +bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream) +{ + while (xmlStream.readNextStartElement()) + { + if (STRICMP(xmlStream.name(), name)) + { + return true; + } + xmlStream.skipCurrentElement(); + } + return false; +} + // --------------------------------------------------------- // XML Content Handler Implementation // --------------------------------------------------------- @@ -627,26 +753,26 @@ bool AnalyzeTask_XmlHandler::startElement(const QString &namespaceURI, const QSt switch (m_stack.size()) { case 1: - if (!STR_EQ(qName, "mediaInfo")) + if (!STRICMP(qName, "mediaInfo")) { qWarning("Invalid XML structure was detected! (1)"); return false; } - if (!STR_EQ(atts.value("version"), QString().sprintf("0.%u.%02u", m_version / 100U, m_version % 100))) + 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 (!STR_EQ(qName, "file")) + if (!STRICMP(qName, "file")) { qWarning("Invalid XML structure was detected! (2)"); return false; } return true; case 3: - if (!STR_EQ(qName, "track")) + if (!STRICMP(qName, "track")) { qWarning("Invalid XML structure was detected! (3)"); return false; @@ -654,11 +780,11 @@ bool AnalyzeTask_XmlHandler::startElement(const QString &namespaceURI, const QSt else { const QString value = atts.value("type").trimmed(); - if (STR_EQ(value, "general")) + if (STRICMP(value, "general")) { m_trackType = trackType_gen; } - else if (STR_EQ(value, "audio")) + else if (STRICMP(value, "audio")) { if (m_trackIdx++) { diff --git a/src/Thread_FileAnalyzer_Task.h b/src/Thread_FileAnalyzer_Task.h index 24150191..2efd47fd 100644 --- a/src/Thread_FileAnalyzer_Task.h +++ b/src/Thread_FileAnalyzer_Task.h @@ -34,7 +34,7 @@ class AudioFileModel; class QFile; class QDir; class QFileInfo; -class LockedFile; +class QXmlStreamReader; //////////////////////////////////////////////////////////// // Splash Thread @@ -48,14 +48,23 @@ public: AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt &abortFlag); ~AnalyzeTask(void); - enum fileType_t + typedef enum { fileTypeNormal = 0, fileTypeCDDA = 1, fileTypeDenied = 2, fileTypeCueSheet = 3, fileTypeUnknown = 4 - }; + } + fileType_t; + + typedef enum + { + trackType_non = 0, + trackType_gen = 1, + trackType_aud = 2, + } + MI_trackType_t; signals: void fileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file); @@ -69,14 +78,15 @@ private: const AudioFileModel& analyzeFile(const QString &filePath, AudioFileModel &audioFile, int *const type); const AudioFileModel& analyzeMediaFile(const QString &filePath, AudioFileModel &audioFile); const AudioFileModel& parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile); - //void updateInfo(AudioFileModel &audioFile, bool &skipNext, QPair &id_val, quint32 &coverType, QByteArray &coverData, const QString &key, const QString &value); - unsigned int parseYear(const QString &str); + void parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile); bool checkFile_CDDA(QFile &file); void retrieveCover(AudioFileModel &audioFile, const quint32 coverType, const QByteArray &coverData); bool analyzeAvisynthFile(const QString &filePath, AudioFileModel &info); - const unsigned int m_taskId; + static quint32 parseYear(const QString &str); + static bool findNextElement(const QString &name, QXmlStreamReader &xmlStream); + const unsigned int m_taskId; const QString m_mediaInfoBin; const quint32 m_mediaInfoVer; const QString m_avs2wavBin;