diff --git a/README.md b/README.md
index c8f01a7b..9e039e50 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,16 @@
# LMS - Lightweight Music Server
-[](https://travis-ci.org/epoupon/lms)  [](https://lgtm.com/projects/g/epoupon/lms/context:cpp)
+ [](https://travis-ci.org/epoupon/lms) [](https://lgtm.com/projects/g/epoupon/lms/context:cpp)
_LMS_ is a self-hosted music streaming software: access your music collection from anywhere using a web interface!
-A [demo](http://lms.demo.poupon.io) instance is available. Note the administration panel is not available.
+A [demo instance](http://lms.demo.poupon.io) is available. Note the administration panel is not available.
## Main features
-* Low memory requirements: the demo instance runs on a Raspberry Pi3B+
+* Low memory requirements: the demo instance runs on a _Raspberry Pi Zero W_
* Recommendation engine
* Audio transcode for maximum interoperability and low bandwith requirements
-* Multi-value tags: artists, genres, ...
-* Custom tags support: _mood_, _genre_, _albummood_, _albumgrouping_, ...
+* Multi-value tags: artists, genres, composers, lyricists, moods, ...
* Compilation support
* [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) support to handle duplicated artist and release names
* Disc subtitles support
@@ -31,6 +30,7 @@ _LMS_ provides several ways to help you find the music you like:
* Radio mode, based on what is in the current playqueue
* Searches in album, artist and track names (including sort names)
* Starred Albums/Artists/Tracks
+* Custom tags support to help you filter your music: _mood_, _albummood_, _albumgenre_, _albumgrouping_, ...
* Random/Starred/Most played/Recently played/Recently added for Artist/Albums/Tracks, allowing you to search for things like:
* Recently added _Electronic_ artists
* Random _Metal_ and _Aggressive_ albums
@@ -232,11 +232,3 @@ journalctl -u lms.service
To connect to _LMS_, just open your favorite browser and go to http://localhost:5082
-## Credits
-* Bootstrap Notify: https://github.com/mouse0270/bootstrap-notify
-* Bootstrap3 (https://getbootstrap.com/)
-* Bootswatch (https://bootswatch.com/)
-* Ffmpeg project (https://ffmpeg.org/)
-* GraphicsMagick++ (http://www.graphicsmagick.org/)
-* MetaBrainz (https://metabrainz.org/)
-* Wt (http://www.webtoolkit.eu/)
diff --git a/approot/messages.xml b/approot/messages.xml
index 2bbb4907..7c792916 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -143,7 +143,12 @@
All artists
Track artists
+Composers
+Lyricists
+Mixers
+Producers
Album artists
+Remixers
Similar albums
diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml
index 0b66924a..c90d20c4 100644
--- a/approot/messages_fr.xml
+++ b/approot/messages_fr.xml
@@ -143,7 +143,12 @@
Tous les artistes
Artistes de piste
+Compositeurs
+Paroliers
+Mixers
+Producteurs
Artistes d'album
+Remixers
Albums similaires
diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp
index 2f41aa80..5ade2f65 100644
--- a/src/libs/database/impl/Artist.cpp
+++ b/src/libs/database/impl/Artist.cpp
@@ -81,7 +81,7 @@ createQuery(Session& session,
const std::string& queryStr,
const std::set& clusterIds,
const std::vector& keywords,
- std::optional linkType)
+ std::optional linkType)
{
session.checkSharedLocked();
@@ -214,7 +214,7 @@ Artist::getAllIds(Session& session)
}
std::vector
-Artist::getAllIdsRandom(Session& session, const std::set& clusters, std::optional linkType, std::optional size)
+Artist::getAllIdsRandom(Session& session, const std::set& clusters, std::optional linkType, std::optional size)
{
session.checkSharedLocked();
@@ -265,7 +265,7 @@ std::vector
Artist::getByFilter(Session& session,
const std::set& clusters,
const std::vector& keywords,
- std::optional linkType,
+ std::optional linkType,
SortMethod sortMethod,
std::optional range,
bool& moreResults)
@@ -306,7 +306,7 @@ std::vector
Artist::getLastWritten(Session& session,
std::optional after,
const std::set& clusters,
- std::optional linkType,
+ std::optional linkType,
std::optional range, bool& moreResults)
{
session.checkSharedLocked();
@@ -338,7 +338,7 @@ std::vector
Artist::getStarred(Session& session,
User::pointer user,
const std::set& clusters,
- std::optional linkType,
+ std::optional linkType,
SortMethod sortMethod,
std::optional range, bool& moreResults)
{
@@ -443,7 +443,7 @@ Artist::getReleaseCount() const
}
std::vector>
-Artist::getTracks(std::optional linkType) const
+Artist::getTracks(std::optional linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
@@ -462,7 +462,7 @@ Artist::getTracks(std::optional linkType) const
}
std::vector>
-Artist::getTracksWithRelease(std::optional linkType) const
+Artist::getTracksWithRelease(std::optional linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
@@ -497,13 +497,14 @@ Artist::getRandomTracks(std::optional count) const
}
std::vector>
-Artist::getSimilarArtists(std::optional offset, std::optional count) const
+Artist::getSimilarArtists(EnumSet artistLinkTypes, std::optional range) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
- Wt::Dbo::Query query {session()->query(
+ std::ostringstream oss;
+ oss <<
"SELECT a FROM artist a"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track t ON t.id = t_a_l.track_id"
@@ -515,14 +516,34 @@ Artist::getSimilarArtists(std::optional offset, std::optional ?"
- )
+ " AND a.id <> ?";
+
+ if (!artistLinkTypes.empty())
+ {
+ oss << " AND t_a_l.type IN (";
+
+ bool first {true};
+ for (TrackArtistLinkType type : artistLinkTypes)
+ {
+ (void) type;
+ if (!first)
+ oss << ", ";
+ oss << "?";
+ first = false;
+ }
+ oss << ")";
+ }
+
+ Wt::Dbo::Query query {session()->query(oss.str())
.bind(self()->id())
.bind(self()->id())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()")
- .limit(count ? static_cast(*count) : -1)
- .offset(offset ? static_cast(*offset) : -1)};
+ .limit(range ? static_cast(range->limit) : -1)
+ .offset(range ? static_cast(range->offset) : -1)};
+
+ for (TrackArtistLinkType type : artistLinkTypes)
+ query.bind(type);
Wt::Dbo::collection res = query;
return std::vector(res.begin(), res.end());
diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp
index 6b476f91..473e7e08 100644
--- a/src/libs/database/impl/Release.cpp
+++ b/src/libs/database/impl/Release.cpp
@@ -19,13 +19,14 @@
#include "database/Release.hpp"
-#include "utils/Logger.hpp"
+#include
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp"
+#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
namespace Database
@@ -431,7 +432,7 @@ Release::getCopyrightURL() const
}
std::vector>
-Release::getArtists(TrackArtistLink::Type linkType) const
+Release::getArtists(TrackArtistLinkType linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp
index 2782be74..2cbf47ac 100644
--- a/src/libs/database/impl/Session.cpp
+++ b/src/libs/database/impl/Session.cpp
@@ -40,7 +40,7 @@
namespace Database {
-#define LMS_DATABASE_VERSION 26
+#define LMS_DATABASE_VERSION 27
using Version = std::size_t;
@@ -278,6 +278,12 @@ CREATE TABLE "user_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
+ else if (version == 26)
+ {
+ // Composer, mixer, etc. support
+ // Just increment the scan version of the settings to make the next scheduled scan rescan everything
+ ScanSettings::get(*this).modify()->incScanVersion();
+ }
else
{
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp
index aaf4b53a..d71d410a 100644
--- a/src/libs/database/impl/Track.cpp
+++ b/src/libs/database/impl/Track.cpp
@@ -24,6 +24,7 @@
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
#include "database/Release.hpp"
+#include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/Session.hpp"
#include "utils/Logger.hpp"
@@ -480,31 +481,81 @@ Track::getCopyrightURL() const
}
std::vector>
-Track::getArtists(TrackArtistLink::Type type) const
+Track::getArtists(EnumSet linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
- Wt::Dbo::collection> artists {session()->query("SELECT a from artist a INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id INNER JOIN track t ON t.id = t_a_l.track_id")
- .where("t.id = ?").bind(self()->id())
- .where("t_a_l.type = ?").bind(type)};
+ std::ostringstream oss;
+ oss <<
+ "SELECT a from artist a"
+ " INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id"
+ " INNER JOIN track t ON t.id = t_a_l.track_id";
- return std::vector>(artists.begin(), artists.end());
+ if (!linkTypes.empty())
+ {
+ oss << " AND t_a_l.type IN (";
+
+ bool first {true};
+ for (TrackArtistLinkType type : linkTypes)
+ {
+ (void) type;
+ if (!first)
+ oss << ", ";
+ oss << "?";
+ first = false;
+ }
+ oss << ")";
+ }
+
+ Wt::Dbo::Query query {session()->query(oss.str())
+ .where("t.id = ?").bind(self()->id())};
+
+ for (TrackArtistLinkType type : linkTypes)
+ query.bind(type);
+
+ Wt::Dbo::collection res = query;
+ return std::vector(std::begin(res), std::end(res));
}
std::vector
-Track::getArtistIds(TrackArtistLink::Type type) const
+Track::getArtistIds(EnumSet linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
- Wt::Dbo::collection artists {session()->query("SELECT a.id from artist a INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id INNER JOIN track t ON t.id = t_a_l.track_id")
- .where("t.id = ?").bind(self()->id())
- .where("t_a_l.type = ?").bind(type)};
+ std::ostringstream oss;
+ oss <<
+ "SELECT a.id from artist a"
+ " INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id"
+ " INNER JOIN track t ON t.id = t_a_l.track_id";
- return std::vector(artists.begin(), artists.end());
+ if (!linkTypes.empty())
+ {
+ oss << " AND t_a_l.type IN (";
+
+ bool first {true};
+ for (TrackArtistLinkType type : linkTypes)
+ {
+ (void) type;
+ if (!first)
+ oss << ", ";
+ oss << "?";
+ first = false;
+ }
+ oss << ")";
+ }
+
+ Wt::Dbo::Query query {session()->query(oss.str())
+ .where("t.id = ?").bind(self()->id())};
+
+ for (TrackArtistLinkType type : linkTypes)
+ query.bind(type);
+
+ Wt::Dbo::collection res = query;
+ return std::vector(std::begin(res), std::end(res));
}
std::vector>
diff --git a/src/libs/database/impl/TrackArtistLink.cpp b/src/libs/database/impl/TrackArtistLink.cpp
index 57526eab..45b62bbf 100644
--- a/src/libs/database/impl/TrackArtistLink.cpp
+++ b/src/libs/database/impl/TrackArtistLink.cpp
@@ -25,7 +25,7 @@
namespace Database {
-TrackArtistLink::TrackArtistLink(Wt::Dbo::ptr