Added support for Opus Audio Codec, based on Opus-Tools v0.1.3 (2012-07-10) by Xiph...
[LameXP.git] / src / Model_CueSheet.cpp
blob579c1b071672c24262b659b578f0fc6b853f9a6b
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.
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>
31 #include <QTextCodec>
32 #include <QTextStream>
34 #include <float.h>
35 #include <limits>
37 #define UNQUOTE(STR) STR.split("\"", QString::SkipEmptyParts).first().trimmed()
39 ////////////////////////////////////////////////////////////
40 // Helper Classes
41 ////////////////////////////////////////////////////////////
43 class CueSheetItem
45 public:
46 virtual const char* type(void) = 0;
47 virtual bool isValid(void) { return false; }
50 class CueSheetTrack : public CueSheetItem
52 public:
53 CueSheetTrack(CueSheetFile *parent, int trackNo)
55 m_parent(parent),
56 m_trackNo(trackNo)
58 m_startIndex = std::numeric_limits<double>::quiet_NaN();
59 m_duration = std::numeric_limits<double>::infinity();
60 m_year = 0;
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;
89 class CueSheetFile : public CueSheetItem
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;
106 ////////////////////////////////////////////////////////////
107 // Constructor & Destructor
108 ////////////////////////////////////////////////////////////
110 QMutex CueSheetModel::m_mutex(QMutex::Recursive);
112 CueSheetModel::CueSheetModel()
114 m_fileIcon(":/icons/music.png"),
115 m_trackIcon(":/icons/control_play_blue.png")
117 int trackNo = 0;
118 m_albumYear = 0;
120 for(int i = 0; i < 5; i++)
122 CueSheetFile *currentFile = new CueSheetFile(QString().sprintf("File %02d.wav", i+1));
123 for(int j = 0; j < 8; j++)
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);
130 m_files.append(currentFile);
134 CueSheetModel::~CueSheetModel(void)
136 while(!m_files.isEmpty()) delete m_files.takeLast();
139 ////////////////////////////////////////////////////////////
140 // Model Functions
141 ////////////////////////////////////////////////////////////
143 QModelIndex CueSheetModel::index(int row, int column, const QModelIndex &parent) const
145 QMutexLocker lock(&m_mutex);
147 if(!parent.isValid())
149 return (row < m_files.count()) ? createIndex(row, column, m_files.at(row)) : QModelIndex();
152 CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
153 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
155 return (row < filePtr->trackCount()) ? createIndex(row, column, filePtr->track(row)) : QModelIndex();
158 return QModelIndex();
161 int CueSheetModel::columnCount(const QModelIndex &parent) const
163 QMutexLocker lock(&m_mutex);
164 return 4;
167 int CueSheetModel::rowCount(const QModelIndex &parent) const
169 QMutexLocker lock(&m_mutex);
171 if(!parent.isValid())
173 return m_files.count();
176 CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
177 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
179 return filePtr->trackCount();
182 return 0;
185 QModelIndex CueSheetModel::parent(const QModelIndex &child) const
187 QMutexLocker lock(&m_mutex);
189 if(child.isValid())
191 CueSheetItem *childItem = static_cast<CueSheetItem*>(child.internalPointer());
192 if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(childItem))
194 return createIndex(m_files.indexOf(trackPtr->parent()), 0, trackPtr->parent());
198 return QModelIndex();
201 QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, int role) const
203 QMutexLocker lock(&m_mutex);
205 if(role == Qt::DisplayRole)
207 switch(section)
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;
226 else
228 return QVariant();
232 QVariant CueSheetModel::data(const QModelIndex &index, int role) const
234 QMutexLocker lock(&m_mutex);
236 if(role == Qt::DisplayRole)
238 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
240 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
242 switch(index.column())
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;
255 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
257 switch(index.column())
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())
265 return QString("%1 - %2").arg(trackPtr->performer(), trackPtr->title());
267 else if(!trackPtr->title().isEmpty())
269 return QString("%1 - %2").arg(tr("Unknown Artist"), trackPtr->title());
271 else if(!trackPtr->performer().isEmpty())
273 return QString("%1 - %2").arg(trackPtr->performer(), tr("Unknown Title"));
275 else
277 return QString("%1 - %2").arg(tr("Unknown Artist"), tr("Unknown Title"));
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;
292 else if(role == Qt::ToolTipRole)
294 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
296 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
298 return QDir::toNativeSeparators(filePtr->fileName());
300 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
302 return QDir::toNativeSeparators(trackPtr->parent()->fileName());
305 else if(role == Qt::DecorationRole)
307 if(index.column() == 0)
309 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
311 if(dynamic_cast<CueSheetFile*>(item))
313 return m_fileIcon;
315 else if(dynamic_cast<CueSheetTrack*>(item))
317 return m_trackIcon;
321 else if(role == Qt::FontRole)
323 QFont font("Monospace");
324 font.setStyleHint(QFont::TypeWriter);
325 if((index.column() == 1))
327 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
328 font.setBold(dynamic_cast<CueSheetFile*>(item) != NULL);
330 return font;
332 else if(role == Qt::ForegroundRole)
334 if((index.column() == 1))
336 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
337 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
339 return (QFileInfo(filePtr->fileName()).size() > 4) ? QColor("mediumblue") : QColor("darkred");
342 else if((index.column() == 3))
344 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
345 if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
347 if(trackPtr->duration() == std::numeric_limits<double>::infinity())
349 return QColor("dimgrey");
355 return QVariant();
358 void CueSheetModel::clearData(void)
360 QMutexLocker lock(&m_mutex);
362 beginResetModel();
363 while(!m_files.isEmpty()) delete m_files.takeLast();
364 endResetModel();
367 ////////////////////////////////////////////////////////////
368 // External API
369 ////////////////////////////////////////////////////////////
371 int CueSheetModel::getFileCount(void)
373 QMutexLocker lock(&m_mutex);
374 return m_files.count();
377 QString CueSheetModel::getFileName(int fileIndex)
379 QMutexLocker lock(&m_mutex);
381 if(fileIndex < 0 || fileIndex >= m_files.count())
383 return QString();
386 return m_files.at(fileIndex)->fileName();
389 int CueSheetModel::getTrackCount(int fileIndex)
391 QMutexLocker lock(&m_mutex);
393 if(fileIndex < 0 || fileIndex >= m_files.count())
395 return -1;
398 return m_files.at(fileIndex)->trackCount();
401 int CueSheetModel::getTrackNo(int fileIndex, int trackIndex)
403 QMutexLocker lock(&m_mutex);
405 if(fileIndex >= 0 && fileIndex < m_files.count())
407 CueSheetFile *currentFile = m_files.at(fileIndex);
408 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
410 return currentFile->track(trackIndex)->trackNo();
414 return -1;
417 void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration)
419 QMutexLocker lock(&m_mutex);
421 *startIndex = std::numeric_limits<double>::quiet_NaN();
422 *duration = std::numeric_limits<double>::quiet_NaN();
424 if(fileIndex >= 0 && fileIndex < m_files.count())
426 CueSheetFile *currentFile = m_files.at(fileIndex);
427 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
429 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
430 *startIndex = currentTrack->startIndex();
431 *duration = currentTrack->duration();
436 QString CueSheetModel::getTrackPerformer(int fileIndex, int trackIndex)
438 QMutexLocker lock(&m_mutex);
440 if(fileIndex >= 0 && fileIndex < m_files.count())
442 CueSheetFile *currentFile = m_files.at(fileIndex);
443 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
445 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
446 return currentTrack->performer();
450 return QString();
453 QString CueSheetModel::getTrackTitle(int fileIndex, int trackIndex)
455 QMutexLocker lock(&m_mutex);
457 if(fileIndex >= 0 && fileIndex < m_files.count())
459 CueSheetFile *currentFile = m_files.at(fileIndex);
460 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
462 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
463 return currentTrack->title();
467 return QString();
470 QString CueSheetModel::getTrackGenre(int fileIndex, int trackIndex)
472 QMutexLocker lock(&m_mutex);
474 if(fileIndex >= 0 && fileIndex < m_files.count())
476 CueSheetFile *currentFile = m_files.at(fileIndex);
477 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
479 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
480 return currentTrack->genre();
484 return QString();
487 unsigned int CueSheetModel::getTrackYear(int fileIndex, int trackIndex)
489 QMutexLocker lock(&m_mutex);
491 if(fileIndex >= 0 && fileIndex < m_files.count())
493 CueSheetFile *currentFile = m_files.at(fileIndex);
494 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
496 CueSheetTrack *currentTrack = currentFile->track(trackIndex);
497 return currentTrack->year();
501 return 0;
504 QString CueSheetModel::getAlbumPerformer(void)
506 QMutexLocker lock(&m_mutex);
507 return m_albumPerformer;
510 QString CueSheetModel::getAlbumTitle(void)
512 QMutexLocker lock(&m_mutex);
513 return m_albumTitle;
516 QString CueSheetModel::getAlbumGenre(void)
518 QMutexLocker lock(&m_mutex);
519 return m_albumGenre;
522 unsigned int CueSheetModel::getAlbumYear(void)
524 QMutexLocker lock(&m_mutex);
525 return m_albumYear;
527 ////////////////////////////////////////////////////////////
528 // Cue Sheet Parser
529 ////////////////////////////////////////////////////////////
531 int CueSheetModel::loadCueSheet(const QString &cueFileName, QCoreApplication *application, QTextCodec *forceCodec)
533 QMutexLocker lock(&m_mutex);
534 const QTextCodec *codec = (forceCodec != NULL) ? forceCodec : QTextCodec::codecForName("System");
536 QFile cueFile(cueFileName);
537 if(!cueFile.open(QIODevice::ReadOnly))
539 return ErrorIOFailure;
542 clearData();
544 beginResetModel();
545 int iResult = parseCueFile(cueFile, QDir(QFileInfo(cueFile).canonicalPath()), application, codec);
546 endResetModel();
548 return iResult;
551 int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplication *application, const QTextCodec *codec)
553 cueFile.reset();
554 qDebug("\n[Cue Sheet Import]");
555 bool bForceLatin1 = false;
557 //Reject very large files, as parsing might take until forever
558 if(cueFile.size() >= 10485760i64)
560 qWarning("File is very big. Probably not a Cue Sheet. Rejecting...");
561 return 2;
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))
570 qWarning("Decoding error using selected codepage (%s). Enforcing Latin-1.", codec->name().constData());
571 bForceLatin1 = true;
573 testData.clear();
575 //Init text stream
576 QTextStream cueStream(&cueFile);
577 cueStream.setAutoDetectUnicode(false);
578 cueStream.setCodec(bForceLatin1 ? "latin1" : codec->name());
579 cueStream.seek(0i64);
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);
590 bool bPreamble = true;
591 bool bUnsupportedTrack = false;
593 CueSheetFile *currentFile = NULL;
594 CueSheetTrack *currentTrack = NULL;
596 m_albumTitle.clear();
597 m_albumPerformer.clear();
598 m_albumGenre.clear();
599 m_albumYear = 0;
601 //Loop over the Cue Sheet until all lines were processed
602 for(int lines = 0; lines < INT_MAX; lines++)
604 if(application)
606 application->processEvents();
607 if(lines < 128) Sleep(10);
610 if(cueStream.atEnd())
612 qDebug("End of Cue Sheet file.");
613 break;
616 QString line = cueStream.readLine().trimmed();
618 /* --- FILE --- */
619 if(rxFile.indexIn(line) >= 0)
621 qDebug("%03d File: <%s> <%s>", lines, rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData());
622 if(currentFile)
624 if(currentTrack)
626 if(currentTrack->isValid())
628 currentFile->addTrack(currentTrack);
629 currentTrack = NULL;
631 else
633 LAMEXP_DELETE(currentTrack);
636 if(currentFile->isValid())
638 m_files.append(currentFile);
639 currentFile = NULL;
641 else
643 LAMEXP_DELETE(currentFile);
646 else
648 LAMEXP_DELETE(currentTrack);
650 if(!rxFile.cap(2).compare("WAVE", Qt::CaseInsensitive) || !rxFile.cap(2).compare("MP3", Qt::CaseInsensitive) || !rxFile.cap(2).compare("AIFF", Qt::CaseInsensitive))
652 currentFile = new CueSheetFile(baseDir.absoluteFilePath(UNQUOTE(rxFile.cap(1))));
653 qDebug("%03d File path: <%s>", lines, currentFile->fileName().toUtf8().constData());
655 else
657 bUnsupportedTrack = true;
658 qWarning("%03d Skipping unsupported file of type '%s'.", lines, rxFile.cap(2).toUtf8().constData());
659 currentFile = NULL;
661 bPreamble = false;
662 currentTrack = NULL;
663 continue;
666 /* --- TRACK --- */
667 if(rxTrack.indexIn(line) >= 0)
669 if(currentFile)
671 qDebug("%03d Track: <%s> <%s>", lines, rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData());
672 if(currentTrack)
674 if(currentTrack->isValid())
676 currentFile->addTrack(currentTrack);
677 currentTrack = NULL;
679 else
681 LAMEXP_DELETE(currentTrack);
684 if(!rxTrack.cap(2).compare("AUDIO", Qt::CaseInsensitive))
686 currentTrack = new CueSheetTrack(currentFile, rxTrack.cap(1).toInt());
688 else
690 bUnsupportedTrack = true;
691 qWarning("%03d Skipping unsupported track of type '%s'.", lines, rxTrack.cap(2).toUtf8().constData());
692 currentTrack = NULL;
695 else
697 LAMEXP_DELETE(currentTrack);
699 bPreamble = false;
700 continue;
703 /* --- INDEX --- */
704 if(rxIndex.indexIn(line) >= 0)
706 if(currentFile && currentTrack)
708 qDebug("%03d Index: <%s> <%s>", lines, rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData());
709 if(rxIndex.cap(1).toInt() == 1)
711 currentTrack->setStartIndex(parseTimeIndex(rxIndex.cap(2)));
714 continue;
717 /* --- TITLE --- */
718 if(rxTitle.indexIn(line) >= 0)
720 if(bPreamble)
722 m_albumTitle = UNQUOTE(rxTitle.cap(1)).simplified();
724 else if(currentFile && currentTrack)
726 qDebug("%03d Title: <%s>", lines, rxTitle.cap(1).toUtf8().constData());
727 currentTrack->setTitle(UNQUOTE(rxTitle.cap(1)).simplified());
729 continue;
732 /* --- PERFORMER --- */
733 if(rxPerformer.indexIn(line) >= 0)
735 if(bPreamble)
737 m_albumPerformer = UNQUOTE(rxPerformer.cap(1)).simplified();
739 else if(currentFile && currentTrack)
741 qDebug("%03d Title: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
742 currentTrack->setPerformer(UNQUOTE(rxPerformer.cap(1)).simplified());
744 continue;
747 /* --- GENRE --- */
748 if(rxGenre.indexIn(line) >= 0)
750 if(bPreamble)
752 QString temp = UNQUOTE(rxGenre.cap(1)).simplified();
753 for(int i = 0; g_lamexp_generes[i]; i++)
755 if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
757 m_albumGenre = QString(g_lamexp_generes[i]);
758 break;
762 else if(currentFile && currentTrack)
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++)
768 if(temp.compare(g_lamexp_generes[i], Qt::CaseInsensitive) == 0)
770 currentTrack->setGenre(QString(g_lamexp_generes[i]));
771 break;
775 continue;
778 /* --- YEAR --- */
779 if(rxYear.indexIn(line) >= 0)
781 if(bPreamble)
783 bool ok = false;
784 unsigned int temp = rxYear.cap(1).toUInt(&ok);
785 if(ok) m_albumYear = temp;
787 else if(currentFile && currentTrack)
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);
794 continue;
798 //Append the very last track/file that is still pending
799 if(currentFile)
801 if(currentTrack)
803 if(currentTrack->isValid())
805 currentFile->addTrack(currentTrack);
806 currentTrack = NULL;
808 else
810 LAMEXP_DELETE(currentTrack);
813 if(currentFile->isValid())
815 m_files.append(currentFile);
816 currentFile = NULL;
818 else
820 LAMEXP_DELETE(currentFile);
824 //Finally calculate duration of each track
825 int nFiles = m_files.count();
826 for(int i = 0; i < nFiles; i++)
828 if(application)
830 application->processEvents();
831 Sleep(10);
834 CueSheetFile *currentFile = m_files.at(i);
835 int nTracks = currentFile->trackCount();
836 if(nTracks > 1)
838 for(int j = 1; j < nTracks; j++)
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));
848 //Sanity check of track numbers
849 if(nFiles > 0)
851 bool hasTracks = false;
852 int previousTrackNo = -1;
853 bool trackNo[100];
854 for(int i = 0; i < 100; i++)
856 trackNo[i] = false;
859 for(int i = 0; i < nFiles; i++)
861 if(application)
863 application->processEvents();
864 Sleep(10);
866 CueSheetFile *currentFile = m_files.at(i);
867 int nTracks = currentFile->trackCount();
868 if(nTracks > 1)
870 for(int j = 0; j < nTracks; j++)
872 int currentTrackNo = currentFile->track(j)->trackNo();
873 if(currentTrackNo > 99)
875 qWarning("Track #%02d is invalid (maximum is 99), Cue Sheet is inconsistent!", currentTrackNo);
876 return ErrorInconsistent;
878 if(currentTrackNo <= previousTrackNo)
880 qWarning("Non-increasing track numbers (%02d -> %02d), Cue Sheet is inconsistent!", previousTrackNo, currentTrackNo);
881 return ErrorInconsistent;
883 if(trackNo[currentTrackNo])
885 qWarning("Track #%02d exists multiple times, Cue Sheet is inconsistent!", currentTrackNo);
886 return ErrorInconsistent;
888 trackNo[currentTrackNo] = true;
889 previousTrackNo = currentTrackNo;
890 hasTracks = true;
895 if(!hasTracks)
897 qWarning("Could not find at least one valid track in the Cue Sheet!");
898 return ErrorInconsistent;
901 return ErrorSuccess;
903 else
905 qWarning("Could not find at least one valid input file in the Cue Sheet!");
906 return bUnsupportedTrack ? ErrorUnsupported : ErrorBadFile;
910 double CueSheetModel::parseTimeIndex(const QString &index)
912 QRegExp rxTimeIndex("\\s*(\\d+)\\s*:\\s*(\\d+)\\s*:\\s*(\\d+)\\s*");
914 if(rxTimeIndex.indexIn(index) >= 0)
916 int min, sec, frm;
917 bool minOK, secOK, frmOK;
919 min = rxTimeIndex.cap(1).toInt(&minOK);
920 sec = rxTimeIndex.cap(2).toInt(&secOK);
921 frm = rxTimeIndex.cap(3).toInt(&frmOK);
923 if(minOK && secOK && frmOK)
925 return static_cast<double>(60 * min) + static_cast<double>(sec) + (static_cast<double>(frm) / 75.0);
929 qWarning(" Bad time index: '%s'", index.toUtf8().constData());
930 return std::numeric_limits<double>::quiet_NaN();
933 QString CueSheetModel::indexToString(const double index) const
935 if(!_finite(index) || (index < 0.0) || (index > 86400.0))
937 return QString("??:??.???");
940 QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
942 if(time.minute() < 100)
944 return time.toString("mm:ss.zzz");
946 else
948 return QString("99:99.999");