OSDN Git Service

Updated list of update mirrors.
[lamexp/LameXP.git] / src / Model_CueSheet.cpp
index 30b52e7..579c1b0 100644 (file)
@@ -1,6 +1,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 // LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2011 LoRd_MuldeR <MuldeR2@GMX.de>
+// Copyright (C) 2004-2012 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
 #include "Genres.h"
 
 #include <QApplication>
-#include <QMessageBox>
-#include <QInputDialog>
+#include <QDir>
 #include <QFileInfo>
+#include <QFont>
+#include <QTime>
+#include <QTextCodec>
+#include <QTextStream>
 
 #include <float.h>
 #include <limits>
 
+#define UNQUOTE(STR) STR.split("\"",  QString::SkipEmptyParts).first().trimmed()
+
 ////////////////////////////////////////////////////////////
 // Helper Classes
 ////////////////////////////////////////////////////////////
@@ -39,6 +44,7 @@ class CueSheetItem
 {
 public:
        virtual const char* type(void) = 0;
+       virtual bool isValid(void) { return false; }
 };
 
 class CueSheetTrack : public CueSheetItem
@@ -50,22 +56,33 @@ public:
                m_trackNo(trackNo)
        {
                m_startIndex = std::numeric_limits<double>::quiet_NaN();
+               m_duration = std::numeric_limits<double>::infinity();
+               m_year = 0;
        }
        int trackNo(void) { return m_trackNo; }
        double startIndex(void) { return m_startIndex; }
+       double duration(void) { return m_duration; }
        QString title(void) { return m_title; }
        QString performer(void) { return m_performer; }
+       QString genre(void) { return m_genre; }
+       unsigned int year(void) { return m_year; }
        CueSheetFile *parent(void) { return m_parent; }
        void setStartIndex(double startIndex) { m_startIndex = startIndex; }
-       void setTitle(const QString &title) { m_title = title; }
-       void setPerformer(const QString &performer) { m_performer = performer; }
-       bool isValid(void) { return !(_isnan(m_startIndex) || (m_trackNo < 0)); }
+       void setDuration(double duration) { m_duration = duration; }
+       void setTitle(const QString &title, bool update = false) { if(!update || (m_title.isEmpty() && !title.isEmpty())) m_title = title; }
+       void setPerformer(const QString &performer, bool update = false) { if(!update || (m_performer.isEmpty() && !performer.isEmpty())) m_performer = performer; }
+       void setGenre(const QString &genre, bool update = false) { if(!update || (m_genre.isEmpty() && !m_genre.isEmpty())) m_genre = genre; }
+       void setYear(const unsigned int year, bool update = false) { if(!update || (year == 0)) m_year = year; }
+       virtual bool isValid(void) { return !(_isnan(m_startIndex) || (m_trackNo < 0)); }
        virtual const char* type(void) { return "CueSheetTrack"; }
 private:
        int m_trackNo;
        double m_startIndex;
+       double m_duration;
        QString m_title;
        QString m_performer;
+       QString m_genre;
+       unsigned int m_year;
        CueSheetFile *m_parent;
 };
 
@@ -79,6 +96,7 @@ public:
        void clearTracks(void) { while(!m_tracks.isEmpty()) delete m_tracks.takeLast(); }
        CueSheetTrack *track(int index) { return m_tracks.at(index); }
        int trackCount(void) { return m_tracks.count(); }
+       virtual bool isValid(void) { return m_tracks.count() > 0; }
        virtual const char* type(void) { return "CueSheetFile"; }
 private:
        const QString m_fileName;
@@ -89,9 +107,15 @@ private:
 // Constructor & Destructor
 ////////////////////////////////////////////////////////////
 
+QMutex CueSheetModel::m_mutex(QMutex::Recursive);
+
 CueSheetModel::CueSheetModel()
+:
+       m_fileIcon(":/icons/music.png"),
+       m_trackIcon(":/icons/control_play_blue.png")
 {
        int trackNo = 0;
+       m_albumYear = 0;
        
        for(int i = 0; i < 5; i++)
        {
@@ -118,15 +142,17 @@ CueSheetModel::~CueSheetModel(void)
 
 QModelIndex CueSheetModel::index(int row, int column, const QModelIndex &parent) const
 {
+       QMutexLocker lock(&m_mutex);
+       
        if(!parent.isValid())
        {
-               return createIndex(row, column, m_files.at(row));
+               return (row < m_files.count()) ? createIndex(row, column, m_files.at(row)) : QModelIndex();
        }
 
        CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
        if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
        {
-               return createIndex(row, column, filePtr->track(row));
+               return (row < filePtr->trackCount()) ? createIndex(row, column, filePtr->track(row)) : QModelIndex();
        }
 
        return QModelIndex();
@@ -134,11 +160,14 @@ QModelIndex CueSheetModel::index(int row, int column, const QModelIndex &parent)
 
 int CueSheetModel::columnCount(const QModelIndex &parent) const
 {
-       return 3;
+       QMutexLocker lock(&m_mutex);
+       return 4;
 }
 
 int CueSheetModel::rowCount(const QModelIndex &parent) const
 {
+       QMutexLocker lock(&m_mutex);
+
        if(!parent.isValid())
        {
                return m_files.count();
@@ -155,6 +184,8 @@ int CueSheetModel::rowCount(const QModelIndex &parent) const
 
 QModelIndex CueSheetModel::parent(const QModelIndex &child) const
 {
+       QMutexLocker lock(&m_mutex);
+       
        if(child.isValid())
        {
                CueSheetItem *childItem = static_cast<CueSheetItem*>(child.internalPointer());
@@ -169,6 +200,8 @@ QModelIndex CueSheetModel::parent(const QModelIndex &child) const
 
 QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, int role) const
 {
+       QMutexLocker lock(&m_mutex);
+       
        if(role == Qt::DisplayRole)
        {
                switch(section)
@@ -182,6 +215,9 @@ QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, in
                case 2:
                        return tr("Index");
                        break;
+               case 3:
+                       return tr("Duration");
+                       break;
                default:
                        return QVariant();
                        break;
@@ -195,6 +231,8 @@ QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, in
 
 QVariant CueSheetModel::data(const QModelIndex &index, int role) const
 {
+       QMutexLocker lock(&m_mutex);
+
        if(role == Qt::DisplayRole)
        {
                CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
@@ -207,7 +245,7 @@ QVariant CueSheetModel::data(const QModelIndex &index, int role) const
                                return tr("File %1").arg(QString().sprintf("%02d", index.row() + 1)).append(" ");
                                break;
                        case 1:
-                               return QString("[%1]").arg(QFileInfo(filePtr->fileName()).fileName());
+                               return QFileInfo(filePtr->fileName()).fileName();
                                break;
                        default:
                                return QVariant();
@@ -242,48 +280,279 @@ QVariant CueSheetModel::data(const QModelIndex &index, int role) const
                        case 2:
                                return indexToString(trackPtr->startIndex());
                                break;
+                       case 3:
+                               return indexToString(trackPtr->duration());
+                               break;
                        default:
                                return QVariant();
                                break;
                        }
                }
        }
+       else if(role == Qt::ToolTipRole)
+       {
+               CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
+
+               if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
+               {
+                       return QDir::toNativeSeparators(filePtr->fileName());
+               }
+               else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
+               {
+                       return QDir::toNativeSeparators(trackPtr->parent()->fileName());
+               }
+       }
+       else if(role == Qt::DecorationRole)
+       {
+               if(index.column() == 0)
+               {
+                       CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
+
+                       if(dynamic_cast<CueSheetFile*>(item))
+                       {
+                               return m_fileIcon;
+                       }
+                       else if(dynamic_cast<CueSheetTrack*>(item))
+                       {
+                               return m_trackIcon;
+                       }
+               }
+       }
+       else if(role == Qt::FontRole)
+       {
+               QFont font("Monospace");
+               font.setStyleHint(QFont::TypeWriter);
+               if((index.column() == 1))
+               {
+                       CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
+                       font.setBold(dynamic_cast<CueSheetFile*>(item) != NULL);
+               }
+               return font;
+       }
+       else if(role == Qt::ForegroundRole)
+       {
+               if((index.column() == 1))
+               {
+                       CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
+                       if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
+                       {
+                               return (QFileInfo(filePtr->fileName()).size() > 4) ? QColor("mediumblue") : QColor("darkred");
+                       }
+               }
+               else if((index.column() == 3))
+               {
+                       CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
+                       if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
+                       {
+                               if(trackPtr->duration() == std::numeric_limits<double>::infinity())
+                               {
+                                       return QColor("dimgrey");
+                               }
+                       }
+               }
+       }
 
        return QVariant();
 }
 
 void CueSheetModel::clearData(void)
 {
+       QMutexLocker lock(&m_mutex);
+       
        beginResetModel();
        while(!m_files.isEmpty()) delete m_files.takeLast();
        endResetModel();
 }
 
 ////////////////////////////////////////////////////////////
+// External API
+////////////////////////////////////////////////////////////
+
+int CueSheetModel::getFileCount(void)
+{
+       QMutexLocker lock(&m_mutex);
+       return m_files.count();
+}
+
+QString CueSheetModel::getFileName(int fileIndex)
+{
+       QMutexLocker lock(&m_mutex);
+       
+       if(fileIndex < 0 || fileIndex >= m_files.count())
+       {
+               return QString();
+       }
+
+       return m_files.at(fileIndex)->fileName();
+}
+
+int CueSheetModel::getTrackCount(int fileIndex)
+{
+       QMutexLocker lock(&m_mutex);
+
+       if(fileIndex < 0 || fileIndex >= m_files.count())
+       {
+               return -1;
+       }
+
+       return m_files.at(fileIndex)->trackCount();
+}
+
+int CueSheetModel::getTrackNo(int fileIndex, int trackIndex)
+{
+       QMutexLocker lock(&m_mutex);
+       
+       if(fileIndex >= 0 && fileIndex < m_files.count())
+       {
+               CueSheetFile *currentFile = m_files.at(fileIndex);
+               if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
+               {
+                       return currentFile->track(trackIndex)->trackNo();
+               }
+       }
+
+       return -1;
+}
+
+void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration)
+{
+       QMutexLocker lock(&m_mutex);
+       
+       *startIndex = std::numeric_limits<double>::quiet_NaN();
+       *duration = std::numeric_limits<double>::quiet_NaN();
+
+       if(fileIndex >= 0 && fileIndex < m_files.count())
+       {
+               CueSheetFile *currentFile = m_files.at(fileIndex);
+               if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
+               {
+                       CueSheetTrack *currentTrack = currentFile->track(trackIndex);
+                       *startIndex = currentTrack->startIndex();
+                       *duration = currentTrack->duration();
+               }
+       }
+}
+
+QString CueSheetModel::getTrackPerformer(int fileIndex, int trackIndex)
+{      
+       QMutexLocker lock(&m_mutex);
+       
+       if(fileIndex >= 0 && fileIndex < m_files.count())
+       {
+               CueSheetFile *currentFile = m_files.at(fileIndex);
+               if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
+               {
+                       CueSheetTrack *currentTrack = currentFile->track(trackIndex);
+                       return currentTrack->performer();
+               }
+       }
+       
+       return QString();
+}
+
+QString CueSheetModel::getTrackTitle(int fileIndex, int trackIndex)
+{
+       QMutexLocker lock(&m_mutex);
+       
+       if(fileIndex >= 0 && fileIndex < m_files.count())
+       {
+               CueSheetFile *currentFile = m_files.at(fileIndex);
+               if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
+               {
+                       CueSheetTrack *currentTrack = currentFile->track(trackIndex);
+                       return currentTrack->title();
+               }
+       }
+       
+       return QString();
+}
+
+QString CueSheetModel::getTrackGenre(int fileIndex, int trackIndex)
+{
+       QMutexLocker lock(&m_mutex);
+       
+       if(fileIndex >= 0 && fileIndex < m_files.count())
+       {
+               CueSheetFile *currentFile = m_files.at(fileIndex);
+               if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
+               {
+                       CueSheetTrack *currentTrack = currentFile->track(trackIndex);
+                       return currentTrack->genre();
+               }
+       }
+       
+       return QString();
+}
+
+unsigned int CueSheetModel::getTrackYear(int fileIndex, int trackIndex)
+{
+       QMutexLocker lock(&m_mutex);
+       
+       if(fileIndex >= 0 && fileIndex < m_files.count())
+       {
+               CueSheetFile *currentFile = m_files.at(fileIndex);
+               if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
+               {
+                       CueSheetTrack *currentTrack = currentFile->track(trackIndex);
+                       return currentTrack->year();
+               }
+       }
+       
+       return 0;
+}
+
+QString CueSheetModel::getAlbumPerformer(void)
+{
+       QMutexLocker lock(&m_mutex);
+       return m_albumPerformer;
+}
+
+QString CueSheetModel::getAlbumTitle(void)
+{
+       QMutexLocker lock(&m_mutex);
+       return m_albumTitle;
+}
+
+QString CueSheetModel::getAlbumGenre(void)
+{
+       QMutexLocker lock(&m_mutex);
+       return m_albumGenre;
+}
+
+unsigned int CueSheetModel::getAlbumYear(void)
+{
+       QMutexLocker lock(&m_mutex);
+       return m_albumYear;
+}
+////////////////////////////////////////////////////////////
 // Cue Sheet Parser
 ////////////////////////////////////////////////////////////
 
-int CueSheetModel::loadCueSheet(const QString &cueFileName, QCoreApplication *application)
+int CueSheetModel::loadCueSheet(const QString &cueFileName, QCoreApplication *application, QTextCodec *forceCodec)
 {
+       QMutexLocker lock(&m_mutex);
+       const QTextCodec *codec = (forceCodec != NULL) ? forceCodec : QTextCodec::codecForName("System");
+       
        QFile cueFile(cueFileName);
        if(!cueFile.open(QIODevice::ReadOnly))
        {
-               return 1;
+               return ErrorIOFailure;
        }
 
        clearData();
 
        beginResetModel();
-       int iResult = parseCueFile(cueFile, application);
+       int iResult = parseCueFile(cueFile, QDir(QFileInfo(cueFile).canonicalPath()), application, codec);
        endResetModel();
 
        return iResult;
 }
 
-int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
+int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplication *application, const QTextCodec *codec)
 {
-       cueFile.seek(0);
+       cueFile.reset();
        qDebug("\n[Cue Sheet Import]");
+       bool bForceLatin1 = false;
 
        //Reject very large files, as parsing might take until forever
        if(cueFile.size() >= 10485760i64)
@@ -292,17 +561,31 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                return 2;
        }
 
-       //Check for UTF-8 BOM in order to guess encoding
-       QByteArray bomCheck = cueFile.peek(128);
-       bool bUTF8 = bomCheck.contains("\xef\xbb\xbf");
-       qDebug("Encoding is %s.", (bUTF8 ? "UTF-8" : "Local 8-Bit"));
-       bomCheck.clear();
+       //Test selected Codepage for decoding errors
+       qDebug("Character encoding is: %s.", codec->name().constData());
+       const QString replacementSymbol = QString(QChar(QChar::ReplacementCharacter));
+       QByteArray testData = cueFile.peek(1048576);
+       if((!testData.isEmpty()) && codec->toUnicode(testData.constData(), testData.size()).contains(replacementSymbol))
+       {
+               qWarning("Decoding error using selected codepage (%s). Enforcing Latin-1.", codec->name().constData());
+               bForceLatin1 = true;
+       }
+       testData.clear();
+
+       //Init text stream
+       QTextStream cueStream(&cueFile);
+       cueStream.setAutoDetectUnicode(false);
+       cueStream.setCodec(bForceLatin1 ? "latin1" : codec->name());
+       cueStream.seek(0i64);
 
-       QRegExp rxFile("^FILE\\s+\"([^\"]+)\"\\s+(\\w+)$", Qt::CaseInsensitive);
+       //Create regular expressions
+       QRegExp rxFile("^FILE\\s+(\"[^\"]+\"|\\S+)\\s+(\\w+)$", Qt::CaseInsensitive);
        QRegExp rxTrack("^TRACK\\s+(\\d+)\\s(\\w+)$", Qt::CaseInsensitive);
        QRegExp rxIndex("^INDEX\\s+(\\d+)\\s+([0-9:]+)$", Qt::CaseInsensitive);
-       QRegExp rxTitle("^TITLE\\s+\"([^\"]+)\"$", Qt::CaseInsensitive);
-       QRegExp rxPerformer("^PERFORMER\\s+\"([^\"]+)\"$", Qt::CaseInsensitive);
+       QRegExp rxTitle("^TITLE\\s+(\"[^\"]+\"|\\S+)$", Qt::CaseInsensitive);
+       QRegExp rxPerformer("^PERFORMER\\s+(\"[^\"]+\"|\\S+)$", Qt::CaseInsensitive);
+       QRegExp rxGenre("^REM\\s+GENRE\\s+(\"[^\"]+\"|\\S+)$", Qt::CaseInsensitive);
+       QRegExp rxYear("^REM\\s+DATE\\s+(\\d+)$", Qt::CaseInsensitive);
        
        bool bPreamble = true;
        bool bUnsupportedTrack = false;
@@ -310,45 +593,38 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
        CueSheetFile *currentFile = NULL;
        CueSheetTrack *currentTrack = NULL;
 
-       QString albumTitle;
-       QString albumPerformer;
+       m_albumTitle.clear();
+       m_albumPerformer.clear();
+       m_albumGenre.clear();
+       m_albumYear = 0;
 
        //Loop over the Cue Sheet until all lines were processed
-       while(true)
+       for(int lines = 0; lines < INT_MAX; lines++)
        {
                if(application)
                {
                        application->processEvents();
-                       Sleep(25);
+                       if(lines < 128) Sleep(10);
                }
                
-               QByteArray lineData = cueFile.readLine();
-               if(lineData.size() <= 0)
+               if(cueStream.atEnd())
                {
                        qDebug("End of Cue Sheet file.");
                        break;
                }
 
-               QString line = bUTF8 ? QString::fromUtf8(lineData.constData(), lineData.size()).trimmed() : QString::fromLocal8Bit(lineData.constData(), lineData.size()).trimmed();
+               QString line = cueStream.readLine().trimmed();
                
                /* --- FILE --- */
                if(rxFile.indexIn(line) >= 0)
                {
-                       qDebug("File: <%s> <%s>", rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData());
+                       qDebug("%03d File: <%s> <%s>", lines, rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData());
                        if(currentFile)
                        {
                                if(currentTrack)
                                {
                                        if(currentTrack->isValid())
                                        {
-                                               if(currentTrack->title().isEmpty() && !albumTitle.isEmpty())
-                                               {
-                                                       currentTrack->setTitle(albumTitle);
-                                               }
-                                               if(currentTrack->performer().isEmpty() && !albumPerformer.isEmpty())
-                                               {
-                                                       currentTrack->setPerformer(albumPerformer);
-                                               }
                                                currentFile->addTrack(currentTrack);
                                                currentTrack = NULL;
                                        }
@@ -357,7 +633,7 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                                                LAMEXP_DELETE(currentTrack);
                                        }
                                }
-                               if(currentFile->trackCount() > 0)
+                               if(currentFile->isValid())
                                {
                                        m_files.append(currentFile);
                                        currentFile = NULL;
@@ -373,12 +649,13 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                        }
                        if(!rxFile.cap(2).compare("WAVE", Qt::CaseInsensitive) || !rxFile.cap(2).compare("MP3", Qt::CaseInsensitive) || !rxFile.cap(2).compare("AIFF", Qt::CaseInsensitive))
                        {
-                               currentFile = new CueSheetFile(rxFile.cap(1));
+                               currentFile = new CueSheetFile(baseDir.absoluteFilePath(UNQUOTE(rxFile.cap(1))));
+                               qDebug("%03d File path: <%s>", lines, currentFile->fileName().toUtf8().constData());
                        }
                        else
                        {
                                bUnsupportedTrack = true;
-                               qWarning("Skipping unsupported file of type '%s'.", rxFile.cap(2).toUtf8().constData());
+                               qWarning("%03d Skipping unsupported file of type '%s'.", lines, rxFile.cap(2).toUtf8().constData());
                                currentFile = NULL;
                        }
                        bPreamble = false;
@@ -391,19 +668,11 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                {
                        if(currentFile)
                        {
-                               qDebug("  Track: <%s> <%s>", rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData());
+                               qDebug("%03d   Track: <%s> <%s>", lines, rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData());
                                if(currentTrack)
                                {
                                        if(currentTrack->isValid())
                                        {
-                                               if(currentTrack->title().isEmpty() && !albumTitle.isEmpty())
-                                               {
-                                                       currentTrack->setTitle(albumTitle);
-                                               }
-                                               if(currentTrack->performer().isEmpty() && !albumPerformer.isEmpty())
-                                               {
-                                                       currentTrack->setPerformer(albumPerformer);
-                                               }
                                                currentFile->addTrack(currentTrack);
                                                currentTrack = NULL;
                                        }
@@ -419,7 +688,7 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                                else
                                {
                                        bUnsupportedTrack = true;
-                                       qWarning("  Skipping unsupported track of type '%s'.", rxTrack.cap(2).toUtf8().constData());
+                                       qWarning("%03d   Skipping unsupported track of type '%s'.", lines, rxTrack.cap(2).toUtf8().constData());
                                        currentTrack = NULL;
                                }
                        }
@@ -436,7 +705,7 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                {
                        if(currentFile && currentTrack)
                        {
-                               qDebug("    Index: <%s> <%s>", rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData());
+                               qDebug("%03d     Index: <%s> <%s>", lines, rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData());
                                if(rxIndex.cap(1).toInt() == 1)
                                {
                                        currentTrack->setStartIndex(parseTimeIndex(rxIndex.cap(2)));
@@ -450,12 +719,12 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                {
                        if(bPreamble)
                        {
-                               albumTitle = rxTitle.cap(1);
+                               m_albumTitle = UNQUOTE(rxTitle.cap(1)).simplified();
                        }
                        else if(currentFile && currentTrack)
                        {
-                               qDebug("    Title: <%s>", rxTitle.cap(1).toUtf8().constData());
-                               currentTrack->setTitle(rxTitle.cap(1));
+                               qDebug("%03d     Title: <%s>", lines, rxTitle.cap(1).toUtf8().constData());
+                               currentTrack->setTitle(UNQUOTE(rxTitle.cap(1)).simplified());
                        }
                        continue;
                }
@@ -465,32 +734,74 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                {
                        if(bPreamble)
                        {
-                               albumPerformer = rxPerformer.cap(1);
+                               m_albumPerformer = UNQUOTE(rxPerformer.cap(1)).simplified();
+                       }
+                       else if(currentFile && currentTrack)
+                       {
+                               qDebug("%03d     Title: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
+                               currentTrack->setPerformer(UNQUOTE(rxPerformer.cap(1)).simplified());
+                       }
+                       continue;
+               }
+
+               /* --- GENRE --- */
+               if(rxGenre.indexIn(line) >= 0)
+               {
+                       if(bPreamble)
+                       {
+                               QString temp = UNQUOTE(rxGenre.cap(1)).simplified();
+                               for(int i = 0; g_lamexp_generes[i]; i++)
+                               {
+                                       if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
+                                       {
+                                               m_albumGenre = QString(g_lamexp_generes[i]);
+                                               break;
+                                       }
+                               }
+                       }
+                       else if(currentFile && currentTrack)
+                       {
+                               qDebug("%03d     Genre: <%s>", lines, rxGenre.cap(1).toUtf8().constData());
+                               QString temp = UNQUOTE(rxGenre.cap(1).simplified());
+                               for(int i = 0; g_lamexp_generes[i]; i++)
+                               {
+                                       if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
+                                       {
+                                               currentTrack->setGenre(QString(g_lamexp_generes[i]));
+                                               break;
+                                       }
+                               }
+                       }
+                       continue;
+               }
+
+               /* --- YEAR --- */
+               if(rxYear.indexIn(line) >= 0)
+               {
+                       if(bPreamble)
+                       {
+                               bool ok = false;
+                               unsigned int temp = rxYear.cap(1).toUInt(&ok);
+                               if(ok) m_albumYear =  temp;
                        }
                        else if(currentFile && currentTrack)
                        {
-                               qDebug("    Title: <%s>", rxPerformer.cap(1).toUtf8().constData());
-                               currentTrack->setPerformer(rxPerformer.cap(1));
+                               qDebug("%03d     Year: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
+                               bool ok = false;
+                               unsigned int temp = rxYear.cap(1).toUInt(&ok);
+                               if(ok) currentTrack->setYear(temp);
                        }
                        continue;
                }
        }
 
-       //Finally append the very last track/file
+       //Append the very last track/file that is still pending
        if(currentFile)
        {
                if(currentTrack)
                {
                        if(currentTrack->isValid())
                        {
-                               if(currentTrack->title().isEmpty() && !albumTitle.isEmpty())
-                               {
-                                       currentTrack->setTitle(albumTitle);
-                               }
-                               if(currentTrack->performer().isEmpty() && !albumPerformer.isEmpty())
-                               {
-                                       currentTrack->setPerformer(albumPerformer);
-                               }
                                currentFile->addTrack(currentTrack);
                                currentTrack = NULL;
                        }
@@ -499,7 +810,7 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                                LAMEXP_DELETE(currentTrack);
                        }
                }
-               if(currentFile->trackCount() > 0)
+               if(currentFile->isValid())
                {
                        m_files.append(currentFile);
                        currentFile = NULL;
@@ -510,7 +821,90 @@ int CueSheetModel::parseCueFile(QFile &cueFile, QCoreApplication *application)
                }
        }
 
-       return (m_files.count() > 0) ? 0 : (bUnsupportedTrack ? 3 : 2);
+       //Finally calculate duration of each track
+       int nFiles = m_files.count();
+       for(int i = 0; i < nFiles; i++)
+       {
+               if(application)
+               {
+                       application->processEvents();
+                       Sleep(10);
+               }
+
+               CueSheetFile *currentFile = m_files.at(i);
+               int nTracks = currentFile->trackCount();
+               if(nTracks > 1)
+               {
+                       for(int j = 1; j < nTracks; j++)
+                       {
+                               CueSheetTrack *currentTrack = currentFile->track(j);
+                               CueSheetTrack *previousTrack = currentFile->track(j-1);
+                               double duration = currentTrack->startIndex() - previousTrack->startIndex();
+                               previousTrack->setDuration(qMax(0.0, duration));
+                       }
+               }
+       }
+       
+       //Sanity check of track numbers
+       if(nFiles > 0)
+       {
+               bool hasTracks = false;
+               int previousTrackNo = -1;
+               bool trackNo[100];
+               for(int i = 0; i < 100; i++)
+               {
+                       trackNo[i] = false;
+               }
+
+               for(int i = 0; i < nFiles; i++)
+               {
+                       if(application)
+                       {
+                               application->processEvents();
+                               Sleep(10);
+                       }
+                       CueSheetFile *currentFile = m_files.at(i);
+                       int nTracks = currentFile->trackCount();
+                       if(nTracks > 1)
+                       {
+                               for(int j = 0; j < nTracks; j++)
+                               {
+                                       int currentTrackNo = currentFile->track(j)->trackNo();
+                                       if(currentTrackNo > 99)
+                                       {
+                                               qWarning("Track #%02d is invalid (maximum is 99), Cue Sheet is inconsistent!", currentTrackNo);
+                                               return ErrorInconsistent;
+                                       }
+                                       if(currentTrackNo <= previousTrackNo)
+                                       {
+                                               qWarning("Non-increasing track numbers (%02d -> %02d), Cue Sheet is inconsistent!", previousTrackNo, currentTrackNo);
+                                               return ErrorInconsistent;
+                                       }
+                                       if(trackNo[currentTrackNo])
+                                       {
+                                               qWarning("Track #%02d exists multiple times, Cue Sheet is inconsistent!", currentTrackNo);
+                                               return ErrorInconsistent;
+                                       }
+                                       trackNo[currentTrackNo] = true;
+                                       previousTrackNo = currentTrackNo;
+                                       hasTracks = true;
+                               }
+                       }
+               }
+               
+               if(!hasTracks)
+               {
+                       qWarning("Could not find at least one valid track in the Cue Sheet!");
+                       return ErrorInconsistent;
+               }
+
+               return ErrorSuccess;
+       }
+       else
+       {
+               qWarning("Could not find at least one valid input file in the Cue Sheet!");
+               return bUnsupportedTrack ? ErrorUnsupported : ErrorBadFile;
+       }
 }
 
 double CueSheetModel::parseTimeIndex(const QString &index)
@@ -528,7 +922,7 @@ double CueSheetModel::parseTimeIndex(const QString &index)
 
                if(minOK && secOK && frmOK)
                {
-                       return static_cast<double>(60 * min) + static_cast<double>(sec) + ((1.0/75.0) * static_cast<double>(frm));
+                       return static_cast<double>(60 * min) + static_cast<double>(sec) + (static_cast<double>(frm) / 75.0);
                }
        }
 
@@ -538,10 +932,19 @@ double CueSheetModel::parseTimeIndex(const QString &index)
 
 QString CueSheetModel::indexToString(const double index) const
 {
-       int temp = static_cast<int>(index * 100.0);
-
-       int msec = temp % 100;
-       int secs = temp / 100;
+       if(!_finite(index) || (index < 0.0) || (index > 86400.0))
+       {
+               return QString("??:??.???");
+       }
+       
+       QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
 
-       return QString().sprintf("%02d:%02d.%02d", (secs / 60), (secs % 60), msec);
+       if(time.minute() < 100)
+       {
+               return time.toString("mm:ss.zzz");
+       }
+       else
+       {
+               return QString("99:99.999");
+       }
 }