Adapted MediaInfo parsing code for new MediaInfo XML output.

aka "...when MediaInfo changes its XML output format *one* day after we implemented parsing of XML-based MediaInfo output" ;-)
This commit is contained in:
LoRd_MuldeR 2017-11-04 18:23:10 +01:00
parent 98fd505e68
commit 9b01f44804
3 changed files with 132 additions and 145 deletions

View File

@ -35,7 +35,7 @@
#define VER_LAMEXP_MINOR_LO 6 #define VER_LAMEXP_MINOR_LO 6
#define VER_LAMEXP_TYPE Alpha #define VER_LAMEXP_TYPE Alpha
#define VER_LAMEXP_PATCH 8 #define VER_LAMEXP_PATCH 8
#define VER_LAMEXP_BUILD 2048 #define VER_LAMEXP_BUILD 2050
#define VER_LAMEXP_CONFG 2002 #define VER_LAMEXP_CONFG 2002
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@ -31,6 +31,7 @@
//MUtils //MUtils
#include <MUtils/Global.h> #include <MUtils/Global.h>
#include <MUtils/OSSupport.h> #include <MUtils/OSSupport.h>
#include <MUtils/Lazy.h>
#include <MUtils/Exception.h> #include <MUtils/Exception.h>
//Qt //Qt
@ -66,7 +67,7 @@ while(0)
#define ADD_PROPTERY_MAPPING_2(TYPE, MI_NAME, LX_NAME) do \ #define ADD_PROPTERY_MAPPING_2(TYPE, MI_NAME, LX_NAME) do \
{ \ { \
builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#MI_NAME)), propertyId_##LX_NAME); \ builder->insert(qMakePair(AnalyzeTask::trackType_##TYPE, QString::fromLatin1(#MI_NAME)), AnalyzeTask::propertyId_##LX_NAME); \
} \ } \
while(0) while(0)
@ -80,6 +81,85 @@ while(0)
#define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0) #define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
////////////////////////////////////////////////////////////
// Static initialization
////////////////////////////////////////////////////////////
class AnalyzeTask_StaticInit_MediaInfoIdx : public MUtils::Lazy<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>>
{
virtual QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t> *create()
{
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_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;
}
}
s_mediaInfoIdx;
class AnalyzeTask_StaticInit_AvisynthIdx : public MUtils::Lazy<const QMap<QString, AnalyzeTask::MI_propertyId_t>>
{
virtual QMap<QString, AnalyzeTask::MI_propertyId_t> *create()
{
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;
}
}
s_avisynthIdx;
class AnalyzeTask_StaticInit_MimiTypes : public MUtils::Lazy<const QMap<QString, QString>>
{
virtual QMap<QString, QString> *create()
{
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;
}
}
s_mimeTypes;
class AnalyzeTask_StaticInit_TrackTypes : public MUtils::Lazy<const QMap<QString, AnalyzeTask::MI_trackType_t>>
{
virtual QMap<QString, AnalyzeTask::MI_trackType_t> *create()
{
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;
}
}
s_trackTypes;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Constructor // Constructor
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@ -92,10 +172,10 @@ AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt
m_mediaInfoVer(lamexp_tools_version("mediainfo.exe")), m_mediaInfoVer(lamexp_tools_version("mediainfo.exe")),
m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")), m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")),
m_abortFlag(abortFlag), m_abortFlag(abortFlag),
m_mediaInfoIdx(initMediaInfoIdx()), m_mediaInfoIdx(*s_mediaInfoIdx),
m_avisynthIdx(initAvisynthIdx()), m_avisynthIdx(*s_avisynthIdx),
m_mimeTypes(initMimeTypes()), m_mimeTypes(*s_mimeTypes),
m_trackTypes(initTrackTypes()) m_trackTypes(*s_trackTypes)
{ {
if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty()) if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
{ {
@ -108,122 +188,6 @@ AnalyzeTask::~AnalyzeTask(void)
emit taskCompleted(m_taskId); emit taskCompleted(m_taskId);
} }
////////////////////////////////////////////////////////////
// Static initialization
////////////////////////////////////////////////////////////
QReadWriteLock AnalyzeTask::s_lock;
QScopedPointer<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>> AnalyzeTask::s_pMediaInfoIdx;
QScopedPointer<const QMap<QString, AnalyzeTask::MI_propertyId_t>> AnalyzeTask::s_pAvisynthIdx;
QScopedPointer<const QMap<QString, QString>> AnalyzeTask::s_pMimeTypes;
QScopedPointer<const QMap<QString, AnalyzeTask::MI_trackType_t>> AnalyzeTask::s_pTrackTypes;
const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t> &AnalyzeTask::initMediaInfoIdx(void)
{
QReadLocker rdLocker(&s_lock);
if (s_pMediaInfoIdx.isNull())
{
rdLocker.unlock();
QWriteLocker wrLocker(&s_lock);
if (s_pMediaInfoIdx.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_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_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);
s_pMediaInfoIdx.reset(builder);
}
wrLocker.unlock();
rdLocker.relock();
}
return (*s_pMediaInfoIdx);
}
const QMap<QString, AnalyzeTask::MI_propertyId_t> &AnalyzeTask::initAvisynthIdx(void)
{
QReadLocker rdLocker(&s_lock);
if (s_pAvisynthIdx.isNull())
{
rdLocker.unlock();
QWriteLocker wrLocker(&s_lock);
if (s_pAvisynthIdx.isNull())
{
QMap<QString, MI_propertyId_t> *const builder = new QMap<QString, MI_propertyId_t>();
builder->insert(QLatin1String("totalseconds"), propertyId_duration);
builder->insert(QLatin1String("samplespersec"), propertyId_samplingrate);
builder->insert(QLatin1String("channels"), propertyId_channel_s_);
builder->insert(QLatin1String("bitspersample"), propertyId_bitdepth);
s_pAvisynthIdx.reset(builder);
}
wrLocker.unlock();
rdLocker.relock();
}
return (*s_pAvisynthIdx);
}
const QMap<QString, AnalyzeTask::MI_trackType_t> &AnalyzeTask::initTrackTypes(void)
{
QReadLocker rdLocker(&s_lock);
if (s_pTrackTypes.isNull())
{
rdLocker.unlock();
QWriteLocker wrLocker(&s_lock);
if (s_pTrackTypes.isNull())
{
QMap<QString, MI_trackType_t> *const builder = new QMap<QString, MI_trackType_t>();
builder->insert("general", trackType_gen);
builder->insert("audio", trackType_aud);
s_pTrackTypes.reset(builder);
}
wrLocker.unlock();
rdLocker.relock();
}
return (*s_pTrackTypes);
}
const QMap<QString, QString> &AnalyzeTask::initMimeTypes(void)
{
QReadLocker rdLocker(&s_lock);
if (s_pMimeTypes.isNull())
{
rdLocker.unlock();
QWriteLocker wrLocker(&s_lock);
if (s_pMimeTypes.isNull())
{
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]));
}
s_pMimeTypes.reset(builder);
}
wrLocker.unlock();
rdLocker.relock();
}
return (*s_pMimeTypes);
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Thread Main // Thread Main
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@ -412,27 +376,43 @@ const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, Aud
const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile) const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile)
{ {
QXmlStreamReader xmlStream(data); QXmlStreamReader xmlStream(data);
bool firstFile = true; bool firstMediaFile = true;
if (findNextElement(QLatin1String("MediaInfo"), xmlStream)) if (findNextElement(QLatin1String("MediaInfo"), xmlStream))
{ {
const QString version = findAttribute(QLatin1String("Version"), xmlStream.attributes()); const QString versionXml = findAttribute(QLatin1String("Version"), xmlStream.attributes());
if (version.isEmpty() || (!STRICMP(version, QString().sprintf("0.%u.%02u", m_mediaInfoVer / 100U, m_mediaInfoVer % 100)))) 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; 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")))
{ {
firstFile = false; qWarning("Invalid library identiofier property: \"%s\"", MUTILS_UTF8(identifier));
parseFileInfo(xmlStream, audioFile); return audioFile;
} }
else if (versionLib.isEmpty() || (!checkVersionStr(versionLib, m_mediaInfoVer / 100U, m_mediaInfoVer % 100U)))
{ {
qWarning("Skipping non-primary file!"); qWarning("Invalid library version property: \"%s\"", MUTILS_UTF8(versionLib));
xmlStream.skipCurrentElement(); return audioFile;
}
while (findNextElement(QLatin1String("Media"), xmlStream))
{
qWarning("Found a media!");
if (firstMediaFile || audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty())
{
firstMediaFile = false;
parseFileInfo(xmlStream, audioFile);
}
else
{
qWarning("Skipping non-primary file!");
xmlStream.skipCurrentElement();
}
} }
} }
} }
@ -473,6 +453,7 @@ void AnalyzeTask::parseFileInfo(QXmlStreamReader &xmlStream, AudioFileModel &aud
MI_trackType_t trackType; MI_trackType_t trackType;
while (findNextElement(QLatin1String("Track"), xmlStream)) while (findNextElement(QLatin1String("Track"), xmlStream))
{ {
qWarning("Found a track!");
const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes()); const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes());
if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1)) if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1))
{ {
@ -785,6 +766,23 @@ QString AnalyzeTask::findAttribute(const QString &name, const QXmlStreamAttribut
return QString(); return QString();
} }
bool AnalyzeTask::checkVersionStr(const QString &str, const quint32 expectedMajor, const quint32 expectedMinor)
{
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 // Public Functions
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -57,7 +57,6 @@ public:
} }
fileType_t; fileType_t;
protected:
typedef enum typedef enum
{ {
trackType_non = 0, trackType_non = 0,
@ -111,11 +110,6 @@ private:
bool checkFile_CDDA(QFile &file); bool checkFile_CDDA(QFile &file);
bool analyzeAvisynthFile(const QString &filePath, AudioFileModel &info); bool analyzeAvisynthFile(const QString &filePath, AudioFileModel &info);
static const QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t> &initMediaInfoIdx(void);
static const QMap<QString, MI_propertyId_t> &initAvisynthIdx(void);
static const QMap<QString, QString> &initMimeTypes(void);
static const QMap<QString, MI_trackType_t> &initTrackTypes(void);
static QString decodeStr(const QString &str, const QString &encoding); static QString decodeStr(const QString &str, const QString &encoding);
static bool parseUnsigned(const QString &str, quint32 &value); static bool parseUnsigned(const QString &str, quint32 &value);
static bool parseDuration(const QString &str, quint32 &value); static bool parseDuration(const QString &str, quint32 &value);
@ -124,6 +118,7 @@ private:
static QString cleanAsciiStr(const QString &str); static QString cleanAsciiStr(const QString &str);
static bool findNextElement(const QString &name, QXmlStreamReader &xmlStream); static bool findNextElement(const QString &name, QXmlStreamReader &xmlStream);
static QString findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes); static QString findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes);
static bool checkVersionStr(const QString &str, const quint32 expectedMajor, const quint32 expectedMinor);
const QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t> &m_mediaInfoIdx; const QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t> &m_mediaInfoIdx;
const QMap<QString, MI_propertyId_t> &m_avisynthIdx; const QMap<QString, MI_propertyId_t> &m_avisynthIdx;
@ -137,10 +132,4 @@ private:
const QString m_inputFile; const QString m_inputFile;
QAtomicInt &m_abortFlag; QAtomicInt &m_abortFlag;
static QReadWriteLock s_lock;
static QScopedPointer<const QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t>> s_pMediaInfoIdx;
static QScopedPointer<const QMap<QString, MI_propertyId_t>> s_pAvisynthIdx;
static QScopedPointer<const QMap<QString, QString>> s_pMimeTypes;
static QScopedPointer<const QMap<QString, MI_trackType_t>> s_pTrackTypes;
}; };