diff --git a/gui/CueSheetImport.ui b/gui/CueSheetImport.ui index 39536534..9c86aa7d 100644 --- a/gui/CueSheetImport.ui +++ b/gui/CueSheetImport.ui @@ -6,8 +6,8 @@ 0 0 - 621 - 478 + 700 + 463 @@ -286,7 +286,7 @@ - Abort + Discard @@ -345,6 +345,8 @@ + + diff --git a/src/Config.h b/src/Config.h index 7820a09d..99e82b38 100644 --- a/src/Config.h +++ b/src/Config.h @@ -30,7 +30,7 @@ #define VER_LAMEXP_MINOR_LO 2 #define VER_LAMEXP_TYPE Alpha #define VER_LAMEXP_PATCH 14 -#define VER_LAMEXP_BUILD 505 +#define VER_LAMEXP_BUILD 508 /////////////////////////////////////////////////////////////////////////////// // Tools versions diff --git a/src/Dialog_CueImport.cpp b/src/Dialog_CueImport.cpp index c7205691..d4684c9b 100644 --- a/src/Dialog_CueImport.cpp +++ b/src/Dialog_CueImport.cpp @@ -49,6 +49,7 @@ CueImportDialog::CueImportDialog(QWidget *parent) //Create model m_model = new CueSheetModel(); + connect(m_model, SIGNAL(modelReset()), this, SLOT(modelChanged())); //Setup table view treeView->setModel(m_model); @@ -57,10 +58,10 @@ CueImportDialog::CueImportDialog(QWidget *parent) treeView->header()->setResizeMode(1, QHeaderView::Stretch); treeView->header()->setMovable(false); treeView->setItemsExpandable(false); - treeView->expandAll(); //Enable up/down button connect(imprtButton, SIGNAL(clicked()), this, SLOT(importButtonClicked())); + connect(browseButton, SIGNAL(clicked()), this, SLOT(importButtonClicked())); //Translate labelHeaderText->setText(QString("%1
%2").arg(tr("Import Cue Sheet"), tr("The following Cue Sheet will be split and imported into LameXP."))); @@ -71,55 +72,54 @@ CueImportDialog::~CueImportDialog(void) LAMEXP_DELETE(m_model); } +//////////////////////////////////////////////////////////// +// EVENTS +//////////////////////////////////////////////////////////// + +void CueImportDialog::showEvent(QShowEvent *event) +{ + QDialog::showEvent(event); + modelChanged(); +} + //////////////////////////////////////////////////////////// // Slots //////////////////////////////////////////////////////////// int CueImportDialog::exec(const QString &cueFile) { - /* - MetaInfoModel *model = new MetaInfoModel(&audioFile); - tableView->setModel(model); - tableView->show(); - frameArtwork->hide(); - setWindowTitle(QString("Meta Information: %1").arg(QFileInfo(audioFile.filePath()).fileName())); - editButton->setEnabled(true); - upButton->setEnabled(allowUp); - downButton->setEnabled(allowDown); - buttonArtwork->setChecked(false); - - if(!audioFile.fileCover().isEmpty()) + int iResult = m_model->loadCueSheet(cueFile); + + if(iResult) { - QImage artwork; - if(artwork.load(audioFile.fileCover())) + QString errorMsg = tr("An unknown error has occured!"); + + switch(iResult) { - if((artwork.width() > 256) || (artwork.height() > 256)) - { - artwork = artwork.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - labelArtwork->setPixmap(QPixmap::fromImage(artwork)); + case 1: + errorMsg = tr("The file could not be opened for reading!"); + break; + case 2: + errorMsg = tr("The file does not look like a valid Cue Sheet file!"); + break; + case 3: + errorMsg = tr("Could not find a supported audio track in the Cue Sheet!"); + break; } - else - { - qWarning("Error: Failed to load cover art!"); - labelArtwork->setPixmap(QPixmap::fromImage(QImage(":/images/CD.png"))); - } - } - else - { - labelArtwork->setPixmap(QPixmap::fromImage(QImage(":/images/CD.png"))); + + QString text = QString("%1
%2

%3").arg(tr("Failed to load the Cue Sheet file:"), cueFile, errorMsg).replace("-", "−"); + QMessageBox::warning(dynamic_cast(parent()), tr("Cue Sheet Error"), text); + return iResult; } - int iResult = QDialog::exec(); - - tableView->setModel(NULL); - LAMEXP_DELETE(model); - - return iResult;*/ - return QDialog::exec(); } +void CueImportDialog::modelChanged(void) +{ + treeView->expandAll(); +} + void CueImportDialog::importButtonClicked(void) { QMessageBox::information(this, "Not implemenred", "Sorry, not yet. Please try again in a later version!"); diff --git a/src/Dialog_CueImport.h b/src/Dialog_CueImport.h index d17dee90..b9180559 100644 --- a/src/Dialog_CueImport.h +++ b/src/Dialog_CueImport.h @@ -38,8 +38,12 @@ public: int exec(const QString &cueFile); +protected: + void CueImportDialog::showEvent(QShowEvent *event); + private slots: void importButtonClicked(void); + void modelChanged(void); private: CueSheetModel *m_model; diff --git a/src/Dialog_MainWindow.cpp b/src/Dialog_MainWindow.cpp index 2c815bba..acc3437e 100644 --- a/src/Dialog_MainWindow.cpp +++ b/src/Dialog_MainWindow.cpp @@ -2567,9 +2567,13 @@ void MainWindow::importCueSheetActionTriggered(bool checked) TEMP_HIDE_DROPBOX ( - CueImportDialog *cueImporter = new CueImportDialog(this); - cueImporter->exec(QString()); - LAMEXP_DELETE(cueImporter); + QString selectedCueFile = QFileDialog::getOpenFileName(this, tr("Open Cue Sheet"), QString(), QString("%1 (*.cue)").arg(tr("Cue Sheet File"))); + if(!selectedCueFile.isEmpty()) + { + CueImportDialog *cueImporter = new CueImportDialog(this); + cueImporter->exec(selectedCueFile); + LAMEXP_DELETE(cueImporter); + } ) } diff --git a/src/Model_CueSheet.cpp b/src/Model_CueSheet.cpp index 70d87e98..4be2e16f 100644 --- a/src/Model_CueSheet.cpp +++ b/src/Model_CueSheet.cpp @@ -19,6 +19,7 @@ // http://www.gnu.org/licenses/gpl-2.0.txt /////////////////////////////////////////////////////////////////////////////// +#include "Global.h" #include "Model_CueSheet.h" #include "Genres.h" @@ -217,25 +218,28 @@ QVariant CueSheetModel::data(const QModelIndex &index, int role) const switch(index.column()) { case 0: - return tr("Track %1").arg(QString().sprintf("%02d", trackPtr->trackNo() + 1)).append(" "); + return tr("Track %1").arg(QString().sprintf("%02d", trackPtr->trackNo())).append(" "); break; case 1: if(!trackPtr->title().isEmpty() && !trackPtr->performer().isEmpty()) { - return QString("%1 / %2").arg(trackPtr->performer(), trackPtr->title()); + return QString("%1 - %2").arg(trackPtr->performer(), trackPtr->title()); } else if(!trackPtr->title().isEmpty()) { - return trackPtr->title(); + return QString("%1 - %2").arg(tr("Unknown Artist"), trackPtr->title()); } else if(!trackPtr->performer().isEmpty()) { - return trackPtr->performer(); + return QString("%1 - %2").arg(trackPtr->performer(), tr("Unknown Title")); + } + else + { + return QString("%1 - %2").arg(tr("Unknown Artist"), tr("Unknown Title")); } - return QVariant(); break; case 2: - return QString().sprintf("%07.2f", trackPtr->startIndex()); + return indexToString(trackPtr->startIndex()); break; default: return QVariant(); @@ -253,3 +257,275 @@ void CueSheetModel::clearData(void) while(!m_files.isEmpty()) delete m_files.takeLast(); endResetModel(); } + +//////////////////////////////////////////////////////////// +// Cue Sheet Parser +//////////////////////////////////////////////////////////// + +int CueSheetModel::loadCueSheet(const QString &cueFileName) +{ + QFile cueFile(cueFileName); + if(!cueFile.open(QIODevice::ReadOnly)) + { + return 1; + } + + clearData(); + + beginResetModel(); + int iResult = parseCueFile(cueFile); + endResetModel(); + + return iResult; +} + +int CueSheetModel::parseCueFile(QFile &cueFile) +{ + cueFile.seek(0); + + //Check for UTF-8 BOM to guess encoding + bool bUTF8 = false; + QByteArray bomCheck = cueFile.peek(3); + if(bomCheck.size() == 3) + { + bUTF8 = ((bomCheck.at(0) == '\xef') && (bomCheck.at(1) == '\xbb') && (bomCheck.at(2) == '\xbf')); + qDebug("Encoding is %s.", (bUTF8 ? "UTF-8" : "Local 8-Bit")); + } + + QRegExp rxFile("^FILE\\s+\"([^\"]+)\"\\s+(\\w+)$", Qt::CaseInsensitive); + QRegExp rxTrack("^TRACK\\s+(\\d+)\\s(\\w+)$", Qt::CaseInsensitive); + QRegExp rxIndex("^INDEX\\s+(\\d+)\\s+([0-9:]+)$", Qt::CaseInsensitive); + QRegExp rxTitle("^TITLE\\s+\"([^\"]+)\"$", Qt::CaseInsensitive); + QRegExp rxPerformer("^PERFORMER\\s+\"([^\"]+)\"$", Qt::CaseInsensitive); + + CueSheetFile *currentFile = NULL; + CueSheetTrack *currentTrack = NULL; + + bool bPreamble = true; + bool bUnsupportedTrack = false; + + QString albumTitle; + QString albumPerformer; + + //Loop over the Cue Sheet until all lines were processed + while(true) + { + QByteArray lineData = cueFile.readLine(); + if(lineData.size() <= 0) + { + qDebug("End of Cue Sheet file."); + break; + } + + QString line = bUTF8 ? QString::fromUtf8(lineData.constData(), lineData.size()).trimmed() : QString::fromLocal8Bit(lineData.constData(), lineData.size()).trimmed(); + + /* --- FILE --- */ + if(rxFile.indexIn(line) >= 0) + { + qDebug("File: <%s> <%s>", rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData()); + if(currentFile) + { + if(currentTrack) + { + if(currentTrack->isValid()) + { + if(currentTrack->title().isEmpty() && !albumTitle.isEmpty()) + { + currentTrack->setTitle(albumTitle); + } + if(currentTrack->performer().isEmpty() && !albumPerformer.isEmpty()) + { + currentTrack->setPerformer(albumPerformer); + } + currentFile->addTrack(currentTrack); + currentTrack = NULL; + } + else + { + LAMEXP_DELETE(currentTrack); + } + } + if(currentFile->trackCount() > 0) + { + m_files.append(currentFile); + currentFile = NULL; + } + else + { + LAMEXP_DELETE(currentFile); + } + } + if(!rxFile.cap(2).compare("WAVE", Qt::CaseInsensitive) || !rxFile.cap(2).compare("MP3", Qt::CaseInsensitive) || !rxFile.cap(2).compare("AIFF", Qt::CaseInsensitive)) + { + currentFile = new CueSheetFile(rxFile.cap(1)); + } + else + { + bUnsupportedTrack = true; + qWarning("Skipping unsupported file of type '%s'.", rxFile.cap(2).toUtf8().constData()); + currentFile = NULL; + } + bPreamble = false; + currentTrack = NULL; + continue; + } + + /* --- TRACK --- */ + if(rxTrack.indexIn(line) >= 0) + { + if(currentFile) + { + qDebug(" Track: <%s> <%s>", rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData()); + if(currentTrack) + { + if(currentTrack->isValid()) + { + if(currentTrack->title().isEmpty() && !albumTitle.isEmpty()) + { + currentTrack->setTitle(albumTitle); + } + if(currentTrack->performer().isEmpty() && !albumPerformer.isEmpty()) + { + currentTrack->setPerformer(albumPerformer); + } + currentFile->addTrack(currentTrack); + currentTrack = NULL; + } + else + { + LAMEXP_DELETE(currentTrack); + } + } + if(!rxTrack.cap(2).compare("AUDIO", Qt::CaseInsensitive)) + { + currentTrack = new CueSheetTrack(currentFile, rxTrack.cap(1).toInt()); + } + else + { + bUnsupportedTrack = true; + qWarning(" Skipping unsupported track of type '%s'.", rxTrack.cap(2).toUtf8().constData()); + currentTrack = NULL; + } + } + else + { + LAMEXP_DELETE(currentTrack); + } + bPreamble = false; + continue; + } + + /* --- INDEX --- */ + if(rxIndex.indexIn(line) >= 0) + { + if(currentFile && currentTrack) + { + qDebug(" Index: <%s> <%s>", rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData()); + if(rxIndex.cap(1).toInt() == 1) + { + currentTrack->setStartIndex(parseTimeIndex(rxIndex.cap(2))); + } + } + continue; + } + + /* --- TITLE --- */ + if(rxTitle.indexIn(line) >= 0) + { + if(bPreamble) + { + albumTitle = rxTitle.cap(1); + } + else if(currentFile && currentTrack) + { + qDebug(" Title: <%s>", rxTitle.cap(1).toUtf8().constData()); + currentTrack->setTitle(rxTitle.cap(1)); + } + continue; + } + + /* --- PERFORMER --- */ + if(rxPerformer.indexIn(line) >= 0) + { + if(bPreamble) + { + albumPerformer = rxPerformer.cap(1); + } + else if(currentFile && currentTrack) + { + qDebug(" Title: <%s>", rxPerformer.cap(1).toUtf8().constData()); + currentTrack->setPerformer(rxPerformer.cap(1)); + } + continue; + } + } + + //Finally append the very last track/file + if(currentFile) + { + if(currentTrack) + { + if(currentTrack->isValid()) + { + if(currentTrack->title().isEmpty() && !albumTitle.isEmpty()) + { + currentTrack->setTitle(albumTitle); + } + if(currentTrack->performer().isEmpty() && !albumPerformer.isEmpty()) + { + currentTrack->setPerformer(albumPerformer); + } + currentFile->addTrack(currentTrack); + currentTrack = NULL; + } + else + { + LAMEXP_DELETE(currentTrack); + } + } + if(currentFile->trackCount() > 0) + { + m_files.append(currentFile); + currentFile = NULL; + } + else + { + LAMEXP_DELETE(currentFile); + } + } + + return (m_files.count() > 0) ? 0 : (bUnsupportedTrack ? 3 : 2); +} + +double CueSheetModel::parseTimeIndex(const QString &index) +{ + QRegExp rxTimeIndex("\\s*(\\d+)\\s*:\\s*(\\d+)\\s*:\\s*(\\d+)\\s*"); + + if(rxTimeIndex.indexIn(index) >= 0) + { + int min, sec, frm; + bool minOK, secOK, frmOK; + + min = rxTimeIndex.cap(1).toInt(&minOK); + sec = rxTimeIndex.cap(2).toInt(&secOK); + frm = rxTimeIndex.cap(3).toInt(&frmOK); + + if(minOK && secOK && frmOK) + { + return static_cast(60 * min) + static_cast(sec) + ((1.0/75.0) * static_cast(frm)); + } + } + + qWarning(" Bad time index: '%s'", index.toUtf8().constData()); + return std::numeric_limits::quiet_NaN(); +} + +QString CueSheetModel::indexToString(const double index) const +{ + int temp = static_cast(index * 100.0); + + int msec = temp % 100; + int secs = temp / 100; + + return QString().sprintf("%02d:%02d.%02d", (secs / 60), (secs % 60), msec); +} diff --git a/src/Model_CueSheet.h b/src/Model_CueSheet.h index 0f23aa33..7cad7aa8 100644 --- a/src/Model_CueSheet.h +++ b/src/Model_CueSheet.h @@ -44,6 +44,12 @@ public: QModelIndex parent(const QModelIndex &child) const; void clearData(void); + //Cue Sheet functions + int loadCueSheet(const QString &cueFile); + private: + int parseCueFile(QFile &cueFile); + double parseTimeIndex(const QString &index); + QString indexToString(const double index) const; QList m_files; };