mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 03:27:40 +01:00
Parse FMPS ratings and played counts from mp3 id3v2 tags
This commit is contained in:
parent
6c36198103
commit
eb9660edad
@ -42,6 +42,7 @@ set(SOURCES
|
||||
core/encoding.cpp
|
||||
core/filesystemmusicstorage.cpp
|
||||
core/fht.cpp
|
||||
core/fmpsparser.cpp
|
||||
core/globalshortcutbackend.cpp
|
||||
core/globalshortcuts.cpp
|
||||
core/gnomeglobalshortcutbackend.cpp
|
||||
|
98
src/core/fmpsparser.cpp
Normal file
98
src/core/fmpsparser.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
/* This file is part of Clementine.
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fmpsparser.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
FMPSParser::FMPSParser()
|
||||
: float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"),
|
||||
string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"),
|
||||
escape_re_("\\\\([\\\\:;])")
|
||||
{
|
||||
}
|
||||
|
||||
template <char Separator, typename F, typename T>
|
||||
static int ParseContainer(const QStringRef& data, F f, QList<T>* ret) {
|
||||
ret->clear();
|
||||
|
||||
T value;
|
||||
int pos = 0;
|
||||
while (pos < data.length()) {
|
||||
const int len = data.length() - pos;
|
||||
int matched_len = f(QStringRef(data.string(), data.position() + pos, len), &value);
|
||||
if (matched_len == -1 || matched_len > len)
|
||||
break;
|
||||
|
||||
ret->append(value);
|
||||
pos += matched_len;
|
||||
|
||||
if (pos + 2 <= data.length() && data.at(pos) == Separator
|
||||
&& data.at(pos+1) == Separator) {
|
||||
pos += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool FMPSParser::Parse(const QString& data) {
|
||||
result_ = Result();
|
||||
return ParseListList(data, &result_) == data.length();
|
||||
}
|
||||
|
||||
int FMPSParser::ParseValue(const QString& data, QVariant* ret) const {
|
||||
return ParseValueRef(QStringRef(&data), ret);
|
||||
}
|
||||
|
||||
int FMPSParser::ParseValueRef(const QStringRef& data, QVariant* ret) const {
|
||||
int pos = float_re_.indexIn(*data.string(), data.position());
|
||||
if (pos == data.position()) {
|
||||
*ret = float_re_.cap(1).toDouble();
|
||||
return float_re_.matchedLength();
|
||||
}
|
||||
|
||||
pos = string_re_.indexIn(*data.string(), data.position());
|
||||
if (pos == data.position()) {
|
||||
QString value = string_re_.cap(1);
|
||||
value.replace(escape_re_, "\\1");
|
||||
*ret = value;
|
||||
return string_re_.matchedLength();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FMPSParser::ParseList(const QString& data, QVariantList* ret) const {
|
||||
return ParseListRef(QStringRef(&data), ret);
|
||||
}
|
||||
|
||||
int FMPSParser::ParseListRef(const QStringRef& data, QVariantList* ret) const {
|
||||
return ParseContainer<':'>(data, boost::bind(&FMPSParser::ParseValueRef, this, _1, _2), ret);
|
||||
}
|
||||
|
||||
int FMPSParser::ParseListList(const QString& data, Result* ret) const {
|
||||
return ParseListListRef(QStringRef(&data), ret);
|
||||
}
|
||||
|
||||
int FMPSParser::ParseListListRef(const QStringRef& data, Result* ret) const {
|
||||
return ParseContainer<';'>(data, boost::bind(&FMPSParser::ParseListRef, this, _1, _2), ret);
|
||||
}
|
50
src/core/fmpsparser.h
Normal file
50
src/core/fmpsparser.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* This file is part of Clementine.
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FMPSPARSER_H
|
||||
#define FMPSPARSER_H
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QVariantList>
|
||||
|
||||
class FMPSParser {
|
||||
public:
|
||||
FMPSParser();
|
||||
|
||||
typedef QList<QVariantList> Result;
|
||||
|
||||
bool Parse(const QString& data);
|
||||
Result result() const { return result_; }
|
||||
|
||||
bool is_empty() const { return result().isEmpty() || result()[0].isEmpty(); }
|
||||
|
||||
int ParseValue(const QString& data, QVariant* ret) const;
|
||||
int ParseValueRef(const QStringRef& data, QVariant* ret) const;
|
||||
|
||||
int ParseList(const QString& data, QVariantList* ret) const;
|
||||
int ParseListRef(const QStringRef& data, QVariantList* ret) const;
|
||||
|
||||
int ParseListList(const QString& data, Result* ret) const;
|
||||
int ParseListListRef(const QStringRef& data, Result* ret) const;
|
||||
|
||||
private:
|
||||
QRegExp float_re_;
|
||||
QRegExp string_re_;
|
||||
QRegExp escape_re_;
|
||||
Result result_;
|
||||
};
|
||||
|
||||
#endif // FMPSPARSER_H
|
@ -14,6 +14,7 @@
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fmpsparser.h"
|
||||
#include "song.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -148,6 +149,8 @@ Song::Private::Private()
|
||||
sampler_(false),
|
||||
forced_compilation_on_(false),
|
||||
forced_compilation_off_(false),
|
||||
rating_(-1.0),
|
||||
playcount_(0),
|
||||
length_(-1),
|
||||
bitrate_(-1),
|
||||
samplerate_(-1),
|
||||
@ -279,6 +282,17 @@ void Song::InitFromFile(const QString& filename, int directory_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse FMPS frames
|
||||
for (int i=0 ; i<map["TXXX"].size() ; ++i) {
|
||||
const TagLib::ID3v2::UserTextIdentificationFrame* frame =
|
||||
dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i]);
|
||||
|
||||
if (frame->description().startsWith("FMPS_")) {
|
||||
ParseFMPSFrame(TStringToQString(frame->description()),
|
||||
TStringToQString(frame->fieldList()[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (TagLib::Ogg::Vorbis::File* file = dynamic_cast<TagLib::Ogg::Vorbis::File*>(fileref->file())) {
|
||||
if (file->tag()) {
|
||||
@ -322,6 +336,41 @@ void Song::InitFromFile(const QString& filename, int directory_id) {
|
||||
GuessFileType(fileref.get());
|
||||
}
|
||||
|
||||
void Song::ParseFMPSFrame(const QString& name, const QString& value) {
|
||||
FMPSParser parser;
|
||||
if (!parser.Parse(value) || parser.is_empty())
|
||||
return;
|
||||
|
||||
QVariant var;
|
||||
if (name == "FMPS_Rating") {
|
||||
var = parser.result()[0][0];
|
||||
if (var.type() == QVariant::Double) {
|
||||
d->rating_ = var.toDouble();
|
||||
}
|
||||
} else if (name == "FMPS_Rating_User") {
|
||||
// Take a user rating only if there's no rating already set
|
||||
if (d->rating_ == -1 && parser.result()[0].count() >= 2) {
|
||||
var = parser.result()[0][1];
|
||||
if (var.type() == QVariant::Double) {
|
||||
d->rating_ = var.toDouble();
|
||||
}
|
||||
}
|
||||
} else if (name == "FMPS_PlayCount") {
|
||||
var = parser.result()[0][0];
|
||||
if (var.type() == QVariant::Double) {
|
||||
d->playcount_ = var.toDouble();
|
||||
}
|
||||
} else if (name == "FMPS_PlayCount_User") {
|
||||
// Take a user rating only if there's no playcount already set
|
||||
if (d->rating_ == -1 && parser.result()[0].count() >= 2) {
|
||||
var = parser.result()[0][1];
|
||||
if (var.type() == QVariant::Double) {
|
||||
d->playcount_ = var.toDouble();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Song::ParseOggTag(const TagLib::Ogg::FieldListMap& map, const QTextCodec* codec,
|
||||
QString* disc, QString* compilation) {
|
||||
if (!map["COMPOSER"].isEmpty())
|
||||
@ -409,9 +458,9 @@ void Song::InitFromQuery(const SqlRow& q, int col) {
|
||||
d->art_manual_ = q.value(col + 23).toString();
|
||||
|
||||
d->filetype_ = FileType(q.value(col + 24).toInt());
|
||||
// playcount = 25
|
||||
d->playcount_ = q.value(col + 25).isNull() ? 0 : q.value(col + 25).toInt();
|
||||
// lastplayed = 26
|
||||
// rating = 27
|
||||
d->rating_ = tofloat(col + 27);
|
||||
|
||||
d->forced_compilation_on_ = q.value(col + 28).toBool();
|
||||
d->forced_compilation_off_ = q.value(col + 29).toBool();
|
||||
@ -457,6 +506,8 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
|
||||
d->ctime_ = track->time_added;
|
||||
d->filesize_ = track->size;
|
||||
d->filetype_ = track->type2 ? Type_Mpeg : Type_Mp4;
|
||||
d->rating_ = float(track->rating) / 100; // 100 = 20 * 5 stars
|
||||
d->playcount_ = track->playcount;
|
||||
|
||||
d->filename_ = QString::fromLocal8Bit(track->ipod_path);
|
||||
d->filename_.replace(':', '/');
|
||||
@ -485,6 +536,8 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
|
||||
track->type1 = 0;
|
||||
track->type2 = d->filetype_ == Type_Mp4 ? 0 : 1;
|
||||
track->mediatype = 1; // Audio
|
||||
track->rating = d->rating_ * 100; // 100 = 20 * 5 stars
|
||||
track->playcount = d->playcount_;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -508,6 +561,9 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
|
||||
d->mtime_ = track->modificationdate;
|
||||
d->ctime_ = track->modificationdate;
|
||||
|
||||
d->rating_ = float(track->rating) / 100;
|
||||
d->playcount_ = track->usecount;
|
||||
|
||||
switch (track->filetype) {
|
||||
case LIBMTP_FILETYPE_WAV: d->filetype_ = Type_Wav; break;
|
||||
case LIBMTP_FILETYPE_MP3: d->filetype_ = Type_Mpeg; break;
|
||||
@ -544,8 +600,8 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
|
||||
track->wavecodec = 0;
|
||||
track->bitrate = d->bitrate_;
|
||||
track->bitratetype = 0;
|
||||
track->rating = 0;
|
||||
track->usecount = 0;
|
||||
track->rating = d->rating_ * 100;
|
||||
track->usecount = d->playcount_;
|
||||
track->filesize = d->filesize_;
|
||||
track->modificationdate = d->mtime_;
|
||||
|
||||
@ -848,9 +904,9 @@ void Song::BindToQuery(QSqlQuery *query) const {
|
||||
query->bindValue(":art_manual", d->art_manual_);
|
||||
|
||||
query->bindValue(":filetype", d->filetype_);
|
||||
query->bindValue(":playcount", 0); // TODO
|
||||
query->bindValue(":playcount", d->playcount_);
|
||||
query->bindValue(":lastplayed", -1); // TODO
|
||||
query->bindValue(":rating", -1);
|
||||
query->bindValue(":rating", intval(d->rating_));
|
||||
|
||||
query->bindValue(":forced_compilation_on", d->forced_compilation_on_ ? 1 : 0);
|
||||
query->bindValue(":forced_compilation_off", d->forced_compilation_off_ ? 1 : 0);
|
||||
|
@ -157,6 +157,8 @@ class Song {
|
||||
return (d->compilation_ || d->sampler_ || d->forced_compilation_on_)
|
||||
&& ! d->forced_compilation_off_;
|
||||
}
|
||||
float rating() const { return d->rating_; }
|
||||
int playcount() const { return d->playcount_; }
|
||||
|
||||
int length() const { return d->length_; }
|
||||
int bitrate() const { return d->bitrate_; }
|
||||
@ -213,6 +215,8 @@ class Song {
|
||||
void set_image(const QImage& i) { d->image_ = i; }
|
||||
void set_forced_compilation_on(bool v) { d->forced_compilation_on_ = v; }
|
||||
void set_forced_compilation_off(bool v) { d->forced_compilation_off_ = v; }
|
||||
void set_rating(float v) { d->rating_ = v; }
|
||||
void set_playcount(int v) { d->playcount_ = v; }
|
||||
|
||||
// Setters that should only be used by tests
|
||||
void set_filename(const QString& v) { d->filename_ = v; }
|
||||
@ -229,6 +233,7 @@ class Song {
|
||||
// Helper methods for taglib
|
||||
static void SetTextFrame(const QString& id, const QString& value,
|
||||
TagLib::ID3v2::Tag* tag);
|
||||
void ParseFMPSFrame(const QString& name, const QString& value);
|
||||
|
||||
private:
|
||||
struct Private : public QSharedData {
|
||||
@ -253,6 +258,9 @@ class Song {
|
||||
bool forced_compilation_on_; // Set by the user
|
||||
bool forced_compilation_off_; // Set by the user
|
||||
|
||||
float rating_;
|
||||
int playcount_;
|
||||
|
||||
int length_;
|
||||
int bitrate_;
|
||||
int samplerate_;
|
||||
|
@ -100,6 +100,7 @@ add_test_file(asxparser_test.cpp false)
|
||||
add_test_file(asxiniparser_test.cpp false)
|
||||
add_test_file(database_test.cpp false)
|
||||
add_test_file(fileformats_test.cpp false)
|
||||
add_test_file(fmpsparser_test.cpp false)
|
||||
add_test_file(librarybackend_test.cpp false)
|
||||
add_test_file(librarymodel_test.cpp true)
|
||||
add_test_file(m3uparser_test.cpp false)
|
||||
|
BIN
tests/data/fmpsplaycount.mp3
Normal file
BIN
tests/data/fmpsplaycount.mp3
Normal file
Binary file not shown.
BIN
tests/data/fmpsplaycountboth.mp3
Normal file
BIN
tests/data/fmpsplaycountboth.mp3
Normal file
Binary file not shown.
BIN
tests/data/fmpsplaycountuser.mp3
Normal file
BIN
tests/data/fmpsplaycountuser.mp3
Normal file
Binary file not shown.
BIN
tests/data/fmpsrating.mp3
Normal file
BIN
tests/data/fmpsrating.mp3
Normal file
Binary file not shown.
BIN
tests/data/fmpsratingboth.mp3
Normal file
BIN
tests/data/fmpsratingboth.mp3
Normal file
Binary file not shown.
BIN
tests/data/fmpsratinguser.mp3
Normal file
BIN
tests/data/fmpsratinguser.mp3
Normal file
Binary file not shown.
@ -15,5 +15,11 @@
|
||||
<file>secretagent.asx</file>
|
||||
<file>secretagent.pls</file>
|
||||
<file>test.asxini</file>
|
||||
<file>fmpsplaycount.mp3</file>
|
||||
<file>fmpsplaycountboth.mp3</file>
|
||||
<file>fmpsplaycountuser.mp3</file>
|
||||
<file>fmpsrating.mp3</file>
|
||||
<file>fmpsratingboth.mp3</file>
|
||||
<file>fmpsratinguser.mp3</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
214
tests/fmpsparser_test.cpp
Normal file
214
tests/fmpsparser_test.cpp
Normal file
@ -0,0 +1,214 @@
|
||||
/* This file is part of Clementine.
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "test_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "core/fmpsparser.h"
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
class FMPSParserTest : public testing::Test {
|
||||
protected:
|
||||
FMPSParser parser_;
|
||||
};
|
||||
|
||||
TEST_F(FMPSParserTest, ParseFloats) {
|
||||
QVariant value;
|
||||
|
||||
EXPECT_EQ(1, parser_.ParseValue("0", &value));
|
||||
EXPECT_EQ(QVariant::Double, value.type());
|
||||
EXPECT_EQ(0, value.toDouble());
|
||||
|
||||
EXPECT_EQ(3, parser_.ParseValue("123", &value));
|
||||
EXPECT_EQ(QVariant::Double, value.type());
|
||||
EXPECT_EQ(123, value.toDouble());
|
||||
|
||||
EXPECT_EQ(3, parser_.ParseValue("0.0", &value));
|
||||
EXPECT_EQ(QVariant::Double, value.type());
|
||||
EXPECT_EQ(0, value.toDouble());
|
||||
|
||||
EXPECT_EQ(2, parser_.ParseValue("-1", &value));
|
||||
EXPECT_EQ(QVariant::Double, value.type());
|
||||
EXPECT_EQ(-1, value.toDouble());
|
||||
|
||||
EXPECT_EQ(5, parser_.ParseValue("-1.23", &value));
|
||||
EXPECT_EQ(QVariant::Double, value.type());
|
||||
EXPECT_EQ(-1.23, value.toDouble());
|
||||
|
||||
EXPECT_EQ(4, parser_.ParseValue("+123", &value));
|
||||
EXPECT_EQ(QVariant::Double, value.type());
|
||||
EXPECT_EQ(123, value.toDouble());
|
||||
|
||||
parser_.ParseValue("1.", &value);
|
||||
EXPECT_NE(QVariant::Double, value.type());
|
||||
|
||||
parser_.ParseValue("abc", &value);
|
||||
EXPECT_NE(QVariant::Double, value.type());
|
||||
}
|
||||
|
||||
TEST_F(FMPSParserTest, ParseStrings) {
|
||||
QVariant value;
|
||||
|
||||
EXPECT_EQ(3, parser_.ParseValue("abc", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("abc", value.toString());
|
||||
|
||||
EXPECT_EQ(8, parser_.ParseValue("foo\\\\bar", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("foo\\bar", value.toString());
|
||||
|
||||
EXPECT_EQ(8, parser_.ParseValue("foo\\:bar", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("foo:bar", value.toString());
|
||||
|
||||
EXPECT_EQ(8, parser_.ParseValue("foo\\;bar", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("foo;bar", value.toString());
|
||||
|
||||
EXPECT_EQ(12, parser_.ParseValue("foo\\\\\\:\\;bar", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("foo\\:;bar", value.toString());
|
||||
|
||||
EXPECT_EQ(2, parser_.ParseValue("1.", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("1.", value.toString());
|
||||
|
||||
EXPECT_EQ(5, parser_.ParseValue("1.abc", &value));
|
||||
EXPECT_EQ(QVariant::String, value.type());
|
||||
EXPECT_EQ("1.abc", value.toString());
|
||||
|
||||
EXPECT_EQ(-1, parser_.ParseValue("foo\\bar", &value));
|
||||
|
||||
EXPECT_EQ(-1, parser_.ParseValue("foo:bar", &value));
|
||||
|
||||
EXPECT_EQ(-1, parser_.ParseValue("foo;bar", &value));
|
||||
}
|
||||
|
||||
TEST_F(FMPSParserTest, ParseLists) {
|
||||
QVariantList value;
|
||||
|
||||
EXPECT_EQ(3, parser_.ParseList("abc", &value));
|
||||
EXPECT_EQ(1, value.length());
|
||||
EXPECT_EQ("abc", value[0]);
|
||||
|
||||
EXPECT_EQ(3, parser_.ParseList("123", &value));
|
||||
EXPECT_EQ(1, value.length());
|
||||
EXPECT_EQ(123, value[0]);
|
||||
|
||||
EXPECT_EQ(8, parser_.ParseList("abc::def", &value));
|
||||
EXPECT_EQ(2, value.length());
|
||||
EXPECT_EQ("abc", value[0]);
|
||||
EXPECT_EQ("def", value[1]);
|
||||
|
||||
EXPECT_EQ(13, parser_.ParseList("abc::def::ghi", &value));
|
||||
EXPECT_EQ(3, value.length());
|
||||
EXPECT_EQ("abc", value[0]);
|
||||
EXPECT_EQ("def", value[1]);
|
||||
EXPECT_EQ("ghi", value[2]);
|
||||
|
||||
EXPECT_EQ(12, parser_.ParseList("ab\\:c::\\\\def", &value));
|
||||
EXPECT_EQ(2, value.length());
|
||||
EXPECT_EQ("ab:c", value[0]);
|
||||
EXPECT_EQ("\\def", value[1]);
|
||||
|
||||
EXPECT_EQ(5, parser_.ParseList("abc::def:", &value));
|
||||
EXPECT_EQ(1, value.length());
|
||||
EXPECT_EQ("abc", value[0]);
|
||||
}
|
||||
|
||||
TEST_F(FMPSParserTest, ParseListLists) {
|
||||
FMPSParser::Result value;
|
||||
|
||||
EXPECT_EQ(8, parser_.ParseListList("abc::def", &value));
|
||||
EXPECT_EQ(1, value.length());
|
||||
EXPECT_EQ(2, value[0].length());
|
||||
EXPECT_EQ("abc", value[0][0]);
|
||||
EXPECT_EQ("def", value[0][1]);
|
||||
|
||||
EXPECT_EQ(18, parser_.ParseListList("abc::def;;123::456", &value));
|
||||
EXPECT_EQ(2, value.length());
|
||||
EXPECT_EQ(2, value[0].length());
|
||||
EXPECT_EQ(2, value[1].length());
|
||||
EXPECT_EQ("abc", value[0][0]);
|
||||
EXPECT_EQ("def", value[0][1]);
|
||||
EXPECT_EQ(123, value[1][0]);
|
||||
EXPECT_EQ(456, value[1][1]);
|
||||
}
|
||||
|
||||
TEST_F(FMPSParserTest, Parse) {
|
||||
EXPECT_TRUE(parser_.Parse("abc"));
|
||||
EXPECT_TRUE(parser_.Parse("abc::def"));
|
||||
EXPECT_TRUE(parser_.Parse("abc::def;;123::456;;foo::bar"));
|
||||
EXPECT_TRUE(parser_.Parse("1."));
|
||||
EXPECT_TRUE(parser_.Parse("1.abc"));
|
||||
|
||||
EXPECT_FALSE(parser_.Parse("1:"));
|
||||
EXPECT_FALSE(parser_.Parse("1;"));
|
||||
EXPECT_FALSE(parser_.Parse("1:abc"));
|
||||
EXPECT_FALSE(parser_.Parse("abc;"));
|
||||
}
|
||||
|
||||
TEST_F(FMPSParserTest, SpecExamples) {
|
||||
FMPSParser::Result expected;
|
||||
|
||||
expected.clear();
|
||||
expected << (QVariantList() << "Alice Abba" << 0.6);
|
||||
expected << (QVariantList() << "Bob Beatles" << 0.8);
|
||||
|
||||
ASSERT_TRUE(parser_.Parse("Alice Abba::0.6;;Bob Beatles::0.8"));
|
||||
EXPECT_EQ(expected, parser_.result());
|
||||
|
||||
expected.clear();
|
||||
expected << (QVariantList() << "Rolling Stone" << "Ralph Gleason" << 0.83);
|
||||
expected << (QVariantList() << "musicOMH.com" << "FMPS_Nothing" << 0.76);
|
||||
expected << (QVariantList() << "Metacritic" << "FMPS_Nothing" << 0.8);
|
||||
expected << (QVariantList() << "FMPS_Nothing" << "Some Dude" << 0.9);
|
||||
|
||||
ASSERT_TRUE(parser_.Parse("Rolling Stone::Ralph Gleason::0.83;;musicOMH.com::FMPS_Nothing::0.76;;Metacritic::FMPS_Nothing::0.8;;FMPS_Nothing::Some Dude::0.9"));
|
||||
EXPECT_EQ(expected, parser_.result());
|
||||
|
||||
expected.clear();
|
||||
expected << (QVariantList() << "Amarok" << "AutoRate" << 0.52);
|
||||
expected << (QVariantList() << "VLC" << "Standard" << 0.6);
|
||||
expected << (QVariantList() << "QuodLibet" << "RatingPlugin:X" << 0.35);
|
||||
expected << (QVariantList() << "The Free Music Player Alliance" << "Rating Algorithm 1" << 0.5);
|
||||
|
||||
ASSERT_TRUE(parser_.Parse("Amarok::AutoRate::0.52;;VLC::Standard::0.6;;QuodLibet::RatingPlugin\\:X::0.35;;The Free Music Player Alliance::Rating Algorithm 1::0.5"));
|
||||
EXPECT_EQ(expected, parser_.result());
|
||||
|
||||
expected.clear();
|
||||
expected << (QVariantList() << "Willy Nelson" << "Guitar");
|
||||
expected << (QVariantList() << "Eric Clapton" << "Guitar (Backup)");
|
||||
expected << (QVariantList() << "B.B. King" << "Vocals");
|
||||
ASSERT_TRUE(parser_.Parse("Willy Nelson::Guitar;;Eric Clapton::Guitar (Backup);;B.B. King::Vocals"));
|
||||
EXPECT_EQ(expected, parser_.result());
|
||||
|
||||
expected.clear();
|
||||
expected << (QVariantList() << "Alice Aardvark" << "[lyrics]");
|
||||
expected << (QVariantList() << "Bob Baboon" << "[lyrics]");
|
||||
expected << (QVariantList() << "http://www.lyricssite.net" << "[lyrics]");
|
||||
ASSERT_TRUE(parser_.Parse("Alice Aardvark::[lyrics];;Bob Baboon::[lyrics];;http\\://www.lyricssite.net::[lyrics]"));
|
||||
EXPECT_EQ(expected, parser_.result());
|
||||
|
||||
expected.clear();
|
||||
expected << (QVariantList() << "Amarok" << "Album" << "2982ab29ef");
|
||||
expected << (QVariantList() << "AmarokUser" << "Compilation" << "My Compilation");
|
||||
expected << (QVariantList() << "Banshee" << "Compilation" << "ad8slpbzl229zier");
|
||||
expected << (QVariantList() << "FMPSAlliance" << "Album" << "de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3");
|
||||
ASSERT_TRUE(parser_.Parse("Amarok::Album::2982ab29ef;;AmarokUser::Compilation::My Compilation;;Banshee::Compilation::ad8slpbzl229zier;;FMPSAlliance::Album::de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3"));
|
||||
EXPECT_EQ(expected, parser_.result());
|
||||
}
|
@ -187,4 +187,46 @@ TEST_F(SongTest, DecodesAmbiguousLatin1AndWindows1252) {
|
||||
EXPECT_EQ(QString::fromUtf8("Sudáfrica"), fixed);
|
||||
}
|
||||
|
||||
TEST_F(SongTest, FMPSRating) {
|
||||
TemporaryResource r(":/testdata/fmpsrating.mp3");
|
||||
Song song;
|
||||
song.InitFromFile(r.fileName(), -1);
|
||||
EXPECT_FLOAT_EQ(0.42, song.rating());
|
||||
}
|
||||
|
||||
TEST_F(SongTest, FMPSRatingUser) {
|
||||
TemporaryResource r(":/testdata/fmpsratinguser.mp3");
|
||||
Song song;
|
||||
song.InitFromFile(r.fileName(), -1);
|
||||
EXPECT_FLOAT_EQ(0.10, song.rating());
|
||||
}
|
||||
|
||||
TEST_F(SongTest, FMPSRatingBoth) {
|
||||
TemporaryResource r(":/testdata/fmpsratingboth.mp3");
|
||||
Song song;
|
||||
song.InitFromFile(r.fileName(), -1);
|
||||
EXPECT_FLOAT_EQ(0.42, song.rating());
|
||||
}
|
||||
|
||||
TEST_F(SongTest, FMPSPlayCount) {
|
||||
TemporaryResource r(":/testdata/fmpsplaycount.mp3");
|
||||
Song song;
|
||||
song.InitFromFile(r.fileName(), -1);
|
||||
EXPECT_EQ(123, song.playcount());
|
||||
}
|
||||
|
||||
TEST_F(SongTest, FMPSPlayCountUser) {
|
||||
TemporaryResource r(":/testdata/fmpsplaycountuser.mp3");
|
||||
Song song;
|
||||
song.InitFromFile(r.fileName(), -1);
|
||||
EXPECT_EQ(42, song.playcount());
|
||||
}
|
||||
|
||||
TEST_F(SongTest, FMPSPlayCountBoth) {
|
||||
TemporaryResource r(":/testdata/fmpsplaycountboth.mp3");
|
||||
Song song;
|
||||
song.InitFromFile(r.fileName(), -1);
|
||||
EXPECT_EQ(123, song.playcount());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -33,6 +33,16 @@ std::ostream& operator <<(std::ostream& stream, const QVariant& var);
|
||||
std::ostream& operator <<(std::ostream& stream, const QUrl& url);
|
||||
std::ostream& operator <<(std::ostream& stream, const QNetworkRequest& req);
|
||||
|
||||
template <typename T>
|
||||
std::ostream& operator <<(std::ostream& stream, const QList<T>& list) {
|
||||
stream << "QList(";
|
||||
foreach (const T& item, list) {
|
||||
stream << item << ",";
|
||||
}
|
||||
stream << ")";
|
||||
return stream;
|
||||
}
|
||||
|
||||
void PrintTo(const ::QString& str, std::ostream& os);
|
||||
void PrintTo(const ::QVariant& var, std::ostream& os);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user