/* * Copyright (C) 2017 Kevin Whitaker * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "GroovePlayer.h" #include "db/UserAction.h" #include #include #include #include "WebInterface.h" #include #include #include #include #include #include #include #include #include #include GroovePlayerMgr::GroovePlayerMgr (std::string dbFile) { sqliteConnection = new Wt::Dbo::backend::Sqlite3(dbFile); connectionPool = new Wt::Dbo::FixedSqlConnectionPool(sqliteConnection, 3); musicScanDir = std::filesystem::current_path()/"music"; Wt::Dbo::Session sqlSession; sqlSession.setConnectionPool(*connectionPool); sqlSession.mapClass("user"); sqlSession.mapClass("tracks"); sqlSession.mapClass("actions"); try { sqlSession.createTables(); } catch(Wt::Dbo::Exception e) { Wt::log("info") << "Using Existing DB."; } groove_init(); grooveAudioScanner = new std::thread([this](){grooveAudioScannerLoop();}); grooveEvents = new std::thread([this](){grooveEventLoop();}); } GroovePlayerMgr::~GroovePlayerMgr() { delete connectionPool; } std::list GroovePlayerMgr::getNextVoteBatch(Wt::Dbo::Session* session) { /** * This method will attempt to pick 3 tracks that will be up for selection next. * * The first track should be from the same album or artist. * The second track should be from the same genre. * The third track should be from something in a different genre. * * What is picked inside those requirements is a mix of random and TODO:based on past user actions(33% of time). * * The third track will be replaced by a recent request item if one exists. */ std::list selectedTracks; Wt::Dbo::Transaction transaction(*session); //First make sure there are at least 3 tracks to suggest. int trackCount = session->query("select count(fingerprint) from tracks"); if(trackCount < 3) { //Return empty list to show there are not enough tracks to allow voting. return selectedTracks; } //Determine first track int trackAlbumCount = session->query("select count(fingerprint) from tracks").where("album = ?").bind(currentTrack.trackAlbumName); int trackArtistCount = session->query("select count(fingerprint) from tracks").where("artist = ?").bind(currentTrack.trackArtistName); int computerSlightOfHand = rand() % 10 + 1; if(trackAlbumCount > 0 && ((computerSlightOfHand > 3 && trackAlbumCount > 1) || (computerSlightOfHand > 3 && trackAlbumCount == 1 && trackArtistCount == 1) || !(trackArtistCount > 1 && trackAlbumCount == 1))) { //Pick item from album Wt::Dbo::ptr eligibleTrack = session->find().where("album = ?").limit(1).offset(rand() % trackAlbumCount).bind(currentTrack.trackAlbumName); //Try to make sure the same track isn't up to vote again. if((*eligibleTrack).trackFingerprint == currentTrack.trackFingerprint && trackAlbumCount > 1) { while((*eligibleTrack).trackFingerprint == currentTrack.trackFingerprint) { eligibleTrack = session->find().where("album = ?").limit(1).offset(rand() % trackAlbumCount).bind(currentTrack.trackAlbumName); } } AudioTrack selected = *eligibleTrack; getPictureFromTrack(&selected); selectedTracks.push_back(selected); } else { //Pick item from artist Wt::Dbo::ptr eligibleTrack = session->find().where("artist = ?").limit(1).offset(rand() % trackArtistCount).bind(currentTrack.trackArtistName); //Try to make sure the same track isn't up to vote again. if((*eligibleTrack).trackFingerprint == currentTrack.trackFingerprint && trackArtistCount > 1) { while((*eligibleTrack).trackFingerprint == currentTrack.trackFingerprint) { eligibleTrack = session->find().where("artist = ?").limit(1).offset(rand() % trackArtistCount).bind(currentTrack.trackArtistName); } } AudioTrack selected = *eligibleTrack; getPictureFromTrack(&selected); selectedTracks.push_back(selected); } //Determine second track int trackGenreCount = session->query("select count(fingerprint) from tracks").where("genre = ?").bind(currentTrack.trackGenre); if(trackGenreCount > 0) { //Pick item from genre Wt::Dbo::ptr eligibleTrack = session->find().where("genre = ?").limit(1).offset(rand() % trackGenreCount).bind(currentTrack.trackGenre); //Try to make sure same track isn't up to vote again and isn't the same as first votable track. if(((*eligibleTrack).trackFingerprint == currentTrack.trackFingerprint || (*eligibleTrack).trackFingerprint == selectedTracks.front().trackFingerprint) && trackGenreCount > 2) { while((*eligibleTrack).trackFingerprint == currentTrack.trackFingerprint || (*eligibleTrack).trackFingerprint == selectedTracks.front().trackFingerprint) { eligibleTrack = session->find().where("genre = ?").limit(1).offset(rand() % trackGenreCount).bind(currentTrack.trackGenre); } } AudioTrack selected = *eligibleTrack; getPictureFromTrack(&selected); selectedTracks.push_back(selected); } //Determine third track int trackNotGenreCount = session->query("select count(fingerprint) from tracks").where("genre != ?").bind(currentTrack.trackGenre); if(requestQueue.size() > 0) { //There's a request. Pick one up front and put as third item. AudioTrack selected = requestQueue.front().first; getPictureFromTrack(&selected); selectedTracks.push_back(selected); } else if(trackNotGenreCount > 0) { //Pick from other genre Wt::Dbo::ptr eligibleTrack = session->find().where("genre != ?").limit(1).offset(rand() % trackNotGenreCount).bind(currentTrack.trackGenre); AudioTrack selected = *eligibleTrack; getPictureFromTrack(&selected); selectedTracks.push_back(selected); } else { //Pick randomly Wt::Dbo::ptr eligibleTrack = session->find().limit(1).offset(rand() % trackCount); AudioTrack selected = *eligibleTrack; getPictureFromTrack(&selected); selectedTracks.push_back(selected); } transaction.commit(); Wt::log("info") << "Next set of tracks to vote picked: 1=>[" << (*selectedTracks.begin()).trackName << "] 2=>[" << (*std::next(selectedTracks.begin(),1)).trackName << "] 3=>[" << (*std::next(selectedTracks.begin(),2)).trackName << "]"; return selectedTracks; } void GroovePlayerMgr::grooveEventLoop() { /** * On first boot, random track that has been voted before will start(or random if no track has been played before). * When voting ends, if no vote was cast, the first will be picked 50% of the time with second being 30% of the time and third being picked 20% of the time. * Exception is if third track is the top request, then it is picked. If another song is voted over this, it will be put in back of request queue. */ Wt::Dbo::Session sqlSession; sqlSession.setConnectionPool(*connectionPool); sqlSession.mapClass("user"); sqlSession.mapClass("tracks"); sqlSession.mapClass("actions"); Wt::Dbo::Transaction transaction(sqlSession); int trackCount = sqlSession.query("select count(fingerprint) from tracks"); transaction.commit(); //Wait until at least 3 tracks are in DB to prevent nothing to vote on. if(trackCount < 3) { while(trackCount < 3) { Wt::Dbo::Transaction transaction(sqlSession); trackCount = sqlSession.query("select count(fingerprint) from tracks"); transaction.commit(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } Wt::Dbo::Transaction bootTransaction(sqlSession); //Pick initial track to bootstrap. int tracksPlayedBefore = sqlSession.query("select count(fingerprint) from tracks as t join actions on (actions.action = 3 or actions.action = 2) and actions.track_id = t.id"); AudioTrack selectedTrack; if (tracksPlayedBefore > 0) { selectedTrack = *sqlSession.query>("select t from tracks as t join actions on (actions.action = 3 or actions.action = 2) and actions.track_id = t.id").limit(1).offset(rand() % tracksPlayedBefore).resultValue(); } else { int trackCount = sqlSession.query("select count(fingerprint) from tracks"); selectedTrack = *sqlSession.find().limit(1).offset(rand() % trackCount).resultValue(); } bootTransaction.commit(); struct GroovePlaylist* playlist = groove_playlist_create(); currentPlaylist = playlist; struct GroovePlayer* player = groove_player_create(); if(!player) {return;} currentPlayer = player; currentRPG = groove_loudness_detector_create(); currentRPG->disable_album = 1; currentRPG->info_queue_size = 1; groove_playlist_insert(playlist, groove_file_open(selectedTrack.trackPath.c_str()),1.0,1.0,nullptr); //Now boostrap player with initial data //groove_loudness_detector_attach(currentRPG,playlist); groove_player_attach(player, playlist); groove_playlist_play(playlist); //Now start loop while(getInstance()->continueEventLoop) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); PlayerEvent event = getNextPlayerEvent(&sqlSession); if(event.eventType == NOTHING) { continue; } else if(event.eventType == GROOVE_NOWPLAYING) { //Clean up playlist if more than 4 if(groove_playlist_count(playlist) > 4) { groove_file_close(currentPlaylist->head->file); groove_playlist_remove(currentPlaylist,currentPlaylist->head); } //clean up state currentSkipRequester.clear(); currentTrack = getCurrentTrackDB(&sqlSession); getPictureFromTrack(¤tTrack); //Pick new batch of tracks to vote on and inform UI of update. currentVoteBatch = getNextVoteBatch(&sqlSession); currentVoteStatus = std::list>(); currentUsersVoted.clear(); for(auto track : currentVoteBatch) { currentVoteStatus.push_back(std::pair(track,0)); } Wt::WServer::instance()->postAll([&]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->songChangedFromServer(currentTrack); static_cast(app)->voteTracksUpdatedFromServer(currentVoteBatch); } } ); Wt::log("info") << "Track playing changed to: " << currentTrack.trackName; //Make sure gain is suggested // struct GrooveLoudnessDetectorInfo info; // groove_loudness_detector_info_get(currentRPG,&info,1); // struct GroovePlaylistItem* item; // groove_player_position(currentPlayer,&item,nullptr); // groove_playlist_set_item_gain(currentPlaylist, item, clamp_rg(-18.0 - info.loudness)); // groove_playlist_set_item_peak(currentPlaylist, item, info.peak); // Wt::log("info") << "Gain being set to:" << clamp_rg(-18.0 - info.loudness) << " and peak to:" << info.peak; } else if(event.eventType == VOTING_ENDED) { //Look at votes and add correct track to end of playlist. //If more than one track has highest votes, pick random. TODO:maybe base off of other play data instead. std::list> trackWinners; for(auto track : currentVoteStatus) { //There nothing yet and there was at least a vote is an initial winner. if(trackWinners.size() == 0 && track.second > 0) { trackWinners.push_back(track); continue; } //If there is a winner. Check if higher. If so, clear the list and add this one. If equal, add to list. if(trackWinners.size() > 0) { if(trackWinners.front().second < track.second) { trackWinners.clear(); trackWinners.push_back(std::pair(track.first, track.second)); } else if(trackWinners.front().second == track.second) { trackWinners.push_back(std::pair(track.first, track.second)); } } } AudioTrack winner; //Randomly pick from winners(in case of tie), if there are any. if(trackWinners.size() > 0) { auto iter = trackWinners.begin(); std::advance(iter,rand() % trackWinners.size()); winner = (*iter).first; //Deal with requests if there are any. if(requestQueue.size() > 0) { //If winning track was a request, now you can remove it from request queue. if(requestQueue.front().first.trackFingerprint == winner.trackFingerprint) { requestQueue.pop_front(); } //Request didn't win. Put request at bottom of queue. else { requestQueue.push_back(requestQueue.front()); requestQueue.pop_front(); } } } else { //Pick request song if there are any. if(requestQueue.size() > 0 && requestQueue.front().first.trackFingerprint == currentVoteBatch.back().trackFingerprint) //Assumes back is track 3. { winner = requestQueue.front().first; requestQueue.pop_front(); } else { //Pick based on percentages. int pick = rand() % 10 +1; if(pick <= 5 && currentVoteBatch.front().trackFingerprint != currentTrack.trackFingerprint) { //First track wins winner = currentVoteBatch.front(); } else if(pick <= 8 && (*std::next(currentVoteBatch.begin(),1)).trackFingerprint != currentTrack.trackFingerprint) { //Second track wins. winner = (*std::next(currentVoteBatch.begin(),1)); } else { //Third track wins winner = (*std::next(currentVoteBatch.begin(),2)); } } } groove_playlist_insert(currentPlaylist,groove_file_open(winner.trackPath.c_str()),1.0,1.0,nullptr); Wt::WServer::instance()->postAll([winner]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->voteNextPollClosedFromServer(winner); } } ); Wt::log("info")<< "Voting has ended. Next track is: " << winner.trackName; } else if(event.eventType == VOTE_CAST) { if(!voteEndedButNotNextTrackYet) { //Add vote to DB Wt::Dbo::Transaction voteTransaction(sqlSession); UserAction* action = new UserAction(); action->action = UserAction::UAction::VoteTrack; action->user = sqlSession.find().where("username = ?").bind(event.userInvolved.username); action->trackInvolved = sqlSession.find().where("fingerprint = ?").bind(event.tracksInvolved.front().trackFingerprint); action->datetime = Wt::WDateTime::currentDateTime(); sqlSession.add(action); voteTransaction.commit(); //Add vote to vote status for(auto voteItem = currentVoteStatus.begin(); voteItem != currentVoteStatus.end(); voteItem++) { if(voteItem->first.trackFingerprint == event.tracksInvolved.front().trackFingerprint) { std::pair newItem = (*voteItem); newItem.second = ++(newItem.second); currentVoteStatus.insert(currentVoteStatus.erase(voteItem),newItem); currentUsersVoted.push_back(event.userInvolved); break; } } //Update vote display on all clients. Wt::WServer::instance()->postAll([event]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->voteNextSongFromServer(event.userInvolved, event.tracksInvolved.front()); } } ); } } else if(event.eventType == PLAYING_PAUSED) { //Add action to DB Wt::Dbo::Transaction pauseTransaction(sqlSession); UserAction* action = new UserAction(); action->action = UserAction::UAction::PlayPause; action->user = sqlSession.find().where("username = ?").bind(event.userInvolved.username); action->trackInvolved = sqlSession.find().where("fingerprint = ?").bind(event.tracksInvolved.front().trackFingerprint); action->datetime = Wt::WDateTime::currentDateTime(); sqlSession.add(action); pauseTransaction.commit(); groove_playlist_pause(currentPlaylist); //Update vote display on all clients. Wt::WServer::instance()->postAll([event]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->playPauseActionFromServer(event.userInvolved, true); } } ); } else if(event.eventType == PLAYING_RESUMED) { //Add action to DB Wt::Dbo::Transaction playTransaction(sqlSession); UserAction* action = new UserAction(); action->action = UserAction::UAction::PlayPause; action->user = sqlSession.find().where("username = ?").bind(event.userInvolved.username); action->trackInvolved = sqlSession.find().where("fingerprint = ?").bind(event.tracksInvolved.front().trackFingerprint); action->datetime = Wt::WDateTime::currentDateTime(); sqlSession.add(action); playTransaction.commit(); groove_playlist_play(currentPlaylist); //Update vote display on all clients. Wt::WServer::instance()->postAll([event]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->playPauseActionFromServer(event.userInvolved, false); } } ); } else if(event.eventType == SKIP_REQUESTED) { if(!voteEndedButNotNextTrackYet) { currentSkipRequester.push_back(event.userInvolved); skipRequestedAt = std::chrono::steady_clock::now(); Wt::Dbo::Transaction skipTransaction(sqlSession); UserAction* action = new UserAction(); action->action = UserAction::UAction::RequestSkip; action->user = sqlSession.find().where("username = ?").bind(event.userInvolved.username); action->trackInvolved = sqlSession.find().where("fingerprint = ?").bind(event.tracksInvolved.front().trackFingerprint); action->datetime = Wt::WDateTime::currentDateTime(); sqlSession.add(action); skipTransaction.commit(); //Display skip request on all clients. Wt::WServer::instance()->postAll([event]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->skipVotedFromServer(event.userInvolved); } } ); } } else if(event.eventType == SKIP_VOTE_CAST_AGAINST) { if(!voteEndedButNotNextTrackYet) { currentSkipRequester.clear(); //Display skip request on all clients. Wt::WServer::instance()->postAll([event]() { Wt::WApplication* app = Wt::WApplication::instance(); if(app != nullptr) { static_cast(app)->skipVoteUpdateFromServer(event.userInvolved, false); } } ); } } else if(event.eventType == SKIP_VOTING_ENDED) { //Don't skip without first determining next track if(!voteEndedButNotNextTrackYet) { voteEndedButNotNextTrackYet = true; lastInternalEvents.push_front(PlayerEvent() = {PlayerEventType::SKIP_VOTING_ENDED}); lastInternalEvents.push_front(PlayerEvent() = {PlayerEventType::VOTING_ENDED}); } else { GroovePlaylistItem* item; groove_player_position(currentPlayer,&item,nullptr); groove_playlist_seek(currentPlaylist, item->next, 0.0); } } else if(event.eventType == ADMIN_FORCE_SKIP) { if(!voteEndedButNotNextTrackYet) { lastInternalEvents.push_front(PlayerEvent() = {PlayerEventType::SKIP_VOTING_ENDED}); } } } currentPlaylist = nullptr; currentPlayer = nullptr; groove_player_detach(player); groove_player_destroy(player); //groove_loudness_detector_detach(currentRPG); groove_loudness_detector_destroy(currentRPG); groove_playlist_destroy(playlist); } void GroovePlayerMgr::shutdown() { continueEventLoop = false; } GroovePlayerMgr::PlayerEvent GroovePlayerMgr::getNextPlayerEvent(Wt::Dbo::Session* session) { double timeIntoTrack; groove_player_position(currentPlayer,nullptr,&timeIntoTrack); if(!voteEndedButNotNextTrackYet && currentTrack.trackName != std::string() && currentTrack.trackLengthSeconds-timeIntoTrack < 3) { voteEndedButNotNextTrackYet = true; return PlayerEvent() = {PlayerEventType::VOTING_ENDED}; } GroovePlayerEvent event; if(groove_player_event_get(this->currentPlayer,&event, 0) > 0) { if(event.type == GROOVE_EVENT_NOWPLAYING) { voteEndedButNotNextTrackYet = false; return PlayerEvent() = {PlayerEventType::GROOVE_NOWPLAYING}; } } if(!voteEndedButNotNextTrackYet && currentSkipRequester.size() > 0 && skipRequestedAt+std::chrono::seconds(10) < std::chrono::steady_clock::now()) { //Skip succeeded. Time to skip song. lastInternalEvents.push_front(PlayerEvent() = {PlayerEventType::SKIP_VOTING_ENDED}); } if(lastInternalEvents.size() > 0) { PlayerEvent event = lastInternalEvents.front(); lastInternalEvents.pop_front(); return event; } return PlayerEvent() = {PlayerEventType::NOTHING}; } AudioTrack GroovePlayerMgr::getCurrentTrackDB(Wt::Dbo::Session* session) { Wt::Dbo::Transaction transaction(*session); GroovePlaylistItem* currentItem; groove_player_position(this->currentPlayer,¤tItem,nullptr); if(currentItem != nullptr) { AudioTrack track = *session->find().where("path = ?").bind(currentItem->file->filename).resultValue(); transaction.commit(); return track; } transaction.commit(); return AudioTrack(); } GroovePlayerMgr::ScanResults GroovePlayerMgr::addFileToTrackDBIfTagged(Wt::Dbo::Session* session, std::filesystem::path file) { //Make sure file matches file type std::string extensionLowered; for(auto elem : file.extension().string()) { extensionLowered.push_back(std::tolower(elem)); } if(extensionLowered != ".mp3") { return ScanResults::WRONG_FILETYPE; } //Now check if tags exist and put into DB. struct GrooveFile* gfile = groove_file_open(file.c_str()); struct GrooveTag* artist_tag = groove_file_metadata_get(gfile, "artist", nullptr, 0); struct GrooveTag* album_tag = groove_file_metadata_get(gfile, "album", nullptr, 0); struct GrooveTag* name_tag = groove_file_metadata_get(gfile, "title", nullptr, 0); struct GrooveTag* genre_tag = groove_file_metadata_get(gfile, "genre", nullptr, 0); if(artist_tag == nullptr || album_tag == nullptr || name_tag == nullptr || genre_tag == nullptr) { //Only accept song with all metadata for DB. Wt::log("info") << "Audio track " << file << " did not have all required tags."; groove_file_close(gfile); if(artist_tag == nullptr && album_tag != nullptr && name_tag != nullptr && genre_tag != nullptr) return ScanResults::MISSING_ARTIST_TAG; if(artist_tag != nullptr && album_tag == nullptr && name_tag == nullptr && genre_tag == nullptr) return ScanResults::MISSING_ALBUM_TAG; if(artist_tag != nullptr && album_tag != nullptr && name_tag == nullptr && genre_tag != nullptr) return ScanResults::MISSING_TITLE_TAG; if(artist_tag != nullptr && album_tag != nullptr && name_tag != nullptr && genre_tag == nullptr) return ScanResults::MISSING_GENRE_TAG; return ScanResults::MISSING_MULTIPLE_TAGS; } //Take fingerprint and compare to DB. struct GrooveFingerprinter* fingerprinter = groove_fingerprinter_create(); struct GrooveFingerprinterInfo info; struct GroovePlaylist* playlist = groove_playlist_create(); groove_playlist_insert(playlist,gfile,1.0,1.0,nullptr); groove_fingerprinter_attach(fingerprinter, playlist); groove_fingerprinter_info_get(fingerprinter, &info, 1); double trackLen = info.duration; char* encodedFP; groove_fingerprinter_encode(info.fingerprint,info.fingerprint_size,&encodedFP); std::string fingerprint = std::string(encodedFP); groove_fingerprinter_dealloc(encodedFP); groove_fingerprinter_free_info(&info); groove_fingerprinter_detach(fingerprinter); groove_fingerprinter_destroy(fingerprinter); groove_playlist_destroy(playlist); Wt::Dbo::Transaction transaction(*session); int existing = session->query("select count(fingerprint) from tracks").where("fingerprint = ?").bind(fingerprint); if(existing >0) { //This track has a duplicate already. groove_file_close(gfile); return ScanResults::DUPLICATE_TRACK; } //Add to DB. AudioTrack* newTrack = new AudioTrack(); newTrack->trackName = groove_tag_value(name_tag); newTrack->trackAlbumName = groove_tag_value(album_tag); newTrack->trackArtistName = groove_tag_value(artist_tag); newTrack->trackGenre = groove_tag_value(genre_tag); newTrack->trackLengthSeconds = trackLen; newTrack->trackFingerprint = fingerprint; newTrack->trackPath = file.string(); //Quickly see if there is cover artist getPictureFromTrack(newTrack); if(newTrack->coverArt.size() == 0) { Wt::log("info") << "Audio track " << newTrack->trackPath << " did not have cover art in the file."; groove_file_close(gfile); return ScanResults::MISSING_COVERART_TAG; } session->add(newTrack); transaction.commit(); groove_file_close(gfile); return ScanResults::ACCEPTED; } void GroovePlayerMgr::removeOrphanedTracks(Wt::Dbo::Session* session) { Wt::Dbo::Transaction transaction(*session); Wt::Dbo::collection> tracks = session->find(); for(Wt::Dbo::collection>::const_iterator i = tracks.begin(); i != tracks.end(); ++i) { if(!std::filesystem::exists(std::filesystem::path((*i)->trackPath))) { Wt::Dbo::ptr item = session->find().where("fingerprint = ?").bind((*i)->trackFingerprint); Wt::log("info") << (*i)->trackPath + " not found in filesystem. Removed from DB."; item.remove(); } } transaction.commit(); } void GroovePlayerMgr::removeTrack(Wt::Dbo::Session* session, AudioTrack track) { Wt::Dbo::Transaction transaction(*session); Wt::Dbo::ptr DBtrack = session->find().where("fingerprint = ?").bind(track.trackFingerprint); Wt::log("info") << (*DBtrack).trackPath + " being removed."; if(std::filesystem::exists(std::filesystem::path((*DBtrack).trackPath))) { remove((*DBtrack).trackPath.c_str()); } DBtrack.remove(); transaction.commit(); } void GroovePlayerMgr::getPictureFromTrack(AudioTrack* trackToFill) { if(trackToFill->trackPath != "") { TagLib::String file = TagLib::String(trackToFill->trackPath); if(file.substr(file.size()-3).upper() == "MP3" && TagLib::MPEG::File(trackToFill->trackPath.c_str()).hasID3v2Tag()) { TagLib::MPEG::File file(trackToFill->trackPath.c_str()); TagLib::ID3v2::Tag* tag = file.ID3v2Tag(); if(!tag->frameListMap()["APIC"].isEmpty() && dynamic_cast(tag->frameListMap()["APIC"].front()) != nullptr) { TagLib::ID3v2::AttachedPictureFrame* pic = dynamic_cast(tag->frameListMap()["APIC"].front()); trackToFill->coverArt = TagLib::ByteVector(pic->picture().data(),pic->picture().size()); trackToFill->coverMimeType = std::string(pic->mimeType().toCString(true)); } } // else if(dynamic_cast(tagTrack.tag()) != nullptr && !dynamic_cast(tagTrack.tag())->isEmpty()) // { // TagLib::Ogg::XiphComment* tag = dynamic_cast(tagTrack.tag()); // if(tag->fieldListMap()["METADATA_BLOCK_PICTURE"].isEmpty()) // { // tag->fieldListMap()["METADATA_BLOCK_PICTURE"];//TODO? // } // } } } bool GroovePlayerMgr::fileExistsInLibrary(std::string filename) { for(std::filesystem::directory_entry p: std::filesystem::directory_iterator(musicScanDir)) { if(p.path().filename() == filename) { return true; } } return false; } void GroovePlayerMgr::grooveAudioScannerLoop() { Wt::Dbo::Session sqlSession; sqlSession.setConnectionPool(*connectionPool); sqlSession.mapClass("user"); sqlSession.mapClass("tracks"); sqlSession.mapClass("actions"); if(!std::filesystem::exists(musicScanDir)) { std::filesystem::create_directory(musicScanDir); Wt::log("info") << "Directory "+std::filesystem::canonical(musicScanDir).string()+" didn't exist, so created."; } else { Wt::log("info") << "Directory "+std::filesystem::canonical(musicScanDir).string()+" exists."; } for(std::filesystem::directory_entry p: std::filesystem::directory_iterator(musicScanDir)) { ScanResults result = addFileToTrackDBIfTagged(&sqlSession, p.path()); if(result == ScanResults::ACCEPTED) { Wt::log("info") << p.path().string() << " was added to DB"; } else if(result != ScanResults::DUPLICATE_TRACK && result != ScanResults::WRONG_FILETYPE) { Wt::log("info") << p.path().string() + " was not accepted."; } } //Now check for tracks in DB without a file. removeOrphanedTracks(&sqlSession); }