1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2011 LoRd_MuldeR <MuldeR2@GMX.de>
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.
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 ///////////////////////////////////////////////////////////////////////////////
23 #include "Model_CueSheet.h"
26 #include <QApplication>
35 #define UNQUOTE(STR) STR.split("\"", QString::SkipEmptyParts).first().trimmed()
37 ////////////////////////////////////////////////////////////
39 ////////////////////////////////////////////////////////////
44 virtual const char* type(void) = 0;
45 virtual bool isValid(void) { return false; }
48 class CueSheetTrack
: public CueSheetItem
51 CueSheetTrack(CueSheetFile
*parent
, int trackNo
)
56 m_startIndex
= std::numeric_limits
<double>::quiet_NaN();
57 m_duration
= std::numeric_limits
<double>::infinity();
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"; }
84 CueSheetFile
*m_parent
;
87 class CueSheetFile
: public CueSheetItem
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"; }
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")
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 ////////////////////////////////////////////////////////////
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
);
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();
183 QModelIndex
CueSheetModel::parent(const QModelIndex
&child
) const
185 QMutexLocker
lock(&m_mutex
);
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
)
211 return tr("File / Track");
217 return tr("Duration");
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())
243 return tr("File %1").arg(QString().sprintf("%02d", index
.row() + 1)).append(" ");
246 return QFileInfo(filePtr
->fileName()).fileName();
253 else if(CueSheetTrack
*trackPtr
= dynamic_cast<CueSheetTrack
*>(item
))
255 switch(index
.column())
258 return tr("Track %1").arg(QString().sprintf("%02d", trackPtr
->trackNo())).append(" ");
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"));
275 return QString("%1 - %2").arg(tr("Unknown Artist"), tr("Unknown Title"));
279 return indexToString(trackPtr
->startIndex());
282 return indexToString(trackPtr
->duration());
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
))
313 else if(dynamic_cast<CueSheetTrack
*>(item
))
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
);
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");
356 void CueSheetModel::clearData(void)
358 QMutexLocker
lock(&m_mutex
);
361 while(!m_files
.isEmpty()) delete m_files
.takeLast();
365 ////////////////////////////////////////////////////////////
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())
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())
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();
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();
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();
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();
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();
502 QString
CueSheetModel::getAlbumPerformer(void)
504 QMutexLocker
lock(&m_mutex
);
505 return m_albumPerformer
;
508 QString
CueSheetModel::getAlbumTitle(void)
510 QMutexLocker
lock(&m_mutex
);
514 QString
CueSheetModel::getAlbumGenre(void)
516 QMutexLocker
lock(&m_mutex
);
520 unsigned int CueSheetModel::getAlbumYear(void)
522 QMutexLocker
lock(&m_mutex
);
525 ////////////////////////////////////////////////////////////
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
;
542 int iResult
= parseCueFile(cueFile
, QDir(QFileInfo(cueFile
).canonicalPath()), application
);
548 int CueSheetModel::parseCueFile(QFile
&cueFile
, const QDir
&baseDir
, QCoreApplication
*application
)
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...");
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"));
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();
585 //Loop over the Cue Sheet until all lines were processed
586 for(int lines
= 0; lines
< INT_MAX
; lines
++)
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.");
601 QString line
= bUTF8
? QString::fromUtf8(lineData
.constData(), lineData
.size()).trimmed() : QString::fromLocal8Bit(lineData
.constData(), lineData
.size()).trimmed();
604 if(rxFile
.indexIn(line
) >= 0)
606 qDebug("%03d File: <%s> <%s>", lines
, rxFile
.cap(1).toUtf8().constData(), rxFile
.cap(2).toUtf8().constData());
611 if(currentTrack
->isValid())
613 currentFile
->addTrack(currentTrack
);
618 LAMEXP_DELETE(currentTrack
);
621 if(currentFile
->isValid())
623 m_files
.append(currentFile
);
628 LAMEXP_DELETE(currentFile
);
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());
642 bUnsupportedTrack
= true;
643 qWarning("%03d Skipping unsupported file of type '%s'.", lines
, rxFile
.cap(2).toUtf8().constData());
652 if(rxTrack
.indexIn(line
) >= 0)
656 qDebug("%03d Track: <%s> <%s>", lines
, rxTrack
.cap(1).toUtf8().constData(), rxTrack
.cap(2).toUtf8().constData());
659 if(currentTrack
->isValid())
661 currentFile
->addTrack(currentTrack
);
666 LAMEXP_DELETE(currentTrack
);
669 if(!rxTrack
.cap(2).compare("AUDIO", Qt::CaseInsensitive
))
671 currentTrack
= new CueSheetTrack(currentFile
, rxTrack
.cap(1).toInt());
675 bUnsupportedTrack
= true;
676 qWarning("%03d Skipping unsupported track of type '%s'.", lines
, rxTrack
.cap(2).toUtf8().constData());
682 LAMEXP_DELETE(currentTrack
);
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)));
703 if(rxTitle
.indexIn(line
) >= 0)
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());
717 /* --- PERFORMER --- */
718 if(rxPerformer
.indexIn(line
) >= 0)
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());
733 if(rxGenre
.indexIn(line
) >= 0)
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
]);
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
]));
764 if(rxYear
.indexIn(line
) >= 0)
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());
776 unsigned int temp
= rxYear
.cap(1).toUInt(&ok
);
777 if(ok
) currentTrack
->setYear(temp
);
783 //Append the very last track/file that is still pending
788 if(currentTrack
->isValid())
790 currentFile
->addTrack(currentTrack
);
795 LAMEXP_DELETE(currentTrack
);
798 if(currentFile
->isValid())
800 m_files
.append(currentFile
);
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
++)
815 application
->processEvents();
819 CueSheetFile
*currentFile
= m_files
.at(i
);
820 int nTracks
= currentFile
->trackCount();
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
836 bool hasTracks
= false;
837 int previousTrackNo
= -1;
839 for(int i
= 0; i
< 100; i
++)
844 for(int i
= 0; i
< nFiles
; i
++)
848 application
->processEvents();
851 CueSheetFile
*currentFile
= m_files
.at(i
);
852 int nTracks
= currentFile
->trackCount();
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
;
882 qWarning("Could not find at least one valid track in the Cue Sheet!");
883 return ErrorInconsistent
;
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)
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");
933 return QString("99:99.999");