From d7fe4600b8bccb5d02ccce6409ca1231309f1a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bara?= Date: Thu, 23 Dec 2010 21:13:43 +0000 Subject: [PATCH] an initial version of .cue sheets parser initial support for multipart media files in Song ('beginning' and 'end' fields) --- src/CMakeLists.txt | 2 + src/core/song.cpp | 54 +++-- src/core/song.h | 23 +- src/playlistparsers/cueparser.cpp | 289 +++++++++++++++++++++++++ src/playlistparsers/cueparser.h | 82 +++++++ src/playlistparsers/playlistparser.cpp | 2 + tests/CMakeLists.txt | 1 + tests/cueparser_test.cpp | 79 +++++++ tests/data/onesong.cue | 6 + tests/data/testdata.qrc | 2 + tests/data/twosongs.cue | 9 + 11 files changed, 528 insertions(+), 21 deletions(-) create mode 100644 src/playlistparsers/cueparser.cpp create mode 100644 src/playlistparsers/cueparser.h create mode 100644 tests/cueparser_test.cpp create mode 100644 tests/data/onesong.cue create mode 100644 tests/data/twosongs.cue diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a9ae0c1d4..c48093264 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,6 +116,7 @@ set(SOURCES playlistparsers/asxparser.cpp playlistparsers/asxiniparser.cpp + playlistparsers/cueparser.cpp playlistparsers/m3uparser.cpp playlistparsers/parserbase.cpp playlistparsers/playlistparser.cpp @@ -279,6 +280,7 @@ set(HEADERS playlistparsers/asxparser.h playlistparsers/asxiniparser.h + playlistparsers/cueparser.h playlistparsers/m3uparser.h playlistparsers/parserbase.h playlistparsers/playlistparser.h diff --git a/src/core/song.cpp b/src/core/song.cpp index a49bdb710..c1fc47270 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -158,7 +158,8 @@ Song::Private::Private() skipcount_(0), lastplayed_(-1), score_(0), - length_(-1), + beginning_(0), + end_(-1), bitrate_(-1), samplerate_(-1), directory_id_(-1), @@ -193,10 +194,23 @@ Song::Song(FileRefFactory* factory) void Song::Init(const QString& title, const QString& artist, const QString& album, int length) { d->valid_ = true; + d->title_ = title; d->artist_ = artist; d->album_ = album; - d->length_ = length; + + set_length(length); +} + +void Song::Init(const QString& title, const QString& artist, const QString& album, int beginning, int end) { + d->valid_ = true; + + d->title_ = title; + d->artist_ = artist; + d->album_ = album; + + d->beginning_ = beginning; + d->end_ = end; } void Song::set_genre(int id) { @@ -339,8 +353,8 @@ void Song::InitFromFile(const QString& filename, int directory_id) { if (fileref->audioProperties()) { d->bitrate_ = fileref->audioProperties()->bitrate(); - d->length_ = fileref->audioProperties()->length(); d->samplerate_ = fileref->audioProperties()->sampleRate(); + set_length(fileref->audioProperties()->length()); } // Get the filetype if we can @@ -452,7 +466,8 @@ void Song::InitFromQuery(const SqlRow& q, int col) { d->comment_ = tostr(col + 11); d->compilation_ = q.value(col + 12).toBool(); - d->length_ = toint(col + 13); + // TODO: this should be replaced by beginning and end + set_length(toint(col + 13)); d->bitrate_ = toint(col + 14); d->samplerate_ = toint(col + 15); @@ -496,7 +511,8 @@ void Song::InitFromLastFM(const lastfm::Track& track) { d->album_ = track.album(); d->artist_ = track.artist(); d->track_ = track.trackNumber(); - d->length_ = track.duration(); + + set_length(track.duration()); } #endif // HAVE_LIBLASTFM @@ -516,7 +532,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { d->genre_ = QString::fromUtf8(track->genre); d->comment_ = QString::fromUtf8(track->comment); d->compilation_ = track->compilation; - d->length_ = track->tracklen / 1000; + set_length(track->tracklen / 1000); d->bitrate_ = track->bitrate; d->samplerate_ = track->samplerate; d->mtime_ = track->time_modified; @@ -546,7 +562,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { track->genre = strdup(d->genre_.toUtf8().constData()); track->comment = strdup(d->comment_.toUtf8().constData()); track->compilation = d->compilation_; - track->tracklen = d->length_ * 1000; + track->tracklen = length() * 1000; track->bitrate = d->bitrate_; track->samplerate = d->samplerate_; track->time_modified = d->mtime_; @@ -575,7 +591,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { d->basefilename_ = d->filename_; d->track_ = track->tracknumber; - d->length_ = track->duration / 1000; + set_length(track->duration / 1000); d->samplerate_ = track->samplerate; d->bitrate_ = track->bitrate; d->filesize_ = track->filesize; @@ -615,7 +631,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { track->filename = strdup(d->basefilename_.toUtf8().constData()); track->tracknumber = d->track_; - track->duration = d->length_ * 1000; + track->duration = length() * 1000; track->samplerate = d->samplerate_; track->nochannels = 0; track->wavecodec = 0; @@ -749,7 +765,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { d->filename_ = item_value.toString(); else if (wcscmp(name, g_wszWMDMDuration) == 0) - d->length_ = item_value.toULongLong() / 10000000ll; + set_length(item_value.toULongLong() / 10000000ll); else if (wcscmp(name, L"WMDM/FileSize") == 0) d->filesize_ = item_value.toULongLong(); @@ -813,7 +829,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { if (!d->title_.isEmpty() || !d->artist_.isEmpty() || !d->album_.isEmpty() || !d->comment_.isEmpty() || !d->genre_.isEmpty() || d->track_ != -1 || d->year_ != -1 || - d->length_ != -1) { + length() != -1) { d->filetype_ = Song::Type_Unknown; break; } @@ -847,7 +863,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) { AddWmdmItem(metadata, g_wszWMDMComposer, d->composer_); AddWmdmItem(metadata, g_wszWMDMBitrate, d->bitrate_); AddWmdmItem(metadata, g_wszWMDMFileName, d->basefilename_); - AddWmdmItem(metadata, g_wszWMDMDuration, qint64(d->length_) * 10000000ll); + AddWmdmItem(metadata, g_wszWMDMDuration, qint64(length()) * 10000000ll); AddWmdmItem(metadata, L"WMDM/FileSize", d->filesize_); WMDM_FORMATCODE format; @@ -885,7 +901,7 @@ void Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) { if (!bundle.genre.isEmpty()) d->genre_ = Decode(bundle.genre, codec); if (!bundle.bitrate.isEmpty()) d->bitrate_ = bundle.bitrate.toInt(); if (!bundle.samplerate.isEmpty()) d->samplerate_ = bundle.samplerate.toInt(); - if (!bundle.length.isEmpty()) d->length_ = bundle.length.toInt(); + if (!bundle.length.isEmpty()) set_length(bundle.length.toInt()); if (!bundle.year.isEmpty()) d->year_ = bundle.year.toInt(); if (!bundle.tracknr.isEmpty()) d->track_ = bundle.tracknr.toInt(); } @@ -910,7 +926,8 @@ void Song::BindToQuery(QSqlQuery *query) const { query->bindValue(":comment", strval(d->comment_)); query->bindValue(":compilation", d->compilation_ ? 1 : 0); - query->bindValue(":length", intval(d->length_)); + // TODO: replace this with beginning and end + query->bindValue(":length", intval(length())); query->bindValue(":bitrate", intval(d->bitrate_)); query->bindValue(":samplerate", intval(d->samplerate_)); @@ -959,7 +976,7 @@ void Song::ToLastFM(lastfm::Track* track) const { mtrack.setArtist(d->artist_); mtrack.setAlbum(d->album_); mtrack.setTitle(d->title_); - mtrack.setDuration(d->length_); + mtrack.setDuration(length()); mtrack.setTrackNumber(d->track_); mtrack.setSource(lastfm::Track::Player); } @@ -987,10 +1004,10 @@ QString Song::PrettyTitleWithArtist() const { } QString Song::PrettyLength() const { - if (d->length_ == -1) + if (length() == -1) return QString::null; - return Utilities::PrettyTime(d->length_); + return Utilities::PrettyTime(length()); } QString Song::PrettyYear() const { @@ -1025,7 +1042,8 @@ bool Song::IsMetadataEqual(const Song& other) const { d->genre_ == other.d->genre_ && d->comment_ == other.d->comment_ && d->compilation_ == other.d->compilation_ && - d->length_ == other.d->length_ && + // this should be replaced by beginning and end + length() == other.length() && d->bitrate_ == other.d->bitrate_ && d->samplerate_ == other.d->samplerate_ && d->sampler_ == other.d->sampler_ && diff --git a/src/core/song.h b/src/core/song.h index ac0177142..15b47dd86 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -112,6 +112,7 @@ class Song { // Constructors void Init(const QString& title, const QString& artist, const QString& album, int length); + void Init(const QString& title, const QString& artist, const QString& album, int beginning, int end); void InitFromFile(const QString& filename, int directory_id); void InitFromQuery(const SqlRow& query, int col = 0); #ifdef HAVE_LIBLASTFM @@ -170,7 +171,11 @@ class Song { int lastplayed() const { return d->lastplayed_; } int score() const { return d->score_; } - int length() const { return d->length_; } + int beginning() const { return d->beginning_; } + int end() const { return d->end_; } + + int length() const { return d->end_ - d->beginning_; } + int bitrate() const { return d->bitrate_; } int samplerate() const { return d->samplerate_; } @@ -217,7 +222,9 @@ class Song { void set_comment(const QString& v) { d->comment_ = v; } void set_compilation(bool v) { d->compilation_ = v; } void set_sampler(bool v) { d->sampler_ = v; } - void set_length(int v) { d->length_ = v; } + void set_beginning(int v) { d->beginning_ = v; } + void set_end(int v) { d->end_ = v; } + void set_length(int v) { d->end_ = d->beginning_ + v; } void set_bitrate(int v) { d->bitrate_ = v; } void set_samplerate(int v) { d->samplerate_ = v; } void set_mtime(int v) { d->mtime_ = v; } @@ -281,7 +288,17 @@ class Song { int lastplayed_; int score_; - int length_; // Seconds. + // The beginning of the song in seconds. In case of single-part media + // streams, this will equal to 0. In case of multi-part streams on the + // other hand, this will mark the beginning of a section represented by + // this Song object. + int beginning_; + // The end of the song in seconds. In case of single-part media + // streams, this will equal to the song's length. In case of multi-part + // streams on the other hand, this will mark the end of a section + // represented by this Song object. + int end_; + int bitrate_; int samplerate_; diff --git a/src/playlistparsers/cueparser.cpp b/src/playlistparsers/cueparser.cpp new file mode 100644 index 000000000..596239b87 --- /dev/null +++ b/src/playlistparsers/cueparser.cpp @@ -0,0 +1,289 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "cueparser.h" + +#include +#include +#include +#include +#include + +const char* CueParser::kFileLineRegExp = "([^ \t\r\n]+)\\s+(?:\"([^\"]+)\"|([^ \t\r\n]+))\\s*(?:\"([^\"]+)\"|([^ \t\r\n]+))?"; +const char* CueParser::kIndexRegExp = "(\\d{2}):(\\d{2}):(\\d{2})"; + +const char* CueParser::kPerformer = "performer"; +const char* CueParser::kTitle = "title"; +const char* CueParser::kFile = "file"; +const char* CueParser::kTrack = "track"; +const char* CueParser::kIndex = "index"; +const char* CueParser::kAudioTrackType = "audio"; + +// TODO: if some song misses it's next one (because the next one was somehow +// broken), we need to discard the song too (can't really determine where it +// ends +// TODO: utf and regexps (check on Zucchero - there's something wrong) + +CueParser::CueParser(LibraryBackendInterface* library, QObject* parent) + : ParserBase(library, parent) +{ +} + +SongList CueParser::Load(QIODevice* device, const QDir& dir) const { + SongList ret; + + QTextStream text_stream(device); + QString dir_path = dir.absolutePath(); + + QString line; + + QString album_artist; + QString album; + QString file; + + // header + while (!(line = text_stream.readLine()).isNull()) { + QStringList splitted = SplitCueLine(line); + + // uninteresting or incorrect line + if(splitted.size() < 2) { + continue; + } + + QString line_name = splitted[0].toLower(); + QString line_value = splitted[1]; + + // PERFORMER + if(line_name == kPerformer) { + + album_artist = line_value; + + // TITLE + } else if(line_name == kTitle) { + + album = line_value; + + // FILE + } else if(line_name == kFile) { + + file = QDir::isAbsolutePath(line_value) + ? line_value + : dir.absoluteFilePath(line_value); + + // end of the header -> go into the track mode + } else if(line_name == kTrack) { + + break; + + } + + // just ignore the rest of possible field types for now... + } + + if(line.isNull()) { + qWarning() << "the .cue file from " << dir_path << " defines no tracks!"; + return ret; + } + + QString track_type; + QString index; + QString artist; + QString title; + + QList entries; + + // tracks + do { + QStringList splitted = SplitCueLine(line); + + // uninteresting or incorrect line + if(splitted.size() < 2) { + continue; + } + + QString line_name = splitted[0].toLower(); + QString line_value = splitted[1]; + QString line_additional = splitted.size() > 2 ? splitted[2].toLower() : ""; + + if(line_name == kTrack) { + + // the beginning of another track's definition - we're saving the current one + // for later (if it's valid of course) + if(!index.isEmpty() && (track_type.isEmpty() || track_type == kAudioTrackType)) { + entries.append(CueEntry(file, index, title, artist, album_artist, album)); + } + + // clear the state + track_type = index = artist = title = ""; + + if(!line_additional.isEmpty()) { + track_type = line_additional; + } + + } else if(line_name == kIndex) { + + // we need the index's position field + if(!line_additional.isEmpty()) { + + // if there's none "01" index, we'll just take the first one + // also, we'll take the "01" index even if it's the last one + if(line_value == "01" || index.isEmpty()) { + + index = line_additional; + + } + + } + + } else if(line_name == kPerformer) { + + artist = line_value; + + } else if(line_name == kTitle) { + + title = line_value; + + } + + // just ignore the rest of possible field types for now... + } while(!(line = text_stream.readLine()).isNull()); + + // we didn't add the last song yet... + if(!index.isEmpty() && (track_type.isEmpty() || track_type == kAudioTrackType)) { + entries.append(CueEntry(file, index, title, artist, album_artist, album)); + } + + // finalize parsing songs + for(int i = 0; i < entries.length(); i++) { + CueEntry entry = entries.at(i); + + Song current; + if (!ParseTrackLocation(entry.file, dir, ¤t)) { + qWarning() << "failed to parse location in .cue file from " << dir_path; + + } else { + // overwrite the stuff, we may have read from the file, using + // the .cue's metadata + if(i + 1 < entries.size()) { + // incorrect indices? + if(!UpdateSong(entry, entries.at(i + 1).index, ¤t)) { + continue; + } + } else { + // incorrect index? + if(!UpdateLastSong(entry, ¤t)) { + continue; + } + } + + Song final; + // TODO: make this work + // load this song from the library if it's there + // Song final = LoadLibrarySong(current.filename()); + + Song to_add = final.is_valid() ? final : current; + + ret << to_add; + } + } + + return ret; +} + +// This and the kFileLineRegExp do most of the "dirty" work, namely: splitting the raw .cue +// line into logical parts and getting rid of all the unnecessary whitespaces and quoting. +// This also validates the input: if returned list has less than two positions, the given +// line should be considered irrelevant. +QStringList CueParser::SplitCueLine(const QString& line) const { + QRegExp line_regexp(kFileLineRegExp); + if(!line_regexp.exactMatch(line.trimmed())) { + return QStringList(); + } + + // let's remove the empty entries while we're at it + return line_regexp.capturedTexts().filter(QRegExp(".+")).mid(1, -1); +} + +// Updates the song with data from the .cue entry. This one mustn't be used for the +// last song in the .cue file. +bool CueParser::UpdateSong(const CueEntry& entry, const QString& next_index, Song* song) const { + int beginning = IndexToMarker(entry.index); + int end = IndexToMarker(next_index); + + // incorrect indices (we won't be able to calculate beginning or end) + if(beginning == -1 || end == -1) { + return false; + } + + song->Init(entry.title, entry.PrettyArtist(), + entry.album, beginning, end); + + return true; +} + +// Updates the song with data from the .cue entry. This one must be used only for the +// last song in the .cue file. +bool CueParser::UpdateLastSong(const CueEntry& entry, Song* song) const { + int beginning = IndexToMarker(entry.index); + + // incorrect index (we won't be able to calculate beginning) + if(beginning == -1) { + return false; + } + + song->set_title(entry.title); + song->set_artist(entry.PrettyArtist()); + song->set_album(entry.album); + + // we don't do anything with the end here because it's already set to + // the end of the media file (if it exists) + song->set_beginning(beginning); + + return true; +} + +int CueParser::IndexToMarker(const QString& index) const { + QRegExp index_regexp(kIndexRegExp); + if(!index_regexp.exactMatch(index)) { + return -1; + } + + QStringList splitted = index_regexp.capturedTexts().mid(1, -1); + // TODO: use frames when #1166 is fixed + return splitted.at(0).toInt() * 60 + splitted.at(1).toInt(); +} + +void CueParser::Save(const SongList &songs, QIODevice *device, const QDir &dir) const { + // TODO +} + +// Looks for a track starting with one of the .cue's keywords. +bool CueParser::TryMagic(const QByteArray &data) const { + QStringList splitted = QString::fromUtf8(data.constData()).split('\n'); + + for(int i = 0; i < splitted.length(); i++) { + QString line = splitted.at(i).trimmed(); + if(line.startsWith(kPerformer, Qt::CaseInsensitive) || + line.startsWith(kTitle, Qt::CaseInsensitive) || + line.startsWith(kFile, Qt::CaseInsensitive) || + line.startsWith(kTrack, Qt::CaseInsensitive)) { + return true; + } + } + + return false; +} diff --git a/src/playlistparsers/cueparser.h b/src/playlistparsers/cueparser.h new file mode 100644 index 000000000..9569f07ae --- /dev/null +++ b/src/playlistparsers/cueparser.h @@ -0,0 +1,82 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef CUEPARSER_H +#define CUEPARSER_H + +#include "parserbase.h" + +#include + +class CueParser : public ParserBase { + Q_OBJECT + + public: + static const char* kFileLineRegExp; + static const char* kIndexRegExp; + + static const char* kPerformer; + static const char* kTitle; + static const char* kFile; + static const char* kTrack; + static const char* kIndex; + static const char* kAudioTrackType; + + CueParser(LibraryBackendInterface* library, QObject* parent = 0); + + QString name() const { return "CUE"; } + QStringList file_extensions() const { return QStringList() << "cue"; } + QString mime_type() const { return "application/x-cue"; } + + bool TryMagic(const QByteArray& data) const; + + SongList Load(QIODevice* device, const QDir& dir = QDir()) const; + void Save(const SongList& songs, QIODevice* device, const QDir& dir = QDir()) const; + + private: + // A single TRACK entry in .cue file. + struct CueEntry { + QString file; + + QString index; + + QString title; + QString artist; + QString album_artist; + QString album; + + QString PrettyArtist() const { return artist.isEmpty() ? album_artist : artist; } + + CueEntry(QString& file, QString& index, QString& title, QString& artist, + QString& album_artist, QString& album) { + this->file = file; + this->index = index; + this->title = title; + this->artist = artist; + this->album_artist = album_artist; + this->album = album; + } + }; + + bool UpdateSong(const CueEntry& entry, const QString& next_index, Song* song) const; + bool UpdateLastSong(const CueEntry& entry, Song* song) const; + + QStringList SplitCueLine(const QString& line) const; + int IndexToMarker(const QString& index) const; +}; + +#endif // CUEPARSER_H diff --git a/src/playlistparsers/playlistparser.cpp b/src/playlistparsers/playlistparser.cpp index 9ab9205ef..17b8236cc 100644 --- a/src/playlistparsers/playlistparser.cpp +++ b/src/playlistparsers/playlistparser.cpp @@ -19,6 +19,7 @@ #include "xspfparser.h" #include "m3uparser.h" #include "plsparser.h" +#include "cueparser.h" #include "asxparser.h" #include "asxiniparser.h" @@ -35,6 +36,7 @@ PlaylistParser::PlaylistParser(LibraryBackendInterface* library, QObject *parent parsers_ << new PLSParser(library, this); parsers_ << new ASXParser(library, this); parsers_ << new AsxIniParser(library, this); + parsers_ << new CueParser(library, this); } QStringList PlaylistParser::file_extensions() const { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e6fbbd649..20f02443c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -98,6 +98,7 @@ endmacro (add_test_file) add_test_file(albumcovermanager_test.cpp true) add_test_file(asxparser_test.cpp false) add_test_file(asxiniparser_test.cpp false) +add_test_file(cueparser_test.cpp false) add_test_file(database_test.cpp false) add_test_file(fileformats_test.cpp false) add_test_file(fmpsparser_test.cpp false) diff --git a/tests/cueparser_test.cpp b/tests/cueparser_test.cpp new file mode 100644 index 000000000..d3e93636e --- /dev/null +++ b/tests/cueparser_test.cpp @@ -0,0 +1,79 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + + +#include "gmock/gmock-matchers.h" +#include "gtest/gtest.h" +#include "test_utils.h" +#include "mock_taglib.h" + +#include "playlistparsers/cueparser.h" + +class CueParserTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + testing::DefaultValue::Set("foobarbaz"); + } + + CueParserTest() + : parser_(NULL) { + } + + CueParser parser_; + MockFileRefFactory taglib_; +}; + +TEST_F(CueParserTest, ParsesASong) { + QFile file(":testdata/onesong.cue"); + file.open(QIODevice::ReadOnly); + + SongList song_list = parser_.Load(&file, QDir("")); + + // one song + ASSERT_EQ(1, song_list.size()); + + // with the specified metadata + Song first_song = song_list.at(0); + ASSERT_EQ("Un soffio caldo", first_song.title()); + ASSERT_EQ("Zucchero", first_song.artist()); + ASSERT_EQ("", first_song.album()); + ASSERT_EQ(1, first_song.beginning()); +} + +TEST_F(CueParserTest, ParsesTwoSongs) { + QFile file(":testdata/twosongs.cue"); + file.open(QIODevice::ReadOnly); + + SongList song_list = parser_.Load(&file, QDir("")); + + // two songs + ASSERT_EQ(2, song_list.size()); + + // with the specified metadata + Song first_song = song_list.at(0); + ASSERT_EQ("Un soffio caldo", first_song.title()); + ASSERT_EQ("Chocabeck", first_song.album()); + ASSERT_EQ("Zucchero himself", first_song.artist()); + ASSERT_EQ(1, first_song.beginning()); + ASSERT_EQ((5 * 60 + 3) - 1, first_song.length()); + + Song second_song = song_list.at(1); + ASSERT_EQ("Somewon Else's Tears", second_song.title()); + ASSERT_EQ("Chocabeck", second_song.album()); + ASSERT_EQ("Zucchero himself", second_song.artist()); + ASSERT_EQ(5 * 60 + 3, second_song.beginning()); +} diff --git a/tests/data/onesong.cue b/tests/data/onesong.cue new file mode 100644 index 000000000..e1ab6d4af --- /dev/null +++ b/tests/data/onesong.cue @@ -0,0 +1,6 @@ +PERFORMER "Zucchero" +FILE "file.mp3" WAVE + TRACK 01 AUDIO + TITLE "Un soffio caldo" + PERFORMER Zucchero + INDEX 01 00:01:00 diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc index e2f318a72..f4f4d5b58 100644 --- a/tests/data/testdata.qrc +++ b/tests/data/testdata.qrc @@ -7,6 +7,8 @@ beep.wav beep.wma beep.m4a + onesong.cue + twosongs.cue pls_one.pls pls_somafm.pls test.m3u diff --git a/tests/data/twosongs.cue b/tests/data/twosongs.cue new file mode 100644 index 000000000..c2add71b1 --- /dev/null +++ b/tests/data/twosongs.cue @@ -0,0 +1,9 @@ +PERFORMER "Zucchero himself" +TITLE "Chocabeck" +FILE files/longer.mp3 WAVE + TRACK 01 AUDIO + TITLE "Un soffio caldo" + INDEX 01 00:01:00 + TRACK 02 AUDIO + TITLE "Somewon Else's Tears" + INDEX 01 05:03:68