diff --git a/README.md b/README.md
index 447c0a10..0d6cd96c 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,10 @@ Conventional features include:
- Audio transcode for maximum interoperability and low bandwith requirements
- Custom tag support
-The recommendation engine makes use of [Self-Organizing Maps](https://en.wikipedia.org/wiki/Self-organizing_map). Please note it may require some significant computation time on large datasets.
+The recommendation engine makes use of [Self-Organizing Maps](https://en.wikipedia.org/wiki/Self-organizing_map)
+Please note this engine:
+- may require some significant computation time on large datasets.
+- makes use of computed data available on [AcousticBrainz](https://acousticbrainz.org/). Therefore your music must contain the [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) for the recommendation engine to work.
## Installation
Here are the required packages to build on Debian Stretch:
diff --git a/approot/artist.xml b/approot/artist.xml
index 28ab28a1..ef4d8032 100644
--- a/approot/artist.xml
+++ b/approot/artist.xml
@@ -20,7 +20,7 @@
${
}${tr:Lms.Explore.various-artists}
${}
${}${year}${}${} (${orig-year})${}
diff --git a/approot/artistlink.xml b/approot/artistlink.xml
index 1b1959b9..cb02a39c 100644
--- a/approot/artistlink.xml
+++ b/approot/artistlink.xml
@@ -5,7 +5,7 @@
- ${name}
+ ${name class="Lms-explore-artistname Lms-explore-artistlink-name"}
diff --git a/approot/artists.xml b/approot/artists.xml
index bff7fa7f..50053f22 100644
--- a/approot/artists.xml
+++ b/approot/artists.xml
@@ -23,7 +23,7 @@
-
${name}
+ ${name class="Lms-explore-artistname Lms-explore-artists-entry-name"}
diff --git a/approot/playhistory.xml b/approot/playhistory.xml
index 11bd1bca..25394beb 100644
--- a/approot/playhistory.xml
+++ b/approot/playhistory.xml
@@ -20,10 +20,10 @@
${name}
- ${}${release-name}${}
+ ${}${release-name class="Lms-explore-releasename"}${}
- ${}${artist-name}${}
+ ${}${artist-name class="Lms-explore-artistname"}${}
diff --git a/approot/playqueue.xml b/approot/playqueue.xml
index b2ba1131..c347bc8d 100644
--- a/approot/playqueue.xml
+++ b/approot/playqueue.xml
@@ -46,10 +46,10 @@
${name}
- ${}${release-name}${}
+ ${}${release-name class="Lms-explore-releasename"}${}
- ${}${artist-name}${}
+ ${}${artist-name class="Lms-explore-artistname"}${}
${play-btn}${del-btn}
diff --git a/approot/release.xml b/approot/release.xml
index dfbce12c..4ac914ed 100644
--- a/approot/release.xml
+++ b/approot/release.xml
@@ -14,7 +14,7 @@
diff --git a/approot/releases.xml b/approot/releases.xml
index b28540ed..96d94c20 100644
--- a/approot/releases.xml
+++ b/approot/releases.xml
@@ -29,9 +29,9 @@
- ${}${artist-name}
${}
+ ${}${artist-name class="Lms-explore-artistname"}
${}
${play-btn}${add-btn}
diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index 95b0799e..0e2cafca 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -1,5 +1,4 @@
a, a:link, a:active, a:visited {
- color: black;
text-decoration: none;
cursor: pointer;
}
@@ -8,6 +7,20 @@ a:hover {
color: #337ab7;
}
+/* Base style for artist names */
+.Lms-explore-artistname
+{
+ color: darkgrey;
+ font-style: italic;
+}
+
+/* Base style for release names */
+.Lms-explore-releasename
+{
+ color: black;
+ font-style: normal;
+}
+
.Lms-vertical-align {
display: flex;
align-items: center;
@@ -141,12 +154,24 @@ a:hover {
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
}
+.Lms-explore-release-entry-tracknumber {
+ color: darkgrey;
+ text-align: center;
+ white-space: nowrap;
+}
+
.Lms-explore-release-entry-name {
margin-top: 4px;
margin-bottom: 4px;
font-weight: inherit;
}
+.Lms-explore-release-entry {
+ border-bottom-style: solid;
+ border-bottom-color: lightgrey;
+ border-bottom-width: thin;
+}
+
.Lms-explore-release-entry:hover {
background-color: lightgrey;
}
@@ -159,6 +184,10 @@ a:hover {
font-weight: bold;
}
+.Lms-explore-release-entry-artist {
+ display: inline-block;
+}
+
.Lms-explore-releaselink {
margin: 0px;
padding-top: 4px;
@@ -169,6 +198,9 @@ a:hover {
background-color: lightgrey;
}
+.Lms-explore-releaselink-artist {
+ color: dimgrey;
+}
.Lms-explore-artists-entry {
min-height: 48px;
@@ -178,6 +210,11 @@ a:hover {
background-color: lightgrey;
}
+.Lms-explore-artists-entry-name {
+ color: black;
+ font-style: normal;
+}
+
.Lms-explore-artist-entry {
padding-top: 8px;
padding-bottom: 8px;
@@ -196,6 +233,10 @@ a:hover {
background-color: lightgrey;
}
+.Lms-explore-artistlink-name {
+ color: black;
+ font-style: normal;
+}
.Lms-explore-tracks-entry {
min-height: 32px;
diff --git a/src/av/AvInfo.cpp b/src/av/AvInfo.cpp
index 1b5f7c6d..62745243 100644
--- a/src/av/AvInfo.cpp
+++ b/src/av/AvInfo.cpp
@@ -19,6 +19,8 @@
#include "AvInfo.hpp"
+#include
+
#include
#include "utils/Logger.hpp"
@@ -93,7 +95,7 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, std::mapkey, tag->value));
+ res[boost::to_upper_copy(tag->key)] = tag->value;
}
}
diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp
index ae479d12..2d98966d 100644
--- a/src/database/Artist.cpp
+++ b/src/database/Artist.cpp
@@ -31,8 +31,8 @@ namespace Database
{
Artist::Artist(const std::string& name, const std::string& MBID)
-: _name(std::string(name, 0 , _maxNameLength)),
-_MBID(MBID)
+: _name{std::string(name, 0 , _maxNameLength)},
+_MBID{MBID}
{
}
@@ -40,7 +40,7 @@ _MBID(MBID)
std::vector
Artist::getByName(Wt::Dbo::Session& session, const std::string& name)
{
- Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
+ Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string{name, 0, _maxNameLength} );
return std::vector(res.begin(), res.end());
}
@@ -72,7 +72,7 @@ Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
std::vector
Artist::getAllOrphans(Wt::Dbo::Session& session)
{
- Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("SELECT DISTINCT a FROM artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL");
+ Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist t_a ON t_a.artist_id = a.id WHERE t.id = t_a.track_id)");
return std::vector(res.begin(), res.end());
}
@@ -93,7 +93,7 @@ getQuery(Wt::Dbo::Session& session,
if (!clusterIds.empty())
{
- oss << " INNER JOIN track t ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
+ oss << " INNER JOIN track t ON t_a.track_id = t.id INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
@@ -145,7 +145,7 @@ Artist::getByFilter(Wt::Dbo::Session& session,
std::vector
Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit)
{
- Wt::Dbo::collection res = session.query("SELECT a from artist a INNER JOIN track t ON a.id = t.artist_id")
+ Wt::Dbo::collection res = session.query("SELECT a from artist a INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN track t ON t.id = t_a.track_id")
.where("t.file_added > ?").bind(after)
.groupBy("a.id")
.orderBy("t.file_added DESC")
@@ -164,7 +164,7 @@ Artist::getReleases(const std::set& clusterIds) const
WhereClause where;
std::ostringstream oss;
- oss << "SELECT DISTINCT r FROM release r INNER JOIN artist a ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id";
+ oss << "SELECT DISTINCT r FROM release r INNER JOIN artist a ON t_a.artist_id = a.id INNER JOIN track_artist t_a ON t_a.track_id = t.id INNER JOIN track t ON t.release_id = r.id";
if (!clusterIds.empty())
{
@@ -215,7 +215,7 @@ Artist::getClusterGroups(std::vector clusterTypes, std::si
WhereClause where;
std::ostringstream oss;
- oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t.artist_id = a.id";
+ oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t_a.artist_id = a.id INNER JOIN track_artist t_a ON t_a.track_id = t.id";
where.And(WhereClause("a.id = ?")).bind(std::to_string(self()->id()));
{
diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp
index ddc4410a..9f73635e 100644
--- a/src/database/Artist.hpp
+++ b/src/database/Artist.hpp
@@ -60,8 +60,8 @@ class Artist : public Wt::Dbo::Dbo
static std::vector getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1);
// Accessors
- std::string getName(void) const { return _name; }
- std::string getMBID(void) const { return _MBID; }
+ const std::string& getName(void) const { return _name; }
+ const std::string& getMBID(void) const { return _MBID; }
// Get the releases that have at least one track for this artist that belongs to optional cluster filters
std::vector> getReleases(const std::set& clusterIds = std::set()) const;
@@ -84,7 +84,7 @@ class Artist : public Wt::Dbo::Dbo
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
- Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist");
+ Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_artist", "", Wt::Dbo::OnDeleteCascade);
}
private:
diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp
index c9165e4b..5ba449f4 100644
--- a/src/database/DatabaseHandler.cpp
+++ b/src/database/DatabaseHandler.cpp
@@ -137,7 +137,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.execute("CREATE INDEX IF NOT EXISTS track_name_idx ON track(name)");
_session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
_session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)");
- _session.execute("CREATE INDEX IF NOT EXISTS track_artist_idx ON track(artist_id)");
_session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)");
_session.execute("CREATE INDEX IF NOT EXISTS cluster_name_idx ON cluster(name)");
_session.execute("CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)");
diff --git a/src/database/Release.cpp b/src/database/Release.cpp
index 6f97e3a3..66063557 100644
--- a/src/database/Release.cpp
+++ b/src/database/Release.cpp
@@ -225,7 +225,7 @@ Release::getArtists() const
assert(session());
Wt::Dbo::collection> res = session()->query>(
- "SELECT DISTINCT a FROM artist a INNER JOIN release r ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id")
+ "SELECT DISTINCT a FROM artist a INNER JOIN release r ON t.id = t_a.track_id INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN track t ON t.release_id = r.id")
.where("r.id = ?")
.bind(id());
diff --git a/src/database/Track.cpp b/src/database/Track.cpp
index 1c8c2c78..8548798d 100644
--- a/src/database/Track.cpp
+++ b/src/database/Track.cpp
@@ -218,6 +218,28 @@ Track::getByFilter(Wt::Dbo::Session& session,
return getByFilter(session, clusters, std::vector(), -1, -1, moreResults);
}
+void
+Track::setArtists(const std::vector>& artists)
+{
+ _artists.clear();
+ for (const Wt::Dbo::ptr& artist : artists)
+ _artists.insert(artist);
+}
+
+void
+Track::setClusters(const std::vector>& clusters)
+{
+ _clusters.clear();
+ for (const Wt::Dbo::ptr& cluster : clusters)
+ _clusters.insert(cluster);
+}
+
+void
+Track::setFeatures(const Wt::Dbo::ptr& features)
+{
+ _trackFeatures = features;
+}
+
boost::optional
Track::getTrackNumber(void) const
{
diff --git a/src/database/Track.hpp b/src/database/Track.hpp
index 2a1e831b..1f42f27e 100644
--- a/src/database/Track.hpp
+++ b/src/database/Track.hpp
@@ -75,9 +75,6 @@ class Track : public Wt::Dbo::Dbo