Switch to using QXmlStreamReader instead of SAX parser (part #1).
[LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
blob23f4c55eedb0e1f78edc1946113f5b7774727e89
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2017 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
23 #include "Thread_FileAnalyzer_Task.h"
25 //Internal
26 #include "Global.h"
27 #include "LockedFile.h"
28 #include "Model_AudioFile.h"
29 #include "MimeTypes.h"
31 //MUtils
32 #include <MUtils/Global.h>
33 #include <MUtils/OSSupport.h>
34 #include <MUtils/Exception.h>
36 //Qt
37 #include <QDir>
38 #include <QFileInfo>
39 #include <QProcess>
40 #include <QDate>
41 #include <QTime>
42 #include <QDebug>
43 #include <QImage>
44 #include <QReadLocker>
45 #include <QWriteLocker>
46 #include <QThread>
47 #include <QXmlSimpleReader>
48 #include <QXmlInputSource>
49 #include <QXmlStreamReader>
50 #include <QStack>
52 //CRT
53 #include <math.h>
54 #include <time.h>
55 #include <assert.h>
57 #define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
58 #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
59 #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
61 #define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
63 #define ADD_PROPTERY_MAPPING(TYPE, NAME) do \
64 { \
65 builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#NAME)), propertyId_##TYPE##_##NAME); \
66 } \
67 while(0)
69 ////////////////////////////////////////////////////////////
70 // Static Data
71 ////////////////////////////////////////////////////////////
73 typedef enum
75 propertyId_gen_format,
76 propertyId_gen_format_profile,
77 propertyId_gen_duration,
78 propertyId_aud_format,
79 propertyId_aud_format_version,
80 propertyId_aud_format_profile,
81 propertyId_aud_channel_s_,
82 propertyId_aud_samplingrate
84 MI_propertyId_t;
86 static QReadWriteLock g_properties_lock;
87 static QScopedPointer<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, MI_propertyId_t>> g_properties_data;
89 ////////////////////////////////////////////////////////////
90 // XML Content Handler
91 ////////////////////////////////////////////////////////////
93 class AnalyzeTask_XmlHandler : public QXmlDefaultHandler
95 public:
96 AnalyzeTask_XmlHandler(AudioFileModel &audioFile, const quint32 &version) :
97 m_audioFile(audioFile), m_version(version), m_trackType(trackType_non), m_trackIdx(0), m_properties(initializeProperties()) {}
99 protected:
100 virtual bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts);
101 virtual bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName);
102 virtual bool characters(const QString& ch);
104 private:
105 typedef enum
107 trackType_non = 0,
108 trackType_gen = 1,
109 trackType_aud = 2,
111 trackType_t;
113 typedef enum
115 propertyId_gen_format,
116 propertyId_gen_format_profile,
117 propertyId_gen_duration,
118 propertyId_aud_format,
119 propertyId_aud_format_version,
120 propertyId_aud_format_profile,
121 propertyId_aud_channel_s_,
122 propertyId_aud_samplingrate
124 propertyId_t;
126 const quint32 m_version;
127 const QMap<QPair<trackType_t, QString>, propertyId_t> &m_properties;
129 QStack<QString> m_stack;
130 AudioFileModel &m_audioFile;
131 trackType_t m_trackType;
132 quint32 m_trackIdx;
133 propertyId_t m_currentProperty;
135 static QReadWriteLock s_propertiesMutex;
136 static QScopedPointer<const QMap<QPair<trackType_t, QString>, propertyId_t>> s_propertiesMap;
138 bool updatePropertry(const propertyId_t &idx, const QString &value);
140 static const QMap<QPair<trackType_t, QString>, propertyId_t> &initializeProperties();
141 static bool parseUnsigned(const QString &str, quint32 &value);
142 static quint32 decodeTime(quint32 &value);
145 ////////////////////////////////////////////////////////////
146 // Constructor
147 ////////////////////////////////////////////////////////////
149 AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt &abortFlag)
151 m_taskId(taskId),
152 m_inputFile(inputFile),
153 m_mediaInfoBin(lamexp_tools_lookup("mediainfo.exe")),
154 m_mediaInfoVer(lamexp_tools_version("mediainfo.exe")),
155 m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")),
156 m_abortFlag(abortFlag)
158 if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
160 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
163 QReadLocker rdLocker(&g_properties_lock);
164 if (g_properties_data.isNull())
166 rdLocker.unlock();
167 QWriteLocker wrLocker(&g_properties_lock);
168 if (g_properties_data.isNull())
170 QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t> *const builder = new QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t>();
171 ADD_PROPTERY_MAPPING(gen, format);
172 ADD_PROPTERY_MAPPING(gen, format_profile);
173 ADD_PROPTERY_MAPPING(gen, duration);
174 ADD_PROPTERY_MAPPING(aud, format);
175 ADD_PROPTERY_MAPPING(aud, format_version);
176 ADD_PROPTERY_MAPPING(aud, format_profile);
177 ADD_PROPTERY_MAPPING(aud, channel_s_);
178 ADD_PROPTERY_MAPPING(aud, samplingrate);
179 g_properties_data.reset(builder);
184 AnalyzeTask::~AnalyzeTask(void)
186 emit taskCompleted(m_taskId);
189 ////////////////////////////////////////////////////////////
190 // Thread Main
191 ////////////////////////////////////////////////////////////
193 void AnalyzeTask::run()
197 run_ex();
199 catch(const std::exception &error)
201 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
202 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
204 catch(...)
206 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
207 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
211 void AnalyzeTask::run_ex(void)
213 int fileType = fileTypeNormal;
214 QString currentFile = QDir::fromNativeSeparators(m_inputFile);
215 qDebug("Analyzing: %s", MUTILS_UTF8(currentFile));
217 AudioFileModel fileInfo(currentFile);
218 analyzeFile(currentFile, fileInfo, &fileType);
220 if(MUTILS_BOOLIFY(m_abortFlag))
222 qWarning("Operation cancelled by user!");
223 return;
226 switch(fileType)
228 case fileTypeDenied:
229 qWarning("Cannot access file for reading, skipping!");
230 break;
231 case fileTypeCDDA:
232 qWarning("Dummy CDDA file detected, skipping!");
233 break;
234 default:
235 if(fileInfo.metaInfo().title().isEmpty() || fileInfo.techInfo().containerType().isEmpty() || fileInfo.techInfo().audioType().isEmpty())
237 fileType = fileTypeUnknown;
238 if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
240 qWarning("Cue Sheet file detected, skipping!");
241 fileType = fileTypeCueSheet;
243 else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
245 qDebug("Found a potential Avisynth script, investigating...");
246 if(analyzeAvisynthFile(currentFile, fileInfo))
248 fileType = fileTypeNormal;
250 else
252 qDebug("Rejected Avisynth file: %s", MUTILS_UTF8(fileInfo.filePath()));
255 else
257 qDebug("Rejected file of unknown type: %s", MUTILS_UTF8(fileInfo.filePath()));
260 break;
263 //Emit the file now!
264 emit fileAnalyzed(m_taskId, fileType, fileInfo);
267 ////////////////////////////////////////////////////////////
268 // Privtae Functions
269 ////////////////////////////////////////////////////////////
271 const AudioFileModel& AnalyzeTask::analyzeFile(const QString &filePath, AudioFileModel &audioFile, int *const type)
273 *type = fileTypeNormal;
274 QFile readTest(filePath);
276 if (!readTest.open(QIODevice::ReadOnly))
278 *type = fileTypeDenied;
279 return audioFile;
282 if (checkFile_CDDA(readTest))
284 *type = fileTypeCDDA;
285 return audioFile;
288 readTest.close();
289 return analyzeMediaFile(filePath, audioFile);
292 const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, AudioFileModel &audioFile)
294 //bool skipNext = false;
295 QPair<quint32, quint32> id_val(UINT_MAX, UINT_MAX);
296 quint32 coverType = UINT_MAX;
297 QByteArray coverData;
299 QStringList params;
300 params << QString("--Language=raw");
301 params << QString("-f");
302 params << QString("--Output=XML");
303 params << QDir::toNativeSeparators(filePath);
305 QProcess process;
306 MUtils::init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
307 process.start(m_mediaInfoBin, params);
309 QByteArray data;
310 data.reserve(16384);
312 if(!process.waitForStarted())
314 qWarning("MediaInfo process failed to create!");
315 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
316 process.kill();
317 process.waitForFinished(-1);
318 return audioFile;
321 while(process.state() != QProcess::NotRunning)
323 if(MUTILS_BOOLIFY(m_abortFlag))
325 process.kill();
326 qWarning("Process was aborted on user request!");
327 break;
330 if(!process.waitForReadyRead())
332 if(process.state() == QProcess::Running)
334 qWarning("MediaInfo time out. Killing the process now!");
335 process.kill();
336 process.waitForFinished(-1);
337 break;
341 forever
343 const QByteArray dataNext = process.readAll();
344 if (dataNext.isEmpty()) {
345 break; /*no more input data*/
347 data += dataNext;
351 process.waitForFinished();
352 if (process.state() != QProcess::NotRunning)
354 process.kill();
355 process.waitForFinished(-1);
358 while (!process.atEnd())
360 const QByteArray dataNext = process.readAll();
361 if (dataNext.isEmpty()) {
362 break; /*no more input data*/
364 data += dataNext;
367 qDebug("!!!--START-->>>\n%s\n<<<--END--!!!", data.constData());
368 return parseMediaInfo(data, audioFile);
370 /* if(audioFile.metaInfo().title().isEmpty())
372 QString baseName = QFileInfo(filePath).fileName();
373 int index = baseName.lastIndexOf(".");
375 if(index >= 0)
377 baseName = baseName.left(index);
380 baseName = baseName.replace("_", " ").simplified();
381 index = baseName.lastIndexOf(" - ");
383 if(index >= 0)
385 baseName = baseName.mid(index + 3).trimmed();
388 audioFile.metaInfo().setTitle(baseName);
391 if((coverType != UINT_MAX) && (!coverData.isEmpty()))
393 retrieveCover(audioFile, coverType, coverData);
396 if((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
398 if(audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
401 return audioFile;*/
404 const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile)
406 QMap<QString, MI_trackType_t> trackTypes;
407 trackTypes.insert("general", trackType_gen);
408 trackTypes.insert("audio", trackType_aud);
410 QXmlStreamReader xmlStream(data);
411 bool firstFile = true;
412 QSet<MI_trackType_t> tracksFound;
414 if (findNextElement(QLatin1String("MediaInfo"), xmlStream))
416 const QStringRef version = xmlStream.attributes().value(QLatin1String("version"));
417 if (version.isEmpty() || (!STRICMP(version, QString().sprintf("0.%u.%02u", m_mediaInfoVer / 100U, m_mediaInfoVer % 100))))
419 qWarning("Invalid version property \"%s\" was detected!", MUTILS_UTF8(version));
420 return audioFile;
422 while (findNextElement(QLatin1String("File"), xmlStream))
424 if (firstFile)
426 firstFile = false;
427 while (findNextElement(QLatin1String("Track"), xmlStream))
429 const QString typeAttr = xmlStream.attributes().value(QLatin1String("type")).toString().simplified().toLower();
430 const MI_trackType_t trackType = trackTypes.value(typeAttr, MI_trackType_t(-1));
431 if (trackType != MI_trackType_t(-1))
433 if (!tracksFound.contains(trackType))
435 tracksFound << trackType;
436 parseTrackInfo(xmlStream, trackType, audioFile);
438 else
440 qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeAttr));
441 xmlStream.skipCurrentElement();
446 else
448 qWarning("Skipping non-primary file!");
449 xmlStream.skipCurrentElement();
454 if (!(audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty()))
456 if (audioFile.metaInfo().title().isEmpty())
458 QString baseName = QFileInfo(audioFile.filePath()).fileName();
459 int index;
460 if ((index = baseName.lastIndexOf(".")) >= 0)
462 baseName = baseName.left(index);
464 baseName = baseName.replace("_", " ").simplified();
465 if ((index = baseName.lastIndexOf(" - ")) >= 0)
467 baseName = baseName.mid(index + 3).trimmed();
469 audioFile.metaInfo().setTitle(baseName);
471 if ((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
473 if (audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
476 else
478 qWarning("Audio file format could *not* be recognized!");
481 return audioFile;
484 void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile)
486 while (xmlStream.readNextStartElement())
488 qWarning("%d::%s", trackType, MUTILS_UTF8(xmlStream.name()));
489 const MI_propertyId_t idx = g_properties_data->value(qMakePair(trackType, xmlStream.name().toString().simplified().toLower()), MI_propertyId_t(-1));
490 if (idx != MI_propertyId_t(-1))
492 const QString value = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
493 if (!value.isEmpty())
495 qWarning("--> %d: \"%s\"", idx, MUTILS_UTF8(value));
498 else
500 xmlStream.skipCurrentElement();
506 bool AnalyzeTask::checkFile_CDDA(QFile &file)
508 file.reset();
509 QByteArray data = file.read(128);
511 int i = data.indexOf("RIFF");
512 int j = data.indexOf("CDDA");
513 int k = data.indexOf("fmt ");
515 return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
518 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, const quint32 coverType, const QByteArray &coverData)
520 qDebug("Retrieving cover! (MIME_TYPES_MAX=%u)", MIME_TYPES_MAX);
522 static const QString ext = QString::fromLatin1(MIME_TYPES[qBound(0U, coverType, MIME_TYPES_MAX)].ext[0]);
523 if(!(QImage::fromData(coverData, ext.toUpper().toLatin1().constData()).isNull()))
525 QFile coverFile(QString("%1/%2.%3").arg(MUtils::temp_folder(), MUtils::next_rand_str(), ext));
526 if(coverFile.open(QIODevice::WriteOnly))
528 coverFile.write(coverData);
529 coverFile.close();
530 audioFile.metaInfo().setCover(coverFile.fileName(), true);
533 else
535 qWarning("Image data seems to be invalid :-(");
539 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
541 QProcess process;
542 MUtils::init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
544 process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
546 if(!process.waitForStarted())
548 qWarning("AVS2WAV process failed to create!");
549 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
550 process.kill();
551 process.waitForFinished(-1);
552 return false;
555 bool bInfoHeaderFound = false;
557 while(process.state() != QProcess::NotRunning)
559 if(MUTILS_BOOLIFY(m_abortFlag))
561 process.kill();
562 qWarning("Process was aborted on user request!");
563 break;
566 if(!process.waitForReadyRead())
568 if(process.state() == QProcess::Running)
570 qWarning("AVS2WAV time out. Killing process and skipping file!");
571 process.kill();
572 process.waitForFinished(-1);
573 return false;
577 QByteArray data;
579 while(process.canReadLine())
581 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
582 if(!line.isEmpty())
584 int index = line.indexOf(':');
585 if(index > 0)
587 QString key = line.left(index).trimmed();
588 QString val = line.mid(index+1).trimmed();
590 if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
592 if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
594 bool ok = false;
595 unsigned int duration = val.toUInt(&ok);
596 if(ok) info.techInfo().setDuration(duration);
598 if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
600 bool ok = false;
601 unsigned int samplerate = val.toUInt(&ok);
602 if(ok) info.techInfo().setAudioSamplerate (samplerate);
604 if(key.compare("Channels", Qt::CaseInsensitive) == 0)
606 bool ok = false;
607 unsigned int channels = val.toUInt(&ok);
608 if(ok) info.techInfo().setAudioChannels(channels);
610 if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
612 bool ok = false;
613 unsigned int bitdepth = val.toUInt(&ok);
614 if(ok) info.techInfo().setAudioBitdepth(bitdepth);
618 else
620 if(line.contains("[Audio Info]", Qt::CaseInsensitive))
622 info.techInfo().setAudioType("Avisynth");
623 info.techInfo().setContainerType("Avisynth");
624 bInfoHeaderFound = true;
631 process.waitForFinished();
632 if(process.state() != QProcess::NotRunning)
634 process.kill();
635 process.waitForFinished(-1);
638 //Check exit code
639 switch(process.exitCode())
641 case 0:
642 qDebug("Avisynth script was analyzed successfully.");
643 return true;
644 break;
645 case -5:
646 qWarning("It appears that Avisynth is not installed on the system!");
647 return false;
648 break;
649 default:
650 qWarning("Failed to open the Avisynth script, bad AVS file?");
651 return false;
652 break;
656 // ---------------------------------------------------------
657 // Utility Functions
658 // ---------------------------------------------------------
660 quint32 AnalyzeTask::parseYear(const QString &str)
662 if (str.startsWith("UTC", Qt::CaseInsensitive))
664 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
665 if (date.isValid())
667 return date.year();
669 else
671 return 0;
674 else
676 bool ok = false;
677 int year = str.toInt(&ok);
678 if (ok && year > 0)
680 return year;
682 else
684 return 0;
689 bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
691 while (xmlStream.readNextStartElement())
693 if (STRICMP(xmlStream.name(), name))
695 return true;
697 xmlStream.skipCurrentElement();
699 return false;
702 // ---------------------------------------------------------
703 // XML Content Handler Implementation
704 // ---------------------------------------------------------
706 #define DEFINE_PROPTERY_MAPPING(TYPE, NAME) do \
708 builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#NAME)), propertyId_##TYPE##_##NAME); \
710 while(0)
712 #define SET_OPTIONAL(TYPE, IF_CMD, THEN_CMD) do \
714 TYPE _tmp;\
715 if((IF_CMD)) { THEN_CMD; } \
717 while(0)
719 QReadWriteLock AnalyzeTask_XmlHandler::s_propertiesMutex;
720 QScopedPointer<const QMap<QPair<AnalyzeTask_XmlHandler::trackType_t, QString>, AnalyzeTask_XmlHandler::propertyId_t>> AnalyzeTask_XmlHandler::s_propertiesMap;
722 const QMap<QPair<AnalyzeTask_XmlHandler::trackType_t, QString>, AnalyzeTask_XmlHandler::propertyId_t> &AnalyzeTask_XmlHandler::initializeProperties(void)
724 QReadLocker rdLocker(&s_propertiesMutex);
725 if (!s_propertiesMap.isNull())
727 return *s_propertiesMap.data();
730 rdLocker.unlock();
731 QWriteLocker wrLocker(&s_propertiesMutex);
733 if (s_propertiesMap.isNull())
735 QMap<QPair<trackType_t, QString>, propertyId_t> *const builder = new QMap<QPair<trackType_t, QString>, propertyId_t>();
736 DEFINE_PROPTERY_MAPPING(gen, format);
737 DEFINE_PROPTERY_MAPPING(gen, format_profile);
738 DEFINE_PROPTERY_MAPPING(gen, duration);
739 DEFINE_PROPTERY_MAPPING(aud, format);
740 DEFINE_PROPTERY_MAPPING(aud, format_version);
741 DEFINE_PROPTERY_MAPPING(aud, format_profile);
742 DEFINE_PROPTERY_MAPPING(aud, channel_s_);
743 DEFINE_PROPTERY_MAPPING(aud, samplingrate);
744 s_propertiesMap.reset(builder);
747 return *s_propertiesMap.data();
750 bool AnalyzeTask_XmlHandler::startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts)
752 m_stack.push(qName);
753 switch (m_stack.size())
755 case 1:
756 if (!STRICMP(qName, "mediaInfo"))
758 qWarning("Invalid XML structure was detected! (1)");
759 return false;
761 if (!STRICMP(atts.value("version"), QString().sprintf("0.%u.%02u", m_version / 100U, m_version % 100)))
763 qWarning("Invalid version property was detected!");
764 return false;
766 return true;
767 case 2:
768 if (!STRICMP(qName, "file"))
770 qWarning("Invalid XML structure was detected! (2)");
771 return false;
773 return true;
774 case 3:
775 if (!STRICMP(qName, "track"))
777 qWarning("Invalid XML structure was detected! (3)");
778 return false;
780 else
782 const QString value = atts.value("type").trimmed();
783 if (STRICMP(value, "general"))
785 m_trackType = trackType_gen;
787 else if (STRICMP(value, "audio"))
789 if (m_trackIdx++)
791 qWarning("Skipping non-primary audio track!");
792 m_trackType = trackType_non;
794 else
796 m_trackType = trackType_aud;
799 else /*e.g. video*/
801 qWarning("Skipping a non-audio track!");
802 m_trackType = trackType_non;
804 return true;
806 case 4:
807 switch (m_trackType)
809 case trackType_gen:
810 m_currentProperty = m_properties.value(qMakePair(trackType_gen, qName.simplified().toLower()), propertyId_t(-1));
811 return true;
812 case trackType_aud:
813 m_currentProperty = m_properties.value(qMakePair(trackType_aud, qName.simplified().toLower()), propertyId_t(-1));
814 return true;
815 default:
816 m_currentProperty = propertyId_t(-1);
817 return true;
819 default:
820 return true;
824 bool AnalyzeTask_XmlHandler::endElement(const QString &namespaceURI, const QString &localName, const QString &qName)
826 m_stack.pop();
827 return true;
830 bool AnalyzeTask_XmlHandler::characters(const QString& ch)
832 if ((m_currentProperty != propertyId_t(-1)) && (m_stack.size() == 4))
834 const QString value = ch.simplified();
835 if (!value.isEmpty())
837 updatePropertry(m_currentProperty, value);
840 return true;
843 bool AnalyzeTask_XmlHandler::updatePropertry(const propertyId_t &idx, const QString &value)
845 switch (idx)
847 case propertyId_gen_format: m_audioFile.techInfo().setContainerType(value); return true;
848 case propertyId_gen_format_profile: m_audioFile.techInfo().setContainerProfile(value); return true;
849 case propertyId_gen_duration: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), m_audioFile.techInfo().setDuration(decodeTime(_tmp))); return true;
850 case propertyId_aud_format: m_audioFile.techInfo().setAudioType(value); return true;
851 case propertyId_aud_format_version: m_audioFile.techInfo().setAudioVersion(value); return true;
852 case propertyId_aud_format_profile: m_audioFile.techInfo().setAudioProfile(value); return true;
853 case propertyId_aud_channel_s_: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), m_audioFile.techInfo().setAudioChannels(_tmp)); return true;
854 case propertyId_aud_samplingrate: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), m_audioFile.techInfo().setAudioSamplerate(_tmp)); return true;
855 default: MUTILS_THROW_FMT("Invalid property ID: %d", idx);
859 bool AnalyzeTask_XmlHandler::parseUnsigned(const QString &str, quint32 &value)
861 bool okay = false;
862 value = str.toUInt(&okay);
863 return okay;
866 quint32 AnalyzeTask_XmlHandler::decodeTime(quint32 &value)
868 return (value + 500U) / 1000U;
871 ////////////////////////////////////////////////////////////
872 // Public Functions
873 ////////////////////////////////////////////////////////////
875 /*NONE*/
877 ////////////////////////////////////////////////////////////
878 // EVENTS
879 ////////////////////////////////////////////////////////////
881 /*NONE*/