Updated FAQ document + small installer fix.
[LameXP.git] / src / Model_CueSheet.cpp
blob88c8bd9450d1b2f472a9527cdc9938bde2be4795
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.
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
22 #include "Global.h"
23 #include "Model_CueSheet.h"
24 #include "Genres.h"
26 #include <QApplication>
27 #include <QDir>
28 #include <QFileInfo>
29 #include <QFont>
30 #include <QTime>
32 #include <float.h>
33 #include <limits>
35 #define UNQUOTE(STR) STR.split("\"", QString::SkipEmptyParts).first().trimmed()
37 ////////////////////////////////////////////////////////////
38 // Helper Classes
39 ////////////////////////////////////////////////////////////
41 class CueSheetItem
43 public:
44 virtual const char* type(void) = 0;
45 virtual bool isValid(void) { return false; }
48 class CueSheetTrack : public CueSheetItem
50 public:
51 CueSheetTrack(CueSheetFile *parent, int trackNo)
53 m_parent(parent),
54 m_trackNo(trackNo)
56 m_startIndex = std::numeric_limits<double>::quiet_NaN();
57 m_duration = std::numeric_limits<double>::infinity();
58 m_year = 0;
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;
87 class CueSheetFile : public CueSheetItem
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;
104 ////////////////////////////////////////////////////////////
105 // Constructor & Destructor
106 ////////////////////////////////////////////////////////////
108 QMutex CueSheetModel::m_mutex(QMutex::Recursive);
110 CueSheetModel::CueSheetModel()
112 m_fileIcon(":/icons/music.png"),
113 m_trackIcon(":/icons/control_play_blue.png")
115 int trackNo = 0;
116 m_albumYear = 0;
118 for(int i = 0; i < 5; i++)
120 CueSheetFile *currentFile = new CueSheetFile(QString().sprintf("File %02d.wav", i+1));
121 for(int j = 0; j < 8; j++)
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);
128 m_files.append(currentFile);
132 CueSheetModel::~CueSheetModel(void)
134 while(!m_files.isEmpty()) delete m_files.takeLast();
137 ////////////////////////////////////////////////////////////
138 // Model Functions
139 ////////////////////////////////////////////////////////////
141 QModelIndex CueSheetModel::index(int row, int column, const QModelIndex &parent) const
143 QMutexLocker lock(&m_mutex);
145 if(!parent.isValid())
147 return (row < m_files.count()) ? createIndex(row, column, m_files.at(row)) : QModelIndex();
150 CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
151 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
153 return (row < filePtr->trackCount()) ? createIndex(row, column, filePtr->track(row)) : QModelIndex();
156 return QModelIndex();
159 int CueSheetModel::columnCount(const QModelIndex &parent) const
161 QMutexLocker lock(&m_mutex);
162 return 4;
165 int CueSheetModel::rowCount(const QModelIndex &parent) const
167 QMutexLocker lock(&m_mutex);
169 if(!parent.isValid())
171 return m_files.count();
174 CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
175 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
177 return filePtr->trackCount();
180 return 0;
183 QModelIndex CueSheetModel::parent(const QModelIndex &child) const
185 QMutexLocker lock(&m_mutex);
187 if(child.isValid())
189 CueSheetItem *childItem = static_cast<CueSheetItem*>(child.internalPointer());
190 if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(childItem))
192 return createIndex(m_files.indexOf(trackPtr->parent()), 0, trackPtr->parent());
196 return QModelIndex();
199 QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, int role) const
201 QMutexLocker lock(&m_mutex);
203 if(role == Qt::DisplayRole)
205 switch(section)
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;
224 else
226 return QVariant();
230 QVariant CueSheetModel::data(const QModelIndex &index, int role) const
232 QMutexLocker lock(&m_mutex);
234 if(role == Qt::DisplayRole)
236 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
238 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
240 switch(index.column())
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;
253 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
255 switch(index.column())
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())
263 return QString("%1 - %2").arg(trackPtr->performer(), trackPtr->title());
265 else if(!trackPtr->title().isEmpty())
267 return QString("%1 - %2").arg(tr("Unknown Artist"), trackPtr->title());
269 else if(!trackPtr->performer().isEmpty())
271 return QString("%1 - %2").arg(trackPtr->performer(), tr("Unknown Title"));
273 else
275 return QString("%1 - %2").arg(tr("Unknown Artist"), tr("Unknown Title"));
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;
290 else if(role == Qt::ToolTipRole)
292 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
294 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
296 return QDir::toNativeSeparators(filePtr->fileName());
298 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
300 return QDir::toNativeSeparators(trackPtr->parent()->fileName());
303 else if(role == Qt::DecorationRole)
305 if(index.column() == 0)
307 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
309 if(dynamic_cast<CueSheetFile*>(item))
311 return m_fileIcon;
313 else if(dynamic_cast<CueSheetTrack*>(item))
315 return m_trackIcon;
319 else if(role == Qt::FontRole)
321 QFont font("Monospace");
322 font.setStyleHint(QFont::TypeWriter);
323 if((index.column() == 1))
325 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
326 font.setBold(dynamic_cast<CueSheetFile*>(item) != NULL);
328 return font;
330 else if(role == Qt::ForegroundRole)
332 if((index.column() == 1))
334 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
335 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
337 return (QFileInfo(filePtr->fileName()).size() > 4) ? QColor("mediumblue") : QColor("darkred");
340 else if((index.column() == 3))
342 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
343 if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
345 if(trackPtr->duration() == std::numeric_limits<double>::infinity())
347 return QColor("dimgrey");
353 return QVariant();
356 void CueSheetModel::clearData(void)
358 QMutexLocker lock(&m_mutex);
360 beginResetModel();
361 while(!m_files.isEmpty()) delete m_files.takeLast();
362 endResetModel();
365 ////////////////////////////////////////////////////////////
366 // External API
367 ////////////////////////////////////////////////////////////
369 int CueSheetModel::getFileCount(void)
371 QMutexLocker lock(&m_mutex);
372 return m_files.count();
375 QString CueSheetModel::getFileName(int fileIndex)
377 QMutexLocker lock(&m_mutex);
379 if(fileIndex < 0 || fileIndex >= m_files.count())
381 return QString();
384 return m_files.at(fileIndex)->fileName();
387 int CueSheetModel::getTrackCount(int fileIndex)
389 QMutexLocker lock(&m_mutex);
391 if(fileIndex < 0 || fileIndex >= m_files.count())
393 return -1;
396 return m_files.at(fileIndex)->trackCount();
399 int CueSheetModel::getTrackNo(int fileIndex, int trackIndex)
401 QMutexLocker lock(&m_mutex);
403 if(fileIndex >= 0 && fileIndex < m_files.count())
405 CueSheetFile *currentFile = m_files.at(fileIndex);
406 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
408 return currentFile->track(trackIndex)->trackNo();
412 return -1;
415 void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration)
417 QMutexLocker lock(&m_mutex);
419 *startIndex = std::numeric_limits<double>::quiet_NaN();
420 *duration = std::numeric_limits<double>::quiet_NaN();
422 if(fileIndex >= 0 && fileIndex < m_files.count())
424 CueSheetFile *currentFile = m_files.at(fileIndex);
425 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
427 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
428 *startIndex = currentTrack->startIndex();
429 *duration = currentTrack->duration();
434 QString CueSheetModel::getTrackPerformer(int fileIndex, int trackIndex)
436 QMutexLocker lock(&m_mutex);
438 if(fileIndex >= 0 && fileIndex < m_files.count())
440 CueSheetFile *currentFile = m_files.at(fileIndex);
441 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
443 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
444 return currentTrack->performer();
448 return QString();
451 QString CueSheetModel::getTrackTitle(int fileIndex, int trackIndex)
453 QMutexLocker lock(&m_mutex);
455 if(fileIndex >= 0 && fileIndex < m_files.count())
457 CueSheetFile *currentFile = m_files.at(fileIndex);
458 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
460 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
461 return currentTrack->title();
465 return QString();
468 QString CueSheetModel::getTrackGenre(int fileIndex, int trackIndex)
470 QMutexLocker lock(&m_mutex);
472 if(fileIndex >= 0 && fileIndex < m_files.count())
474 CueSheetFile *currentFile = m_files.at(fileIndex);
475 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
477 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
478 return currentTrack->genre();
482 return QString();
485 unsigned int CueSheetModel::getTrackYear(int fileIndex, int trackIndex)
487 QMutexLocker lock(&m_mutex);
489 if(fileIndex >= 0 && fileIndex < m_files.count())
491 CueSheetFile *currentFile = m_files.at(fileIndex);
492 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
494 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
495 return currentTrack->year();
499 return 0;
502 QString CueSheetModel::getAlbumPerformer(void)
504 QMutexLocker lock(&m_mutex);
505 return m_albumPerformer;
508 QString CueSheetModel::getAlbumTitle(void)
510 QMutexLocker lock(&m_mutex);
511 return m_albumTitle;
514 QString CueSheetModel::getAlbumGenre(void)
516 QMutexLocker lock(&m_mutex);
517 return m_albumGenre;
520 unsigned int CueSheetModel::getAlbumYear(void)
522 QMutexLocker lock(&m_mutex);
523 return m_albumYear;
525 ////////////////////////////////////////////////////////////
526 // Cue Sheet Parser
527 ////////////////////////////////////////////////////////////
529 int CueSheetModel::loadCueSheet(const QString &cueFileName, QCoreApplication *application)
531 QMutexLocker lock(&m_mutex);
533 QFile cueFile(cueFileName);
534 if(!cueFile.open(QIODevice::ReadOnly))
536 return ErrorIOFailure;
539 clearData();
541 beginResetModel();
542 int iResult = parseCueFile(cueFile, QDir(QFileInfo(cueFile).canonicalPath()), application);
543 endResetModel();
545 return iResult;
548 int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplication *application)
550 cueFile.seek(0);
551 qDebug("\n[Cue Sheet Import]");
553 //Reject very large files, as parsing might take until forever
554 if(cueFile.size() >= 10485760i64)
556 qWarning("File is very big. Probably not a Cue Sheet. Rejecting...");
557 return 2;
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();
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);
574 bool bPreamble = true;
575 bool bUnsupportedTrack = false;
577 CueSheetFile *currentFile = NULL;
578 CueSheetTrack *currentTrack = NULL;
580 m_albumTitle.clear();
581 m_albumPerformer.clear();
582 m_albumGenre.clear();
583 m_albumYear = 0;
585 //Loop over the Cue Sheet until all lines were processed
586 for(int lines = 0; lines < INT_MAX; lines++)
588 if(application)
590 application->processEvents();
591 if(lines < 128) Sleep(10);
594 QByteArray lineData = cueFile.readLine();
595 if(lineData.size() <= 0)
597 qDebug("End of Cue Sheet file.");
598 break;
601 QString line = bUTF8 ? QString::fromUtf8(lineData.constData(), lineData.size()).trimmed() : QString::fromLocal8Bit(lineData.constData(), lineData.size()).trimmed();
603 /* --- FILE --- */
604 if(rxFile.indexIn(line) >= 0)
606 qDebug("%03d File: <%s> <%s>", lines, rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData());
607 if(currentFile)
609 if(currentTrack)
611 if(currentTrack->isValid())
613 currentFile->addTrack(currentTrack);
614 currentTrack = NULL;
616 else
618 LAMEXP_DELETE(currentTrack);
621 if(currentFile->isValid())
623 m_files.append(currentFile);
624 currentFile = NULL;
626 else
628 LAMEXP_DELETE(currentFile);
631 else
633 LAMEXP_DELETE(currentTrack);
635 if(!rxFile.cap(2).compare("WAVE", Qt::CaseInsensitive) || !rxFile.cap(2).compare("MP3", Qt::CaseInsensitive) || !rxFile.cap(2).compare("AIFF", Qt::CaseInsensitive))
637 currentFile = new CueSheetFile(baseDir.absoluteFilePath(UNQUOTE(rxFile.cap(1))));
638 qDebug("%03d File path: <%s>", lines, currentFile->fileName().toUtf8().constData());
640 else
642 bUnsupportedTrack = true;
643 qWarning("%03d Skipping unsupported file of type '%s'.", lines, rxFile.cap(2).toUtf8().constData());
644 currentFile = NULL;
646 bPreamble = false;
647 currentTrack = NULL;
648 continue;
651 /* --- TRACK --- */
652 if(rxTrack.indexIn(line) >= 0)
654 if(currentFile)
656 qDebug("%03d Track: <%s> <%s>", lines, rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData());
657 if(currentTrack)
659 if(currentTrack->isValid())
661 currentFile->addTrack(currentTrack);
662 currentTrack = NULL;
664 else
666 LAMEXP_DELETE(currentTrack);
669 if(!rxTrack.cap(2).compare("AUDIO", Qt::CaseInsensitive))
671 currentTrack = new CueSheetTrack(currentFile, rxTrack.cap(1).toInt());
673 else
675 bUnsupportedTrack = true;
676 qWarning("%03d Skipping unsupported track of type '%s'.", lines, rxTrack.cap(2).toUtf8().constData());
677 currentTrack = NULL;
680 else
682 LAMEXP_DELETE(currentTrack);
684 bPreamble = false;
685 continue;
688 /* --- INDEX --- */
689 if(rxIndex.indexIn(line) >= 0)
691 if(currentFile && currentTrack)
693 qDebug("%03d Index: <%s> <%s>", lines, rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData());
694 if(rxIndex.cap(1).toInt() == 1)
696 currentTrack->setStartIndex(parseTimeIndex(rxIndex.cap(2)));
699 continue;
702 /* --- TITLE --- */
703 if(rxTitle.indexIn(line) >= 0)
705 if(bPreamble)
707 m_albumTitle = UNQUOTE(rxTitle.cap(1)).simplified();
709 else if(currentFile && currentTrack)
711 qDebug("%03d Title: <%s>", lines, rxTitle.cap(1).toUtf8().constData());
712 currentTrack->setTitle(UNQUOTE(rxTitle.cap(1)).simplified());
714 continue;
717 /* --- PERFORMER --- */
718 if(rxPerformer.indexIn(line) >= 0)
720 if(bPreamble)
722 m_albumPerformer = UNQUOTE(rxPerformer.cap(1)).simplified();
724 else if(currentFile && currentTrack)
726 qDebug("%03d Title: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
727 currentTrack->setPerformer(UNQUOTE(rxPerformer.cap(1)).simplified());
729 continue;
732 /* --- GENRE --- */
733 if(rxGenre.indexIn(line) >= 0)
735 if(bPreamble)
737 QString temp = UNQUOTE(rxGenre.cap(1)).simplified();
738 for(int i = 0; g_lamexp_generes[i]; i++)
740 if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
742 m_albumGenre = QString(g_lamexp_generes[i]);
743 break;
747 else if(currentFile && currentTrack)
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++)
753 if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
755 currentTrack->setGenre(QString(g_lamexp_generes[i]));
756 break;
760 continue;
763 /* --- YEAR --- */
764 if(rxYear.indexIn(line) >= 0)
766 if(bPreamble)
768 bool ok = false;
769 unsigned int temp = rxYear.cap(1).toUInt(&ok);
770 if(ok) m_albumYear = temp;
772 else if(currentFile && currentTrack)
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);
779 continue;
783 //Append the very last track/file that is still pending
784 if(currentFile)
786 if(currentTrack)
788 if(currentTrack->isValid())
790 currentFile->addTrack(currentTrack);
791 currentTrack = NULL;
793 else
795 LAMEXP_DELETE(currentTrack);
798 if(currentFile->isValid())
800 m_files.append(currentFile);
801 currentFile = NULL;
803 else
805 LAMEXP_DELETE(currentFile);
809 //Finally calculate duration of each track
810 int nFiles = m_files.count();
811 for(int i = 0; i < nFiles; i++)
813 if(application)
815 application->processEvents();
816 Sleep(10);
819 CueSheetFile *currentFile = m_files.at(i);
820 int nTracks = currentFile->trackCount();
821 if(nTracks > 1)
823 for(int j = 1; j < nTracks; j++)
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));
833 //Sanity check of track numbers
834 if(nFiles > 0)
836 bool hasTracks = false;
837 int previousTrackNo = -1;
838 bool trackNo[100];
839 for(int i = 0; i < 100; i++)
841 trackNo[i] = false;
844 for(int i = 0; i < nFiles; i++)
846 if(application)
848 application->processEvents();
849 Sleep(10);
851 CueSheetFile *currentFile = m_files.at(i);
852 int nTracks = currentFile->trackCount();
853 if(nTracks > 1)
855 for(int j = 0; j < nTracks; j++)
857 int currentTrackNo = currentFile->track(j)->trackNo();
858 if(currentTrackNo > 99)
860 qWarning("Track #%02d is invalid (maximum is 99), Cue Sheet is inconsistent!", currentTrackNo);
861 return ErrorInconsistent;
863 if(currentTrackNo <= previousTrackNo)
865 qWarning("Non-increasing track numbers (%02d -> %02d), Cue Sheet is inconsistent!", previousTrackNo, currentTrackNo);
866 return ErrorInconsistent;
868 if(trackNo[currentTrackNo])
870 qWarning("Track #%02d exists multiple times, Cue Sheet is inconsistent!", currentTrackNo);
871 return ErrorInconsistent;
873 trackNo[currentTrackNo] = true;
874 previousTrackNo = currentTrackNo;
875 hasTracks = true;
880 if(!hasTracks)
882 qWarning("Could not find at least one valid track in the Cue Sheet!");
883 return ErrorInconsistent;
886 return ErrorSuccess;
888 else
890 qWarning("Could not find at least one valid input file in the Cue Sheet!");
891 return bUnsupportedTrack ? ErrorUnsupported : ErrorBadFile;
895 double CueSheetModel::parseTimeIndex(const QString &index)
897 QRegExp rxTimeIndex("\\s*(\\d+)\\s*:\\s*(\\d+)\\s*:\\s*(\\d+)\\s*");
899 if(rxTimeIndex.indexIn(index) >= 0)
901 int min, sec, frm;
902 bool minOK, secOK, frmOK;
904 min = rxTimeIndex.cap(1).toInt(&minOK);
905 sec = rxTimeIndex.cap(2).toInt(&secOK);
906 frm = rxTimeIndex.cap(3).toInt(&frmOK);
908 if(minOK && secOK && frmOK)
910 return static_cast<double>(60 * min) + static_cast<double>(sec) + (static_cast<double>(frm) / 75.0);
914 qWarning(" Bad time index: '%s'", index.toUtf8().constData());
915 return std::numeric_limits<double>::quiet_NaN();
918 QString CueSheetModel::indexToString(const double index) const
920 if(!_finite(index) || (index < 0.0) || (index > 86400.0))
922 return QString("??:??.???");
925 QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
927 if(time.minute() < 100)
929 return time.toString("mm:ss.zzz");
931 else
933 return QString("99:99.999");