/* * Copyright (C) 2022 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 . */ #include "PlaylistGeneratorService.hpp" #include "services/database/Db.hpp" #include "services/database/Session.hpp" #include "services/database/Track.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "playlist-constraints/ConsecutiveArtists.hpp" #include "playlist-constraints/ConsecutiveReleases.hpp" #include "playlist-constraints/DuplicateTracks.hpp" #include "utils/Logger.hpp" namespace Recommendation { using namespace Database; std::unique_ptr createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService) { return std::make_unique(db, recommendationService); } PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService) : _db{ db } , _recommendationService{ recommendationService } { _constraints.push_back(std::make_unique(_db)); _constraints.push_back(std::make_unique(_db)); _constraints.push_back(std::make_unique()); } std::vector PlaylistGeneratorService::extendPlaylist(TrackListId tracklistId, std::size_t maxCount) const { LMS_LOG(RECOMMENDATION, DEBUG) << "Requested to extend playlist by " << maxCount << " similar tracks"; // supposed to be ordered from most similar to least similar std::vector similarTracks{ _recommendationService.findSimilarTracks(tracklistId, maxCount * 2) }; // ask for more tracks than we need as it will be easier to respect constraints const std::vector startingTracks{ getTracksFromTrackList(tracklistId) }; std::vector finalResult = startingTracks; finalResult.reserve(startingTracks.size() + maxCount); std::vector scores; for (std::size_t i{}; i < maxCount; ++i) { if (similarTracks.empty()) break; scores.resize(similarTracks.size(), {}); // select the similar track that has the best score for (std::size_t trackIndex{}; trackIndex < similarTracks.size(); ++trackIndex) { using namespace Database::Debug; finalResult.push_back(similarTracks[trackIndex]); scores[trackIndex] = 0; for (const auto& constraint : _constraints) scores[trackIndex] += constraint->computeScore(finalResult, finalResult.size() - 1); finalResult.pop_back(); // early exit if we consider we found a track with no constraint violation (since similarTracks sorted from most to least similar) if (scores[trackIndex] < 0.01) break; } // get the best score const std::size_t bestScoreIndex{ static_cast(std::distance(std::cbegin(scores), std::min_element(std::cbegin(scores), std::cend(scores)))) }; finalResult.push_back(similarTracks[bestScoreIndex]); similarTracks.erase(std::begin(similarTracks) + bestScoreIndex); } // for now, just get some more similar tracks return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult)); } TrackContainer PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const { TrackContainer tracks; Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createReadTransaction() }; Track::FindParameters params; params.setTrackList(tracklistId); params.setSortMethod(TrackSortMethod::TrackList); params.setDistinct(false); for (const TrackId trackId : Track::findIds(dbSession, params).results) tracks.push_back(trackId); return tracks; } }