Implemented parsing of cover art + code clean-up.
[LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
blobb4cb41fdcf5e19f6538c593c15d3ff0077430bf2
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 ////////////////////////////////////////////////////////////
58 // Helper Macros
59 ////////////////////////////////////////////////////////////
61 #define ADD_PROPTERY_MAPPING_1(TYPE, NAME) do \
62 { \
63 ADD_PROPTERY_MAPPING_2(TYPE, NAME, NAME); \
64 } \
65 while(0)
67 #define ADD_PROPTERY_MAPPING_2(TYPE, MI_NAME, LX_NAME) do \
68 { \
69 builder->insert(qMakePair(trackType_##TYPE, QString::fromLatin1(#MI_NAME)), propertyId_##LX_NAME); \
70 } \
71 while(0)
74 #define SET_OPTIONAL(TYPE, IF_CMD, THEN_CMD) do \
75 { \
76 TYPE _tmp;\
77 if((IF_CMD)) { THEN_CMD; } \
78 } \
79 while(0)
81 #define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
83 ////////////////////////////////////////////////////////////
84 // Constructor
85 ////////////////////////////////////////////////////////////
87 AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt &abortFlag)
89 m_taskId(taskId),
90 m_inputFile(inputFile),
91 m_mediaInfoBin(lamexp_tools_lookup("mediainfo.exe")),
92 m_mediaInfoVer(lamexp_tools_version("mediainfo.exe")),
93 m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")),
94 m_abortFlag(abortFlag),
95 m_mediaInfoIdx(initMediaInfoIdx()),
96 m_avisynthIdx(initAvisynthIdx()),
97 m_mimeTypes(initMimeTypes()),
98 m_trackTypes(initTrackTypes())
100 if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
102 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
106 AnalyzeTask::~AnalyzeTask(void)
108 emit taskCompleted(m_taskId);
111 ////////////////////////////////////////////////////////////
112 // Static initialization
113 ////////////////////////////////////////////////////////////
115 QReadWriteLock AnalyzeTask::s_lock;
116 QScopedPointer<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>> AnalyzeTask::s_pMediaInfoIdx;
117 QScopedPointer<const QMap<QString, AnalyzeTask::MI_propertyId_t>> AnalyzeTask::s_pAvisynthIdx;
118 QScopedPointer<const QMap<QString, QString>> AnalyzeTask::s_pMimeTypes;
119 QScopedPointer<const QMap<QString, AnalyzeTask::MI_trackType_t>> AnalyzeTask::s_pTrackTypes;
121 const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t> &AnalyzeTask::initMediaInfoIdx(void)
123 QReadLocker rdLocker(&s_lock);
124 if (s_pMediaInfoIdx.isNull())
126 rdLocker.unlock();
127 QWriteLocker wrLocker(&s_lock);
128 if (s_pMediaInfoIdx.isNull())
130 QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t> *const builder = new QMap<QPair<MI_trackType_t, QString>, MI_propertyId_t>();
131 ADD_PROPTERY_MAPPING_2(gen, format, container);
132 ADD_PROPTERY_MAPPING_2(gen, format_profile, container_profile);
133 ADD_PROPTERY_MAPPING_1(gen, duration);
134 ADD_PROPTERY_MAPPING_1(gen, title);
135 ADD_PROPTERY_MAPPING_2(gen, track, title);
136 ADD_PROPTERY_MAPPING_1(gen, artist);
137 ADD_PROPTERY_MAPPING_2(gen, performer, artist);
138 ADD_PROPTERY_MAPPING_1(gen, album);
139 ADD_PROPTERY_MAPPING_1(gen, genre);
140 ADD_PROPTERY_MAPPING_1(gen, released_date);
141 ADD_PROPTERY_MAPPING_2(gen, recorded_date, released_date);
142 ADD_PROPTERY_MAPPING_1(gen, track_position);
143 ADD_PROPTERY_MAPPING_1(gen, comment);
144 ADD_PROPTERY_MAPPING_1(aud, format);
145 ADD_PROPTERY_MAPPING_1(aud, format_version);
146 ADD_PROPTERY_MAPPING_1(aud, format_profile);
147 ADD_PROPTERY_MAPPING_1(aud, duration);
148 ADD_PROPTERY_MAPPING_1(aud, channel_s_);
149 ADD_PROPTERY_MAPPING_1(aud, samplingrate);
150 ADD_PROPTERY_MAPPING_1(aud, bitdepth);
151 ADD_PROPTERY_MAPPING_1(aud, bitrate);
152 ADD_PROPTERY_MAPPING_1(aud, bitrate_mode);
153 ADD_PROPTERY_MAPPING_1(aud, encoded_library);
154 ADD_PROPTERY_MAPPING_2(gen, cover_mime, cover_mime);
155 ADD_PROPTERY_MAPPING_2(gen, cover_data, cover_data);
156 s_pMediaInfoIdx.reset(builder);
158 wrLocker.unlock();
159 rdLocker.relock();
161 return (*s_pMediaInfoIdx);
164 const QMap<QString, AnalyzeTask::MI_propertyId_t> &AnalyzeTask::initAvisynthIdx(void)
166 QReadLocker rdLocker(&s_lock);
167 if (s_pAvisynthIdx.isNull())
169 rdLocker.unlock();
170 QWriteLocker wrLocker(&s_lock);
171 if (s_pAvisynthIdx.isNull())
173 QMap<QString, MI_propertyId_t> *const builder = new QMap<QString, MI_propertyId_t>();
174 builder->insert(QLatin1String("totalseconds"), propertyId_duration);
175 builder->insert(QLatin1String("samplespersec"), propertyId_samplingrate);
176 builder->insert(QLatin1String("channels"), propertyId_channel_s_);
177 builder->insert(QLatin1String("bitspersample"), propertyId_bitdepth);
178 s_pAvisynthIdx.reset(builder);
180 wrLocker.unlock();
181 rdLocker.relock();
183 return (*s_pAvisynthIdx);
185 const QMap<QString, AnalyzeTask::MI_trackType_t> &AnalyzeTask::initTrackTypes(void)
187 QReadLocker rdLocker(&s_lock);
188 if (s_pTrackTypes.isNull())
190 rdLocker.unlock();
191 QWriteLocker wrLocker(&s_lock);
192 if (s_pTrackTypes.isNull())
194 QMap<QString, MI_trackType_t> *const builder = new QMap<QString, MI_trackType_t>();
195 builder->insert("general", trackType_gen);
196 builder->insert("audio", trackType_aud);
197 s_pTrackTypes.reset(builder);
199 wrLocker.unlock();
200 rdLocker.relock();
202 return (*s_pTrackTypes);
205 const QMap<QString, QString> &AnalyzeTask::initMimeTypes(void)
207 QReadLocker rdLocker(&s_lock);
208 if (s_pMimeTypes.isNull())
210 rdLocker.unlock();
211 QWriteLocker wrLocker(&s_lock);
212 if (s_pMimeTypes.isNull())
214 QMap<QString, QString> *const builder = new QMap<QString, QString>();
215 for (size_t i = 0U; MIME_TYPES[i].type; ++i)
217 builder->insert(QString::fromLatin1(MIME_TYPES[i].type), QString::fromLatin1(MIME_TYPES[i].ext[0]));
219 s_pMimeTypes.reset(builder);
221 wrLocker.unlock();
222 rdLocker.relock();
224 return (*s_pMimeTypes);
227 ////////////////////////////////////////////////////////////
228 // Thread Main
229 ////////////////////////////////////////////////////////////
231 void AnalyzeTask::run()
235 run_ex();
237 catch(const std::exception &error)
239 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
240 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
242 catch(...)
244 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
245 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
249 void AnalyzeTask::run_ex(void)
251 int fileType = fileTypeNormal;
252 QString currentFile = QDir::fromNativeSeparators(m_inputFile);
253 qDebug("Analyzing: %s", MUTILS_UTF8(currentFile));
255 AudioFileModel fileInfo(currentFile);
256 analyzeFile(currentFile, fileInfo, &fileType);
258 if(MUTILS_BOOLIFY(m_abortFlag))
260 qWarning("Operation cancelled by user!");
261 return;
264 switch(fileType)
266 case fileTypeDenied:
267 qWarning("Cannot access file for reading, skipping!");
268 break;
269 case fileTypeCDDA:
270 qWarning("Dummy CDDA file detected, skipping!");
271 break;
272 default:
273 if(fileInfo.metaInfo().title().isEmpty() || fileInfo.techInfo().containerType().isEmpty() || fileInfo.techInfo().audioType().isEmpty())
275 fileType = fileTypeUnknown;
276 if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
278 qWarning("Cue Sheet file detected, skipping!");
279 fileType = fileTypeCueSheet;
281 else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
283 qDebug("Found a potential Avisynth script, investigating...");
284 if(analyzeAvisynthFile(currentFile, fileInfo))
286 fileType = fileTypeNormal;
288 else
290 qDebug("Rejected Avisynth file: %s", MUTILS_UTF8(fileInfo.filePath()));
293 else
295 qDebug("Rejected file of unknown type: %s", MUTILS_UTF8(fileInfo.filePath()));
298 break;
301 //Emit the file now!
302 emit fileAnalyzed(m_taskId, fileType, fileInfo);
305 ////////////////////////////////////////////////////////////
306 // Privtae Functions
307 ////////////////////////////////////////////////////////////
309 const AudioFileModel& AnalyzeTask::analyzeFile(const QString &filePath, AudioFileModel &audioFile, int *const type)
311 *type = fileTypeNormal;
312 QFile readTest(filePath);
314 if (!readTest.open(QIODevice::ReadOnly))
316 *type = fileTypeDenied;
317 return audioFile;
320 if (checkFile_CDDA(readTest))
322 *type = fileTypeCDDA;
323 return audioFile;
326 readTest.close();
327 return analyzeMediaFile(filePath, audioFile);
330 const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, AudioFileModel &audioFile)
332 //bool skipNext = false;
333 QPair<quint32, quint32> id_val(UINT_MAX, UINT_MAX);
334 quint32 coverType = UINT_MAX;
335 QByteArray coverData;
337 QStringList params;
338 params << QString("--Language=raw");
339 params << QString("-f");
340 params << QString("--Output=XML");
341 params << QDir::toNativeSeparators(filePath);
343 QProcess process;
344 MUtils::init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
345 process.start(m_mediaInfoBin, params);
347 QByteArray data;
348 data.reserve(16384);
350 if(!process.waitForStarted())
352 qWarning("MediaInfo process failed to create!");
353 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
354 process.kill();
355 process.waitForFinished(-1);
356 return audioFile;
359 while(process.state() != QProcess::NotRunning)
361 if(MUTILS_BOOLIFY(m_abortFlag))
363 process.kill();
364 qWarning("Process was aborted on user request!");
365 break;
368 if(!process.waitForReadyRead())
370 if(process.state() == QProcess::Running)
372 qWarning("MediaInfo time out. Killing the process now!");
373 process.kill();
374 process.waitForFinished(-1);
375 break;
379 forever
381 const QByteArray dataNext = process.readAll();
382 if (dataNext.isEmpty()) {
383 break; /*no more input data*/
385 data += dataNext;
389 process.waitForFinished();
390 if (process.state() != QProcess::NotRunning)
392 process.kill();
393 process.waitForFinished(-1);
396 while (!process.atEnd())
398 const QByteArray dataNext = process.readAll();
399 if (dataNext.isEmpty()) {
400 break; /*no more input data*/
402 data += dataNext;
405 #if MUTILS_DEBUG
406 qDebug("!!!--MEDIA_INFO-->>>\n%s\n<<<--MEDIA_INFO--!!!", data.constData());
407 #endif //MUTILS_DEBUG
409 return parseMediaInfo(data, audioFile);
412 const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile)
414 QXmlStreamReader xmlStream(data);
415 bool firstFile = true;
417 if (findNextElement(QLatin1String("MediaInfo"), xmlStream))
419 const QString version = findAttribute(QLatin1String("Version"), xmlStream.attributes());
420 if (version.isEmpty() || (!STRICMP(version, QString().sprintf("0.%u.%02u", m_mediaInfoVer / 100U, m_mediaInfoVer % 100))))
422 qWarning("Invalid version property \"%s\" was detected!", MUTILS_UTF8(version));
423 return audioFile;
425 while (findNextElement(QLatin1String("File"), xmlStream))
427 if (firstFile)
429 firstFile = false;
430 parseFileInfo(xmlStream, audioFile);
432 else
434 qWarning("Skipping non-primary file!");
435 xmlStream.skipCurrentElement();
440 if (!(audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty()))
442 if (audioFile.metaInfo().title().isEmpty())
444 QString baseName = QFileInfo(audioFile.filePath()).fileName();
445 int index;
446 if ((index = baseName.lastIndexOf(".")) >= 0)
448 baseName = baseName.left(index);
450 baseName = baseName.replace("_", " ").simplified();
451 if ((index = baseName.lastIndexOf(" - ")) >= 0)
453 baseName = baseName.mid(index + 3).trimmed();
455 audioFile.metaInfo().setTitle(baseName);
457 if ((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
459 if (audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
462 else
464 qWarning("Audio file format could *not* be recognized!");
467 return audioFile;
470 void AnalyzeTask::parseFileInfo(QXmlStreamReader &xmlStream, AudioFileModel &audioFile)
472 QSet<MI_trackType_t> tracksProcessed;
473 MI_trackType_t trackType;
474 while (findNextElement(QLatin1String("Track"), xmlStream))
476 const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes());
477 if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1))
479 if (!tracksProcessed.contains(trackType))
481 tracksProcessed << trackType;
482 parseTrackInfo(xmlStream, trackType, audioFile);
484 else
486 qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeString));
487 xmlStream.skipCurrentElement();
490 else
492 qWarning("Skipping unsupported '%s' track!", MUTILS_UTF8(typeString));
493 xmlStream.skipCurrentElement();
498 void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile)
500 QString coverMimeType;
501 while (xmlStream.readNextStartElement())
503 const MI_propertyId_t idx = m_mediaInfoIdx.value(qMakePair(trackType, xmlStream.name().toString().simplified().toLower()), MI_propertyId_t(-1));
504 if (idx != MI_propertyId_t(-1))
506 const QString encoding = findAttribute(QLatin1String("dt"), xmlStream.attributes());
507 const QString value = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
508 if (!value.isEmpty())
510 parseProperty(encoding.isEmpty() ? value : decodeStr(value, encoding), idx, audioFile, coverMimeType);
513 else
515 xmlStream.skipCurrentElement();
520 void AnalyzeTask::parseProperty(const QString &value, const MI_propertyId_t propertyIdx, AudioFileModel &audioFile, QString &coverMimeType)
522 #if MUTILS_DEBUG
523 qDebug("Property #%d = \"%s\"", propertyIdx, MUTILS_UTF8(value.left(24)));
524 #endif
525 switch (propertyIdx)
527 case propertyId_container: audioFile.techInfo().setContainerType(value); return;
528 case propertyId_container_profile: audioFile.techInfo().setContainerProfile(value); return;
529 case propertyId_duration: SET_OPTIONAL(quint32, parseDuration(value, _tmp), audioFile.techInfo().setDuration(_tmp)); return;
530 case propertyId_title: audioFile.metaInfo().setTitle(value); return;
531 case propertyId_artist: audioFile.metaInfo().setArtist(value); return;
532 case propertyId_album: audioFile.metaInfo().setAlbum(value); return;
533 case propertyId_genre: audioFile.metaInfo().setGenre(value); return;
534 case propertyId_released_date: SET_OPTIONAL(quint32, parseYear(value, _tmp), audioFile.metaInfo().setYear(_tmp)); return;
535 case propertyId_track_position: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.metaInfo().setPosition(_tmp)); return;
536 case propertyId_comment: audioFile.metaInfo().setComment(value); return;
537 case propertyId_format: audioFile.techInfo().setAudioType(value); return;
538 case propertyId_format_version: audioFile.techInfo().setAudioVersion(value); return;
539 case propertyId_format_profile: audioFile.techInfo().setAudioProfile(value); return;
540 case propertyId_channel_s_: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioChannels(_tmp)); return;
541 case propertyId_samplingrate: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioSamplerate(_tmp)); return;
542 case propertyId_bitdepth: SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioBitdepth(_tmp)); return;
543 case propertyId_bitrate: SET_OPTIONAL(quint32, parseDuration(value, _tmp), audioFile.techInfo().setAudioBitrate(_tmp)); return;
544 case propertyId_bitrate_mode: SET_OPTIONAL(quint32, parseRCMode(value, _tmp), audioFile.techInfo().setAudioBitrateMode(_tmp)); return;
545 case propertyId_encoded_library: audioFile.techInfo().setAudioEncodeLib(cleanAsciiStr(value)); return;
546 case propertyId_cover_mime: coverMimeType = value; return;
547 case propertyId_cover_data: retrieveCover(audioFile, coverMimeType, value); return;
548 default: MUTILS_THROW_FMT("Invalid property ID: %d", propertyIdx);
552 bool AnalyzeTask::checkFile_CDDA(QFile &file)
554 file.reset();
555 QByteArray data = file.read(128);
557 int i = data.indexOf("RIFF");
558 int j = data.indexOf("CDDA");
559 int k = data.indexOf("fmt ");
561 return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
564 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, const QString &coverType, const QString &coverData)
566 static const QByteArray content = QByteArray::fromBase64(coverData.toLatin1());
567 static const QString type = m_mimeTypes.value(coverType.toLower());
568 qDebug("Retrieving cover! (mime=\"%s\", type=\"%s\", len=%d)", MUTILS_L1STR(coverType), MUTILS_L1STR(type), content.size());
569 if(!QImage::fromData(content, type.isEmpty() ? NULL : MUTILS_L1STR(type.toUpper())).isNull())
571 QFile coverFile(QString("%1/%2.%3").arg(MUtils::temp_folder(), MUtils::next_rand_str(), type.isEmpty() ? QLatin1String("jpg") : type));
572 if(coverFile.open(QIODevice::WriteOnly))
574 coverFile.write(content);
575 coverFile.close();
576 audioFile.metaInfo().setCover(coverFile.fileName(), true);
579 else
581 qWarning("Image data seems to be invalid! [Header:%s]", content.left(32).toHex().constData());
586 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
588 QProcess process;
589 MUtils::init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
591 process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
593 if(!process.waitForStarted())
595 qWarning("AVS2WAV process failed to create!");
596 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
597 process.kill();
598 process.waitForFinished(-1);
599 return false;
602 bool bInfoHeaderFound = false;
604 while(process.state() != QProcess::NotRunning)
606 if(MUTILS_BOOLIFY(m_abortFlag))
608 process.kill();
609 qWarning("Process was aborted on user request!");
610 break;
613 if(!process.waitForReadyRead())
615 if(process.state() == QProcess::Running)
617 qWarning("AVS2WAV time out. Killing process and skipping file!");
618 process.kill();
619 process.waitForFinished(-1);
620 return false;
624 while(process.canReadLine())
626 const QString line = QString::fromUtf8(process.readLine().constData()).simplified();
627 if(!line.isEmpty())
629 if(bInfoHeaderFound)
631 const qint32 index = line.indexOf(':');
632 if (index > 0)
634 const QString key = line.left(index).trimmed();
635 const QString val = line.mid(index + 1).trimmed();
636 if (!(key.isEmpty() || val.isEmpty()))
638 switch (m_avisynthIdx.value(key.toLower(), MI_propertyId_t(-1)))
640 case propertyId_duration: SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setDuration(_tmp)); break;
641 case propertyId_samplingrate: SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioSamplerate(_tmp)); break;
642 case propertyId_channel_s_: SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioChannels(_tmp)); break;
643 case propertyId_bitdepth: SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioBitdepth(_tmp)); break;
648 else
650 if(line.contains("[Audio Info]", Qt::CaseInsensitive))
652 info.techInfo().setAudioType("Avisynth");
653 info.techInfo().setContainerType("Avisynth");
654 bInfoHeaderFound = true;
661 process.waitForFinished();
662 if(process.state() != QProcess::NotRunning)
664 process.kill();
665 process.waitForFinished(-1);
668 //Check exit code
669 switch(process.exitCode())
671 case 0:
672 qDebug("Avisynth script was analyzed successfully.");
673 return true;
674 break;
675 case -5:
676 qWarning("It appears that Avisynth is not installed on the system!");
677 return false;
678 break;
679 default:
680 qWarning("Failed to open the Avisynth script, bad AVS file?");
681 return false;
682 break;
686 // ---------------------------------------------------------
687 // Utility Functions
688 // ---------------------------------------------------------
690 QString AnalyzeTask::decodeStr(const QString &str, const QString &encoding)
692 if (STRICMP(encoding, QLatin1String("binary.base64")))
694 const QString decoded = QString::fromUtf8(QByteArray::fromBase64(str.toLatin1()));
695 return decoded;
697 return QString();
700 bool AnalyzeTask::parseUnsigned(const QString &str, quint32 &value)
702 bool okay = false;
703 value = str.toUInt(&okay);
704 return okay;
706 bool AnalyzeTask::parseDuration(const QString &str, quint32 &value)
708 if (parseUnsigned(str, value))
710 value = (value + 500U) / 1000U;
711 return true;
713 return false;
716 bool AnalyzeTask::parseYear(const QString &str, quint32 &value)
718 if (str.startsWith(QLatin1String("UTC"), Qt::CaseInsensitive))
720 const QDate date = QDate::fromString(str.mid(3).trimmed().left(10), QLatin1String("yyyy-MM-dd"));
721 if (date.isValid())
723 value = date.year();
724 return true;
726 return false;
728 else
730 return parseUnsigned(str, value);
734 bool AnalyzeTask::parseRCMode(const QString &str, quint32 &value)
736 if (STRICMP(str, QLatin1String("CBR")))
738 value = AudioFileModel::BitrateModeConstant;
739 return true;
741 if (STRICMP(str, QLatin1String("VBR")))
743 value = AudioFileModel::BitrateModeVariable;
744 return true;
746 return false;
749 QString AnalyzeTask::cleanAsciiStr(const QString &str)
751 QByteArray ascii = str.toLatin1();
752 for (QByteArray::Iterator iter = ascii.begin(); iter != ascii.end(); ++iter)
754 if ((*iter < 0x20) || (*iter >= 0x7F)) *iter = 0x3F;
756 return QString::fromLatin1(ascii).remove(QLatin1Char('?')).simplified();
759 bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
761 while (xmlStream.readNextStartElement())
763 if (STRICMP(xmlStream.name(), name))
765 return true;
767 xmlStream.skipCurrentElement();
769 return false;
772 QString AnalyzeTask::findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes)
774 for (QXmlStreamAttributes::ConstIterator iter = xmlAttributes.constBegin(); iter != xmlAttributes.constEnd(); ++iter)
776 if (STRICMP(iter->name(), name))
778 const QString value = iter->value().toString().simplified();
779 if (!value.isEmpty())
781 return value; /*found*/
785 return QString();
788 ////////////////////////////////////////////////////////////
789 // Public Functions
790 ////////////////////////////////////////////////////////////
792 /*NONE*/
794 ////////////////////////////////////////////////////////////
795 // EVENTS
796 ////////////////////////////////////////////////////////////
798 /*NONE*/