OSDN Git Service

Bump version.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2020 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; always including the non-optional
9 // LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "License.txt" file!
10 //
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.
15 //
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.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "Thread_FileAnalyzer_Task.h"
24
25 //Internal
26 #include "Global.h"
27 #include "LockedFile.h"
28 #include "Model_AudioFile.h"
29 #include "MimeTypes.h"
30
31 //MUtils
32 #include <MUtils/Global.h>
33 #include <MUtils/OSSupport.h>
34 #include <MUtils/Lazy.h>
35 #include <MUtils/Exception.h>
36
37 //Qt
38 #include <QDir>
39 #include <QFileInfo>
40 #include <QProcess>
41 #include <QDate>
42 #include <QTime>
43 #include <QDebug>
44 #include <QImage>
45 #include <QReadLocker>
46 #include <QWriteLocker>
47 #include <QThread>
48 #include <QXmlSimpleReader>
49 #include <QXmlInputSource>
50 #include <QXmlStreamReader>
51 #include <QStack>
52
53 //CRT
54 #include <math.h>
55 #include <time.h>
56 #include <assert.h>
57
58 ////////////////////////////////////////////////////////////
59 // Helper Macros
60 ////////////////////////////////////////////////////////////
61
62 #define ADD_PROPTERY_MAPPING_1(TYPE, NAME) do \
63 { \
64         ADD_PROPTERY_MAPPING_2(TYPE, NAME, NAME); \
65 } \
66 while(0)
67
68 #define ADD_PROPTERY_MAPPING_2(TYPE, MI_NAME, LX_NAME) do \
69 { \
70         builder->insert(qMakePair(AnalyzeTask::trackType_##TYPE, QString::fromLatin1(#MI_NAME)), AnalyzeTask::propertyId_##LX_NAME); \
71 } \
72 while(0)
73
74 #define SET_OPTIONAL(TYPE, IF_CMD, THEN_CMD) do \
75 { \
76         TYPE _tmp;\
77         if((IF_CMD)) { THEN_CMD; } \
78 } \
79 while(0)
80
81 #define DIV_RND(A,B) (((A) + ((B) / 2U)) / (B))
82 #define STRICMP(A,B) ((A).compare((B), Qt::CaseInsensitive) == 0)
83
84 ////////////////////////////////////////////////////////////
85 // Static initialization
86 ////////////////////////////////////////////////////////////
87
88 static MUtils::Lazy<const QMap<QPair<AnalyzeTask::MI_trackType_t, QString>, AnalyzeTask::MI_propertyId_t>> s_mediaInfoIdx([]
89 {
90         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>();
91         ADD_PROPTERY_MAPPING_2(gen, format, container);
92         ADD_PROPTERY_MAPPING_2(gen, format_profile, container_profile);
93         ADD_PROPTERY_MAPPING_1(gen, duration);
94         ADD_PROPTERY_MAPPING_1(gen, title);
95         ADD_PROPTERY_MAPPING_2(gen, track, title);
96         ADD_PROPTERY_MAPPING_1(gen, artist);
97         ADD_PROPTERY_MAPPING_2(gen, performer, artist);
98         ADD_PROPTERY_MAPPING_1(gen, album);
99         ADD_PROPTERY_MAPPING_1(gen, genre);
100         ADD_PROPTERY_MAPPING_1(gen, released_date);
101         ADD_PROPTERY_MAPPING_2(gen, recorded_date, released_date);
102         ADD_PROPTERY_MAPPING_1(gen, track_position);
103         ADD_PROPTERY_MAPPING_1(gen, comment);
104         ADD_PROPTERY_MAPPING_1(aud, format);
105         ADD_PROPTERY_MAPPING_1(aud, format_version);
106         ADD_PROPTERY_MAPPING_1(aud, format_profile);
107         ADD_PROPTERY_MAPPING_2(aud, format_additionalfeatures, format_profile);
108         ADD_PROPTERY_MAPPING_1(aud, duration);
109         ADD_PROPTERY_MAPPING_1(aud, channel_s_);
110         ADD_PROPTERY_MAPPING_1(aud, samplingrate);
111         ADD_PROPTERY_MAPPING_1(aud, bitdepth);
112         ADD_PROPTERY_MAPPING_1(aud, bitrate);
113         ADD_PROPTERY_MAPPING_1(aud, bitrate_mode);
114         ADD_PROPTERY_MAPPING_1(aud, encoded_library);
115         ADD_PROPTERY_MAPPING_2(gen, cover_mime, cover_mime);
116         ADD_PROPTERY_MAPPING_2(gen, cover_data, cover_data);
117         return builder;
118 });
119
120 static MUtils::Lazy<const QMap<QString, AnalyzeTask::MI_propertyId_t>> s_avisynthIdx([]
121 {
122         QMap<QString, AnalyzeTask::MI_propertyId_t> *const builder = new QMap<QString, AnalyzeTask::MI_propertyId_t>();
123         builder->insert(QLatin1String("totalseconds"),  AnalyzeTask::propertyId_duration);
124         builder->insert(QLatin1String("samplespersec"), AnalyzeTask::propertyId_samplingrate);
125         builder->insert(QLatin1String("channels"),      AnalyzeTask::propertyId_channel_s_);
126         builder->insert(QLatin1String("bitspersample"), AnalyzeTask::propertyId_bitdepth);
127         return builder;
128 });
129
130 static MUtils::Lazy<const QMap<QString, QString>> s_mimeTypes([]
131 {
132         QMap<QString, QString> *const builder = new QMap<QString, QString>();
133         for (size_t i = 0U; MIME_TYPES[i].type; ++i)
134         {
135                 builder->insert(QString::fromLatin1(MIME_TYPES[i].type), QString::fromLatin1(MIME_TYPES[i].ext[0]));
136         }
137         return builder;
138 });
139
140 static MUtils::Lazy<const QMap<QString, AnalyzeTask::MI_trackType_t>> s_trackTypes([]
141 {
142         QMap<QString, AnalyzeTask::MI_trackType_t> *const builder = new QMap<QString, AnalyzeTask::MI_trackType_t>();
143         builder->insert("general", AnalyzeTask::trackType_gen);
144         builder->insert("audio",   AnalyzeTask::trackType_aud);
145         return builder;
146 });
147
148 ////////////////////////////////////////////////////////////
149 // Constructor
150 ////////////////////////////////////////////////////////////
151
152 AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, QAtomicInt &abortFlag)
153 :
154         m_taskId(taskId),
155         m_inputFile(inputFile),
156         m_mediaInfoBin(lamexp_tools_lookup("mediainfo.exe")),
157         m_mediaInfoVer(lamexp_tools_version("mediainfo.exe")),
158         m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")),
159         m_abortFlag(abortFlag),
160         m_mediaInfoIdx(*s_mediaInfoIdx),
161         m_avisynthIdx(*s_avisynthIdx),
162         m_mimeTypes(*s_mimeTypes),
163         m_trackTypes(*s_trackTypes)
164 {
165         if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
166         {
167                 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
168         }
169 }
170
171 AnalyzeTask::~AnalyzeTask(void)
172 {
173         emit taskCompleted(m_taskId);
174 }
175
176 ////////////////////////////////////////////////////////////
177 // Thread Main
178 ////////////////////////////////////////////////////////////
179
180 void AnalyzeTask::run()
181 {
182         try
183         {
184                 run_ex();
185         }
186         catch(const std::exception &error)
187         {
188                 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
189                 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
190         }
191         catch(...)
192         {
193                 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
194                 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
195         }
196 }
197
198 void AnalyzeTask::run_ex(void)
199 {
200         int fileType = fileTypeNormal;
201         QString currentFile = QDir::fromNativeSeparators(m_inputFile);
202         qDebug("Analyzing: %s", MUTILS_UTF8(currentFile));
203         
204         AudioFileModel fileInfo(currentFile);
205         analyzeFile(currentFile, fileInfo, &fileType);
206
207         if(MUTILS_BOOLIFY(m_abortFlag))
208         {
209                 qWarning("Operation cancelled by user!");
210                 return;
211         }
212
213         switch(fileType)
214         {
215         case fileTypeDenied:
216                 qWarning("Cannot access file for reading, skipping!");
217                 break;
218         case fileTypeCDDA:
219                 qWarning("Dummy CDDA file detected, skipping!");
220                 break;
221         default:
222                 if(fileInfo.metaInfo().title().isEmpty() || fileInfo.techInfo().containerType().isEmpty() || fileInfo.techInfo().audioType().isEmpty())
223                 {
224                         fileType = fileTypeUnknown;
225                         if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
226                         {
227                                 qWarning("Cue Sheet file detected, skipping!");
228                                 fileType = fileTypeCueSheet;
229                         }
230                         else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
231                         {
232                                 qDebug("Found a potential Avisynth script, investigating...");
233                                 if(analyzeAvisynthFile(currentFile, fileInfo))
234                                 {
235                                         fileType = fileTypeNormal;
236                                 }
237                                 else
238                                 {
239                                         qDebug("Rejected Avisynth file: %s", MUTILS_UTF8(fileInfo.filePath()));
240                                 }
241                         }
242                         else
243                         {
244                                 qDebug("Rejected file of unknown type: %s", MUTILS_UTF8(fileInfo.filePath()));
245                         }
246                 }
247                 break;
248         }
249
250         //Emit the file now!
251         emit fileAnalyzed(m_taskId, fileType, fileInfo);
252 }
253
254 ////////////////////////////////////////////////////////////
255 // Privtae Functions
256 ////////////////////////////////////////////////////////////
257
258 const AudioFileModel& AnalyzeTask::analyzeFile(const QString &filePath, AudioFileModel &audioFile, int *const type)
259 {
260         *type = fileTypeNormal;
261         QFile readTest(filePath);
262
263         if (!readTest.open(QIODevice::ReadOnly))
264         {
265                 *type = fileTypeDenied;
266                 return audioFile;
267         }
268
269         if (checkFile_CDDA(readTest))
270         {
271                 *type = fileTypeCDDA;
272                 return audioFile;
273         }
274
275         readTest.close();
276         return analyzeMediaFile(filePath, audioFile);
277 }
278
279 const AudioFileModel& AnalyzeTask::analyzeMediaFile(const QString &filePath, AudioFileModel &audioFile)
280 {
281         //bool skipNext = false;
282         QPair<quint32, quint32> id_val(UINT_MAX, UINT_MAX);
283         quint32 coverType = UINT_MAX;
284         QByteArray coverData;
285
286         QStringList params;
287         params << L1S("--Language=raw") << L1S("--Output=XML") << L1S("--Full") << L1S("--Cover_Data=base64");
288         params << QDir::toNativeSeparators(filePath);
289
290         QProcess process;
291         MUtils::init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
292         process.start(m_mediaInfoBin, params);
293
294         QByteArray data;
295         data.reserve(16384);
296
297         if(!process.waitForStarted())
298         {
299                 qWarning("MediaInfo process failed to create!");
300                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
301                 process.kill();
302                 process.waitForFinished(-1);
303                 return audioFile;
304         }
305
306         while(process.state() != QProcess::NotRunning)
307         {
308                 if(MUTILS_BOOLIFY(m_abortFlag))
309                 {
310                         process.kill();
311                         qWarning("Process was aborted on user request!");
312                         break;
313                 }
314                 
315                 if(!process.waitForReadyRead())
316                 {
317                         if(process.state() == QProcess::Running)
318                         {
319                                 qWarning("MediaInfo time out. Killing the process now!");
320                                 process.kill();
321                                 process.waitForFinished(-1);
322                                 break;
323                         }
324                 }
325
326                 forever
327                 {
328                         const QByteArray dataNext = process.readAll();
329                         if (dataNext.isEmpty()) {
330                                 break; /*no more input data*/
331                         }
332                         data += dataNext.trimmed();
333                 }
334         }
335
336         process.waitForFinished();
337         if (process.state() != QProcess::NotRunning)
338         {
339                 process.kill();
340                 process.waitForFinished(-1);
341         }
342
343         while (!process.atEnd())
344         {
345                 const QByteArray dataNext = process.readAll();
346                 if (dataNext.isEmpty()) {
347                         break; /*no more input data*/
348                 }
349                 data += dataNext.trimmed();
350         }
351
352 #if MUTILS_DEBUG
353         qDebug("-----BEGIN MEDIAINFO-----\n%s\n-----END MEDIAINFO-----", data.constData());
354 #endif //MUTILS_DEBUG
355
356         return parseMediaInfo(data, audioFile);
357 }
358
359 const AudioFileModel& AnalyzeTask::parseMediaInfo(const QByteArray &data, AudioFileModel &audioFile)
360 {
361         QXmlStreamReader xmlStream(data);
362         bool firstMediaFile = true;
363
364         if (findNextElement(QLatin1String("MediaInfo"), xmlStream))
365         {
366                 const QString versionXml = findAttribute(QLatin1String("Version"),  xmlStream.attributes());
367                 if (versionXml.isEmpty() || (!checkVersionStr(versionXml, 2U, 0U)))
368                 {
369                         qWarning("Invalid file format version property: \"%s\"", MUTILS_UTF8(versionXml));
370                         return audioFile;
371                 }
372                 if (findNextElement(QLatin1String("CreatingLibrary"), xmlStream))
373                 {
374                         const QString versionLib = findAttribute(QLatin1String("Version"), xmlStream.attributes());
375                         const QString identifier = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
376                         if (!STRICMP(identifier, QLatin1String("MediaInfoLib")))
377                         {
378                                 qWarning("Invalid library identiofier property: \"%s\"", MUTILS_UTF8(identifier));
379                                 return audioFile;
380                         }
381                         if (!versionLib.isEmpty())
382                         {
383                                 if (m_mediaInfoVer != UINT_MAX)
384                                 {
385                                         const quint32 mediaInfoVer = (m_mediaInfoVer > 9999U) ? m_mediaInfoVer / 10U : m_mediaInfoVer;
386                                         if (!checkVersionStr(versionLib, mediaInfoVer / 100U, mediaInfoVer % 100U))
387                                         {
388                                                 qWarning("Invalid library version property: \"%s\"", MUTILS_UTF8(versionLib));
389                                                 return audioFile;
390                                         }
391                                 }
392                         }
393                         else
394                         {
395                                 qWarning("Library version property not found!");
396                                 return audioFile;
397                         }
398                         while (findNextElement(QLatin1String("Media"), xmlStream))
399                         {
400                                 if (firstMediaFile || audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty())
401                                 {
402                                         firstMediaFile = false;
403                                         parseFileInfo(xmlStream, audioFile);
404                                 }
405                                 else
406                                 {
407                                         qWarning("Skipping non-primary file!");
408                                         xmlStream.skipCurrentElement();
409                                 }
410                         }
411                 }
412         }
413
414         if (!(audioFile.techInfo().containerType().isEmpty() || audioFile.techInfo().audioType().isEmpty()))
415         {
416                 if (audioFile.metaInfo().title().isEmpty())
417                 {
418                         QString baseName = QFileInfo(audioFile.filePath()).fileName();
419                         int index;
420                         if ((index = baseName.lastIndexOf(".")) >= 0)
421                         {
422                                 baseName = baseName.left(index);
423                         }
424                         baseName = baseName.replace("_", " ").simplified();
425                         if ((index = baseName.lastIndexOf(" - ")) >= 0)
426                         {
427                                 baseName = baseName.mid(index + 3).trimmed();
428                         }
429                         audioFile.metaInfo().setTitle(baseName);
430                 }
431                 if ((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
432                 {
433                         if (audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
434                 }
435         }
436         else
437         {
438                 qWarning("Audio file format could *not* be recognized!");
439         }
440
441         return audioFile;
442 }
443
444 void AnalyzeTask::parseFileInfo(QXmlStreamReader &xmlStream, AudioFileModel &audioFile)
445 {
446         QSet<MI_trackType_t> tracksProcessed;
447         MI_trackType_t trackType;
448         while (findNextElement(QLatin1String("Track"), xmlStream))
449         {
450                 const QString typeString = findAttribute(QLatin1String("Type"), xmlStream.attributes());
451                 if ((trackType = m_trackTypes.value(typeString.toLower(), MI_trackType_t(-1))) != MI_trackType_t(-1))
452                 {
453                         if (!tracksProcessed.contains(trackType))
454                         {
455                                 tracksProcessed << trackType;
456                                 parseTrackInfo(xmlStream, trackType, audioFile);
457                         }
458                         else
459                         {
460                                 qWarning("Skipping non-primary '%s' track!", MUTILS_UTF8(typeString));
461                                 xmlStream.skipCurrentElement();
462                         }
463                 }
464                 else
465                 {
466                         qWarning("Skipping unsupported '%s' track!", MUTILS_UTF8(typeString));
467                         xmlStream.skipCurrentElement();
468                 }
469         }
470 }
471
472 void AnalyzeTask::parseTrackInfo(QXmlStreamReader &xmlStream, const MI_trackType_t trackType, AudioFileModel &audioFile)
473 {
474         QString coverMimeType;
475         while (xmlStream.readNextStartElement())
476         {
477                 const MI_propertyId_t idx = m_mediaInfoIdx.value(qMakePair(trackType, xmlStream.name().toString().simplified().toLower()), MI_propertyId_t(-1));
478                 if (idx != MI_propertyId_t(-1))
479                 {
480                         const QString encoding = findAttribute(QLatin1String("dt"), xmlStream.attributes());
481                         const QString value = xmlStream.readElementText(QXmlStreamReader::SkipChildElements).simplified();
482                         if (!value.isEmpty())
483                         {
484                                 parseProperty(encoding.isEmpty() ? value : decodeStr(value, encoding), idx, audioFile, coverMimeType);
485                         }
486                 }
487                 else
488                 {
489                         xmlStream.skipCurrentElement();
490                 }
491         }
492 }
493
494 void AnalyzeTask::parseProperty(const QString &value, const MI_propertyId_t propertyIdx, AudioFileModel &audioFile, QString &coverMimeType)
495 {
496 #if MUTILS_DEBUG
497         qDebug("Property #%d = \"%s\"", propertyIdx, MUTILS_UTF8(value.left(24)));
498 #endif
499         switch (propertyIdx)
500         {
501                 case propertyId_container:         audioFile.techInfo().setContainerType(value);                                                                  return;
502                 case propertyId_container_profile: audioFile.techInfo().setContainerProfile(value);                                                               return;
503                 case propertyId_duration:          SET_OPTIONAL(double, parseFloat(value, _tmp), audioFile.techInfo().setDuration(qRound(_tmp)));                 return;
504                 case propertyId_title:             audioFile.metaInfo().setTitle(value);                                                                          return;
505                 case propertyId_artist:            audioFile.metaInfo().setArtist(value);                                                                         return;
506                 case propertyId_album:             audioFile.metaInfo().setAlbum(value);                                                                          return;
507                 case propertyId_genre:             audioFile.metaInfo().setGenre(value);                                                                          return;
508                 case propertyId_released_date:     SET_OPTIONAL(quint32, parseYear(value, _tmp), audioFile.metaInfo().setYear(_tmp));                             return;
509                 case propertyId_track_position:    SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.metaInfo().setPosition(_tmp));                     return;
510                 case propertyId_comment:           audioFile.metaInfo().setComment(value);                                                                        return;
511                 case propertyId_format:            audioFile.techInfo().setAudioType(value);                                                                      return;
512                 case propertyId_format_version:    audioFile.techInfo().setAudioVersion(value);                                                                   return;
513                 case propertyId_format_profile:    audioFile.techInfo().setAudioProfile(value);                                                                   return;
514                 case propertyId_channel_s_:        SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioChannels(_tmp));                return;
515                 case propertyId_samplingrate:      SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioSamplerate(_tmp));              return;
516                 case propertyId_bitdepth:          SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioBitdepth(_tmp));                return;
517                 case propertyId_bitrate:           SET_OPTIONAL(quint32, parseUnsigned(value, _tmp), audioFile.techInfo().setAudioBitrate(DIV_RND(_tmp, 1000U))); return;
518                 case propertyId_bitrate_mode:      SET_OPTIONAL(quint32, parseRCMode(value, _tmp), audioFile.techInfo().setAudioBitrateMode(_tmp));               return;
519                 case propertyId_encoded_library:   audioFile.techInfo().setAudioEncodeLib(cleanAsciiStr(value));                                                  return;
520                 case propertyId_cover_mime:        coverMimeType = value;                                                                                         return;
521                 case propertyId_cover_data:        retrieveCover(audioFile, coverMimeType, value);                                                                return;
522                 default: MUTILS_THROW_FMT("Invalid property ID: %d", propertyIdx);
523         }
524 }
525
526 bool AnalyzeTask::checkFile_CDDA(QFile &file)
527 {
528         file.reset();
529         QByteArray data = file.read(128);
530         
531         int i = data.indexOf("RIFF");
532         int j = data.indexOf("CDDA");
533         int k = data.indexOf("fmt ");
534
535         return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
536 }
537
538 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, const QString &coverType, const QString &coverData)
539 {
540         const QByteArray content = QByteArray::fromBase64(coverData.toLatin1());
541         const QString type = m_mimeTypes.value(coverType.toLower());
542         qDebug("Retrieving cover! (mime=\"%s\", type=\"%s\", len=%d)", MUTILS_L1STR(coverType), MUTILS_L1STR(type), content.size());
543         if(!QImage::fromData(content, type.isEmpty() ? NULL : MUTILS_L1STR(type.toUpper())).isNull())
544         {
545                 QFile coverFile(QString("%1/%2.%3").arg(MUtils::temp_folder(), MUtils::next_rand_str(), type.isEmpty() ? QLatin1String("jpg") : type));
546                 if(coverFile.open(QIODevice::WriteOnly))
547                 {
548                         coverFile.write(content);
549                         coverFile.close();
550                         audioFile.metaInfo().setCover(coverFile.fileName(), true);
551                 }
552         }
553         else
554         {
555                 qWarning("Image data seems to be invalid! [Header:%s]", content.left(32).toHex().constData());
556         }
557 }
558
559
560 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
561 {
562         QProcess process;
563         MUtils::init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
564
565         process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
566
567         if(!process.waitForStarted())
568         {
569                 qWarning("AVS2WAV process failed to create!");
570                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
571                 process.kill();
572                 process.waitForFinished(-1);
573                 return false;
574         }
575
576         bool bInfoHeaderFound = false;
577
578         while(process.state() != QProcess::NotRunning)
579         {
580                 if(MUTILS_BOOLIFY(m_abortFlag))
581                 {
582                         process.kill();
583                         qWarning("Process was aborted on user request!");
584                         break;
585                 }
586                 
587                 if(!process.waitForReadyRead())
588                 {
589                         if(process.state() == QProcess::Running)
590                         {
591                                 qWarning("AVS2WAV time out. Killing process and skipping file!");
592                                 process.kill();
593                                 process.waitForFinished(-1);
594                                 return false;
595                         }
596                 }
597
598                 while(process.canReadLine())
599                 {
600                         const QString line = QString::fromUtf8(process.readLine().constData()).simplified();
601                         if(!line.isEmpty())
602                         {
603                                 if(bInfoHeaderFound)
604                                 {
605                                         const qint32 index = line.indexOf(':');
606                                         if (index > 0)
607                                         {
608                                                 const QString key = line.left(index).trimmed();
609                                                 const QString val = line.mid(index + 1).trimmed();
610                                                 if (!(key.isEmpty() || val.isEmpty()))
611                                                 {
612                                                         switch (m_avisynthIdx.value(key.toLower(), MI_propertyId_t(-1)))
613                                                         {
614                                                                 case propertyId_duration:     SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setDuration(_tmp));        break;
615                                                                 case propertyId_samplingrate: SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioSamplerate(_tmp)); break;
616                                                                 case propertyId_channel_s_:   SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioChannels(_tmp));   break;
617                                                                 case propertyId_bitdepth:     SET_OPTIONAL(quint32, parseUnsigned(val, _tmp), info.techInfo().setAudioBitdepth(_tmp));   break;
618                                                         }
619                                                 }
620                                         }
621                                 }
622                                 else
623                                 {
624                                         if(line.contains("[Audio Info]", Qt::CaseInsensitive))
625                                         {
626                                                 info.techInfo().setAudioType("Avisynth");
627                                                 info.techInfo().setContainerType("Avisynth");
628                                                 bInfoHeaderFound = true;
629                                         }
630                                 }
631                         }
632                 }
633         }
634         
635         process.waitForFinished();
636         if(process.state() != QProcess::NotRunning)
637         {
638                 process.kill();
639                 process.waitForFinished(-1);
640         }
641
642         //Check exit code
643         switch(process.exitCode())
644         {
645         case 0:
646                 qDebug("Avisynth script was analyzed successfully.");
647                 return true;
648                 break;
649         case -5:
650                 qWarning("It appears that Avisynth is not installed on the system!");
651                 return false;
652                 break;
653         default:
654                 qWarning("Failed to open the Avisynth script, bad AVS file?");
655                 return false;
656                 break;
657         }
658 }
659
660 // ---------------------------------------------------------
661 // Utility Functions
662 // ---------------------------------------------------------
663
664 QString AnalyzeTask::decodeStr(const QString &str, const QString &encoding)
665 {
666         if (STRICMP(encoding, QLatin1String("binary.base64")))
667         {
668                 const QString decoded = QString::fromUtf8(QByteArray::fromBase64(str.toLatin1()));
669                 return decoded;
670         }
671         return QString();
672 }
673
674 bool AnalyzeTask::parseUnsigned(const QString &str, quint32 &value)
675 {
676         bool okay = false;
677         value = str.toUInt(&okay);
678         return okay;
679 }
680 bool AnalyzeTask::parseFloat(const QString &str, double &value)
681 {
682         bool okay = false;
683         value = QLocale::c().toDouble(str, &okay);
684         return okay;
685 }
686
687 bool AnalyzeTask::parseYear(const QString &str, quint32 &value)
688 {
689         if (str.startsWith(QLatin1String("UTC"), Qt::CaseInsensitive))
690         {
691                 const QDate date = QDate::fromString(str.mid(3).trimmed().left(10), QLatin1String("yyyy-MM-dd"));
692                 if (date.isValid())
693                 {
694                         value = date.year();
695                         return true;
696                 }
697                 return false;
698         }
699         else
700         {
701                 return parseUnsigned(str, value);
702         }
703 }
704
705 bool AnalyzeTask::parseRCMode(const QString &str, quint32 &value)
706 {
707         if (STRICMP(str, QLatin1String("CBR")))
708         {
709                 value = AudioFileModel::BitrateModeConstant;
710                 return true;
711         }
712         if (STRICMP(str, QLatin1String("VBR")))
713         {
714                 value = AudioFileModel::BitrateModeVariable;
715                 return true;
716         }
717         return false;
718 }
719
720 QString AnalyzeTask::cleanAsciiStr(const QString &str)
721 {
722         QByteArray ascii = str.toLatin1();
723         for (QByteArray::Iterator iter = ascii.begin(); iter != ascii.end(); ++iter)
724         {
725                 if ((*iter < 0x20) || (*iter >= 0x7F)) *iter = 0x3F;
726         }
727         return QString::fromLatin1(ascii).remove(QLatin1Char('?')).simplified();
728 }
729
730 bool AnalyzeTask::findNextElement(const QString &name, QXmlStreamReader &xmlStream)
731 {
732         while (xmlStream.readNextStartElement())
733         {
734                 if (STRICMP(xmlStream.name(), name))
735                 {
736                         return true;
737                 }
738                 xmlStream.skipCurrentElement();
739         }
740         return false;
741 }
742
743 QString AnalyzeTask::findAttribute(const QString &name, const QXmlStreamAttributes &xmlAttributes)
744 {
745         for (QXmlStreamAttributes::ConstIterator iter = xmlAttributes.constBegin(); iter != xmlAttributes.constEnd(); ++iter)
746         {
747                 if (STRICMP(iter->name(), name))
748                 {
749                         const QString value = iter->value().toString().simplified();
750                         if (!value.isEmpty())
751                         {
752                                 return value; /*found*/
753                         }
754                 }
755         }
756         return QString();
757 }
758
759 bool AnalyzeTask::checkVersionStr(const QString &str, const quint32 expectedMajor, const quint32 expectedMinor)
760 {
761         QRegExp version("^(\\d+)\\.(\\d+)($|\\.)");
762         if (version.indexIn(str) >= 0)
763         {
764                 quint32 actual[2];
765                 if (MUtils::regexp_parse_uint32(version, actual, 2))
766                 {
767                         if ((actual[0] == expectedMajor) && (actual[1] >= expectedMinor))
768                         {
769                                 return true;
770                         }
771                 }
772         }
773         return false;
774 }
775
776 ////////////////////////////////////////////////////////////
777 // Public Functions
778 ////////////////////////////////////////////////////////////
779
780 /*NONE*/
781
782 ////////////////////////////////////////////////////////////
783 // EVENTS
784 ////////////////////////////////////////////////////////////
785
786 /*NONE*/