Added composer, conductor, lyricist, mixer, producer, remixer tag support

This commit is contained in:
emeric
2020-11-12 13:19:22 +01:00
parent cf6ebe0911
commit b78967262c
37 changed files with 675 additions and 285 deletions
+34 -13
View File
@@ -81,7 +81,7 @@ createQuery(Session& session,
const std::string& queryStr,
const std::set<IdType>& clusterIds,
const std::vector<std::string>& keywords,
std::optional<TrackArtistLink::Type> linkType)
std::optional<TrackArtistLinkType> linkType)
{
session.checkSharedLocked();
@@ -214,7 +214,7 @@ Artist::getAllIds(Session& session)
}
std::vector<IdType>
Artist::getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLink::Type> linkType, std::optional<std::size_t> size)
Artist::getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size)
{
session.checkSharedLocked();
@@ -265,7 +265,7 @@ std::vector<Artist::pointer>
Artist::getByFilter(Session& session,
const std::set<IdType>& clusters,
const std::vector<std::string>& keywords,
std::optional<TrackArtistLink::Type> linkType,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
std::optional<Range> range,
bool& moreResults)
@@ -306,7 +306,7 @@ std::vector<Artist::pointer>
Artist::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
std::optional<TrackArtistLink::Type> linkType,
std::optional<TrackArtistLinkType> linkType,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
@@ -338,7 +338,7 @@ std::vector<Artist::pointer>
Artist::getStarred(Session& session,
User::pointer user,
const std::set<IdType>& clusters,
std::optional<TrackArtistLink::Type> linkType,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
std::optional<Range> range, bool& moreResults)
{
@@ -443,7 +443,7 @@ Artist::getReleaseCount() const
}
std::vector<Wt::Dbo::ptr<Track>>
Artist::getTracks(std::optional<TrackArtistLink::Type> linkType) const
Artist::getTracks(std::optional<TrackArtistLinkType> linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
@@ -462,7 +462,7 @@ Artist::getTracks(std::optional<TrackArtistLink::Type> linkType) const
}
std::vector<Wt::Dbo::ptr<Track>>
Artist::getTracksWithRelease(std::optional<TrackArtistLink::Type> linkType) const
Artist::getTracksWithRelease(std::optional<TrackArtistLinkType> linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
@@ -497,13 +497,14 @@ Artist::getRandomTracks(std::optional<std::size_t> count) const
}
std::vector<Wt::Dbo::ptr<Artist>>
Artist::getSimilarArtists(std::optional<std::size_t> offset, std::optional<std::size_t> count) const
Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::Query<pointer> query {session()->query<pointer>(
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<std::size_t> offset, std::optional<std::
" INNER JOIN artist a ON a.id = t_a_l.artist_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
" WHERE a.id = ?)"
" AND a.id <> ?"
)
" 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<pointer> query {session()->query<pointer>(oss.str())
.bind(self()->id())
.bind(self()->id())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(count ? static_cast<int>(*count) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
.limit(range ? static_cast<int>(range->limit) : -1)
.offset(range ? static_cast<int>(range->offset) : -1)};
for (TrackArtistLinkType type : artistLinkTypes)
query.bind(type);
Wt::Dbo::collection<pointer> res = query;
return std::vector<pointer>(res.begin(), res.end());
+3 -2
View File
@@ -19,13 +19,14 @@
#include "database/Release.hpp"
#include "utils/Logger.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#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<Wt::Dbo::ptr<Artist>>
Release::getArtists(TrackArtistLink::Type linkType) const
Release::getArtists(TrackArtistLinkType linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
+7 -1
View File
@@ -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";
+61 -10
View File
@@ -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<Wt::Dbo::ptr<Artist>>
Track::getArtists(TrackArtistLink::Type type) const
Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> artists {session()->query<Artist::pointer>("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<Wt::Dbo::ptr<Artist>>(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<Artist::pointer> query {session()->query<Artist::pointer>(oss.str())
.where("t.id = ?").bind(self()->id())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
Wt::Dbo::collection<Artist::pointer> res = query;
return std::vector<Artist::pointer>(std::begin(res), std::end(res));
}
std::vector<IdType>
Track::getArtistIds(TrackArtistLink::Type type) const
Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
Wt::Dbo::collection<IdType> artists {session()->query<IdType>("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<IdType>(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<IdType> query {session()->query<IdType>(oss.str())
.where("t.id = ?").bind(self()->id())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
Wt::Dbo::collection<IdType> res = query;
return std::vector<IdType>(std::begin(res), std::end(res));
}
std::vector<Wt::Dbo::ptr<TrackArtistLink>>
+12 -2
View File
@@ -25,7 +25,7 @@
namespace Database {
TrackArtistLink::TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, Type type)
TrackArtistLink::TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type)
: _type {type},
_track {track},
_artist {artist}
@@ -33,7 +33,7 @@ _artist {artist}
}
TrackArtistLink::pointer
TrackArtistLink::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type)
TrackArtistLink::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type)
{
session.checkUniqueLocked();
@@ -43,5 +43,15 @@ TrackArtistLink::create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::pt
return res;
}
EnumSet<TrackArtistLinkType>
TrackArtistLink::getUsedTypes(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackArtistLinkType> collection = session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link");
return EnumSet<TrackArtistLinkType>(std::begin(collection), std::end(collection));
}
}
+3 -3
View File
@@ -165,7 +165,7 @@ TrackList::getEntriesReverse(std::optional<std::size_t> offset, std::optional<st
static
Wt::Dbo::Query<Artist::pointer>
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType)
createArtistsQuery(Wt::Dbo::Session& session, const std::string& queryStr, IdType tracklistId, const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType)
{
auto query {session.query<Artist::pointer>(queryStr)};
query.join("track t ON t.id = t_a_l.track_id");
@@ -270,7 +270,7 @@ createTracksQuery(Wt::Dbo::Session& session, IdType tracklistId, const std::set<
}
std::vector<Artist::pointer>
TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
@@ -417,7 +417,7 @@ TrackList::getDuration() const
}
std::vector<Artist::pointer>
TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
assert(IdIsValid(self()->id()));
+12 -8
View File
@@ -21,14 +21,15 @@
#include <optional>
#include <string>
#include <unordered_set>
#include <vector>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
#include "TrackArtistLink.hpp"
#include "Types.hpp"
namespace Database
@@ -39,6 +40,7 @@ class ClusterType;
class Release;
class Session;
class Track;
class TrackArtistLink;
class User;
class Artist : public Wt::Dbo::Dbo<Artist>
@@ -68,7 +70,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<Range> range,
bool& moreExpected);
@@ -77,19 +79,19 @@ class Artist : public Wt::Dbo::Dbo<Artist>
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod);
static std::vector<pointer> getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults);
static std::vector<IdType> getAllIds(Session& session);
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLink::Type> linkType, std::optional<std::size_t> size = {});
static std::vector<IdType> getAllIdsRandom(Session& session, const std::set<IdType>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size = {});
static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::set<IdType>& clusters,
std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<Range>,
bool& moreResults);
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getStarred(Session& session,
Wt::Dbo::ptr<User> user,
const std::set<IdType>& clusters,
std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
SortMethod sortMethod,
std::optional<Range>, bool& moreResults);
@@ -100,10 +102,12 @@ class Artist : public Wt::Dbo::Dbo<Artist>
std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const;
std::vector<Wt::Dbo::ptr<Track>> getTracks(std::optional<TrackArtistLink::Type> linkType = {}) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksWithRelease(std::optional<TrackArtistLink::Type> linkType = {}) const;
std::vector<Wt::Dbo::ptr<Track>> getTracks(std::optional<TrackArtistLinkType> linkType = {}) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksWithRelease(std::optional<TrackArtistLinkType> linkType = {}) const;
std::vector<Wt::Dbo::ptr<Track>> getRandomTracks(std::optional<std::size_t> count) const;
std::vector<pointer> getSimilarArtists(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
// No artistLinkTypes means get them all
std::vector<pointer> getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes = {}, std::optional<Range> range = std::nullopt) const;
// Get the cluster of the tracks made by this artist
// Each clusters are grouped by cluster type, sorted by the number of occurence
@@ -20,11 +20,12 @@
#pragma once
#include <optional>
#include <set>
#include <Wt/Dbo/WtSqlTraits.h>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include "utils/UUID.hpp"
#include "TrackArtistLink.hpp"
#include "Types.hpp"
namespace Database
@@ -34,6 +35,7 @@ class Artist;
class Cluster;
class ClusterType;
class Release;
class Session;
class Track;
class User;
@@ -95,8 +97,8 @@ class Release : public Wt::Dbo::Dbo<Release>
Wt::WDateTime getLastWritten() const;
// Get the artists of this release
std::vector<Wt::Dbo::ptr<Artist> > getArtists(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const;
std::vector<Wt::Dbo::ptr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLink::Type::ReleaseArtist); }
std::vector<Wt::Dbo::ptr<Artist> > getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<Wt::Dbo::ptr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
bool hasVariousArtists() const;
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
+8 -4
View File
@@ -26,12 +26,13 @@
#include <unordered_set>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/WtSqlTraits.h>
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
#include "TrackArtistLink.hpp"
#include "Types.hpp"
namespace Database {
@@ -40,6 +41,8 @@ class Artist;
class Cluster;
class ClusterType;
class Release;
class Session;
class TrackArtistLink;
class TrackFeatures;
class TrackListEntry;
class TrackStats;
@@ -134,8 +137,9 @@ class Track : public Wt::Dbo::Dbo<Track>
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
std::vector<Wt::Dbo::ptr<Artist>> getArtists(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const;
std::vector<IdType> getArtistIds(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const;
// no artistLinkTypes means get all
std::vector<Wt::Dbo::ptr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<IdType> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const;
std::vector<Wt::Dbo::ptr<TrackArtistLink>> getArtistLinks() const;
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
@@ -19,64 +19,53 @@
#pragma once
#include <string>
#include <Wt/Dbo/Dbo.h>
#include "Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database {
class Artist;
class Session;
class Track;
class TrackArtistLink
namespace Database
{
public:
enum class Type
{
Artist, // regular artist
Arranger,
Composer,
Conductor,
Lyricist,
Mixer,
Performer,
Producer,
ReleaseArtist,
Remixer,
Writer,
};
using pointer = Wt::Dbo::ptr<TrackArtistLink>;
class Artist;
class Session;
class Track;
TrackArtistLink() = default;
TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, Type type);
class TrackArtistLink
{
public:
using pointer = Wt::Dbo::ptr<TrackArtistLink>;
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type);
TrackArtistLink() = default;
TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type);
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
Type getType() const { return _type; }
static pointer create(Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, TrackArtistLinkType type);
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _type, "name");
static EnumSet<TrackArtistLinkType> getUsedTypes(Session& session);
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
}
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
TrackArtistLinkType getType() const { return _type; }
private:
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _type, "name");
Type _type;
std::string _name;
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
}
Wt::Dbo::ptr<Track> _track;
Wt::Dbo::ptr<Artist> _artist;
};
private:
TrackArtistLinkType _type;
std::string _name;
Wt::Dbo::ptr<Track> _track;
Wt::Dbo::ptr<Artist> _artist;
};
}
@@ -25,7 +25,6 @@
#include <Wt/Dbo/Dbo.h>
#include "TrackArtistLink.hpp"
#include "Types.hpp"
namespace Database {
@@ -53,7 +52,7 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
// Stats utility
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
@@ -85,7 +84,7 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntries(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntriesReverse(std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}) const;
std::vector<Wt::Dbo::ptr<Artist>> getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLink::Type> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Artist>> getArtistsReverse(const std::set<IdType>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Release>> getReleasesReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
std::vector<Wt::Dbo::ptr<Track>> getTracksReverse(const std::set<IdType>& clusterIds, std::optional<Range> range, bool& moreResults) const;
@@ -35,5 +35,21 @@ namespace Database
std::size_t offset {};
std::size_t limit {};
};
enum class TrackArtistLinkType
{
Artist, // regular artist
Arranger,
Composer,
Conductor,
Lyricist,
Mixer,
Performer,
Producer,
ReleaseArtist,
Remixer,
Writer,
};
}
+19 -47
View File
@@ -42,13 +42,13 @@ namespace MetaData
template<typename T>
std::vector<T>
getPropertyValuesFirstMatchAs(const TagLib::PropertyMap& properties, const std::set<std::string>& keys)
getPropertyValuesFirstMatchAs(const TagLib::PropertyMap& properties, const std::vector<std::string_view>& keys)
{
std::vector<T> res;
for (const std::string& key : keys)
for (std::string_view key : keys)
{
const TagLib::StringList& values {properties[key]};
const TagLib::StringList& values {properties[std::string {key}]};
if (values.isEmpty())
continue;
@@ -91,12 +91,13 @@ splitAndTrimString(const std::string& str, const std::string& delimiters)
static
std::vector<Artist>
getArtists(const TagLib::PropertyMap& properties)
getArtists(const TagLib::PropertyMap& properties,
const std::vector<std::string_view>& artistTagNames,
const std::vector<std::string_view>& artistSortTagNames,
const std::vector<std::string_view>& artistMBIDTagNames
)
{
std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ARTISTS")};
if (artistNames.empty())
artistNames = getPropertyValuesAs<std::string>(properties, "ARTIST");
const std::vector<std::string> artistNames {getPropertyValuesFirstMatchAs<std::string>(properties, artistTagNames)};
if (artistNames.empty())
return {};
@@ -106,7 +107,7 @@ getArtists(const TagLib::PropertyMap& properties)
[&](const std::string& name) { return Artist {name}; });
{
const std::vector<std::string> artistSortNames {getPropertyValuesAs<std::string>(properties, "ARTISTSORT")};
const std::vector<std::string> artistSortNames {getPropertyValuesFirstMatchAs<std::string>(properties, artistSortTagNames)};
if (artistSortNames.size() == artists.size())
{
for (std::size_t i {}; i < artistSortNames.size(); ++i)
@@ -115,7 +116,7 @@ getArtists(const TagLib::PropertyMap& properties)
}
{
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"})};
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, artistMBIDTagNames)};
if (artistNames.size() == artistsMBID.size())
{
@@ -128,41 +129,6 @@ getArtists(const TagLib::PropertyMap& properties)
return artists;
}
static
std::vector<Artist>
getAlbumArtists(const TagLib::PropertyMap& properties)
{
std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ALBUMARTIST")};
if (artistNames.empty())
return {};
std::vector<Artist> artists;
artists.reserve(artistNames.size());
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(artists),
[&](const std::string& name) { return Artist {name}; });
{
const std::vector<std::string> artistSortNames {getPropertyValuesAs<std::string>(properties, "ALBUMARTISTSORT")};
if (artistSortNames.size() == artists.size())
{
for (std::size_t i {}; i < artistSortNames.size(); ++i)
artists[i].sortName = artistSortNames[i];
}
}
{
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"})};
if (artistsMBID.size() == artists.size())
{
for (std::size_t i {}; i < artistsMBID.size(); ++i)
artists[i].musicBrainzArtistID = artistsMBID[i];
}
}
return artists;
}
static
std::optional<Album>
getAlbum(const TagLib::PropertyMap& properties)
@@ -430,9 +396,15 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
processTag(track, tag, values, debug);
}
track.artists = getArtists(properties);
track.albumArtists = getAlbumArtists(properties);
track.album = getAlbum(properties);
track.artists = getArtists(properties, {"ARTIST", "ARTISTS"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"});
track.albumArtists = getArtists(properties, {"ALBUMARTIST"}, {"ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"});
track.conductorArtists = getArtists(properties, {"CONDUCTOR"}, {""}, {});
track.composerArtists = getArtists(properties, {"COMPOSER"}, {"COMPOSERSORT"}, {});
track.lyricistArtists = getArtists(properties, {"LYRICIST"}, {"LYRICISTSORT"}, {});
track.mixerArtists = getArtists(properties, {"MIXER"}, {""}, {});
track.producerArtists = getArtists(properties, {"PRODUCER"}, {""}, {});
track.remixerArtists = getArtists(properties, {"REMIXER", "ModifiedBy"}, {""}, {});
return track;
}
@@ -78,6 +78,12 @@ namespace MetaData
std::optional<float> trackReplayGain;
std::optional<float> albumReplayGain;
std::string discSubtitle;
std::vector<Artist> conductorArtists;
std::vector<Artist> composerArtists;
std::vector<Artist> lyricistArtists;
std::vector<Artist> mixerArtists;
std::vector<Artist> producerArtists;
std::vector<Artist> remixerArtists;
};
class IParser
+5 -2
View File
@@ -132,7 +132,10 @@ Engine::getSimilarReleases(Database::Session& dbSession, Database::IdType releas
}
std::unordered_set<Database::IdType>
Engine::getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std::size_t maxCount)
Engine::getSimilarArtists(Database::Session& dbSession,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount)
{
std::unordered_set<Database::IdType> res;
@@ -144,7 +147,7 @@ Engine::getSimilarArtists(Database::Session& dbSession, Database::IdType artistI
continue;
const IClassifier& classifier {*itClassifier->second};
res = classifier.getSimilarArtists(dbSession, artistId, maxCount);
res = classifier.getSimilarArtists(dbSession, artistId, linkTypes, maxCount);
if (!res.empty())
{
LMS_LOG(RECOMMENDATION, DEBUG) << "Got " << res.size() << " similar artists using classifier '" << classifier.getName() << "'";
+7 -4
View File
@@ -56,10 +56,13 @@ namespace Recommendation
void load(bool forceReload, const ProgressCallback& progressCallback) override;
void cancelLoad() override;
std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override;
std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) override;
std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override;
std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) override;
ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) override;
ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) override;
ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) override;
ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) override;
void setClassifierPriorities(const std::vector<ClassifierType>& classifierTypes);
void clearClassifiers();
+9 -4
View File
@@ -24,6 +24,7 @@
#include <unordered_set>
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database
{
@@ -49,10 +50,14 @@ namespace Recommendation
virtual bool load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback) = 0;
virtual void requestCancelLoad() = 0;
virtual std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const = 0;
virtual std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const = 0;
virtual std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const = 0;
virtual std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) const = 0;
using ResultContainer = std::unordered_set<Database::IdType>;
virtual ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const = 0;
virtual ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes, std::size_t maxCount) const = 0;
};
} // ns Recommendation
@@ -83,7 +83,10 @@ ClusterClassifier::getSimilarReleases(Database::Session& dbSession, Database::Id
}
std::unordered_set<Database::IdType>
ClusterClassifier::getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std::size_t maxCount) const
ClusterClassifier::getSimilarArtists(Database::Session& dbSession,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> artistLinkTypes,
std::size_t maxCount) const
{
std::unordered_set<Database::IdType> res;
@@ -93,7 +96,7 @@ ClusterClassifier::getSimilarArtists(Database::Session& dbSession, Database::IdT
if (!artist)
return res;
const auto artists {artist->getSimilarArtists(0, maxCount)};
const auto artists {artist->getSimilarArtists(artistLinkTypes, Database::Range {0, maxCount})};
std::transform(std::cbegin(artists), std::cend(artists), std::inserter(res, std::end(res)),
[](const auto& artist) { return artist.id(); });
@@ -40,10 +40,13 @@ namespace Recommendation
bool load(Database::Session&, bool, const ProgressCallback&) override { return true; }
void requestCancelLoad() override {}
std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) const override;
ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
};
} // namespace Recommendation
@@ -25,6 +25,7 @@
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/TrackList.hpp"
#include "som/DataNormalizer.hpp"
@@ -308,9 +309,34 @@ FeaturesClassifier::getSimilarReleases(Database::Session& session, Database::IdT
}
std::unordered_set<Database::IdType>
FeaturesClassifier::getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) const
FeaturesClassifier::getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const
{
auto similarArtistIds {getSimilarObjects({artistId}, _artistsMap, _artistPositions, maxCount)};
auto getSimilarArtistIdsForLinkType {[&] (Database::TrackArtistLinkType linkType)
{
std::unordered_set<Database::IdType> similarArtistIds;
const auto itArtists {_artistsMap.find(linkType)};
if (itArtists == std::cend(_artistsMap))
{
return similarArtistIds;
}
similarArtistIds = getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
return similarArtistIds;
}};
std::unordered_set<Database::IdType> similarArtistIds;
for (Database::TrackArtistLinkType linkType : linkTypes)
{
const auto similarArtistIdsForLinkType {getSimilarArtistIdsForLinkType(linkType)};
similarArtistIds.insert(std::begin(similarArtistIdsForLinkType), std::end(similarArtistIdsForLinkType));
}
if (!similarArtistIds.empty())
{
// Report only existing ids
@@ -319,13 +345,16 @@ FeaturesClassifier::getSimilarArtists(Database::Session& session, Database::IdTy
for (auto it {std::begin(similarArtistIds)}; it != std::end(similarArtistIds);)
{
const Database::IdType similarArtistId {*it};
if (!Database::Release::getById(session, similarArtistId))
if (!Database::Artist::getById(session, similarArtistId))
it = similarArtistIds.erase(it);
else
it++;
}
}
while (similarArtistIds.size() > maxCount)
similarArtistIds.erase(Random::pickRandom(similarArtistIds));
return similarArtistIds;
}
@@ -339,6 +368,7 @@ bool
FeaturesClassifier::load(Database::Session& session, bool forceReload, const ProgressCallback& progressCallback)
{
if (forceReload)
{
FeaturesClassifierCache::invalidate();
}
@@ -377,7 +407,6 @@ FeaturesClassifier::load(Database::Session& session,
const SOM::Coordinate width {network.getWidth()};
const SOM::Coordinate height {network.getHeight()};
_artistsMap = MatrixOfObjects {width, height};
_releasesMap = MatrixOfObjects {width, height};
_tracksMap = MatrixOfObjects {width, height};
@@ -407,10 +436,18 @@ FeaturesClassifier::load(Database::Session& session,
_releasePositions[track->getRelease().id()].insert(position);
_releasesMap[position].insert(track->getRelease().id());
}
for (const auto& artist : track->getArtists())
for (const auto& artistLink : track->getArtistLinks())
{
_artistPositions[artist.id()].insert(position);
_artistsMap[position].insert(artist.id());
_artistPositions[artistLink->getArtist().id()].insert(position);
auto itArtists {_artistsMap.find(artistLink->getType())};
if (itArtists == std::cend(_artistsMap))
{
auto [it, inserted] = _artistsMap.try_emplace(artistLink->getType(), MatrixOfObjects {});
assert(inserted);
itArtists = it;
itArtists->second = MatrixOfObjects {width, height};
}
itArtists->second[position].insert(artistLink->getArtist().id());
}
}
}
@@ -65,7 +65,10 @@ class FeaturesClassifier : public IClassifier
std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) const override;
std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) const override;
bool loadFromCache(Database::Session& session, const FeaturesClassifierCache& cache);
@@ -99,8 +102,8 @@ class FeaturesClassifier : public IClassifier
std::unique_ptr<SOM::Network> _network;
double _networkRefVectorsDistanceMedian {};
MatrixOfObjects _artistsMap;
ObjectPositions _artistPositions;
ObjectPositions _artistPositions;
std::unordered_map<Database::TrackArtistLinkType, MatrixOfObjects> _artistsMap;
MatrixOfObjects _releasesMap;
ObjectPositions _releasePositions;
@@ -20,9 +20,11 @@
#pragma once
#include <functional>
#include <optional>
#include <unordered_set>
#include "database/Types.hpp"
#include "utils/EnumSet.hpp"
namespace Database
{
@@ -46,10 +48,15 @@ namespace Recommendation
virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0;
virtual void cancelLoad() = 0;
virtual std::unordered_set<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0;
virtual std::unordered_set<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) = 0;
virtual std::unordered_set<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) = 0;
virtual std::unordered_set<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) = 0;
using ResultContainer = std::unordered_set<Database::IdType>;
virtual ResultContainer getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) = 0;
virtual ResultContainer getSimilarArtists(Database::Session& session,
Database::IdType artistId,
EnumSet<Database::TrackArtistLinkType> linkTypes,
std::size_t maxCount) = 0;
};
std::unique_ptr<IEngine> createEngine(Database::Db& db);
+26 -20
View File
@@ -29,6 +29,7 @@
#include "database/Release.hpp"
#include "database/ScanSettings.hpp"
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp"
#include "metadata/TagLibParser.hpp"
#include "recommendation/IEngine.hpp"
@@ -750,20 +751,6 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
title = file.filename().string();
}
// ***** Clusters
std::vector<Cluster::pointer> clusters {getOrCreateClusters(_dbSession, trackInfo->clusters)};
// ***** Artists
std::vector<Artist::pointer> artists {getOrCreateArtists(_dbSession, trackInfo->artists)};
// ***** Release artists
std::vector<Artist::pointer> releaseArtists {getOrCreateArtists(_dbSession, trackInfo->albumArtists)};
// ***** Release
Release::pointer release;
if (trackInfo->album)
release = getOrCreateRelease(_dbSession, *trackInfo->album);
// If file already exist, update data
// Otherwise, create it
if (!track)
@@ -784,15 +771,34 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
assert(track);
track.modify()->clearArtistLinks();
for (const auto& artist : artists)
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, artist, Database::TrackArtistLink::Type::Artist));
for (const Artist::pointer& artist : getOrCreateArtists(_dbSession, trackInfo->artists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, artist, Database::TrackArtistLinkType::Artist));
for (const auto& releaseArtist : releaseArtists)
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, releaseArtist, Database::TrackArtistLink::Type::ReleaseArtist));
for (const Artist::pointer& releaseArtist : getOrCreateArtists(_dbSession, trackInfo->albumArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, releaseArtist, Database::TrackArtistLinkType::ReleaseArtist));
for (const Artist::pointer& conductor : getOrCreateArtists(_dbSession, trackInfo->conductorArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, conductor, Database::TrackArtistLinkType::Conductor));
for (const Artist::pointer& composer : getOrCreateArtists(_dbSession, trackInfo->composerArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, composer, Database::TrackArtistLinkType::Composer));
for (const Artist::pointer& lyricist : getOrCreateArtists(_dbSession, trackInfo->lyricistArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, lyricist, Database::TrackArtistLinkType::Lyricist));
for (const Artist::pointer& mixer : getOrCreateArtists(_dbSession, trackInfo->mixerArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, mixer, Database::TrackArtistLinkType::Mixer));
for (const Artist::pointer& producer : getOrCreateArtists(_dbSession, trackInfo->producerArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, producer, Database::TrackArtistLinkType::Producer));
for (const Artist::pointer& remixer : getOrCreateArtists(_dbSession, trackInfo->remixerArtists))
track.modify()->addArtistLink(Database::TrackArtistLink::create(_dbSession, track, remixer, Database::TrackArtistLinkType::Remixer));
track.modify()->setScanVersion(_scanVersion);
track.modify()->setRelease(release);
track.modify()->setClusters(clusters);
if (trackInfo->album)
track.modify()->setRelease(getOrCreateRelease(_dbSession, *trackInfo->album));
track.modify()->setClusters(getOrCreateClusters(_dbSession, trackInfo->clusters));
track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title);
track.modify()->setDuration(trackInfo->duration);
+20 -14
View File
@@ -316,7 +316,7 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User:
trackResponse.setAttribute("coverArt", IdToString({Id::Type::Track, track.id()}));
auto artists {track->getArtists()};
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
if (!artists.empty())
{
trackResponse.setAttribute("artist", getArtistNames(artists));
@@ -893,7 +893,10 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString());
}
auto similarArtistsId {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession, id.value, count)};
auto similarArtistsId {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession,
id.value,
{TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist},
count)};
{
auto transaction {context.dbSession.createSharedTransaction()};
@@ -943,13 +946,13 @@ handleGetArtistsRequest(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
std::optional<TrackArtistLink::Type> linkType;
std::optional<TrackArtistLinkType> linkType;
switch (user->getSubsonicArtistListMode())
{
case User::SubsonicArtistListMode::AllArtists:
break;
case User::SubsonicArtistListMode::ReleaseArtists:
linkType = TrackArtistLink::Type::ReleaseArtist;
linkType = TrackArtistLinkType::ReleaseArtist;
break;
}
@@ -1087,13 +1090,13 @@ handleGetIndexesRequest(RequestContext& context)
if (!user)
throw UserNotAuthorizedError {};
std::optional<TrackArtistLink::Type> linkType;
std::optional<TrackArtistLinkType> linkType;
switch (user->getSubsonicArtistListMode())
{
case User::SubsonicArtistListMode::AllArtists:
break;
case User::SubsonicArtistListMode::ReleaseArtists:
linkType = TrackArtistLink::Type::ReleaseArtist;
linkType = TrackArtistLinkType::ReleaseArtist;
break;
}
@@ -1115,36 +1118,39 @@ Response
handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
{
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Artist)
const Id artistId {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (artistId.type != Id::Type::Artist)
throw BadParameterGenericError {"id"};
// Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").value_or(50)};
auto similarArtistsId {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession, id.value, 5)};
auto similarArtistIds {Service<Recommendation::IEngine>::get()->getSimilarArtists(context.dbSession,
artistId.value,
{TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist},
5)};
auto transaction {context.dbSession.createSharedTransaction()};
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
const Artist::pointer artist {Artist::getById(context.dbSession, artistId.value)};
if (!artist)
throw RequestedDataNotFoundError {};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw UserNotAuthorizedError {};
// "Returns a random collection of songs from the given artist and similar artists"
auto tracks {artist->getRandomTracks(count / 2)};
for ( const auto& similarArtistId : similarArtistsId )
for (const Database::IdType similarArtistId : similarArtistIds)
{
Artist::pointer similarArtist {Artist::getById(context.dbSession, similarArtistId)};
const Artist::pointer similarArtist {Artist::getById(context.dbSession, similarArtistId)};
if (!similarArtist)
continue;
auto similarArtistTracks {similarArtist->getRandomTracks((count / 2) / 5)};
tracks.insert(tracks.end(),
tracks.insert(std::end(tracks),
std::make_move_iterator(std::begin(similarArtistTracks)),
std::make_move_iterator(std::end(similarArtistTracks)));
}
+157
View File
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2020 Emeric Poupon
*
* This file is part of LMS.
*
* LMS 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.
*
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cassert>
#include <type_traits>
template <typename T, typename underlying_type = std::uint32_t>
class EnumSet
{
static_assert(std::is_enum<T>::value);
static_assert(std::is_same<underlying_type, std::uint64_t>::value || std::is_same<underlying_type, std::uint32_t>::value);
using index_type = std::uint_fast8_t;
public:
EnumSet() = default;
constexpr EnumSet(std::initializer_list<T> values)
{
for (T value : values)
insert(value);
}
template <typename It>
constexpr EnumSet(It begin, It end)
{
for (It it {begin}; it != end; ++it)
insert(*it);
}
constexpr void insert(T value)
{
assert(static_cast<size_t>(value) < sizeof(_bitfield) * 8);
_bitfield |= (underlying_type{ 1 } << static_cast<underlying_type>(value));
}
constexpr void erase(T value)
{
assert(static_cast<size_t>(value) < sizeof(_bitfield) * 8);
_bitfield &= ~(underlying_type{ 1 } << static_cast<underlying_type>(value));
}
constexpr bool empty() const
{
return _bitfield == 0;
}
constexpr bool contains(T value) const
{
assert(static_cast<size_t>(value) < sizeof(_bitfield) * 8);
return _bitfield & (underlying_type{ 1 } << static_cast<underlying_type>(value));
}
class iterator
{
public:
using value_type = T;
constexpr value_type operator*() const
{
return static_cast<value_type>(_index);
}
constexpr bool operator==(const iterator& _other) const
{
return &_container == &_other._container && _index == _other._index;
}
constexpr bool operator!=(const iterator& _other) const
{
return !(*this == _other);
}
constexpr iterator& operator++()
{
_index = _container.getFirstBitSetIndex(_index + 1);
return *this;
}
private:
friend class EnumSet;
constexpr iterator(const EnumSet& _container, index_type _index)
: _container {_container}
, _index {_index}
{
}
const EnumSet& _container;
index_type _index;
};
constexpr iterator begin() const
{
return iterator {*this, getFirstBitSetIndex()};
}
constexpr iterator end() const
{
return iterator {*this, npos};
}
private:
static_assert(std::numeric_limits<index_type>::max() >= sizeof(underlying_type) * 8);
enum : index_type { npos = sizeof(underlying_type) * 8 };
constexpr index_type getFirstBitSetIndex(index_type start = {}) const
{
assert(start < npos);
// return npos if no bit found
index_type res {countTrailingZero(_bitfield >> start)};
if (res == npos)
return res;
return res + start;
}
static constexpr index_type countTrailingZero(underlying_type bitField)
{
index_type res {};
while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0)
{
++res;
bitField >>= 1;
}
if (res == sizeof(underlying_type) * 8)
res = npos;
return res;
}
underlying_type _bitfield{};
};