finalized the support for loading .cue based media using 'files' tab or drag and drop from the system's file browser
use FILE's PERFORMER as albumartist
This commit is contained in:
parent
34d496aadc
commit
59378166b4
|
@ -20,6 +20,7 @@
|
||||||
#include "library/librarybackend.h"
|
#include "library/librarybackend.h"
|
||||||
#include "library/sqlrow.h"
|
#include "library/sqlrow.h"
|
||||||
#include "playlistparsers/parserbase.h"
|
#include "playlistparsers/parserbase.h"
|
||||||
|
#include "playlistparsers/cueparser.h"
|
||||||
#include "playlistparsers/playlistparser.h"
|
#include "playlistparsers/playlistparser.h"
|
||||||
#include "radio/fixlastfm.h"
|
#include "radio/fixlastfm.h"
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ SongLoader::SongLoader(LibraryBackendInterface* library, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
timeout_timer_(new QTimer(this)),
|
timeout_timer_(new QTimer(this)),
|
||||||
playlist_parser_(new PlaylistParser(library, this)),
|
playlist_parser_(new PlaylistParser(library, this)),
|
||||||
|
cue_parser_(new CueParser(library, this)),
|
||||||
timeout_(kDefaultTimeout),
|
timeout_(kDefaultTimeout),
|
||||||
state_(WaitingForType),
|
state_(WaitingForType),
|
||||||
success_(false),
|
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
|
// Not a playlist, so just assume it's a song
|
||||||
QFileInfo info(filename);
|
QFileInfo info(filename);
|
||||||
|
|
||||||
LibraryQuery query;
|
LibraryQuery query;
|
||||||
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||||
query.AddWhere("filename", info.canonicalFilePath());
|
query.AddWhere("filename", info.canonicalFilePath());
|
||||||
Song song;
|
|
||||||
|
SongList song_list;
|
||||||
|
|
||||||
if (library_->ExecQuery(&query) && query.Next()) {
|
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 {
|
} 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;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
class CueParser;
|
||||||
class LibraryBackendInterface;
|
class LibraryBackendInterface;
|
||||||
class ParserBase;
|
class ParserBase;
|
||||||
class PlaylistParser;
|
class PlaylistParser;
|
||||||
|
@ -97,6 +98,7 @@ private:
|
||||||
|
|
||||||
QTimer* timeout_timer_;
|
QTimer* timeout_timer_;
|
||||||
PlaylistParser* playlist_parser_;
|
PlaylistParser* playlist_parser_;
|
||||||
|
CueParser* cue_parser_;
|
||||||
|
|
||||||
// For async loads
|
// For async loads
|
||||||
int timeout_;
|
int timeout_;
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
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::kIndexRegExp = "(\\d{2}):(\\d{2}):(\\d{2})";
|
||||||
|
|
||||||
const char* CueParser::kPerformer = "performer";
|
const char* CueParser::kPerformer = "performer";
|
||||||
|
@ -33,9 +33,6 @@ const char* CueParser::kTrack = "track";
|
||||||
const char* CueParser::kIndex = "index";
|
const char* CueParser::kIndex = "index";
|
||||||
const char* CueParser::kAudioTrackType = "audio";
|
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)
|
// TODO: utf and regexps (check on Zucchero - there's something wrong)
|
||||||
|
|
||||||
CueParser::CueParser(LibraryBackendInterface* library, QObject* parent)
|
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(),
|
song->Init(entry.title, entry.PrettyArtist(),
|
||||||
entry.album, beginning, end);
|
entry.album, beginning, end);
|
||||||
|
song->set_albumartist(entry.album_artist);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -245,6 +243,7 @@ bool CueParser::UpdateLastSong(const CueEntry& entry, Song* song) const {
|
||||||
song->set_title(entry.title);
|
song->set_title(entry.title);
|
||||||
song->set_artist(entry.PrettyArtist());
|
song->set_artist(entry.PrettyArtist());
|
||||||
song->set_album(entry.album);
|
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
|
// we don't do anything with the end here because it's already set to
|
||||||
// the end of the media file (if it exists)
|
// the end of the media file (if it exists)
|
||||||
|
|
|
@ -50,6 +50,7 @@ TEST_F(CueParserTest, ParsesASong) {
|
||||||
Song first_song = song_list.at(0);
|
Song first_song = song_list.at(0);
|
||||||
ASSERT_EQ("Un soffio caldo", first_song.title());
|
ASSERT_EQ("Un soffio caldo", first_song.title());
|
||||||
ASSERT_EQ("Zucchero", first_song.artist());
|
ASSERT_EQ("Zucchero", first_song.artist());
|
||||||
|
ASSERT_EQ("Zucchero himself", first_song.albumartist());
|
||||||
ASSERT_EQ("", first_song.album());
|
ASSERT_EQ("", first_song.album());
|
||||||
ASSERT_EQ(1, first_song.beginning());
|
ASSERT_EQ(1, first_song.beginning());
|
||||||
}
|
}
|
||||||
|
@ -68,6 +69,7 @@ TEST_F(CueParserTest, ParsesTwoSongs) {
|
||||||
ASSERT_EQ("Un soffio caldo", first_song.title());
|
ASSERT_EQ("Un soffio caldo", first_song.title());
|
||||||
ASSERT_EQ("Chocabeck", first_song.album());
|
ASSERT_EQ("Chocabeck", first_song.album());
|
||||||
ASSERT_EQ("Zucchero himself", first_song.artist());
|
ASSERT_EQ("Zucchero himself", first_song.artist());
|
||||||
|
ASSERT_EQ("Zucchero himself", first_song.albumartist());
|
||||||
ASSERT_EQ(1, first_song.beginning());
|
ASSERT_EQ(1, first_song.beginning());
|
||||||
ASSERT_EQ((5 * 60 + 3) - 1, first_song.length());
|
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("Somewon Else's Tears", second_song.title());
|
||||||
ASSERT_EQ("Chocabeck", second_song.album());
|
ASSERT_EQ("Chocabeck", second_song.album());
|
||||||
ASSERT_EQ("Zucchero himself", second_song.artist());
|
ASSERT_EQ("Zucchero himself", second_song.artist());
|
||||||
|
ASSERT_EQ("Zucchero himself", second_song.albumartist());
|
||||||
ASSERT_EQ(5 * 60 + 3, second_song.beginning());
|
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());
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
||||||
PERFORMER "Zucchero"
|
PERFORMER "Zucchero himself"
|
||||||
FILE "file.mp3" WAVE
|
FILE "file.mp3" WAVE
|
||||||
TRACK 01 AUDIO
|
TRACK 01 AUDIO
|
||||||
TITLE "Un soffio caldo"
|
TITLE "Un soffio caldo"
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<file>beep.wav</file>
|
<file>beep.wav</file>
|
||||||
<file>beep.wma</file>
|
<file>beep.wma</file>
|
||||||
<file>beep.m4a</file>
|
<file>beep.m4a</file>
|
||||||
|
<file>brokensong.cue</file>
|
||||||
<file>onesong.cue</file>
|
<file>onesong.cue</file>
|
||||||
<file>twosongs.cue</file>
|
<file>twosongs.cue</file>
|
||||||
<file>pls_one.pls</file>
|
<file>pls_one.pls</file>
|
||||||
|
|
Loading…
Reference in New Issue