Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ columns_config:
width_percent: 0
```

Available fields: artist, album, title, year, length, path, url, sample_rate, bitrate, channels, track_number, format, filename.
Available fields: artist, album, album_artist, title, year, genre, length, path, url, sample_rate, bitrate, channels, track_number, disc_number, format, filename.

Available alignments: left, right.

Expand Down
3 changes: 3 additions & 0 deletions app/playlist/mpdloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ namespace Playlist {
0,
0
);
track.setAlbumArtist(song.albumArtist);
track.setGenre(song.genre);
track.setDiscNumber(song.discNumber > 0 ? static_cast<quint16>(song.discNumber) : 0);
}
track.generateUidByHashing(playlist_name);
track.setPlaylistName(playlist_name);
Expand Down
23 changes: 23 additions & 0 deletions app/playlist/sorter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ namespace Playlist {
result = compare_artist(t1, t2);
} else if (attr == "ALBUM") {
result = compare_album(t1, t2);
} else if (attr == "ALBUMARTIST") {
result = compare_album_artist(t1, t2);
} else if (attr == "GENRE") {
result = compare_genre(t1, t2);
} else if (attr == "DISCNUMBER") {
result = compare_disc_number(t1, t2);
} else if (attr == "YEAR") {
result = compare_year(t1, t2);
} else if (attr == "TRACKNUMBER") {
Expand Down Expand Up @@ -92,4 +98,21 @@ namespace Playlist {
int Sorter::compare_dir(const Track &t1, const Track &t2) const {
return -QString::localeAwareCompare(t1.dir(), t2.dir());
}

int Sorter::compare_album_artist(const Track &t1, const Track &t2) const {
return -QString::localeAwareCompare(t1.album_artist(), t2.album_artist());
}

int Sorter::compare_genre(const Track &t1, const Track &t2) const {
return -QString::localeAwareCompare(t1.genre(), t2.genre());
}

int Sorter::compare_disc_number(const Track &t1, const Track &t2) const {
if (t1.disc_number() < t2.disc_number()) {
return 1;
} else if (t1.disc_number() > t2.disc_number()) {
return -1;
}
return 0;
}
}
3 changes: 3 additions & 0 deletions app/playlist/sorter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ namespace Playlist {
int compare_title(const Track &t1, const Track &t2) const;
int compare_artist(const Track &t1, const Track &t2) const;
int compare_dir(const Track &t1, const Track &t2) const;
int compare_album_artist(const Track &t1, const Track &t2) const;
int compare_genre(const Track &t1, const Track &t2) const;
int compare_disc_number(const Track &t1, const Track &t2) const;
};
}

Expand Down
6 changes: 6 additions & 0 deletions app/playlist_ui/columnsconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,16 @@ namespace PlaylistUi {
return track.artist();
} else if (fld == "album") {
return track.album();
} else if (fld == "album_artist") {
return track.album_artist();
} else if (fld == "genre") {
return track.genre();
} else if (fld == "title") {
return track.title();
} else if (fld == "year" && track.year() > 0) {
return QString::number(track.year());
} else if (fld == "disc_number" && track.disc_number() > 0) {
return QString::number(track.disc_number());
} else if (fld == "length") {
return track.formattedDuration();
} else if (fld == "path") {
Expand Down
3 changes: 2 additions & 1 deletion app/settings_ui/settingsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ namespace {
};

const QStringList kColumnFields = {
"artist", "album", "title", "year", "length", "track_number",
"artist", "album", "album_artist", "title", "year", "genre",
"length", "track_number", "disc_number",
"path", "url", "filename", "format", "bitrate", "channels", "sample_rate",
};
}
Expand Down
3 changes: 3 additions & 0 deletions app/sort_ui/sortingpresetsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,13 @@ void SortingPresetsDialog::on_buttonHelp_clicked() {
"Fields available to sort (case insensitive):<br />"
" * Artist<br />"
" * Album<br />"
" * AlbumArtist<br />"
" * Title<br />"
" * Year<br />"
" * Genre<br />"
" * Filename<br />"
" * TrackNumber<br />"
" * DiscNumber<br />"
" * Directory</br />"
"<br />"
"By default sort in ascending order. Add - before the field name to change to descending order.<br />"
Expand Down
59 changes: 55 additions & 4 deletions app/track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
#include <QDir>
#include <QHash>

#include <limits>

namespace {
QString firstProperty(const TagLib::PropertyMap &props, const char *key) {
const TagLib::StringList v = props.value(key);
if (v.isEmpty()) {
return QString();
}
return QString(v.front().toCString(true));
}
}

Track::Track() {
_uid = 0;
_begin = 0;
Expand Down Expand Up @@ -136,22 +148,37 @@ bool Track::readMetadata() {
}

if (TagLib::Tag *tag = f.tag()) {
const TagLib::PropertyMap tag_props = tag->properties();
_album_artist = firstProperty(tag_props, "ALBUMARTIST");
_artist = QString(tag->artist().toCString(true));
if (_artist.isEmpty()) {
const TagLib::StringList aa = tag->properties().value("ALBUMARTIST");
if (!aa.isEmpty()) {
_artist = QString(aa.front().toCString(true));
}
_artist = _album_artist;
}
_album = QString(tag->album().toCString(true));
_genre = QString(tag->genre().toCString(true));
_title = QString(tag->title().toCString(true));
_year = static_cast<quint16>(tag->year());
_track_number = static_cast<quint16>(tag->track());
_disc_number = parseDiscNumber(firstProperty(tag_props, "DISCNUMBER"));
}

return true;
}

// DISCNUMBER is commonly "1/2" - take the numerator
quint16 Track::parseDiscNumber(const QString &raw) {
if (raw.isEmpty()) {
return 0;
}
const QString num = raw.section('/', 0, 0).trimmed();
bool ok = false;
const uint value = num.toUInt(&ok);
if (!ok || value > std::numeric_limits<quint16>::max()) {
return 0;
}
return static_cast<quint16>(value);
}

bool Track::reload() {
return readMetadata();
}
Expand All @@ -173,6 +200,18 @@ void Track::setDuration(quint64 dur) {
_duration = dur;
}

void Track::setAlbumArtist(const QString &aa) {
_album_artist = aa;
}

void Track::setGenre(const QString &g) {
_genre = g;
}

void Track::setDiscNumber(quint16 dn) {
_disc_number = dn;
}

void Track::setCue(bool is_cue) {
_cue = is_cue;
}
Expand All @@ -199,6 +238,14 @@ QString Track::album() const {
return _album;
}

QString Track::album_artist() const {
return _album_artist;
}

QString Track::genre() const {
return _genre;
}

QString Track::title() const {
if (_title.isEmpty()) {
if (isStream()) {
Expand Down Expand Up @@ -349,6 +396,10 @@ quint16 Track::track_number() const {
return _track_number;
}

quint16 Track::disc_number() const {
return _disc_number;
}

quint32 Track::begin() const {
return _begin;
}
Expand Down
10 changes: 10 additions & 0 deletions app/track.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ class Track {
explicit Track(const QUrl &stream_url, const QString &filepath_reference);

static QString formattedTime(quint64 tm);
static quint16 parseDiscNumber(const QString &raw);
static AudioProperties audioPropertiesOf(const QString &filepath);

bool isValid() const;

bool reload();
void setDuration(quint64 dur);
void setAlbumArtist(const QString &aa);
void setGenre(const QString &g);
void setDiscNumber(quint16 dn);
void setCue(bool is_cue = true);
void generateUidByHashing(const QString &prefix);
void setPlaylistName(const QString &pln);
Expand All @@ -46,8 +50,11 @@ class Track {
QUrl url() const;
QString artist() const;
QString album() const;
QString album_artist() const;
QString genre() const;
QString title() const;
quint16 year() const;
quint16 disc_number() const;
quint32 duration() const;
quint32 sample_rate() const;
quint8 channels() const;
Expand Down Expand Up @@ -86,8 +93,11 @@ class Track {
QString filepath;
QString _artist;
QString _album;
QString _album_artist;
QString _genre;
QString _title;
quint16 _year;
quint16 _disc_number = 0;
quint64 _duration;
quint32 _sample_rate;
quint8 _channels;
Expand Down
24 changes: 24 additions & 0 deletions tests/tst_columnsconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ private slots:
void valueByFieldName();
void valueForYearZeroIsEmpty();
void valueForUnknownFieldIsEmpty();
void valueForNewTagFields();
void valueForDiscNumberZeroIsEmpty();
void settersReplaceContents();
void serializeRoundTripsThroughDeserialize();
void vaidateThrowsOnMismatchedVectorSizes();
Expand Down Expand Up @@ -86,6 +88,28 @@ void TestColumnsConfig::valueByFieldName() {
QCOMPARE(cfg.value(5, t), t.formattedDuration());
}

void TestColumnsConfig::valueForNewTagFields() {
ColumnsConfig cfg;
cfg.setFields(QVector<QString>{ QStringLiteral("album_artist"),
QStringLiteral("genre"),
QStringLiteral("disc_number") });
Track t = mk();
t.setAlbumArtist(QStringLiteral("Various Artists"));
t.setGenre(QStringLiteral("Jazz"));
t.setDiscNumber(2);
QCOMPARE(cfg.value(1, t), QStringLiteral("Various Artists"));
QCOMPARE(cfg.value(2, t), QStringLiteral("Jazz"));
QCOMPARE(cfg.value(3, t), QStringLiteral("2"));
}

void TestColumnsConfig::valueForDiscNumberZeroIsEmpty() {
ColumnsConfig cfg;
cfg.setFields(QVector<QString>{ QStringLiteral("disc_number") });
Track t = mk();
QCOMPARE(t.disc_number(), quint16{0});
QCOMPARE(cfg.value(1, t), QString());
}

void TestColumnsConfig::valueForYearZeroIsEmpty() {
ColumnsConfig cfg;
Track t = mk(QStringLiteral("/p.mp3"), QStringLiteral("A"),
Expand Down
61 changes: 61 additions & 0 deletions tests/tst_sorter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ private slots:
void unknownCriterionIsIgnored();
void titleArtistAlbumFilenameOrderings();
void sortVectorEndToEnd();
void albumArtistOrdering();
void genreOrdering();
void discNumberOrdering();
void discNumberBeforeTrackNumber();
};

void TestSorter::defaultCriteriaIsCanonical() {
Expand Down Expand Up @@ -126,5 +130,62 @@ void TestSorter::sortVectorEndToEnd() {
QCOMPARE(v.at(2).year(), quint16{2010});
}

void TestSorter::albumArtistOrdering() {
Sorter s(QStringLiteral("ALBUMARTIST"));
Track a = mk({}, {}, {}, {}, 0, 0);
Track b = mk({}, {}, {}, {}, 0, 0);
a.setAlbumArtist(QStringLiteral("Aphex Twin"));
b.setAlbumArtist(QStringLiteral("Boards of Canada"));
QVERIFY(s.condition(a, b));
QVERIFY(!s.condition(b, a));

Sorter desc(QStringLiteral("-ALBUMARTIST"));
QVERIFY(desc.condition(b, a));
}

void TestSorter::genreOrdering() {
Sorter s(QStringLiteral("GENRE"));
Track a = mk({}, {}, {}, {}, 0, 0);
Track b = mk({}, {}, {}, {}, 0, 0);
a.setGenre(QStringLiteral("Ambient"));
b.setGenre(QStringLiteral("Rock"));
QVERIFY(s.condition(a, b));
QVERIFY(!s.condition(b, a));
QVERIFY(!s.condition(a, a));
}

void TestSorter::discNumberOrdering() {
Sorter s(QStringLiteral("DISCNUMBER"));
Track d1 = mk({}, {}, {}, {}, 0, 0);
Track d2 = mk({}, {}, {}, {}, 0, 0);
d1.setDiscNumber(1);
d2.setDiscNumber(2);
QVERIFY(s.condition(d1, d2));
QVERIFY(!s.condition(d2, d1));
QVERIFY(!s.condition(d1, d1));

Sorter desc(QStringLiteral("-DISCNUMBER"));
QVERIFY(desc.condition(d2, d1));
}

// The multi-disc fix: disc 2 track 1 must not sort beside disc 1 track 1.
void TestSorter::discNumberBeforeTrackNumber() {
Sorter s(QStringLiteral("DISCNUMBER / TRACKNUMBER"));
Track d1t1 = mk(QStringLiteral("/d1t1.flac"), {}, {}, {}, 1, 0);
Track d1t2 = mk(QStringLiteral("/d1t2.flac"), {}, {}, {}, 2, 0);
Track d2t1 = mk(QStringLiteral("/d2t1.flac"), {}, {}, {}, 1, 0);
d1t1.setDiscNumber(1);
d1t2.setDiscNumber(1);
d2t1.setDiscNumber(2);

QVector<Track> v{d2t1, d1t2, d1t1};
std::sort(v.begin(), v.end(), [&s](const Track &a, const Track &b) {
return s.condition(a, b);
});
QCOMPARE(v.at(0).filename(), QStringLiteral("d1t1.flac"));
QCOMPARE(v.at(1).filename(), QStringLiteral("d1t2.flac"));
QCOMPARE(v.at(2).filename(), QStringLiteral("d2t1.flac"));
}

QTEST_GUILESS_MAIN(TestSorter)
#include "tst_sorter.moc"
Loading