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 for (
int x = 0; x <
m_songs.count(); x++)
107 cdTracks.append(
m_songs.at(x));
111 for (
int x = 0; x < cdTracks.count(); x++)
113 m_songs.removeAll(cdTracks.at(x));
133 uint insertion_point = 0;
137 insertion_point = ((
uint)where_its_at) - 1;
139 insertion_point = ((
uint)where_its_at) + 1;
166 QMultiMap<uint32_t, MusicMetadata::IdType> songMap;
168 for (
auto song : std::as_const(
m_songs))
170 for (
auto song : std::as_const(songMap))
177 int RatingWeight = 2;
178 int PlayCountWeight = 2;
179 int LastPlayWeight = 2;
180 int RandomWeight = 2;
182 LastPlayWeight, RandomWeight);
185 int playcountMin = 0;
186 int playcountMax = 0;
187 double lastplayMin = 0.0;
188 double lastplayMax = 0.0;
190 for (
int x = 0; x <
m_songs.count(); x++)
202 playcountMin = playcountMax = mdata->
PlayCount();
203 lastplayMin = lastplayMax = mdata->
LastPlay().toSecsSinceEpoch();
209 else if (mdata->
PlayCount() > playcountMax)
212 double lastplaysecs = mdata->
LastPlay().toSecsSinceEpoch();
213 if (lastplaysecs < lastplayMin)
214 lastplayMin = lastplaysecs;
215 else if (lastplaysecs > lastplayMax)
216 lastplayMax = lastplaysecs;
222 std::map<int,double> weights;
223 std::map<int,int> ratings;
224 std::map<int,int> ratingCounts;
225 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
226 for (
int x = 0; x <
m_songs.size(); x++)
233 double lastplaydbl = mdata->
LastPlay().toSecsSinceEpoch();
234 double ratingValue = (double)
rating / 10;
235 double playcountValue = __builtin_nan(
"");
236 double lastplayValue = __builtin_nan(
"");
238 if (playcountMax == playcountMin)
241 playcountValue = (((playcountMin - (double)playcount) / (playcountMax - playcountMin)) + 1);
243 if (lastplayMax == lastplayMin)
246 lastplayValue = (((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin)) + 1);
248 double weight = ((RatingWeight * ratingValue) +
249 (PlayCountWeight * playcountValue) +
250 (LastPlayWeight * lastplayValue)) / TotalWeight;
251 weights[mdata->
ID()] = weight;
259 double totalWeights = 0;
260 auto weightsEnd = weights.end();
261 for (
auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
263 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
264 totalWeights += weightsIt->second;
268 std::map<int,uint32_t> order;
269 uint32_t orderCpt = 1;
270 while (!weights.empty())
272 double hit = totalWeights *
MythRandom() / std::numeric_limits<uint32_t>::max();
273 auto weightEnd = weights.end();
274 auto weightIt = weights.begin();
276 while (weightIt != weightEnd)
278 pos += weightIt->second;
291 if (weightIt == weightEnd)
294 order[weightIt->first] = orderCpt;
295 totalWeights -= weightIt->second;
296 weights.erase(weightIt);
301 QMultiMap<int, MusicMetadata::IdType> songMap;
302 for (
int x = 0; x <
m_songs.count(); x++)
306 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
307 while (i != songMap.constEnd())
320 using AlbumMap = std::map<QString, uint32_t>;
322 AlbumMap::iterator Ialbum;
327 for (
int x = 0; x <
m_songs.count(); x++)
332 album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());
333 Ialbum = album_map.find(album);
334 if (Ialbum == album_map.end())
335 album_map.insert(AlbumMap::value_type(album, 0));
340 uint32_t album_count = 1;
341 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
343 Ialbum->second = album_count;
348 QMultiMap<int, MusicMetadata::IdType> songMap;
349 for (
int x = 0; x <
m_songs.count(); x++)
354 uint32_t album_order = 1;
355 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
356 Ialbum = album_map.find(album);
357 if (Ialbum == album_map.end())
367 album_order = Ialbum->second * 10000;
371 album_order += mdata->
Track();
373 songMap.insert(album_order,
m_songs.at(x));
378 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
379 while (i != songMap.constEnd())
392 using ArtistMap = std::map<QString, uint32_t>;
393 ArtistMap artist_map;
394 ArtistMap::iterator Iartist;
399 for (
int x = 0; x <
m_songs.count(); x++)
405 Iartist = artist_map.find(artist);
406 if (Iartist == artist_map.end())
407 artist_map.insert(ArtistMap::value_type(artist,0));
412 uint32_t artist_count = 1;
413 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
415 Iartist->second = artist_count;
420 QMultiMap<int, MusicMetadata::IdType> songMap;
421 for (
int x = 0; x <
m_songs.count(); x++)
426 uint32_t artist_order = 1;
428 Iartist = artist_map.find(artist);
429 if (Iartist == artist_map.end())
439 artist_order = Iartist->second * 1000;
441 artist_order += mdata->
Track();
443 songMap.insert(artist_order,
m_songs.at(x));
448 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
449 while (i != songMap.constEnd())
474 LOG(VB_GENERAL, LOG_DEBUG,
475 QString(
"Playlist with name of \"%1\"").arg(name));
476 LOG(VB_GENERAL, LOG_DEBUG,
477 QString(
" playlistid is %1").arg(laylistid));
478 LOG(VB_GENERAL, LOG_DEBUG,
479 QString(
" songlist(raw) is \"%1\"").arg(raw_songlist));
480 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
484 for (
int x = 0; x <
m_songs.count(); x++)
485 msg += QString(
"%1,").arg(
m_songs.at(x));
487 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
491 uint currenttrack, std::chrono::seconds *playedLength)
const
493 std::chrono::milliseconds total = 0ms;
494 std::chrono::milliseconds played = 0ms;
507 if (x < (
int)currenttrack)
508 played += mdata->
Length();
513 *playedLength = duration_cast<std::chrono::seconds>(played);
515 *totalLength = duration_cast<std::chrono::seconds>(total);
522 if (a_host.isEmpty())
524 LOG(VB_GENERAL, LOG_ERR,
LOC +
525 "loadPlaylist() - We need a valid hostname");
531 if (
m_name ==
"default_playlist_storage" ||
532 m_name ==
"stream_playlist")
534 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
535 "FROM music_playlists "
536 "WHERE playlist_name = :NAME"
537 " AND hostname = :HOST;");
543 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
544 "FROM music_playlists "
545 "WHERE playlist_name = :NAME"
546 " AND (hostname = '' OR hostname = :HOST);");
551 if (query.
exec() && query.
size() > 0)
557 rawSonglist = query.
value(2).toString();
578 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
579 "FROM music_playlists "
580 "WHERE playlist_id = :ID"
581 " AND (hostname = '' OR hostname = :HOST);");
592 rawSonglist = query.
value(2).toString();
595 if (
m_name ==
"default_playlist_storage")
596 m_name = tr(
"Default Playlist");
604 bool needUpdate =
false;
606 for (
int x = 0; x <
m_songs.count(); x++)
632 bool badTrack =
false;
634 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
635 for (
const auto & song : std::as_const(list))
649 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
662 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
680 bool removeDuplicates,
685 QString new_songlist;
695 theQuery =
"SELECT song_id FROM music_songs "
696 "LEFT JOIN music_directories ON"
697 " music_songs.directory_id=music_directories.directory_id "
698 "LEFT JOIN music_artists ON"
699 " music_songs.artist_id=music_artists.artist_id "
700 "LEFT JOIN music_albums ON"
701 " music_songs.album_id=music_albums.album_id "
702 "LEFT JOIN music_genres ON"
703 " music_songs.genre_id=music_genres.genre_id "
704 "LEFT JOIN music_artists AS music_comp_artists ON "
705 "music_albums.artist_id=music_comp_artists.artist_id ";
706 if (whereClause.length() > 0)
707 theQuery += whereClause;
709 if (!query.
exec(theQuery))
712 new_songlist.clear();
721 new_songlist +=
"," + query.
value(0).toString();
724 new_songlist.remove(0, 1);
726 if (removeDuplicates && insertOption !=
PL_REPLACE)
729 switch (insertOption)
735 new_songlist = new_songlist +
"," + orig_songlist;
739 new_songlist = orig_songlist +
"," + new_songlist;
744 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
747 for (
const auto& song : std::as_const(list))
749 int an_int = song.toInt();
750 tempList +=
"," + song;
751 if (!bFound && an_int == currentTrackID)
754 tempList +=
"," + new_songlist;
759 tempList = orig_songlist +
"," + new_songlist;
761 new_songlist = tempList.remove(0, 1);
767 new_songlist = orig_songlist;
779 bool removeDuplicates,
784 QString new_songlist;
790 for (
int x = 0; x < songList.count(); x++)
792 new_songlist +=
"," + QString::number(songList.at(x));
794 new_songlist.remove(0, 1);
796 if (removeDuplicates && insertOption !=
PL_REPLACE)
799 switch (insertOption)
805 new_songlist = new_songlist +
"," + orig_songlist;
809 new_songlist = orig_songlist +
"," + new_songlist;
814 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
817 for (
const auto & song : std::as_const(list))
819 int an_int = song.toInt();
820 tempList +=
"," + song;
821 if (!bFound && an_int == currentTrackID)
824 tempList +=
"," + new_songlist;
829 tempList = orig_songlist +
"," + new_songlist;
831 new_songlist = tempList.remove(0, 1);
837 new_songlist = orig_songlist;
845 return songList.count();
850 QString rawList =
"";
860 rawList += QString(
",%1").arg(
id);
864 rawList += QString(
",%1").arg(
id);
870 for (
int x = 0; x <
m_songs.count(); x++)
876 rawList += QString(
",%1").arg(
id);
880 rawList += QString(
",%1").arg(
id);
885 if (!rawList.isEmpty())
886 rawList = rawList.remove(0, 1);
892 bool removeDuplicates,
900 if (categoryID == -1)
902 LOG(VB_GENERAL, LOG_WARNING,
LOC +
903 QString(
"Cannot find Smartplaylist Category: %1") .arg(category));
913 query.
prepare(
"SELECT smartplaylistid, matchtype, orderby, limitto "
914 "FROM music_smartplaylists "
915 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
917 query.
bindValue(
":CATEGORYID", categoryID);
924 ID = query.
value(0).toInt();
925 matchType = (query.
value(1).toString() ==
"All") ?
" AND " :
" OR ";
926 orderBy = query.
value(2).toString();
927 limitTo = query.
value(3).toInt();
931 LOG(VB_GENERAL, LOG_WARNING,
LOC +
932 QString(
"Cannot find smartplaylist: %1").arg(name));
943 QString whereClause =
"WHERE ";
945 query.
prepare(
"SELECT field, operator, value1, value2 "
946 "FROM music_smartplaylist_items "
947 "WHERE smartplaylistid = :ID;");
954 QString fieldName = query.
value(0).toString();
955 QString operatorName = query.
value(1).toString();
956 QString value1 = query.
value(2).toString();
957 QString value2 = query.
value(3).toString();
961 operatorName, value1, value2);
977 whereClause +=
" LIMIT " + QString::number(limitTo);
980 insertOption, currentTrackID);
993 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Saving playlist: " + a_name);
995 m_name = a_name.simplified();
998 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Not saving unnamed playlist");
1002 if (a_host.isEmpty())
1004 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1005 "Not saving playlist without a host name");
1014 std::chrono::seconds playtime = 0s;
1018 bool save_host = (
"default_playlist_storage" == a_name);
1021 QString str_query =
"UPDATE music_playlists SET "
1022 "playlist_songs = :LIST, "
1023 "playlist_name = :NAME, "
1024 "songcount = :SONGCOUNT, "
1025 "length = :PLAYTIME";
1027 str_query +=
", hostname = :HOSTNAME";
1028 str_query +=
" WHERE playlist_id = :ID ;";
1035 QString str_query =
"INSERT INTO music_playlists"
1036 " (playlist_name, playlist_songs,"
1037 " songcount, length";
1039 str_query +=
", hostname";
1040 str_query +=
") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1042 str_query +=
", :HOSTNAME";
1049 query.
bindValue(
":SONGCOUNT", songcount);
1050 query.
bindValue(
":PLAYTIME", qlonglong(playtime.count()));
1076 QStringList removeList = remove_list.split(
",", Qt::SkipEmptyParts);
1077 QStringList sourceList = source_list.split(
",", Qt::SkipEmptyParts);
1080 for (
const auto & song : std::as_const(sourceList))
1082 if (removeList.indexOf(song) == -1)
1083 songlist +=
"," + song;
1085 songlist.remove(0, 1);
1111 if (pos >= 0 && pos <
m_songs.size())
1127#ifdef CD_WRTITING_FIXED
1128void Playlist::computeSize(
double &size_in_MB,
double &size_in_sec)
1137 for (
int x = 0; x <
m_songs.size(); x++)
1146 if (mdata->
Length() > 0ms)
1147 size_in_sec += duration_cast<floatsecs>(mdata->
Length()).count();
1149 LOG(VB_GENERAL, LOG_ERR,
"Computing track lengths. "
1152 size_in_MB += mdata->
FileSize() / 1000000;
1157void Playlist::cdrecordData(
int fd)
1159 if (!m_progress || !m_proc)
1165 buf = m_proc->ReadAll();
1171 static const QRegularExpression newline {
"\\R" };
1172 QStringList list = data.split(newline, Qt::SkipEmptyParts);
1174 for (
int i = 0; i < list.size(); i++)
1176 QString line = list.at(i);
1178 if (line.mid(15, 2) ==
"of")
1180 int mbdone = line.mid(10, 5).trimmed().toInt();
1181 int mbtotal = line.mid(17, 5).trimmed().toInt();
1185 m_progress->setProgress((mbdone * 100) / mbtotal);
1192 buf = m_proc->ReadAllErr();
1194 QTextStream text(buf);
1196 while (!text.atEnd())
1198 QString err = text.readLine();
1199 if (err.contains(
"Drive needs to reload the media") ||
1200 err.contains(
"Input/output error.") ||
1201 err.contains(
"No disk / Wrong disk!"))
1203 LOG(VB_GENERAL, LOG_ERR, err);
1210void Playlist::mkisofsData(
int fd)
1212 if (!m_progress || !m_proc)
1217 buf = m_proc->ReadAll();
1220 buf = m_proc->ReadAllErr();
1222 QTextStream text(buf);
1224 while (!text.atEnd())
1226 QString line = text.readLine();
1229 line = line.mid(0, 3);
1230 m_progress->setProgress(line.trimmed().toInt());
1236void Playlist::processExit(
uint retval)
1238 m_procExitVal = retval;
1241void Playlist::processExit(
void)
1247int Playlist::CreateCDMP3(
void)
1252 LOG(VB_GENERAL, LOG_ERR,
"CD Writer is not enabled.");
1257 if (scsidev.isEmpty())
1259 LOG(VB_GENERAL, LOG_ERR,
"No CD Writer device defined.");
1267 double size_in_MB = 0.0;
1269 QStringList reclist;
1282 QFileInfo testit(mdata->
Filename());
1283 if (!testit.exists())
1285 size_in_MB += testit.size() / 1000000.0;
1289 if (mdata->
Artist().length() > 0)
1290 outline += mdata->
Artist() +
"/";
1291 if (mdata->
Album().length() > 0)
1292 outline += mdata->
Album() +
"/";
1308 if (size_in_MB >= max_size)
1310 LOG(VB_GENERAL, LOG_ERR,
"MP3 CD creation aborted -- cd size too big.");
1315 QString tmptemplate(
"/tmp/mythmusicXXXXXX");
1318 if (tmprecordlist == tmptemplate)
1320 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1325 if (tmprecordisofs == tmptemplate)
1327 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1331 QFile reclistfile(tmprecordlist);
1333 if (!reclistfile.open(QIODevice::WriteOnly))
1335 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1339 QTextStream recstream(&reclistfile);
1341 QStringList::Iterator iter;
1343 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1345 recstream << *iter <<
"\n";
1348 reclistfile.close();
1350 m_progress =
new MythProgressDialog(tr(
"Creating CD File System"),
1352 m_progress->setProgress(1);
1357 command =
"mkisofs";
1358 args <<
"-graft-points";
1359 args <<
"-path-list";
1360 args << tmprecordlist;
1362 args << tmprecordisofs;
1373 Qt::DirectConnection);
1375 Qt::DirectConnection);
1377 Qt::DirectConnection);
1383 std::this_thread::sleep_for(100ms);
1385 uint retval = m_procExitVal;
1387 m_progress->Close();
1388 m_progress->deleteLater();
1389 m_proc->disconnect();
1394 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to run mkisofs: returns %1")
1399 m_progress =
new MythProgressDialog(tr(
"Burning CD"), 100);
1400 m_progress->setProgress(2);
1402 command =
"cdrecord";
1403 args = QStringList();
1406 args << QString(
"dev=%1").arg(scsidev);
1408 if (writespeed.toInt() > 0)
1415 args << tmprecordisofs;
1423 this, &Playlist::cdrecordData, Qt::DirectConnection);
1425 this, qOverload<>(&Playlist::processExit), Qt::DirectConnection);
1427 this, qOverload<uint>(&Playlist::processExit), Qt::DirectConnection);
1432 std::this_thread::sleep_for(100ms);
1434 retval = m_procExitVal;
1436 m_progress->Close();
1437 m_progress->deleteLater();
1438 m_proc->disconnect();
1443 LOG(VB_GENERAL, LOG_ERR,
1444 QString(
"Unable to run cdrecord: returns %1") .arg(retval));
1448 QFile::remove(tmprecordlist);
1449 QFile::remove(tmprecordisofs);
1454int Playlist::CreateCDAudio(
void)
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)