OSDN Git Service

Bump version.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer.cpp
index 5c36e55..f212122 100644 (file)
@@ -1,11 +1,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 // LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
+// Copyright (C) 2004-2020 LoRd_MuldeR <MuldeR2@GMX.de>
 //
 // This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
+// it under the terms of the GNU GENERAL PUBLIC LICENSE as published by
 // the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
+// (at your option) any later version; always including the non-optional
+// LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "License.txt" file!
 //
 // This program is distributed in the hope that it will be useful,
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 #include "Thread_FileAnalyzer.h"
 
+//Internal
 #include "Global.h"
 #include "LockedFile.h"
 #include "Model_AudioFile.h"
+#include "Thread_FileAnalyzer_Task.h"
 #include "PlaylistImporter.h"
 
+//MUtils
+#include <MUtils/Global.h>
+#include <MUtils/Exception.h>
+
+//Qt
 #include <QDir>
 #include <QFileInfo>
 #include <QProcess>
 #include <QTime>
 #include <QDebug>
 #include <QImage>
+#include <QThreadPool>
+#include <QTime>
+#include <QElapsedTimer>
+#include <QTimer>
+#include <QQueue>
 
-#include <math.h>
-
-#define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
+//Insert into QStringList *without* duplicates
+static inline void SAFE_APPEND_STRING(QStringList &list, const QString &str)
+{
+       if(!list.contains(str, Qt::CaseInsensitive))
+       {
+               list << str;
+       }
+}
 
 ////////////////////////////////////////////////////////////
 // Constructor
 
 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
 :
-       m_inputFiles(inputFiles),
-       m_mediaInfoBin(lamexp_lookup_tool("mediainfo.exe")),
-       m_avs2wavBin(lamexp_lookup_tool("avs2wav.exe")),
-       m_templateFile(NULL),
-       m_abortFlag(false)
+       m_tasksCounterNext(0),
+       m_tasksCounterDone(0),
+       m_inputFiles(inputFiles)
 {
-       m_bSuccess = false;
-       m_bAborted = false;
-               
-       if(m_mediaInfoBin.isEmpty())
-       {
-               qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
-       }
-
        m_filesAccepted = 0;
        m_filesRejected = 0;
        m_filesDenied = 0;
        m_filesDummyCDDA = 0;
        m_filesCueSheet = 0;
+
+       moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
+       m_timer.reset(new QElapsedTimer());
 }
 
 FileAnalyzer::~FileAnalyzer(void)
 {
-       if(m_templateFile)
+       if(!m_pool.isNull())
        {
-               QString templatePath = m_templateFile->filePath();
-               LAMEXP_DELETE(m_templateFile);
-               if(QFile::exists(templatePath)) QFile::remove(templatePath);
+               if(!m_pool->waitForDone(2500))
+               {
+                       qWarning("There are still running tasks in the thread pool!");
+               }
        }
 }
 
 ////////////////////////////////////////////////////////////
-// Static data
-////////////////////////////////////////////////////////////
-
-const char *FileAnalyzer::g_tags_gen[] =
-{
-       "Format",
-       "Format_Profile",
-       "Format_Version",
-       "Duration",
-       "Title", "Track",
-       "Track/Position",
-       "Artist", "Performer",
-       "Album",
-       "Genre",
-       "Released_Date", "Recorded_Date",
-       "Comment",
-       "Cover",
-       "Cover_Type",
-       "Cover_Mime",
-       "Cover_Data",
-       NULL
-};
-
-const char *FileAnalyzer::g_tags_aud[] =
-{
-       "Format",
-       "Format_Profile",
-       "Format_Version",
-       "Channel(s)",
-       "SamplingRate",
-       "BitRate",
-       "BitRate_Mode",
-       NULL
-};
-
-////////////////////////////////////////////////////////////
 // Thread Main
 ////////////////////////////////////////////////////////////
 
 void FileAnalyzer::run()
 {
-       m_bSuccess = false;
-       m_bAborted = false;
+       m_bSuccess.fetchAndStoreOrdered(0);
+
+       m_tasksCounterNext = 0;
+       m_tasksCounterDone = 0;
+       m_completedCounter = 0;
+
+       m_completedFiles.clear();
+       m_completedTaskIds.clear();
+       m_runningTaskIds.clear();
 
        m_filesAccepted = 0;
        m_filesRejected = 0;
@@ -126,591 +109,222 @@ void FileAnalyzer::run()
        m_filesDummyCDDA = 0;
        m_filesCueSheet = 0;
 
-       m_inputFiles.sort();
-       m_recentlyAdded.clear();
-       m_abortFlag = false;
+       m_timer->invalidate();
 
-       if(!m_templateFile)
-       {
-               if(!createTemplate())
-               {
-                       qWarning("Failed to create template file!");
-                       return;
-               }
-       }
+       //Sort files
+       MUtils::natural_string_sort(m_inputFiles, true);
 
-       while(!m_inputFiles.isEmpty())
-       {
-               int fileType = fileTypeNormal;
-               QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
-               qDebug("Analyzing: %s", currentFile.toUtf8().constData());
-               emit fileSelected(QFileInfo(currentFile).fileName());
-               AudioFileModel file = analyzeFile(currentFile, &fileType);
-               
-               if(m_abortFlag)
-               {
-                       MessageBeep(MB_ICONERROR);
-                       m_bAborted = true;
-                       qWarning("Operation cancelled by user!");
-                       return;
-               }
-               if(fileType == fileTypeSkip)
-               {
-                       qWarning("File was recently added, skipping!");
-                       continue;
-               }
-               if(fileType == fileTypeDenied)
-               {
-                       m_filesDenied++;
-                       qWarning("Cannot access file for reading, skipping!");
-                       continue;
-               }
-               if(fileType == fileTypeCDDA)
-               {
-                       m_filesDummyCDDA++;
-                       qWarning("Dummy CDDA file detected, skipping!");
-                       continue;
-               }
-               
-               if(file.fileName().isEmpty() || file.formatContainerType().isEmpty() || file.formatAudioType().isEmpty())
-               {
-                       if(PlaylistImporter::importPlaylist(m_inputFiles, currentFile))
-                       {
-                               qDebug("Imported playlist file.");
-                       }
-                       else if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
-                       {
-                               qWarning("Cue Sheet file detected, skipping!");
-                               m_filesCueSheet++;
-                       }
-                       else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
-                       {
-                               qDebug("Found a potential Avisynth script, investigating...");
-                               if(analyzeAvisynthFile(currentFile, file))
-                               {
-                                       m_filesAccepted++;
-                                       emit fileAnalyzed(file);
-                               }
-                               else
-                               {
-                                       qDebug("Rejected Avisynth file: %s", file.filePath().toUtf8().constData());
-                                       m_filesRejected++;
-                               }
-                       }
-                       else
-                       {
-                               qDebug("Rejected file of unknown type: %s", file.filePath().toUtf8().constData());
-                               m_filesRejected++;
-                       }
-                       continue;
-               }
+       //Handle playlist files first!
+       handlePlaylistFiles();
 
-               m_filesAccepted++;
-               m_recentlyAdded.append(file.filePath());
-               emit fileAnalyzed(file);
+       const unsigned int nFiles = m_inputFiles.count();
+       if(nFiles < 1)
+       {
+               qWarning("File list is empty, nothing to do!");
+               return;
        }
 
-       qDebug("All files added.\n");
-       m_bSuccess = true;
-}
-
-////////////////////////////////////////////////////////////
-// Privtae Functions
-////////////////////////////////////////////////////////////
-
-const AudioFileModel FileAnalyzer::analyzeFile(const QString &filePath, int *type)
-{
-       *type = fileTypeNormal;
-       
-       AudioFileModel audioFile(filePath);
+       //Update progress
+       emit progressMaxChanged(nFiles);
+       emit progressValChanged(0);
 
-       if(m_recentlyAdded.contains(filePath, Qt::CaseInsensitive))
+       //Create the thread pool
+       if (m_pool.isNull())
        {
-               *type = fileTypeSkip;
-               return audioFile;
+               m_pool.reset(new QThreadPool());
        }
 
-       QFile readTest(filePath);
-       if(!readTest.open(QIODevice::ReadOnly))
-       {
-               *type = fileTypeDenied;
-               return audioFile;
-       }
-       if(checkFile_CDDA(readTest))
+       //Update thread count
+       const int idealThreadCount = QThread::idealThreadCount();
+       if(idealThreadCount > 0)
        {
-               *type = fileTypeCDDA;
-               return audioFile;
+               m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
        }
-       readTest.close();
 
-       cover_t coverType = coverNone;
-       QByteArray coverData;
+       //Start first N threads
+       QTimer::singleShot(0, this, SLOT(initializeTasks()));
 
-       QStringList params;
-       params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile->filePath()));
-       params << QDir::toNativeSeparators(filePath);
-       
-       QProcess process;
-       process.setProcessChannelMode(QProcess::MergedChannels);
-       process.setReadChannel(QProcess::StandardOutput);
-       process.start(m_mediaInfoBin, params);
-               
-       if(!process.waitForStarted())
+       //Start event processing
+       this->exec();
+
+       //Wait for pending tasks to complete
+       m_pool->waitForDone();
+
+       //Was opertaion aborted?
+       if(MUTILS_BOOLIFY(m_bAborted))
        {
-               qWarning("MediaInfo process failed to create!");
-               qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
-               process.kill();
-               process.waitForFinished(-1);
-               return audioFile;
+               qWarning("Operation cancelled by user!");
+               return;
        }
+       
+       //Update progress
+       emit progressValChanged(nFiles);
 
-       while(process.state() != QProcess::NotRunning)
+       //Emit pending files (this should not be required though!)
+       if(!m_completedFiles.isEmpty())
        {
-               if(m_abortFlag)
-               {
-                       process.kill();
-                       qWarning("Process was aborted on user request!");
-                       break;
-               }
-               
-               if(!process.waitForReadyRead())
-               {
-                       if(process.state() == QProcess::Running)
-                       {
-                               qWarning("MediaInfo time out. Killing process and skipping file!");
-                               process.kill();
-                               process.waitForFinished(-1);
-                               return audioFile;
-                       }
-               }
-
-               QByteArray data;
-
-               while(process.canReadLine())
+               qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
+               QList<unsigned int> keys = m_completedFiles.keys(); qSort(keys);
+               while(!keys.isEmpty())
                {
-                       QString line = QString::fromUtf8(process.readLine().constData()).simplified();
-                       if(!line.isEmpty())
-                       {
-                               //qDebug("Line:%s", line.toUtf8().constData());
-                               
-                               int index = line.indexOf('=');
-                               if(index > 0)
-                               {
-                                       QString key = line.left(index).trimmed();
-                                       QString val = line.mid(index+1).trimmed();
-                                       if(!(key.isEmpty() || val.isEmpty()))
-                                       {
-                                               updateInfo(audioFile, &coverType, &coverData, key, val);
-                                       }
-                               }
-                       }
+                       emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
                }
        }
 
-       if(audioFile.fileName().isEmpty())
-       {
-               QString baseName = QFileInfo(filePath).fileName();
-               int index = baseName.lastIndexOf(".");
+       qDebug("All files added.\n");
+       m_bSuccess.fetchAndStoreOrdered(1);
+       QThread::msleep(333);
+}
 
-               if(index >= 0)
-               {
-                       baseName = baseName.left(index);
-               }
+////////////////////////////////////////////////////////////
+// Privtae Functions
+////////////////////////////////////////////////////////////
 
-               baseName = baseName.replace("_", " ").simplified();
-               index = baseName.lastIndexOf(" - ");
+bool FileAnalyzer::analyzeNextFile(void)
+{
+       if(!(m_inputFiles.isEmpty() || MUTILS_BOOLIFY(m_bAborted)))
+       {
+               const unsigned int taskId = m_tasksCounterNext++;
+               const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
 
-               if(index >= 0)
+               if((!m_timer->isValid()) || (m_timer->elapsed() >= 333))
                {
-                       baseName = baseName.mid(index + 3).trimmed();
+                       emit fileSelected(QFileInfo(currentFile).fileName());
+                       m_timer->restart();
                }
-
-               audioFile.setFileName(baseName);
-       }
        
-       process.waitForFinished();
-       if(process.state() != QProcess::NotRunning)
-       {
-               process.kill();
-               process.waitForFinished(-1);
-       }
+               AnalyzeTask *task = new AnalyzeTask(taskId, currentFile, m_bAborted);
+               connect(task, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel)), Qt::QueuedConnection);
+               connect(task, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection);
+               m_runningTaskIds.insert(taskId); m_pool->start(task);
 
-       if((coverType != coverNone) && (!coverData.isEmpty()))
-       {
-               retrieveCover(audioFile, coverType, coverData);
+               return true;
        }
 
-       return audioFile;
+       return false;
 }
 
-void FileAnalyzer::updateInfo(AudioFileModel &audioFile, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
+void FileAnalyzer::handlePlaylistFiles(void)
 {
-       //qWarning("'%s' -> '%s'", key.toUtf8().constData(), value.toUtf8().constData());
+       QQueue<QVariant> queue;
+       QStringList importedFromPlaylist;
        
-       if(IS_KEY("Gen_Format"))
-       {
-               audioFile.setFormatContainerType(value);
-       }
-       else if(IS_KEY("Gen_Format_Profile"))
-       {
-               audioFile.setFormatContainerProfile(value);
-       }
-       else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
-       {
-               audioFile.setFileName(value);
-       }
-       else if(IS_KEY("Gen_Duration"))
-       {
-               unsigned int tmp = parseDuration(value);
-               if(tmp > 0) audioFile.setFileDuration(tmp);
-       }
-       else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
-       {
-               audioFile.setFileArtist(value);
-       }
-       else if(IS_KEY("Gen_Album"))
-       {
-               audioFile.setFileAlbum(value);
-       }
-       else if(IS_KEY("Gen_Genre"))
-       {
-               audioFile.setFileGenre(value);
-       }
-       else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
-       {
-               unsigned int tmp = parseYear(value);
-               if(tmp > 0) audioFile.setFileYear(tmp);
-       }
-       else if(IS_KEY("Gen_Comment"))
-       {
-               audioFile.setFileComment(value);
-       }
-       else if(IS_KEY("Gen_Track/Position"))
-       {
-               bool ok = false;
-               unsigned int tmp = value.toUInt(&ok);
-               if(ok) audioFile.setFilePosition(tmp);
-       }
-       else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
+       //Import playlist files into "hierarchical" list
+       while(!m_inputFiles.isEmpty())
        {
-               if(*coverType == coverNone)
+               const QString currentFile = m_inputFiles.takeFirst();
+               QStringList importedFiles;
+               if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
                {
-                       *coverType = coverJpeg;
+                       queue.enqueue(importedFiles);
+                       importedFromPlaylist << importedFiles;
                }
-       }
-       else if(IS_KEY("Gen_Cover_Mime"))
-       {
-               QString temp = value.split(" ", QString::SkipEmptyParts).first();
-               if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
-               else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
-               else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
-       }
-       else if(IS_KEY("Gen_Cover_Data"))
-       {
-               if(!coverData->isEmpty()) coverData->clear();
-               coverData->append(QByteArray::fromBase64(value.toLatin1()));
-       }
-       else if(IS_KEY("Aud_Format"))
-       {
-               audioFile.setFormatAudioType(value);
-       }
-       else if(IS_KEY("Aud_Format_Profile"))
-       {
-               audioFile.setFormatAudioProfile(value);
-       }
-       else if(IS_KEY("Aud_Format_Version"))
-       {
-               audioFile.setFormatAudioVersion(value);
-       }
-       else if(IS_KEY("Aud_Channel(s)"))
-       {
-               bool ok = false;
-               unsigned int tmp = value.toUInt(&ok);
-               if(ok) audioFile.setFormatAudioChannels(tmp);
-       }
-       else if(IS_KEY("Aud_SamplingRate"))
-       {
-               bool ok = false;
-               unsigned int tmp = value.toUInt(&ok);
-               if(ok) audioFile.setFormatAudioSamplerate(tmp);
-       }
-       else if(IS_KEY("Aud_BitDepth"))
-       {
-               bool ok = false;
-               unsigned int tmp = value.toUInt(&ok);
-               if(ok) audioFile.setFormatAudioBitdepth(tmp);
-       }
-       else if(IS_KEY("Aud_Duration"))
-       {
-               unsigned int tmp = parseDuration(value);
-               if(tmp > 0) audioFile.setFileDuration(tmp);
-       }
-       else if(IS_KEY("Aud_BitRate"))
-       {
-               bool ok = false;
-               unsigned int tmp = value.toUInt(&ok);
-               if(ok) audioFile.setFormatAudioBitrate(tmp/1000);
-       }
-       else if(IS_KEY("Aud_BitRate_Mode"))
-       {
-               if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeConstant);
-               if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeVariable);
-       }
-       else
-       {
-               qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
-       }
-}
-
-bool FileAnalyzer::checkFile_CDDA(QFile &file)
-{
-       file.reset();
-       QByteArray data = file.read(128);
-       
-       int i = data.indexOf("RIFF");
-       int j = data.indexOf("CDDA");
-       int k = data.indexOf("fmt ");
-
-       return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
-}
-
-void FileAnalyzer::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
-{
-       qDebug("Retrieving cover!");
-       QString extension;
-
-       switch(coverType)
-       {
-       case coverPng:
-               extension = QString::fromLatin1("png");
-               break;
-       case coverGif:
-               extension = QString::fromLatin1("gif");
-               break;
-       default:
-               extension = QString::fromLatin1("jpg");
-               break;
-       }
-       
-       if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
-       {
-               QFile coverFile(QString("%1/%2.%3").arg(lamexp_temp_folder2(), lamexp_rand_str(), extension));
-               if(coverFile.open(QIODevice::WriteOnly))
+               else
                {
-                       coverFile.write(coverData);
-                       coverFile.close();
-                       audioFile.setFileCover(coverFile.fileName(), true);
+                       queue.enqueue(currentFile);
                }
        }
-       else
-       {
-               qWarning("Image data seems to be invalid :-(");
-       }
-}
 
-bool FileAnalyzer::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
-{
-       QProcess process;
-       process.setProcessChannelMode(QProcess::MergedChannels);
-       process.setReadChannel(QProcess::StandardOutput);
-       process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
+       //Reduce temporary list
+       importedFromPlaylist.removeDuplicates();
 
-       if(!process.waitForStarted())
+       //Now build the complete "flat" file list (files imported from playlist take precedence!)
+       while(!queue.isEmpty())
        {
-               qWarning("AVS2WAV process failed to create!");
-               qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
-               process.kill();
-               process.waitForFinished(-1);
-               return false;
-       }
-
-       bool bInfoHeaderFound = false;
-
-       while(process.state() != QProcess::NotRunning)
-       {
-               if(m_abortFlag)
-               {
-                       process.kill();
-                       qWarning("Process was aborted on user request!");
-                       break;
-               }
-               
-               if(!process.waitForReadyRead())
+               const QVariant current = queue.dequeue();
+               if(current.type() == QVariant::String)
                {
-                       if(process.state() == QProcess::Running)
+                       const QString temp = current.toString();
+                       if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
                        {
-                               qWarning("AVS2WAV time out. Killing process and skipping file!");
-                               process.kill();
-                               process.waitForFinished(-1);
-                               return false;
+                               SAFE_APPEND_STRING(m_inputFiles, temp);
                        }
                }
-
-               QByteArray data;
-
-               while(process.canReadLine())
+               else if(current.type() == QVariant::StringList)
                {
-                       QString line = QString::fromUtf8(process.readLine().constData()).simplified();
-                       if(!line.isEmpty())
+                       const QStringList temp = current.toStringList();
+                       for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
                        {
-                               int index = line.indexOf(':');
-                               if(index > 0)
-                               {
-                                       QString key = line.left(index).trimmed();
-                                       QString val = line.mid(index+1).trimmed();
-
-                                       if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
-                                       {
-                                               if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int duration = val.toUInt(&ok);
-                                                       if(ok) info.setFileDuration(duration);
-                                               }
-                                               if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int samplerate = val.toUInt(&ok);
-                                                       if(ok) info.setFormatAudioSamplerate (samplerate);
-                                               }
-                                               if(key.compare("Channels", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int channels = val.toUInt(&ok);
-                                                       if(ok) info.setFormatAudioChannels(channels);
-                                               }
-                                               if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
-                                               {
-                                                       bool ok = false;
-                                                       unsigned int bitdepth = val.toUInt(&ok);
-                                                       if(ok) info.setFormatAudioBitdepth(bitdepth);
-                                               }                                       
-                                       }
-                               }
-                               else
-                               {
-                                       if(line.contains("[Audio Info]", Qt::CaseInsensitive))
-                                       {
-                                               info.setFormatAudioType("Avisynth");
-                                               info.setFormatContainerType("Avisynth");
-                                               bInfoHeaderFound = true;
-                                       }
-                               }
+                               SAFE_APPEND_STRING(m_inputFiles, (*iter));
                        }
                }
-       }
-       
-       process.waitForFinished();
-       if(process.state() != QProcess::NotRunning)
-       {
-               process.kill();
-               process.waitForFinished(-1);
-       }
-
-       //Check exit code
-       switch(process.exitCode())
-       {
-       case 0:
-               qDebug("Avisynth script was analyzed successfully.");
-               return true;
-               break;
-       case -5:
-               qWarning("It appears that Avisynth is not installed on the system!");
-               return false;
-               break;
-       default:
-               qWarning("Failed to open the Avisynth script, bad AVS file?");
-               return false;
-               break;
+               else
+               {
+                       qWarning("Encountered an unexpected variant type!");
+               }
        }
 }
 
-bool FileAnalyzer::createTemplate(void)
-{
-       if(m_templateFile)
-       {
-               qWarning("Template file already exists!");
-               return true;
-       }
-       
-       QString templatePath = QString("%1/%2.txt").arg(lamexp_temp_folder2(), lamexp_rand_str());
-
-       QFile templateFile(templatePath);
-       if(!templateFile.open(QIODevice::WriteOnly))
-       {
-               return false;
-       }
-
-       templateFile.write("General;");
-       for(size_t i = 0; g_tags_gen[i]; i++)
-       {
-               templateFile.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen[i]).toLatin1().constData());
-       }
-       templateFile.write("\r\n");
-
-       templateFile.write("Audio;");
-       for(size_t i = 0; g_tags_aud[i]; i++)
-       {
-               templateFile.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud[i]).toLatin1().constData());
-       }
-       templateFile.write("\r\n");
-
-       bool success = (templateFile.error() == QFile::NoError);
-       templateFile.close();
-       
-       if(!success)
-       {
-               QFile::remove(templatePath);
-               return false;
-       }
+////////////////////////////////////////////////////////////
+// Slot Functions
+////////////////////////////////////////////////////////////
 
-       try
-       {
-               m_templateFile = new LockedFile(templatePath);
-       }
-       catch(...)
+void FileAnalyzer::initializeTasks(void)
+{
+       for(int i = 0; i < m_pool->maxThreadCount(); i++)
        {
-               qWarning("Failed to lock template file!");
-               return false;
+               if(!analyzeNextFile()) break;
        }
-
-       return true;
 }
 
-unsigned int FileAnalyzer::parseYear(const QString &str)
+void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
 {
-       if(str.startsWith("UTC", Qt::CaseInsensitive))
+       m_completedTaskIds.insert(taskId);
+
+       switch(fileType)
        {
-               QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
-               if(date.isValid())
+       case AnalyzeTask::fileTypeNormal:
+               m_filesAccepted++;
+               if(m_tasksCounterDone == taskId)
                {
-                       return date.year();
+                       emit fileAnalyzed(file);
+                       m_tasksCounterDone++;
                }
                else
                {
-                       return 0;
+                       m_completedFiles.insert(taskId, file);
                }
+               break;
+       case AnalyzeTask::fileTypeCDDA:
+               m_filesDummyCDDA++;
+               break;
+       case AnalyzeTask::fileTypeDenied:
+               m_filesDenied++;
+               break;
+       case AnalyzeTask::fileTypeCueSheet:
+               m_filesCueSheet++;
+               break;
+       case AnalyzeTask::fileTypeUnknown:
+               m_filesRejected++;
+               break;
+       default:
+               MUTILS_THROW("Unknown file type identifier!");
        }
-       else
+
+       //Emit all pending files
+       while(m_completedTaskIds.contains(m_tasksCounterDone))
        {
-               bool ok = false;
-               int year = str.toInt(&ok);
-               if(ok && year > 0)
+               if(m_completedFiles.contains(m_tasksCounterDone))
                {
-                       return year;
-               }
-               else
-               {
-                       return 0;
+                       emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
                }
+               m_completedTaskIds.remove(m_tasksCounterDone);
+               m_tasksCounterDone++;
        }
 }
 
-unsigned int FileAnalyzer::parseDuration(const QString &str)
+void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
 {
-       bool ok = false;
-       unsigned int value = str.toUInt(&ok);
-       return ok ? (value/1000) : 0;
+       m_runningTaskIds.remove(taskId);
+       emit progressValChanged(++m_completedCounter);
+
+       if(!analyzeNextFile())
+       {
+               if(m_runningTaskIds.empty())
+               {
+                       QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
+               }
+       }
 }
 
 ////////////////////////////////////////////////////////////