diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index db0390738..63755fe53 100644 --- a/src/core/songloader.cpp +++ b/src/core/songloader.cpp @@ -20,6 +20,7 @@ #include "library/librarybackend.h" #include "library/sqlrow.h" #include "playlistparsers/parserbase.h" +#include "playlistparsers/cueparser.h" #include "playlistparsers/playlistparser.h" #include "radio/fixlastfm.h" @@ -39,6 +40,7 @@ SongLoader::SongLoader(LibraryBackendInterface* library, QObject *parent) : QObject(parent), timeout_timer_(new QTimer(this)), playlist_parser_(new PlaylistParser(library, this)), + cue_parser_(new CueParser(library, this)), timeout_(kDefaultTimeout), state_(WaitingForType), success_(false), @@ -130,17 +132,46 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block, // Not a playlist, so just assume it's a song QFileInfo info(filename); + LibraryQuery query; query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); query.AddWhere("filename", info.canonicalFilePath()); - Song song; + + SongList song_list; + if (library_->ExecQuery(&query) && query.Next()) { - song.InitFromQuery(query); + // we may have many results when the file has many sections + do { + Song song; + song.InitFromQuery(query); + + song_list << song; + } while(query.Next()); } else { - song.InitFromFile(filename, -1); + QString matching_cue = filename.section('.', 0, -2) + ".cue"; + + // it's a cue - create virtual tracks + if(QFile::exists(matching_cue)) { + QFile cue(matching_cue); + cue.open(QIODevice::ReadOnly); + + song_list = cue_parser_->Load(&cue, QDir(filename.section('/', 0, -2))); + + // it's a normal media file + } else { + Song song; + song.InitFromFile(filename, -1); + + song_list << song; + + } } - if (song.is_valid()) - songs_ << song; + + foreach(const Song& song, song_list) { + if (song.is_valid()) + songs_ << song; + } + return Success; } diff --git a/src/core/songloader.h b/src/core/songloader.h index dc543377f..9b766435a 100644 --- a/src/core/songloader.h +++ b/src/core/songloader.h @@ -27,6 +27,7 @@ #include +class CueParser; class LibraryBackendInterface; class ParserBase; class PlaylistParser; @@ -97,6 +98,7 @@ private: QTimer* timeout_timer_; PlaylistParser* playlist_parser_; + CueParser* cue_parser_; // For async loads int timeout_; diff --git a/src/playlistparsers/cueparser.cpp b/src/playlistparsers/cueparser.cpp index 037737c37..cebe09ad2 100644 --- a/src/playlistparsers/cueparser.cpp +++ b/src/playlistparsers/cueparser.cpp @@ -23,7 +23,7 @@ #include #include -const char* CueParser::kFileLineRegExp = "([^ \t\r\n]+)\\s+(?:\"([^\"]+)\"|([^ \t\r\n]+))\\s*(?:\"([^\"]+)\"|([^ \t\r\n]+))?"; +const char* CueParser::kFileLineRegExp = "(\\S+)\\s+(?:\"([^\"]+)\"|(\\S+))\\s*(?:\"([^\"]+)\"|(\\S+))?"; const char* CueParser::kIndexRegExp = "(\\d{2}):(\\d{2}):(\\d{2})"; const char* CueParser::kPerformer = "performer"; @@ -33,9 +33,6 @@ 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) @@ -228,6 +225,7 @@ bool CueParser::UpdateSong(const CueEntry& entry, const QString& next_index, Son song->Init(entry.title, entry.PrettyArtist(), entry.album, beginning, end); + song->set_albumartist(entry.album_artist); return true; } @@ -245,6 +243,7 @@ bool CueParser::UpdateLastSong(const CueEntry& entry, Song* song) const { song->set_title(entry.title); song->set_artist(entry.PrettyArtist()); song->set_album(entry.album); + song->set_albumartist(entry.album_artist); // we don't do anything with the end here because it's already set to // the end of the media file (if it exists) diff --git a/tests/cueparser_test.cpp b/tests/cueparser_test.cpp index d3e93636e..46e0f0368 100644 --- a/tests/cueparser_test.cpp +++ b/tests/cueparser_test.cpp @@ -50,6 +50,7 @@ TEST_F(CueParserTest, ParsesASong) { Song first_song = song_list.at(0); ASSERT_EQ("Un soffio caldo", first_song.title()); ASSERT_EQ("Zucchero", first_song.artist()); + ASSERT_EQ("Zucchero himself", first_song.albumartist()); ASSERT_EQ("", first_song.album()); ASSERT_EQ(1, first_song.beginning()); } @@ -68,6 +69,7 @@ TEST_F(CueParserTest, ParsesTwoSongs) { ASSERT_EQ("Un soffio caldo", first_song.title()); ASSERT_EQ("Chocabeck", first_song.album()); ASSERT_EQ("Zucchero himself", first_song.artist()); + ASSERT_EQ("Zucchero himself", first_song.albumartist()); ASSERT_EQ(1, first_song.beginning()); ASSERT_EQ((5 * 60 + 3) - 1, first_song.length()); @@ -75,5 +77,34 @@ TEST_F(CueParserTest, ParsesTwoSongs) { ASSERT_EQ("Somewon Else's Tears", second_song.title()); ASSERT_EQ("Chocabeck", second_song.album()); ASSERT_EQ("Zucchero himself", second_song.artist()); + ASSERT_EQ("Zucchero himself", second_song.albumartist()); ASSERT_EQ(5 * 60 + 3, second_song.beginning()); } + +TEST_F(CueParserTest, SkipsBrokenSongs) { + QFile file(":testdata/brokensong.cue"); + file.open(QIODevice::ReadOnly); + + SongList song_list = parser_.Load(&file, QDir("")); + + // two songs (the broken one is not in the list) + 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("Zucchero himself", first_song.albumartist()); + ASSERT_EQ(1, first_song.beginning()); + // includes the broken song too; this entry will span from it's + // INDEX (beginning) to the end of the next correct song + ASSERT_EQ((5 * 60) - 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("Zucchero himself", second_song.albumartist()); + ASSERT_EQ(5 * 60, second_song.beginning()); +} diff --git a/tests/data/brokensong.cue b/tests/data/brokensong.cue new file mode 100644 index 000000000..b6575518e --- /dev/null +++ b/tests/data/brokensong.cue @@ -0,0 +1,11 @@ +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 "No index - broken" + TRACK 02 AUDIO + TITLE "Somewon Else's Tears" + INDEX 01 05:00:00 diff --git a/tests/data/onesong.cue b/tests/data/onesong.cue index e1ab6d4af..e32f6204a 100644 --- a/tests/data/onesong.cue +++ b/tests/data/onesong.cue @@ -1,4 +1,4 @@ -PERFORMER "Zucchero" +PERFORMER "Zucchero himself" FILE "file.mp3" WAVE TRACK 01 AUDIO TITLE "Un soffio caldo" diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc index f4f4d5b58..f322fa5ac 100644 --- a/tests/data/testdata.qrc +++ b/tests/data/testdata.qrc @@ -7,6 +7,7 @@ beep.wav beep.wma beep.m4a + brokensong.cue onesong.cue twosongs.cue pls_one.pls