OSDN Git Service

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