12#include <QRegularExpression>
35#define LOC QString("Playlist: ")
36#define LOC_WARN QString("Playlist, Warning: ")
37#define LOC_ERR QString("Playlist, Error: ")
41 return m_songs.contains(trackID);
48 for (
int x = 0; x <
m_songs.size(); x++)
86 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Can't add track, given a bad track ID");
102 cdTracks.reserve(
m_songs.count());
103 for (
int x = 0; x <
m_songs.count(); x++)
108 cdTracks.append(
m_songs.at(x));
112 for (
int x = 0; x < cdTracks.count(); x++)
114 m_songs.removeAll(cdTracks.at(x));
134 uint insertion_point = 0;
138 insertion_point = ((
uint)where_its_at) - 1;
140 insertion_point = ((
uint)where_its_at) + 1;
167 QMultiMap<uint32_t, MusicMetadata::IdType> songMap;
169 for (
auto song : std::as_const(
m_songs))
171 for (
auto song : std::as_const(songMap))
178 int RatingWeight = 2;
179 int PlayCountWeight = 2;
180 int LastPlayWeight = 2;
181 int RandomWeight = 2;
183 LastPlayWeight, RandomWeight);
186 int playcountMin = 0;
187 int playcountMax = 0;
188 double lastplayMin = 0.0;
189 double lastplayMax = 0.0;
191 for (
int x = 0; x <
m_songs.count(); x++)
203 playcountMin = playcountMax = mdata->
PlayCount();
204 lastplayMin = lastplayMax = mdata->
LastPlay().toSecsSinceEpoch();
210 else if (mdata->
PlayCount() > playcountMax)
213 double lastplaysecs = mdata->
LastPlay().toSecsSinceEpoch();
214 if (lastplaysecs < lastplayMin)
215 lastplayMin = lastplaysecs;
216 else if (lastplaysecs > lastplayMax)
217 lastplayMax = lastplaysecs;
223 std::map<int,double> weights;
224 std::map<int,int> ratings;
225 std::map<int,int> ratingCounts;
226 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
227 for (
int x = 0; x <
m_songs.size(); x++)
234 double lastplaydbl = mdata->
LastPlay().toSecsSinceEpoch();
235 double ratingValue = (double)
rating / 10;
236 double playcountValue = __builtin_nan(
"");
237 double lastplayValue = __builtin_nan(
"");
239 if (playcountMax == playcountMin)
242 playcountValue = (((playcountMin - (double)playcount) / (playcountMax - playcountMin)) + 1);
244 if (lastplayMax == lastplayMin)
247 lastplayValue = (((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin)) + 1);
249 double weight = ((RatingWeight * ratingValue) +
250 (PlayCountWeight * playcountValue) +
251 (LastPlayWeight * lastplayValue)) / TotalWeight;
252 weights[mdata->
ID()] = weight;
260 double totalWeights = 0;
261 auto weightsEnd = weights.end();
262 for (
auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
264 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
265 totalWeights += weightsIt->second;
269 std::map<int,uint32_t> order;
270 uint32_t orderCpt = 1;
271 while (!weights.empty())
273 double hit = totalWeights *
MythRandom() / std::numeric_limits<uint32_t>::max();
274 auto weightEnd = weights.end();
275 auto weightIt = weights.begin();
277 while (weightIt != weightEnd)
279 pos += weightIt->second;
292 if (weightIt == weightEnd)
295 order[weightIt->first] = orderCpt;
296 totalWeights -= weightIt->second;
297 weights.erase(weightIt);
302 QMultiMap<int, MusicMetadata::IdType> songMap;
303 for (
int x = 0; x <
m_songs.count(); x++)
307 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
308 while (i != songMap.constEnd())
321 using AlbumMap = std::map<QString, uint32_t>;
323 AlbumMap::iterator Ialbum;
328 for (
int x = 0; x <
m_songs.count(); x++)
333 album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());
334 Ialbum = album_map.find(album);
335 if (Ialbum == album_map.end())
336 album_map.insert(AlbumMap::value_type(album, 0));
341 uint32_t album_count = 1;
342 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
344 Ialbum->second = album_count;
349 QMultiMap<int, MusicMetadata::IdType> songMap;
350 for (
int x = 0; x <
m_songs.count(); x++)
355 uint32_t album_order = 1;
356 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
357 Ialbum = album_map.find(album);
358 if (Ialbum == album_map.end())
368 album_order = Ialbum->second * 10000;
372 album_order += mdata->
Track();
374 songMap.insert(album_order,
m_songs.at(x));
379 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
380 while (i != songMap.constEnd())
393 using ArtistMap = std::map<QString, uint32_t>;
394 ArtistMap artist_map;
395 ArtistMap::iterator Iartist;
400 for (
int x = 0; x <
m_songs.count(); x++)
406 Iartist = artist_map.find(artist);
407 if (Iartist == artist_map.end())
408 artist_map.insert(ArtistMap::value_type(artist,0));
413 uint32_t artist_count = 1;
414 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
416 Iartist->second = artist_count;
421 QMultiMap<int, MusicMetadata::IdType> songMap;
422 for (
int x = 0; x <
m_songs.count(); x++)
427 uint32_t artist_order = 1;
429 Iartist = artist_map.find(artist);
430 if (Iartist == artist_map.end())
440 artist_order = Iartist->second * 1000;
442 artist_order += mdata->
Track();
444 songMap.insert(artist_order,
m_songs.at(x));
449 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
450 while (i != songMap.constEnd())
475 LOG(VB_GENERAL, LOG_DEBUG,
476 QString(
"Playlist with name of \"%1\"").arg(name));
477 LOG(VB_GENERAL, LOG_DEBUG,
478 QString(
" playlistid is %1").arg(laylistid));
479 LOG(VB_GENERAL, LOG_DEBUG,
480 QString(
" songlist(raw) is \"%1\"").arg(raw_songlist));
481 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
485 for (
int x = 0; x <
m_songs.count(); x++)
486 msg += QString(
"%1,").arg(
m_songs.at(x));
488 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
492 uint currenttrack, std::chrono::seconds *playedLength)
const
494 std::chrono::milliseconds total = 0ms;
495 std::chrono::milliseconds played = 0ms;
508 if (x < (
int)currenttrack)
509 played += mdata->
Length();
514 *playedLength = duration_cast<std::chrono::seconds>(played);
516 *totalLength = duration_cast<std::chrono::seconds>(total);
523 if (a_host.isEmpty())
525 LOG(VB_GENERAL, LOG_ERR,
LOC +
526 "loadPlaylist() - We need a valid hostname");
532 if (
m_name ==
"default_playlist_storage" ||
533 m_name ==
"stream_playlist")
535 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
536 "FROM music_playlists "
537 "WHERE playlist_name = :NAME"
538 " AND hostname = :HOST;");
544 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
545 "FROM music_playlists "
546 "WHERE playlist_name = :NAME"
547 " AND (hostname = '' OR hostname = :HOST);");
552 if (query.
exec() && query.
size() > 0)
558 rawSonglist = query.
value(2).toString();
579 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
580 "FROM music_playlists "
581 "WHERE playlist_id = :ID"
582 " AND (hostname = '' OR hostname = :HOST);");
593 rawSonglist = query.
value(2).toString();
596 if (
m_name ==
"default_playlist_storage")
597 m_name = tr(
"Default Playlist");
605 bool needUpdate =
false;
607 for (
int x = 0; x <
m_songs.count(); x++)
633 bool badTrack =
false;
635 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
636 for (
const auto & song : std::as_const(list))
650 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
663 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
681 bool removeDuplicates,
686 QString new_songlist;
696 theQuery =
"SELECT song_id FROM music_songs "
697 "LEFT JOIN music_directories ON"
698 " music_songs.directory_id=music_directories.directory_id "
699 "LEFT JOIN music_artists ON"
700 " music_songs.artist_id=music_artists.artist_id "
701 "LEFT JOIN music_albums ON"
702 " music_songs.album_id=music_albums.album_id "
703 "LEFT JOIN music_genres ON"
704 " music_songs.genre_id=music_genres.genre_id "
705 "LEFT JOIN music_artists AS music_comp_artists ON "
706 "music_albums.artist_id=music_comp_artists.artist_id ";
707 if (whereClause.length() > 0)
708 theQuery += whereClause;
710 if (!query.
exec(theQuery))
713 new_songlist.clear();
722 new_songlist +=
"," + query.
value(0).toString();
725 new_songlist.remove(0, 1);
727 if (removeDuplicates && insertOption !=
PL_REPLACE)
730 switch (insertOption)
736 new_songlist = new_songlist +
"," + orig_songlist;
740 new_songlist = orig_songlist +
"," + new_songlist;
745 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
748 for (
const auto& song : std::as_const(list))
750 int an_int = song.toInt();
751 tempList +=
"," + song;
752 if (!bFound && an_int == currentTrackID)
755 tempList +=
"," + new_songlist;
760 tempList = orig_songlist +
"," + new_songlist;
762 new_songlist = tempList.remove(0, 1);
768 new_songlist = orig_songlist;
780 bool removeDuplicates,
785 QString new_songlist;
791 for (
int x = 0; x < songList.count(); x++)
793 new_songlist +=
"," + QString::number(songList.at(x));
795 new_songlist.remove(0, 1);
797 if (removeDuplicates && insertOption !=
PL_REPLACE)
800 switch (insertOption)
806 new_songlist = new_songlist +
"," + orig_songlist;
810 new_songlist = orig_songlist +
"," + new_songlist;
815 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
818 for (
const auto & song : std::as_const(list))
820 int an_int = song.toInt();
821 tempList +=
"," + song;
822 if (!bFound && an_int == currentTrackID)
825 tempList +=
"," + new_songlist;
830 tempList = orig_songlist +
"," + new_songlist;
832 new_songlist = tempList.remove(0, 1);
838 new_songlist = orig_songlist;
846 return songList.count();
851 QString rawList =
"";
861 rawList += QString(
",%1").arg(
id);
865 rawList += QString(
",%1").arg(
id);
871 for (
int x = 0; x <
m_songs.count(); x++)
877 rawList += QString(
",%1").arg(
id);
881 rawList += QString(
",%1").arg(
id);
886 if (!rawList.isEmpty())
887 rawList = rawList.remove(0, 1);
893 bool removeDuplicates,
901 if (categoryID == -1)
903 LOG(VB_GENERAL, LOG_WARNING,
LOC +
904 QString(
"Cannot find Smartplaylist Category: %1") .arg(category));
914 query.
prepare(
"SELECT smartplaylistid, matchtype, orderby, limitto "
915 "FROM music_smartplaylists "
916 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
918 query.
bindValue(
":CATEGORYID", categoryID);
925 ID = query.
value(0).toInt();
926 matchType = (query.
value(1).toString() ==
"All") ?
" AND " :
" OR ";
927 orderBy = query.
value(2).toString();
928 limitTo = query.
value(3).toInt();
932 LOG(VB_GENERAL, LOG_WARNING,
LOC +
933 QString(
"Cannot find smartplaylist: %1").arg(name));
944 QString whereClause =
"WHERE ";
946 query.
prepare(
"SELECT field, operator, value1, value2 "
947 "FROM music_smartplaylist_items "
948 "WHERE smartplaylistid = :ID;");
955 QString fieldName = query.
value(0).toString();
956 QString operatorName = query.
value(1).toString();
957 QString value1 = query.
value(2).toString();
958 QString value2 = query.
value(3).toString();
962 operatorName, value1, value2);
978 whereClause +=
" LIMIT " + QString::number(limitTo);
981 insertOption, currentTrackID);
994 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Saving playlist: " + a_name);
996 m_name = a_name.simplified();
999 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Not saving unnamed playlist");
1003 if (a_host.isEmpty())
1005 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1006 "Not saving playlist without a host name");
1015 std::chrono::seconds playtime = 0s;
1019 bool save_host = (
"default_playlist_storage" == a_name);
1022 QString str_query =
"UPDATE music_playlists SET "
1023 "playlist_songs = :LIST, "
1024 "playlist_name = :NAME, "
1025 "songcount = :SONGCOUNT, "
1026 "length = :PLAYTIME";
1028 str_query +=
", hostname = :HOSTNAME";
1029 str_query +=
" WHERE playlist_id = :ID ;";
1036 QString str_query =
"INSERT INTO music_playlists"
1037 " (playlist_name, playlist_songs,"
1038 " songcount, length";
1040 str_query +=
", hostname";
1041 str_query +=
") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1043 str_query +=
", :HOSTNAME";
1050 query.
bindValue(
":SONGCOUNT", songcount);
1051 query.
bindValue(
":PLAYTIME", qlonglong(playtime.count()));
1077 QStringList removeList = remove_list.split(
",", Qt::SkipEmptyParts);
1078 QStringList sourceList = source_list.split(
",", Qt::SkipEmptyParts);
1081 for (
const auto & song : std::as_const(sourceList))
1083 if (removeList.indexOf(song) == -1)
1084 songlist +=
"," + song;
1086 songlist.remove(0, 1);
1112 if (pos >= 0 && pos <
m_songs.size())
1128#ifdef CD_WRTITING_FIXED
1129void Playlist::computeSize(
double &size_in_MB,
double &size_in_sec)
1138 for (
int x = 0; x <
m_songs.size(); x++)
1147 if (mdata->
Length() > 0ms)
1148 size_in_sec += duration_cast<floatsecs>(mdata->
Length()).count();
1150 LOG(VB_GENERAL, LOG_ERR,
"Computing track lengths. "
1153 size_in_MB += mdata->
FileSize() / 1000000;
1158void Playlist::cdrecordData(
int fd)
1160 if (!m_progress || !m_proc)
1166 buf = m_proc->ReadAll();
1172 static const QRegularExpression newline {
"\\R" };
1173 QStringList list = data.split(newline, Qt::SkipEmptyParts);
1175 for (
int i = 0; i < list.size(); i++)
1177 QString line = list.at(i);
1179 if (line.mid(15, 2) ==
"of")
1181 int mbdone = line.mid(10, 5).trimmed().toInt();
1182 int mbtotal = line.mid(17, 5).trimmed().toInt();
1186 m_progress->setProgress((mbdone * 100) / mbtotal);
1193 buf = m_proc->ReadAllErr();
1195 QTextStream text(buf);
1197 while (!text.atEnd())
1199 QString err = text.readLine();
1200 if (err.contains(
"Drive needs to reload the media") ||
1201 err.contains(
"Input/output error.") ||
1202 err.contains(
"No disk / Wrong disk!"))
1204 LOG(VB_GENERAL, LOG_ERR, err);
1211void Playlist::mkisofsData(
int fd)
1213 if (!m_progress || !m_proc)
1218 buf = m_proc->ReadAll();
1221 buf = m_proc->ReadAllErr();
1223 QTextStream text(buf);
1225 while (!text.atEnd())
1227 QString line = text.readLine();
1230 line = line.mid(0, 3);
1231 m_progress->setProgress(line.trimmed().toInt());
1237void Playlist::processExit(
uint retval)
1239 m_procExitVal = retval;
1242void Playlist::processExit(
void)
1248int Playlist::CreateCDMP3(
void)
1253 LOG(VB_GENERAL, LOG_ERR,
"CD Writer is not enabled.");
1258 if (scsidev.isEmpty())
1260 LOG(VB_GENERAL, LOG_ERR,
"No CD Writer device defined.");
1268 double size_in_MB = 0.0;
1270 QStringList reclist;
1283 QFileInfo testit(mdata->
Filename());
1284 if (!testit.exists())
1286 size_in_MB += testit.size() / 1000000.0;
1290 if (mdata->
Artist().length() > 0)
1291 outline += mdata->
Artist() +
"/";
1292 if (mdata->
Album().length() > 0)
1293 outline += mdata->
Album() +
"/";
1309 if (size_in_MB >= max_size)
1311 LOG(VB_GENERAL, LOG_ERR,
"MP3 CD creation aborted -- cd size too big.");
1316 QString tmptemplate(
"/tmp/mythmusicXXXXXX");
1319 if (tmprecordlist == tmptemplate)
1321 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1326 if (tmprecordisofs == tmptemplate)
1328 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1332 QFile reclistfile(tmprecordlist);
1334 if (!reclistfile.open(QIODevice::WriteOnly))
1336 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1340 QTextStream recstream(&reclistfile);
1342 QStringList::Iterator iter;
1344 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1346 recstream << *iter <<
"\n";
1349 reclistfile.close();
1351 m_progress =
new MythProgressDialog(tr(
"Creating CD File System"),
1353 m_progress->setProgress(1);
1358 command =
"mkisofs";
1359 args <<
"-graft-points";
1360 args <<
"-path-list";
1361 args << tmprecordlist;
1363 args << tmprecordisofs;
1374 Qt::DirectConnection);
1376 Qt::DirectConnection);
1378 Qt::DirectConnection);
1384 std::this_thread::sleep_for(100ms);
1386 uint retval = m_procExitVal;
1388 m_progress->Close();
1389 m_progress->deleteLater();
1390 m_proc->disconnect();
1395 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to run mkisofs: returns %1")
1400 m_progress =
new MythProgressDialog(tr(
"Burning CD"), 100);
1401 m_progress->setProgress(2);
1403 command =
"cdrecord";
1404 args = QStringList();
1407 args << QString(
"dev=%1").arg(scsidev);
1409 if (writespeed.toInt() > 0)
1416 args << tmprecordisofs;
1424 this, &Playlist::cdrecordData, Qt::DirectConnection);
1426 this, qOverload<>(&Playlist::processExit), Qt::DirectConnection);
1428 this, qOverload<uint>(&Playlist::processExit), Qt::DirectConnection);
1433 std::this_thread::sleep_for(100ms);
1435 retval = m_procExitVal;
1437 m_progress->Close();
1438 m_progress->deleteLater();
1439 m_proc->disconnect();
1444 LOG(VB_GENERAL, LOG_ERR,
1445 QString(
"Unable to run cdrecord: returns %1") .arg(retval));
1449 QFile::remove(tmprecordlist);
1450 QFile::remove(tmprecordisofs);
1455int Playlist::CreateCDAudio(
void)
1461#include "moc_playlist.cpp"
MusicMetadata * getMetadata(int an_id)
bool isValidID(int an_id)
bool isValidID(MusicMetadata::IdType an_id)
MusicMetadata * getMetadata(MusicMetadata::IdType an_id)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
QVariant value(int i) const
int numRowsAffected() const
bool isActive(void) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
QVariant lastInsertId()
Return the id of the last inserted row.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
AllStream * m_all_streams
void playlistChanged(int playlistID)
void activePlaylistChanged(int trackID, bool deleted)
ShuffleMode getShuffleMode(void)
Playlist * getCurrentPlaylist(void)
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
static void DBError(const QString &where, const MSqlQuery &query)
void readDataReady(int fd)
void FillIntelliWeights(int &rating, int &playcount, int &lastplay, int &random) const
void loadPlaylistByID(int id, const QString &a_host)
void removeAllCDTracks(void)
MusicMetadata * getRawSongAt(int pos) const
QString toRawSonglist(bool shuffled=false, bool tracksOnly=false)
void copyTracks(Playlist *to_ptr, bool update_display)
void resync(void)
make sure all tracks are still valid after a scan
bool isActivePlaylist(void)
int fillSonglistFromSmartPlaylist(const QString &category, const QString &name, bool removeDuplicates=false, InsertPLOption insertOption=PL_REPLACE, int currentTrackID=0)
static QString removeItemsFromList(const QString &remove_list, const QString &source_list)
void describeYourself(void) const
void removeAllTracks(void)
int fillSonglistFromQuery(const QString &whereClause, bool removeDuplicates=false, InsertPLOption insertOption=PL_REPLACE, int currentTrackID=0)
void fillSongsFromSonglist(const QString &songList)
PlaylistContainer * m_parent
void disableSaves(void)
whether any changes should be saved to the DB
void savePlaylist(const QString &a_name, const QString &a_host)
void getStats(uint *trackCount, std::chrono::seconds *totalLength, uint currentTrack=0, std::chrono::seconds *playedLength=nullptr) const
void moveTrackUpDown(bool flag, int where_its_at)
void loadPlaylist(const QString &a_name, const QString &a_host)
void shuffleTracks(MusicPlayer::ShuffleMode mode)
MusicMetadata * getSongAt(int pos) const
int fillSonglistFromList(const QList< int > &songList, bool removeDuplicates, InsertPLOption insertOption, int currentTrackID)
void removeTrack(MusicMetadata::IdType trackID)
void addTrack(MusicMetadata::IdType trackID, bool update_display)
Given a tracks ID, add that track to this playlist.
bool checkTrack(MusicMetadata::IdType trackID) const
static int lookupCategoryID(const QString &category)
@ GENERIC_EXIT_OK
Exited with no error.
@ GENERIC_EXIT_RUNNING
Process is running.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString createTempFile(QString name_template, bool dir)
Convenience inline random number generator functions.
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
@ kMSStdErr
allow access to stderr
@ kMSStdOut
allow access to stdout
@ kMSRunShell
run process through shell
@ kMSRunBackground
run child in the background
@ kMSDontDisableDrawing
avoid disabling UI drawing
uint32_t MythRandom()
generate 32 random bits
def rating(profile, smoonURL, gate)
QList< MusicMetadata::IdType > SongList
QString getOrderBySQL(const QString &orderByFields)
QString getCriteriaSQL(const QString &fieldName, const QString &operatorName, QString value1, QString value2)