OSDN Git Service

88c8bd9450d1b2f472a9527cdc9938bde2be4795
[lamexp/LameXP.git] / src / Model_CueSheet.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2011 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.
14 //
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.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Global.h"
23 #include "Model_CueSheet.h"
24 #include "Genres.h"
25
26 #include <QApplication>
27 #include <QDir>
28 #include <QFileInfo>
29 #include <QFont>
30 #include <QTime>
31
32 #include <float.h>
33 #include <limits>
34
35 #define UNQUOTE(STR) STR.split("\"",  QString::SkipEmptyParts).first().trimmed()
36
37 ////////////////////////////////////////////////////////////
38 // Helper Classes
39 ////////////////////////////////////////////////////////////
40
41 class CueSheetItem
42 {
43 public:
44         virtual const char* type(void) = 0;
45         virtual bool isValid(void) { return false; }
46 };
47
48 class CueSheetTrack : public CueSheetItem
49 {
50 public:
51         CueSheetTrack(CueSheetFile *parent, int trackNo)
52         :
53                 m_parent(parent),
54                 m_trackNo(trackNo)
55         {
56                 m_startIndex = std::numeric_limits<double>::quiet_NaN();
57                 m_duration = std::numeric_limits<double>::infinity();
58                 m_year = 0;
59         }
60         int trackNo(void) { return m_trackNo; }
61         double startIndex(void) { return m_startIndex; }
62         double duration(void) { return m_duration; }
63         QString title(void) { return m_title; }
64         QString performer(void) { return m_performer; }
65         QString genre(void) { return m_genre; }
66         unsigned int year(void) { return m_year; }
67         CueSheetFile *parent(void) { return m_parent; }
68         void setStartIndex(double startIndex) { m_startIndex = startIndex; }
69         void setDuration(double duration) { m_duration = duration; }
70         void setTitle(const QString &title, bool update = false) { if(!update || (m_title.isEmpty() && !title.isEmpty())) m_title = title; }
71         void setPerformer(const QString &performer, bool update = false) { if(!update || (m_performer.isEmpty() && !performer.isEmpty())) m_performer = performer; }
72         void setGenre(const QString &genre, bool update = false) { if(!update || (m_genre.isEmpty() && !m_genre.isEmpty())) m_genre = genre; }
73         void setYear(const unsigned int year, bool update = false) { if(!update || (year == 0)) m_year = year; }
74         virtual bool isValid(void) { return !(_isnan(m_startIndex) || (m_trackNo < 0)); }
75         virtual const char* type(void) { return "CueSheetTrack"; }
76 private:
77         int m_trackNo;
78         double m_startIndex;
79         double m_duration;
80         QString m_title;
81         QString m_performer;
82         QString m_genre;
83         unsigned int m_year;
84         CueSheetFile *m_parent;
85 };
86
87 class CueSheetFile : public CueSheetItem
88 {
89 public:
90         CueSheetFile(const QString &fileName) : m_fileName(fileName) {}
91         ~CueSheetFile(void) { while(!m_tracks.isEmpty()) delete m_tracks.takeLast(); }
92         QString fileName(void) { return m_fileName; }
93         void addTrack(CueSheetTrack *track) { m_tracks.append(track); }
94         void clearTracks(void) { while(!m_tracks.isEmpty()) delete m_tracks.takeLast(); }
95         CueSheetTrack *track(int index) { return m_tracks.at(index); }
96         int trackCount(void) { return m_tracks.count(); }
97         virtual bool isValid(void) { return m_tracks.count() > 0; }
98         virtual const char* type(void) { return "CueSheetFile"; }
99 private:
100         const QString m_fileName;
101         QList<CueSheetTrack*> m_tracks;
102 };
103
104 ////////////////////////////////////////////////////////////
105 // Constructor & Destructor
106 ////////////////////////////////////////////////////////////
107
108 QMutex CueSheetModel::m_mutex(QMutex::Recursive);
109
110 CueSheetModel::CueSheetModel()
111 :
112         m_fileIcon(":/icons/music.png"),
113         m_trackIcon(":/icons/control_play_blue.png")
114 {
115         int trackNo = 0;
116         m_albumYear = 0;
117         
118         for(int i = 0; i < 5; i++)
119         {
120                 CueSheetFile *currentFile = new CueSheetFile(QString().sprintf("File %02d.wav", i+1));
121                 for(int j = 0; j < 8; j++)
122                 {
123                         CueSheetTrack *currentTrack = new CueSheetTrack(currentFile, trackNo++);
124                         currentTrack->setTitle("ATWA (Air Trees Water Animals)");
125                         currentTrack->setPerformer("System of a Down");
126                         currentFile->addTrack(currentTrack);
127                 }
128                 m_files.append(currentFile);
129         }
130 }
131
132 CueSheetModel::~CueSheetModel(void)
133 {
134         while(!m_files.isEmpty()) delete m_files.takeLast();
135 }
136
137 ////////////////////////////////////////////////////////////
138 // Model Functions
139 ////////////////////////////////////////////////////////////
140
141 QModelIndex CueSheetModel::index(int row, int column, const QModelIndex &parent) const
142 {
143         QMutexLocker lock(&m_mutex);
144         
145         if(!parent.isValid())
146         {
147                 return (row < m_files.count()) ? createIndex(row, column, m_files.at(row)) : QModelIndex();
148         }
149
150         CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
151         if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
152         {
153                 return (row < filePtr->trackCount()) ? createIndex(row, column, filePtr->track(row)) : QModelIndex();
154         }
155
156         return QModelIndex();
157 }
158
159 int CueSheetModel::columnCount(const QModelIndex &parent) const
160 {
161         QMutexLocker lock(&m_mutex);
162         return 4;
163 }
164
165 int CueSheetModel::rowCount(const QModelIndex &parent) const
166 {
167         QMutexLocker lock(&m_mutex);
168
169         if(!parent.isValid())
170         {
171                 return m_files.count();
172         }
173
174         CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
175         if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
176         {
177                 return filePtr->trackCount();
178         }
179
180         return 0;
181 }
182
183 QModelIndex CueSheetModel::parent(const QModelIndex &child) const
184 {
185         QMutexLocker lock(&m_mutex);
186         
187         if(child.isValid())
188         {
189                 CueSheetItem *childItem = static_cast<CueSheetItem*>(child.internalPointer());
190                 if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(childItem))
191                 {
192                         return createIndex(m_files.indexOf(trackPtr->parent()), 0, trackPtr->parent());
193                 }
194         }
195
196         return QModelIndex();
197 }
198
199 QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, int role) const
200 {
201         QMutexLocker lock(&m_mutex);
202         
203         if(role == Qt::DisplayRole)
204         {
205                 switch(section)
206                 {
207                 case 0:
208                         return tr("No.");
209                         break;
210                 case 1:
211                         return tr("File / Track");
212                         break;
213                 case 2:
214                         return tr("Index");
215                         break;
216                 case 3:
217                         return tr("Duration");
218                         break;
219                 default:
220                         return QVariant();
221                         break;
222                 }
223         }
224         else
225         {
226                 return QVariant();
227         }
228 }
229
230 QVariant CueSheetModel::data(const QModelIndex &index, int role) const
231 {
232         QMutexLocker lock(&m_mutex);
233
234         if(role == Qt::DisplayRole)
235         {
236                 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
237
238                 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
239                 {
240                         switch(index.column())
241                         {
242                         case 0:
243                                 return tr("File %1").arg(QString().sprintf("%02d", index.row() + 1)).append(" ");
244                                 break;
245                         case 1:
246                                 return QFileInfo(filePtr->fileName()).fileName();
247                                 break;
248                         default:
249                                 return QVariant();
250                                 break;
251                         }
252                 }
253                 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
254                 {
255                         switch(index.column())
256                         {
257                         case 0:
258                                 return tr("Track %1").arg(QString().sprintf("%02d", trackPtr->trackNo())).append(" ");
259                                 break;
260                         case 1:
261                                 if(!trackPtr->title().isEmpty() && !trackPtr->performer().isEmpty())
262                                 {
263                                         return QString("%1 - %2").arg(trackPtr->performer(), trackPtr->title());
264                                 }
265                                 else if(!trackPtr->title().isEmpty())
266                                 {
267                                         return QString("%1 - %2").arg(tr("Unknown Artist"), trackPtr->title());
268                                 }
269                                 else if(!trackPtr->performer().isEmpty())
270                                 {
271                                         return QString("%1 - %2").arg(trackPtr->performer(), tr("Unknown Title"));
272                                 }
273                                 else
274                                 {
275                                         return QString("%1 - %2").arg(tr("Unknown Artist"), tr("Unknown Title"));
276                                 }
277                                 break;
278                         case 2:
279                                 return indexToString(trackPtr->startIndex());
280                                 break;
281                         case 3:
282                                 return indexToString(trackPtr->duration());
283                                 break;
284                         default:
285                                 return QVariant();
286                                 break;
287                         }
288                 }
289         }
290         else if(role == Qt::ToolTipRole)
291         {
292                 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
293
294                 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
295                 {
296                         return QDir::toNativeSeparators(filePtr->fileName());
297                 }
298                 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
299                 {
300                         return QDir::toNativeSeparators(trackPtr->parent()->fileName());
301                 }
302         }
303         else if(role == Qt::DecorationRole)
304         {
305                 if(index.column() == 0)
306                 {
307                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
308
309                         if(dynamic_cast<CueSheetFile*>(item))
310                         {
311                                 return m_fileIcon;
312                         }
313                         else if(dynamic_cast<CueSheetTrack*>(item))
314                         {
315                                 return m_trackIcon;
316                         }
317                 }
318         }
319         else if(role == Qt::FontRole)
320         {
321                 QFont font("Monospace");
322                 font.setStyleHint(QFont::TypeWriter);
323                 if((index.column() == 1))
324                 {
325                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
326                         font.setBold(dynamic_cast<CueSheetFile*>(item) != NULL);
327                 }
328                 return font;
329         }
330         else if(role == Qt::ForegroundRole)
331         {
332                 if((index.column() == 1))
333                 {
334                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
335                         if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
336                         {
337                                 return (QFileInfo(filePtr->fileName()).size() > 4) ? QColor("mediumblue") : QColor("darkred");
338                         }
339                 }
340                 else if((index.column() == 3))
341                 {
342                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
343                         if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
344                         {
345                                 if(trackPtr->duration() == std::numeric_limits<double>::infinity())
346                                 {
347                                         return QColor("dimgrey");
348                                 }
349                         }
350                 }
351         }
352
353         return QVariant();
354 }
355
356 void CueSheetModel::clearData(void)
357 {
358         QMutexLocker lock(&m_mutex);
359         
360         beginResetModel();
361         while(!m_files.isEmpty()) delete m_files.takeLast();
362         endResetModel();
363 }
364
365 ////////////////////////////////////////////////////////////
366 // External API
367 ////////////////////////////////////////////////////////////
368
369 int CueSheetModel::getFileCount(void)
370 {
371         QMutexLocker lock(&m_mutex);
372         return m_files.count();
373 }
374
375 QString CueSheetModel::getFileName(int fileIndex)
376 {
377         QMutexLocker lock(&m_mutex);
378         
379         if(fileIndex < 0 || fileIndex >= m_files.count())
380         {
381                 return QString();
382         }
383
384         return m_files.at(fileIndex)->fileName();
385 }
386
387 int CueSheetModel::getTrackCount(int fileIndex)
388 {
389         QMutexLocker lock(&m_mutex);
390
391         if(fileIndex < 0 || fileIndex >= m_files.count())
392         {
393                 return -1;
394         }
395
396         return m_files.at(fileIndex)->trackCount();
397 }
398
399 int CueSheetModel::getTrackNo(int fileIndex, int trackIndex)
400 {
401         QMutexLocker lock(&m_mutex);
402         
403         if(fileIndex >= 0 && fileIndex < m_files.count())
404         {
405                 CueSheetFile *currentFile = m_files.at(fileIndex);
406                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
407                 {
408                         return currentFile->track(trackIndex)->trackNo();
409                 }
410         }
411
412         return -1;
413 }
414
415 void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration)
416 {
417         QMutexLocker lock(&m_mutex);
418         
419         *startIndex = std::numeric_limits<double>::quiet_NaN();
420         *duration = std::numeric_limits<double>::quiet_NaN();
421
422         if(fileIndex >= 0 && fileIndex < m_files.count())
423         {
424                 CueSheetFile *currentFile = m_files.at(fileIndex);
425                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
426                 {
427                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
428                         *startIndex = currentTrack->startIndex();
429                         *duration = currentTrack->duration();
430                 }
431         }
432 }
433
434 QString CueSheetModel::getTrackPerformer(int fileIndex, int trackIndex)
435 {       
436         QMutexLocker lock(&m_mutex);
437         
438         if(fileIndex >= 0 && fileIndex < m_files.count())
439         {
440                 CueSheetFile *currentFile = m_files.at(fileIndex);
441                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
442                 {
443                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
444                         return currentTrack->performer();
445                 }
446         }
447         
448         return QString();
449 }
450
451 QString CueSheetModel::getTrackTitle(int fileIndex, int trackIndex)
452 {
453         QMutexLocker lock(&m_mutex);
454         
455         if(fileIndex >= 0 && fileIndex < m_files.count())
456         {
457                 CueSheetFile *currentFile = m_files.at(fileIndex);
458                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
459                 {
460                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
461                         return currentTrack->title();
462                 }
463         }
464         
465         return QString();
466 }
467
468 QString CueSheetModel::getTrackGenre(int fileIndex, int trackIndex)
469 {
470         QMutexLocker lock(&m_mutex);
471         
472         if(fileIndex >= 0 && fileIndex < m_files.count())
473         {
474                 CueSheetFile *currentFile = m_files.at(fileIndex);
475                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
476                 {
477                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
478                         return currentTrack->genre();
479                 }
480         }
481         
482         return QString();
483 }
484
485 unsigned int CueSheetModel::getTrackYear(int fileIndex, int trackIndex)
486 {
487         QMutexLocker lock(&m_mutex);
488         
489         if(fileIndex >= 0 && fileIndex < m_files.count())
490         {
491                 CueSheetFile *currentFile = m_files.at(fileIndex);
492                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
493                 {
494                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
495                         return currentTrack->year();
496                 }
497         }
498         
499         return 0;
500 }
501
502 QString CueSheetModel::getAlbumPerformer(void)
503 {
504         QMutexLocker lock(&m_mutex);
505         return m_albumPerformer;
506 }
507
508 QString CueSheetModel::getAlbumTitle(void)
509 {
510         QMutexLocker lock(&m_mutex);
511         return m_albumTitle;
512 }
513
514 QString CueSheetModel::getAlbumGenre(void)
515 {
516         QMutexLocker lock(&m_mutex);
517         return m_albumGenre;
518 }
519
520 unsigned int CueSheetModel::getAlbumYear(void)
521 {
522         QMutexLocker lock(&m_mutex);
523         return m_albumYear;
524 }
525 ////////////////////////////////////////////////////////////
526 // Cue Sheet Parser
527 ////////////////////////////////////////////////////////////
528
529 int CueSheetModel::loadCueSheet(const QString &cueFileName, QCoreApplication *application)
530 {
531         QMutexLocker lock(&m_mutex);
532         
533         QFile cueFile(cueFileName);
534         if(!cueFile.open(QIODevice::ReadOnly))
535         {
536                 return ErrorIOFailure;
537         }
538
539         clearData();
540
541         beginResetModel();
542         int iResult = parseCueFile(cueFile, QDir(QFileInfo(cueFile).canonicalPath()), application);
543         endResetModel();
544
545         return iResult;
546 }
547
548 int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplication *application)
549 {
550         cueFile.seek(0);
551         qDebug("\n[Cue Sheet Import]");
552
553         //Reject very large files, as parsing might take until forever
554         if(cueFile.size() >= 10485760i64)
555         {
556                 qWarning("File is very big. Probably not a Cue Sheet. Rejecting...");
557                 return 2;
558         }
559
560         //Check for UTF-8 BOM in order to guess encoding
561         QByteArray bomCheck = cueFile.peek(128);
562         bool bUTF8 = bomCheck.contains("\xef\xbb\xbf");
563         qDebug("Encoding is %s.", (bUTF8 ? "UTF-8" : "Local 8-Bit"));
564         bomCheck.clear();
565
566         QRegExp rxFile("^FILE\\s+(\"[^\"]+\"|\\S+)\\s+(\\w+)$", Qt::CaseInsensitive);
567         QRegExp rxTrack("^TRACK\\s+(\\d+)\\s(\\w+)$", Qt::CaseInsensitive);
568         QRegExp rxIndex("^INDEX\\s+(\\d+)\\s+([0-9:]+)$", Qt::CaseInsensitive);
569         QRegExp rxTitle("^TITLE\\s+(\"[^\"]+\"|\\S+)$", Qt::CaseInsensitive);
570         QRegExp rxPerformer("^PERFORMER\\s+(\"[^\"]+\"|\\S+)$", Qt::CaseInsensitive);
571         QRegExp rxGenre("^REM\\s+GENRE\\s+(\"[^\"]+\"|\\S+)$", Qt::CaseInsensitive);
572         QRegExp rxYear("^REM\\s+DATE\\s+(\\d+)$", Qt::CaseInsensitive);
573         
574         bool bPreamble = true;
575         bool bUnsupportedTrack = false;
576
577         CueSheetFile *currentFile = NULL;
578         CueSheetTrack *currentTrack = NULL;
579
580         m_albumTitle.clear();
581         m_albumPerformer.clear();
582         m_albumGenre.clear();
583         m_albumYear = 0;
584
585         //Loop over the Cue Sheet until all lines were processed
586         for(int lines = 0; lines < INT_MAX; lines++)
587         {
588                 if(application)
589                 {
590                         application->processEvents();
591                         if(lines < 128) Sleep(10);
592                 }
593                 
594                 QByteArray lineData = cueFile.readLine();
595                 if(lineData.size() <= 0)
596                 {
597                         qDebug("End of Cue Sheet file.");
598                         break;
599                 }
600
601                 QString line = bUTF8 ? QString::fromUtf8(lineData.constData(), lineData.size()).trimmed() : QString::fromLocal8Bit(lineData.constData(), lineData.size()).trimmed();
602                 
603                 /* --- FILE --- */
604                 if(rxFile.indexIn(line) >= 0)
605                 {
606                         qDebug("%03d File: <%s> <%s>", lines, rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData());
607                         if(currentFile)
608                         {
609                                 if(currentTrack)
610                                 {
611                                         if(currentTrack->isValid())
612                                         {
613                                                 currentFile->addTrack(currentTrack);
614                                                 currentTrack = NULL;
615                                         }
616                                         else
617                                         {
618                                                 LAMEXP_DELETE(currentTrack);
619                                         }
620                                 }
621                                 if(currentFile->isValid())
622                                 {
623                                         m_files.append(currentFile);
624                                         currentFile = NULL;
625                                 }
626                                 else
627                                 {
628                                         LAMEXP_DELETE(currentFile);
629                                 }
630                         }
631                         else
632                         {
633                                 LAMEXP_DELETE(currentTrack);
634                         }
635                         if(!rxFile.cap(2).compare("WAVE", Qt::CaseInsensitive) || !rxFile.cap(2).compare("MP3", Qt::CaseInsensitive) || !rxFile.cap(2).compare("AIFF", Qt::CaseInsensitive))
636                         {
637                                 currentFile = new CueSheetFile(baseDir.absoluteFilePath(UNQUOTE(rxFile.cap(1))));
638                                 qDebug("%03d File path: <%s>", lines, currentFile->fileName().toUtf8().constData());
639                         }
640                         else
641                         {
642                                 bUnsupportedTrack = true;
643                                 qWarning("%03d Skipping unsupported file of type '%s'.", lines, rxFile.cap(2).toUtf8().constData());
644                                 currentFile = NULL;
645                         }
646                         bPreamble = false;
647                         currentTrack = NULL;
648                         continue;
649                 }
650                 
651                 /* --- TRACK --- */
652                 if(rxTrack.indexIn(line) >= 0)
653                 {
654                         if(currentFile)
655                         {
656                                 qDebug("%03d   Track: <%s> <%s>", lines, rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData());
657                                 if(currentTrack)
658                                 {
659                                         if(currentTrack->isValid())
660                                         {
661                                                 currentFile->addTrack(currentTrack);
662                                                 currentTrack = NULL;
663                                         }
664                                         else
665                                         {
666                                                 LAMEXP_DELETE(currentTrack);
667                                         }
668                                 }
669                                 if(!rxTrack.cap(2).compare("AUDIO", Qt::CaseInsensitive))
670                                 {
671                                         currentTrack = new CueSheetTrack(currentFile, rxTrack.cap(1).toInt());
672                                 }
673                                 else
674                                 {
675                                         bUnsupportedTrack = true;
676                                         qWarning("%03d   Skipping unsupported track of type '%s'.", lines, rxTrack.cap(2).toUtf8().constData());
677                                         currentTrack = NULL;
678                                 }
679                         }
680                         else
681                         {
682                                 LAMEXP_DELETE(currentTrack);
683                         }
684                         bPreamble = false;
685                         continue;
686                 }
687                 
688                 /* --- INDEX --- */
689                 if(rxIndex.indexIn(line) >= 0)
690                 {
691                         if(currentFile && currentTrack)
692                         {
693                                 qDebug("%03d     Index: <%s> <%s>", lines, rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData());
694                                 if(rxIndex.cap(1).toInt() == 1)
695                                 {
696                                         currentTrack->setStartIndex(parseTimeIndex(rxIndex.cap(2)));
697                                 }
698                         }
699                         continue;
700                 }
701
702                 /* --- TITLE --- */
703                 if(rxTitle.indexIn(line) >= 0)
704                 {
705                         if(bPreamble)
706                         {
707                                 m_albumTitle = UNQUOTE(rxTitle.cap(1)).simplified();
708                         }
709                         else if(currentFile && currentTrack)
710                         {
711                                 qDebug("%03d     Title: <%s>", lines, rxTitle.cap(1).toUtf8().constData());
712                                 currentTrack->setTitle(UNQUOTE(rxTitle.cap(1)).simplified());
713                         }
714                         continue;
715                 }
716
717                 /* --- PERFORMER --- */
718                 if(rxPerformer.indexIn(line) >= 0)
719                 {
720                         if(bPreamble)
721                         {
722                                 m_albumPerformer = UNQUOTE(rxPerformer.cap(1)).simplified();
723                         }
724                         else if(currentFile && currentTrack)
725                         {
726                                 qDebug("%03d     Title: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
727                                 currentTrack->setPerformer(UNQUOTE(rxPerformer.cap(1)).simplified());
728                         }
729                         continue;
730                 }
731
732                 /* --- GENRE --- */
733                 if(rxGenre.indexIn(line) >= 0)
734                 {
735                         if(bPreamble)
736                         {
737                                 QString temp = UNQUOTE(rxGenre.cap(1)).simplified();
738                                 for(int i = 0; g_lamexp_generes[i]; i++)
739                                 {
740                                         if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
741                                         {
742                                                 m_albumGenre = QString(g_lamexp_generes[i]);
743                                                 break;
744                                         }
745                                 }
746                         }
747                         else if(currentFile && currentTrack)
748                         {
749                                 qDebug("%03d     Genre: <%s>", lines, rxGenre.cap(1).toUtf8().constData());
750                                 QString temp = UNQUOTE(rxGenre.cap(1).simplified());
751                                 for(int i = 0; g_lamexp_generes[i]; i++)
752                                 {
753                                         if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
754                                         {
755                                                 currentTrack->setGenre(QString(g_lamexp_generes[i]));
756                                                 break;
757                                         }
758                                 }
759                         }
760                         continue;
761                 }
762
763                 /* --- YEAR --- */
764                 if(rxYear.indexIn(line) >= 0)
765                 {
766                         if(bPreamble)
767                         {
768                                 bool ok = false;
769                                 unsigned int temp = rxYear.cap(1).toUInt(&ok);
770                                 if(ok) m_albumYear =  temp;
771                         }
772                         else if(currentFile && currentTrack)
773                         {
774                                 qDebug("%03d     Year: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
775                                 bool ok = false;
776                                 unsigned int temp = rxYear.cap(1).toUInt(&ok);
777                                 if(ok) currentTrack->setYear(temp);
778                         }
779                         continue;
780                 }
781         }
782
783         //Append the very last track/file that is still pending
784         if(currentFile)
785         {
786                 if(currentTrack)
787                 {
788                         if(currentTrack->isValid())
789                         {
790                                 currentFile->addTrack(currentTrack);
791                                 currentTrack = NULL;
792                         }
793                         else
794                         {
795                                 LAMEXP_DELETE(currentTrack);
796                         }
797                 }
798                 if(currentFile->isValid())
799                 {
800                         m_files.append(currentFile);
801                         currentFile = NULL;
802                 }
803                 else
804                 {
805                         LAMEXP_DELETE(currentFile);
806                 }
807         }
808
809         //Finally calculate duration of each track
810         int nFiles = m_files.count();
811         for(int i = 0; i < nFiles; i++)
812         {
813                 if(application)
814                 {
815                         application->processEvents();
816                         Sleep(10);
817                 }
818
819                 CueSheetFile *currentFile = m_files.at(i);
820                 int nTracks = currentFile->trackCount();
821                 if(nTracks > 1)
822                 {
823                         for(int j = 1; j < nTracks; j++)
824                         {
825                                 CueSheetTrack *currentTrack = currentFile->track(j);
826                                 CueSheetTrack *previousTrack = currentFile->track(j-1);
827                                 double duration = currentTrack->startIndex() - previousTrack->startIndex();
828                                 previousTrack->setDuration(qMax(0.0, duration));
829                         }
830                 }
831         }
832         
833         //Sanity check of track numbers
834         if(nFiles > 0)
835         {
836                 bool hasTracks = false;
837                 int previousTrackNo = -1;
838                 bool trackNo[100];
839                 for(int i = 0; i < 100; i++)
840                 {
841                         trackNo[i] = false;
842                 }
843
844                 for(int i = 0; i < nFiles; i++)
845                 {
846                         if(application)
847                         {
848                                 application->processEvents();
849                                 Sleep(10);
850                         }
851                         CueSheetFile *currentFile = m_files.at(i);
852                         int nTracks = currentFile->trackCount();
853                         if(nTracks > 1)
854                         {
855                                 for(int j = 0; j < nTracks; j++)
856                                 {
857                                         int currentTrackNo = currentFile->track(j)->trackNo();
858                                         if(currentTrackNo > 99)
859                                         {
860                                                 qWarning("Track #%02d is invalid (maximum is 99), Cue Sheet is inconsistent!", currentTrackNo);
861                                                 return ErrorInconsistent;
862                                         }
863                                         if(currentTrackNo <= previousTrackNo)
864                                         {
865                                                 qWarning("Non-increasing track numbers (%02d -> %02d), Cue Sheet is inconsistent!", previousTrackNo, currentTrackNo);
866                                                 return ErrorInconsistent;
867                                         }
868                                         if(trackNo[currentTrackNo])
869                                         {
870                                                 qWarning("Track #%02d exists multiple times, Cue Sheet is inconsistent!", currentTrackNo);
871                                                 return ErrorInconsistent;
872                                         }
873                                         trackNo[currentTrackNo] = true;
874                                         previousTrackNo = currentTrackNo;
875                                         hasTracks = true;
876                                 }
877                         }
878                 }
879                 
880                 if(!hasTracks)
881                 {
882                         qWarning("Could not find at least one valid track in the Cue Sheet!");
883                         return ErrorInconsistent;
884                 }
885
886                 return ErrorSuccess;
887         }
888         else
889         {
890                 qWarning("Could not find at least one valid input file in the Cue Sheet!");
891                 return bUnsupportedTrack ? ErrorUnsupported : ErrorBadFile;
892         }
893 }
894
895 double CueSheetModel::parseTimeIndex(const QString &index)
896 {
897         QRegExp rxTimeIndex("\\s*(\\d+)\\s*:\\s*(\\d+)\\s*:\\s*(\\d+)\\s*");
898         
899         if(rxTimeIndex.indexIn(index) >= 0)
900         {
901                 int min, sec, frm;
902                 bool minOK, secOK, frmOK;
903
904                 min = rxTimeIndex.cap(1).toInt(&minOK);
905                 sec = rxTimeIndex.cap(2).toInt(&secOK);
906                 frm = rxTimeIndex.cap(3).toInt(&frmOK);
907
908                 if(minOK && secOK && frmOK)
909                 {
910                         return static_cast<double>(60 * min) + static_cast<double>(sec) + (static_cast<double>(frm) / 75.0);
911                 }
912         }
913
914         qWarning("    Bad time index: '%s'", index.toUtf8().constData());
915         return std::numeric_limits<double>::quiet_NaN();
916 }
917
918 QString CueSheetModel::indexToString(const double index) const
919 {
920         if(!_finite(index) || (index < 0.0) || (index > 86400.0))
921         {
922                 return QString("??:??.???");
923         }
924         
925         QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
926
927         if(time.minute() < 100)
928         {
929                 return time.toString("mm:ss.zzz");
930         }
931         else
932         {
933                 return QString("99:99.999");
934         }
935 }