Updated mpg123 decoder binary to v1.14.2 (2012-05-12), compiled with GCC 4.6.1.
[LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
blob74730238571239eb343948d243544cb2211d1dfa
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 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.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
22 #include "Thread_FileAnalyzer_Task.h"
24 #include "Global.h"
25 #include "LockedFile.h"
26 #include "Model_AudioFile.h"
27 #include "PlaylistImporter.h"
29 #include <QDir>
30 #include <QFileInfo>
31 #include <QProcess>
32 #include <QDate>
33 #include <QTime>
34 #include <QDebug>
35 #include <QImage>
36 #include <QReadLocker>
37 #include <QWriteLocker>
38 #include <QThread>
40 #include <math.h>
41 #include <time.h>
43 #define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
44 #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
45 #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
47 /* static vars */
48 QReadWriteLock AnalyzeTask::s_lock;
49 QMutex AnalyzeTask::s_waitMutex;
50 QWaitCondition AnalyzeTask::s_waitCond;
51 unsigned __int64 AnalyzeTask::s_threadIdx_created;
52 unsigned __int64 AnalyzeTask::s_threadIdx_finished;
53 unsigned int AnalyzeTask::s_filesAccepted;
54 unsigned int AnalyzeTask::s_filesRejected;
55 unsigned int AnalyzeTask::s_filesDenied;
56 unsigned int AnalyzeTask::s_filesDummyCDDA;
57 unsigned int AnalyzeTask::s_filesCueSheet;
58 QStringList AnalyzeTask::s_additionalFiles;
59 QSet<QString> AnalyzeTask::s_recentlyAdded;
61 /*constants*/
62 const int WAITCOND_TIMEOUT = 2500;
63 const int MAX_RETRIES = 60000 / WAITCOND_TIMEOUT;
65 ////////////////////////////////////////////////////////////
66 // Constructor
67 ////////////////////////////////////////////////////////////
69 AnalyzeTask::AnalyzeTask(const QString &inputFile, const QString &templateFile, volatile bool *abortFlag)
71 m_threadIdx(makeThreadIdx()),
72 m_inputFile(inputFile),
73 m_templateFile(templateFile),
74 m_mediaInfoBin(lamexp_lookup_tool("mediainfo.exe")),
75 m_avs2wavBin(lamexp_lookup_tool("avs2wav.exe")),
76 m_abortFlag(abortFlag)
78 if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
80 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
84 AnalyzeTask::~AnalyzeTask(void)
86 s_waitMutex.lock();
87 s_threadIdx_finished = qMax(s_threadIdx_finished, m_threadIdx + 1ui64);
88 s_waitMutex.unlock();
90 s_waitCond.wakeAll();
93 ////////////////////////////////////////////////////////////
94 // Thread Main
95 ////////////////////////////////////////////////////////////
97 void AnalyzeTask::run()
99 try
101 run_ex();
103 catch(...)
105 qWarning("WARNING: Caught an in exception AnalyzeTask thread!");
108 s_waitMutex.lock();
109 s_threadIdx_finished = qMax(s_threadIdx_finished, m_threadIdx + 1ui64);
110 s_waitMutex.unlock();
112 s_waitCond.wakeAll();
115 void AnalyzeTask::run_ex(void)
117 int fileType = fileTypeNormal;
118 QString currentFile = QDir::fromNativeSeparators(m_inputFile);
119 qDebug("Analyzing: %s", currentFile.toUtf8().constData());
121 emit fileSelected(QFileInfo(currentFile).fileName());
122 emit progressValChanged(m_threadIdx + 1);
124 AudioFileModel file = analyzeFile(currentFile, &fileType);
126 if(*m_abortFlag)
128 qWarning("Operation cancelled by user!");
129 return;
131 if(fileType == fileTypeSkip)
133 waitForPreviousThreads();
134 qWarning("File was recently added, skipping!");
135 return;
137 if(fileType == fileTypeDenied)
139 QWriteLocker lock(&s_lock);
140 s_filesDenied++;
141 lock.unlock();
142 waitForPreviousThreads();
143 qWarning("Cannot access file for reading, skipping!");
144 return;
146 if(fileType == fileTypeCDDA)
148 QWriteLocker lock(&s_lock);
149 s_filesDummyCDDA++;
150 lock.unlock();
151 waitForPreviousThreads();
152 qWarning("Dummy CDDA file detected, skipping!");
153 return;
156 //Handle files with *incomplete* meida info
157 if(file.fileName().isEmpty() || file.formatContainerType().isEmpty() || file.formatAudioType().isEmpty())
159 QStringList fileList;
160 if(PlaylistImporter::importPlaylist(fileList, currentFile))
162 waitForPreviousThreads();
163 qDebug("Imported playlist file.");
164 QWriteLocker lock(&s_lock);
165 s_additionalFiles << fileList;
167 else if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
169 QWriteLocker lock(&s_lock);
170 qWarning("Cue Sheet file detected, skipping!");
171 s_filesCueSheet++;
173 else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
175 qDebug("Found a potential Avisynth script, investigating...");
176 if(analyzeAvisynthFile(currentFile, file))
178 QWriteLocker lock(&s_lock);
179 s_filesAccepted++;
180 s_recentlyAdded.insert(file.filePath().toLower());
181 lock.unlock();
182 waitForPreviousThreads();
183 emit fileAnalyzed(file);
185 else
187 QWriteLocker lock(&s_lock);
188 qDebug("Rejected Avisynth file: %s", file.filePath().toUtf8().constData());
189 s_filesRejected++;
192 else
194 QWriteLocker lock(&s_lock);
195 qDebug("Rejected file of unknown type: %s", file.filePath().toUtf8().constData());
196 s_filesRejected++;
198 waitForPreviousThreads();
199 return;
202 //Emit the file now!
203 QWriteLocker lock(&s_lock);
204 s_filesAccepted++;
205 s_recentlyAdded.insert(file.filePath().toLower());
206 lock.unlock();
207 waitForPreviousThreads();
208 emit fileAnalyzed(file);
211 ////////////////////////////////////////////////////////////
212 // Privtae Functions
213 ////////////////////////////////////////////////////////////
215 const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type)
217 *type = fileTypeNormal;
218 AudioFileModel audioFile(filePath);
220 QReadLocker readLock(&s_lock);
221 if(s_recentlyAdded.contains(filePath.toLower()))
223 *type = fileTypeSkip;
224 return audioFile;
226 readLock.unlock();
228 QFile readTest(filePath);
229 if(!readTest.open(QIODevice::ReadOnly))
231 *type = fileTypeDenied;
232 return audioFile;
234 if(checkFile_CDDA(readTest))
236 *type = fileTypeCDDA;
237 return audioFile;
239 readTest.close();
241 bool skipNext = false;
242 unsigned int id_val[2] = {UINT_MAX, UINT_MAX};
243 cover_t coverType = coverNone;
244 QByteArray coverData;
246 QStringList params;
247 params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile));
248 params << QDir::toNativeSeparators(filePath);
250 QProcess process;
251 process.setProcessChannelMode(QProcess::MergedChannels);
252 process.setReadChannel(QProcess::StandardOutput);
253 process.start(m_mediaInfoBin, params);
255 if(!process.waitForStarted())
257 qWarning("MediaInfo process failed to create!");
258 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
259 process.kill();
260 process.waitForFinished(-1);
261 return audioFile;
264 while(process.state() != QProcess::NotRunning)
266 if(*m_abortFlag)
268 process.kill();
269 qWarning("Process was aborted on user request!");
270 break;
273 if(!process.waitForReadyRead())
275 if(process.state() == QProcess::Running)
277 qWarning("MediaInfo time out. Killing process and skipping file!");
278 process.kill();
279 process.waitForFinished(-1);
280 return audioFile;
284 QByteArray data;
286 while(process.canReadLine())
288 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
289 if(!line.isEmpty())
291 //qDebug("Line:%s", line.toUtf8().constData());
293 int index = line.indexOf('=');
294 if(index > 0)
296 QString key = line.left(index).trimmed();
297 QString val = line.mid(index+1).trimmed();
298 if(!key.isEmpty())
300 updateInfo(audioFile, &skipNext, id_val, &coverType, &coverData, key, val);
307 if(audioFile.fileName().isEmpty())
309 QString baseName = QFileInfo(filePath).fileName();
310 int index = baseName.lastIndexOf(".");
312 if(index >= 0)
314 baseName = baseName.left(index);
317 baseName = baseName.replace("_", " ").simplified();
318 index = baseName.lastIndexOf(" - ");
320 if(index >= 0)
322 baseName = baseName.mid(index + 3).trimmed();
325 audioFile.setFileName(baseName);
328 process.waitForFinished();
329 if(process.state() != QProcess::NotRunning)
331 process.kill();
332 process.waitForFinished(-1);
335 if((coverType != coverNone) && (!coverData.isEmpty()))
337 retrieveCover(audioFile, coverType, coverData);
340 return audioFile;
343 void AnalyzeTask::updateInfo(AudioFileModel &audioFile, bool *skipNext, unsigned int *id_val, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
345 //qWarning("'%s' -> '%s'", key.toUtf8().constData(), value.toUtf8().constData());
347 /*New Stream*/
348 if(IS_KEY("Gen_ID") || IS_KEY("Aud_ID"))
350 if(value.isEmpty())
352 *skipNext = false;
354 else
356 //We ignore all ID's, except for the lowest one!
357 bool ok = false;
358 unsigned int id = value.toUInt(&ok);
359 if(ok)
361 if(IS_KEY("Gen_ID")) { id_val[0] = qMin(id_val[0], id); *skipNext = (id > id_val[0]); }
362 if(IS_KEY("Aud_ID")) { id_val[1] = qMin(id_val[1], id); *skipNext = (id > id_val[1]); }
364 else
366 *skipNext = true;
369 if(*skipNext)
371 qWarning("Skipping info for non-primary stream!");
373 return;
376 /*Skip or empty?*/
377 if((*skipNext) || value.isEmpty())
379 return;
382 /*Playlist file?*/
383 if(IS_KEY("Aud_Source"))
385 *skipNext = true;
386 audioFile.setFormatContainerType(QString());
387 audioFile.setFormatAudioType(QString());
388 qWarning("Skipping info for playlist file!");
389 return;
392 /*General Section*/
393 if(IS_SEC("Gen"))
395 if(IS_KEY("Gen_Format"))
397 audioFile.setFormatContainerType(value);
399 else if(IS_KEY("Gen_Format_Profile"))
401 audioFile.setFormatContainerProfile(value);
403 else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
405 audioFile.setFileName(value);
407 else if(IS_KEY("Gen_Duration"))
409 unsigned int tmp = parseDuration(value);
410 if(tmp > 0) audioFile.setFileDuration(tmp);
412 else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
414 audioFile.setFileArtist(value);
416 else if(IS_KEY("Gen_Album"))
418 audioFile.setFileAlbum(value);
420 else if(IS_KEY("Gen_Genre"))
422 audioFile.setFileGenre(value);
424 else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
426 unsigned int tmp = parseYear(value);
427 if(tmp > 0) audioFile.setFileYear(tmp);
429 else if(IS_KEY("Gen_Comment"))
431 audioFile.setFileComment(value);
433 else if(IS_KEY("Gen_Track/Position"))
435 bool ok = false;
436 unsigned int tmp = value.toUInt(&ok);
437 if(ok) audioFile.setFilePosition(tmp);
439 else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
441 if(*coverType == coverNone)
443 *coverType = coverJpeg;
446 else if(IS_KEY("Gen_Cover_Mime"))
448 QString temp = FIRST_TOK(value);
449 if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
450 else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
451 else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
453 else if(IS_KEY("Gen_Cover_Data"))
455 if(!coverData->isEmpty()) coverData->clear();
456 coverData->append(QByteArray::fromBase64(FIRST_TOK(value).toLatin1()));
458 else
460 qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
462 return;
465 /*Audio Section*/
466 if(IS_SEC("Aud"))
469 if(IS_KEY("Aud_Format"))
471 audioFile.setFormatAudioType(value);
473 else if(IS_KEY("Aud_Format_Profile"))
475 audioFile.setFormatAudioProfile(value);
477 else if(IS_KEY("Aud_Format_Version"))
479 audioFile.setFormatAudioVersion(value);
481 else if(IS_KEY("Aud_Channel(s)"))
483 bool ok = false;
484 unsigned int tmp = value.toUInt(&ok);
485 if(ok) audioFile.setFormatAudioChannels(tmp);
487 else if(IS_KEY("Aud_SamplingRate"))
489 bool ok = false;
490 unsigned int tmp = value.toUInt(&ok);
491 if(ok) audioFile.setFormatAudioSamplerate(tmp);
493 else if(IS_KEY("Aud_BitDepth"))
495 bool ok = false;
496 unsigned int tmp = value.toUInt(&ok);
497 if(ok) audioFile.setFormatAudioBitdepth(tmp);
499 else if(IS_KEY("Aud_Duration"))
501 unsigned int tmp = parseDuration(value);
502 if(tmp > 0) audioFile.setFileDuration(tmp);
504 else if(IS_KEY("Aud_BitRate"))
506 bool ok = false;
507 unsigned int tmp = value.toUInt(&ok);
508 if(ok) audioFile.setFormatAudioBitrate(tmp/1000);
510 else if(IS_KEY("Aud_BitRate_Mode"))
512 if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeConstant);
513 if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeVariable);
515 else
517 qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
519 return;
522 /*Section not recognized*/
523 qWarning("Unknown section: %s", key.toUtf8().constData());
526 bool AnalyzeTask::checkFile_CDDA(QFile &file)
528 file.reset();
529 QByteArray data = file.read(128);
531 int i = data.indexOf("RIFF");
532 int j = data.indexOf("CDDA");
533 int k = data.indexOf("fmt ");
535 return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
538 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
540 qDebug("Retrieving cover!");
541 QString extension;
543 switch(coverType)
545 case coverPng:
546 extension = QString::fromLatin1("png");
547 break;
548 case coverGif:
549 extension = QString::fromLatin1("gif");
550 break;
551 default:
552 extension = QString::fromLatin1("jpg");
553 break;
556 if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
558 QFile coverFile(QString("%1/%2.%3").arg(lamexp_temp_folder2(), lamexp_rand_str(), extension));
559 if(coverFile.open(QIODevice::WriteOnly))
561 coverFile.write(coverData);
562 coverFile.close();
563 audioFile.setFileCover(coverFile.fileName(), true);
566 else
568 qWarning("Image data seems to be invalid :-(");
572 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
574 QProcess process;
575 process.setProcessChannelMode(QProcess::MergedChannels);
576 process.setReadChannel(QProcess::StandardOutput);
577 process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
579 if(!process.waitForStarted())
581 qWarning("AVS2WAV process failed to create!");
582 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
583 process.kill();
584 process.waitForFinished(-1);
585 return false;
588 bool bInfoHeaderFound = false;
590 while(process.state() != QProcess::NotRunning)
592 if(*m_abortFlag)
594 process.kill();
595 qWarning("Process was aborted on user request!");
596 break;
599 if(!process.waitForReadyRead())
601 if(process.state() == QProcess::Running)
603 qWarning("AVS2WAV time out. Killing process and skipping file!");
604 process.kill();
605 process.waitForFinished(-1);
606 return false;
610 QByteArray data;
612 while(process.canReadLine())
614 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
615 if(!line.isEmpty())
617 int index = line.indexOf(':');
618 if(index > 0)
620 QString key = line.left(index).trimmed();
621 QString val = line.mid(index+1).trimmed();
623 if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
625 if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
627 bool ok = false;
628 unsigned int duration = val.toUInt(&ok);
629 if(ok) info.setFileDuration(duration);
631 if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
633 bool ok = false;
634 unsigned int samplerate = val.toUInt(&ok);
635 if(ok) info.setFormatAudioSamplerate (samplerate);
637 if(key.compare("Channels", Qt::CaseInsensitive) == 0)
639 bool ok = false;
640 unsigned int channels = val.toUInt(&ok);
641 if(ok) info.setFormatAudioChannels(channels);
643 if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
645 bool ok = false;
646 unsigned int bitdepth = val.toUInt(&ok);
647 if(ok) info.setFormatAudioBitdepth(bitdepth);
651 else
653 if(line.contains("[Audio Info]", Qt::CaseInsensitive))
655 info.setFormatAudioType("Avisynth");
656 info.setFormatContainerType("Avisynth");
657 bInfoHeaderFound = true;
664 process.waitForFinished();
665 if(process.state() != QProcess::NotRunning)
667 process.kill();
668 process.waitForFinished(-1);
671 //Check exit code
672 switch(process.exitCode())
674 case 0:
675 qDebug("Avisynth script was analyzed successfully.");
676 return true;
677 break;
678 case -5:
679 qWarning("It appears that Avisynth is not installed on the system!");
680 return false;
681 break;
682 default:
683 qWarning("Failed to open the Avisynth script, bad AVS file?");
684 return false;
685 break;
689 unsigned int AnalyzeTask::parseYear(const QString &str)
691 if(str.startsWith("UTC", Qt::CaseInsensitive))
693 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
694 if(date.isValid())
696 return date.year();
698 else
700 return 0;
703 else
705 bool ok = false;
706 int year = str.toInt(&ok);
707 if(ok && year > 0)
709 return year;
711 else
713 return 0;
718 unsigned int AnalyzeTask::parseDuration(const QString &str)
720 bool ok = false;
721 unsigned int value = str.toUInt(&ok);
722 return ok ? (value/1000) : 0;
725 unsigned __int64 AnalyzeTask::makeThreadIdx(void)
727 s_waitMutex.lock();
728 unsigned __int64 idx = s_threadIdx_created++;
729 s_waitMutex.unlock();
731 return idx;
734 void AnalyzeTask::waitForPreviousThreads(void)
736 //This function will block until all threads with a *lower* index have terminated.
737 //Required to make sure that the files will be added in the "correct" order!
739 int retryCount = 0;
741 while(retryCount < MAX_RETRIES)
743 s_waitMutex.lock();
745 if((s_threadIdx_finished >= m_threadIdx) || *m_abortFlag)
747 s_waitMutex.unlock();
748 return;
751 if(!s_waitCond.wait(&s_waitMutex, WAITCOND_TIMEOUT))
753 retryCount++;
756 s_waitMutex.unlock();
759 qWarning("AnalyzeTask Timeout, will proceed anyway !!!");
762 ////////////////////////////////////////////////////////////
763 // Public Functions
764 ////////////////////////////////////////////////////////////
766 unsigned int AnalyzeTask::filesAccepted(void)
768 QReadLocker lock(&s_lock);
769 return s_filesAccepted;
772 unsigned int AnalyzeTask::filesRejected(void)
774 QReadLocker lock(&s_lock);
775 return s_filesRejected;
778 unsigned int AnalyzeTask::filesDenied(void)
780 QReadLocker lock(&s_lock);
781 return s_filesDenied;
784 unsigned int AnalyzeTask::filesDummyCDDA(void)
786 QReadLocker lock(&s_lock);
787 return s_filesDummyCDDA;
790 unsigned int AnalyzeTask::filesCueSheet(void)
792 QReadLocker lock(&s_lock);
793 return s_filesCueSheet;
796 int AnalyzeTask::getAdditionalFiles(QStringList &fileList)
798 QReadLocker readLock(&s_lock);
799 int count = s_additionalFiles.count();
800 readLock.unlock();
802 if(count > 0)
804 QWriteLocker lock(&s_lock);
805 count = s_additionalFiles.count();
806 fileList << s_additionalFiles;
807 s_additionalFiles.clear();
808 return count;
811 return 0;
814 bool AnalyzeTask::waitForOneThread(void)
816 bool ret = false;
818 s_waitMutex.lock();
819 ret = s_waitCond.wait(&s_waitMutex, WAITCOND_TIMEOUT);
820 s_waitMutex.unlock();
822 return ret;
825 void AnalyzeTask::reset(void)
827 QWriteLocker lock(&s_lock);
828 s_filesAccepted = 0;
829 s_filesRejected = 0;
830 s_filesDenied = 0;
831 s_filesDummyCDDA = 0;
832 s_filesCueSheet = 0;
833 s_additionalFiles.clear();
834 s_recentlyAdded.clear();
835 lock.unlock();
837 s_waitMutex.lock();
838 s_threadIdx_created = 0;
839 s_threadIdx_finished = 0;
840 s_waitMutex.unlock();
843 ////////////////////////////////////////////////////////////
844 // EVENTS
845 ////////////////////////////////////////////////////////////
847 /*NONE*/