8 #include <QApplication>
31 #define LOC QString("Playlist: ")
32 #define LOC_WARN QString("Playlist, Warning: ")
33 #define LOC_ERR QString("Playlist, Error: ")
37 return m_songs.contains(trackID);
44 for (
int x = 0; x <
m_songs.size(); x++)
81 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Can't add track, given a bad track ID");
96 for (
int x = 0; x <
m_songs.count(); x++)
101 cdTracks.append(
m_songs.at(x));
105 for (
int x = 0; x < cdTracks.count(); x++)
107 m_songs.removeAll(cdTracks.at(x));
127 uint insertion_point = 0;
131 insertion_point = ((
uint)where_its_at) - 1;
133 insertion_point = ((
uint)where_its_at) + 1;
160 QMultiMap<int, MusicMetadata::IdType> songMap;
162 for (
int x = 0; x <
m_songs.count(); x++)
166 songMap.insert(rand(),
m_songs.at(x));
169 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
170 while (i != songMap.constEnd())
181 int RatingWeight = 2;
182 int PlayCountWeight = 2;
183 int LastPlayWeight = 2;
184 int RandomWeight = 2;
186 LastPlayWeight, RandomWeight);
189 int playcountMin = 0;
190 int playcountMax = 0;
191 double lastplayMin = 0.0;
192 double lastplayMax = 0.0;
194 for (
int x = 0; x <
m_songs.count(); x++)
206 playcountMin = playcountMax = mdata->
PlayCount();
207 lastplayMin = lastplayMax = mdata->
LastPlay().toSecsSinceEpoch();
213 else if (mdata->
PlayCount() > playcountMax)
216 double lastplaysecs = mdata->
LastPlay().toSecsSinceEpoch();
217 if (lastplaysecs < lastplayMin)
218 lastplayMin = lastplaysecs;
219 else if (lastplaysecs > lastplayMax)
220 lastplayMax = lastplaysecs;
226 std::map<int,double> weights;
227 std::map<int,int> ratings;
228 std::map<int,int> ratingCounts;
229 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
230 for (
int x = 0; x <
m_songs.size(); x++)
237 double lastplaydbl = mdata->
LastPlay().toSecsSinceEpoch();
238 double ratingValue = (double)(
rating) / 10;
239 double playcountValue = NAN;
240 double lastplayValue = NAN;
242 if (playcountMax == playcountMin)
245 playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);
247 if (lastplayMax == lastplayMin)
250 lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);
252 double weight = (RatingWeight * ratingValue +
253 PlayCountWeight * playcountValue +
254 LastPlayWeight * lastplayValue) / TotalWeight;
255 weights[mdata->
ID()] = weight;
263 double totalWeights = 0;
264 auto weightsEnd = weights.end();
265 for (
auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
267 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
268 totalWeights += weightsIt->second;
272 std::map<int,uint32_t> order;
273 uint32_t orderCpt = 1;
274 while (!weights.empty())
278 double hit = totalWeights * (double)rand() / (double)RAND_MAX;
279 auto weightEnd = weights.end();
280 auto weightIt = weights.begin();
282 while (weightIt != weightEnd)
284 pos += weightIt->second;
297 if (weightIt == weightEnd)
300 order[weightIt->first] = orderCpt;
301 totalWeights -= weightIt->second;
302 weights.erase(weightIt);
307 QMultiMap<int, MusicMetadata::IdType> songMap;
308 for (
int x = 0; x <
m_songs.count(); x++)
312 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
313 while (i != songMap.constEnd())
326 using AlbumMap = std::map<QString, uint32_t>;
328 AlbumMap::iterator Ialbum;
333 for (
int x = 0; x <
m_songs.count(); x++)
338 album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());
339 if ((Ialbum = album_map.find(album)) == album_map.end())
340 album_map.insert(AlbumMap::value_type(album, 0));
345 uint32_t album_count = 1;
346 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
348 Ialbum->second = album_count;
353 QMultiMap<int, MusicMetadata::IdType> songMap;
354 for (
int x = 0; x <
m_songs.count(); x++)
359 uint32_t album_order = 1;
360 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
361 if ((Ialbum = album_map.find(album)) == album_map.end())
371 album_order = Ialbum->second * 10000;
375 album_order += mdata->
Track();
377 songMap.insert(album_order,
m_songs.at(x));
382 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
383 while (i != songMap.constEnd())
396 using ArtistMap = std::map<QString, uint32_t>;
397 ArtistMap artist_map;
398 ArtistMap::iterator Iartist;
403 for (
int x = 0; x <
m_songs.count(); x++)
409 if ((Iartist = artist_map.find(artist)) == artist_map.end())
410 artist_map.insert(ArtistMap::value_type(artist,0));
415 uint32_t artist_count = 1;
416 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
418 Iartist->second = artist_count;
423 QMultiMap<int, MusicMetadata::IdType> songMap;
424 for (
int x = 0; x <
m_songs.count(); x++)
429 uint32_t artist_order = 1;
431 if ((Iartist = artist_map.find(artist)) == artist_map.end())
441 artist_order = Iartist->second * 1000;
443 artist_order += mdata->
Track();
445 songMap.insert(artist_order,
m_songs.at(x));
450 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
451 while (i != songMap.constEnd())
476 LOG(VB_GENERAL, LOG_DEBUG,
477 QString(
"Playlist with name of \"%1\"").
arg(name));
478 LOG(VB_GENERAL, LOG_DEBUG,
479 QString(
" playlistid is %1").
arg(laylistid));
480 LOG(VB_GENERAL, LOG_DEBUG,
481 QString(
" songlist(raw) is \"%1\"").
arg(raw_songlist));
482 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
486 for (
int x = 0; x <
m_songs.count(); x++)
487 msg += QString(
"%1,").arg(
m_songs.at(x));
489 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
508 if (x < (
int)currenttrack)
509 played += mdata->
Length();
514 *playedLength = played / 1000;
516 *totalLength = total / 1000;
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);");
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);");
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 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
636 QStringList list = songList.split(
",", QString::SkipEmptyParts);
638 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
640 for (
const auto & song : qAsConst(list))
652 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;
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;
712 new_songlist.clear();
721 new_songlist +=
"," +
query.
value(0).toString();
723 new_songlist.remove(0, 1);
725 if (removeDuplicates && insertOption !=
PL_REPLACE)
728 switch (insertOption)
734 new_songlist = new_songlist +
"," + orig_songlist;
738 new_songlist = orig_songlist +
"," + new_songlist;
743 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
744 QStringList list = orig_songlist.split(
",", QString::SkipEmptyParts);
746 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
750 for (
const auto& song : qAsConst(list))
752 int an_int = song.toInt();
753 tempList +=
"," + song;
754 if (!bFound && an_int == currentTrackID)
757 tempList +=
"," + new_songlist;
762 tempList = orig_songlist +
"," + new_songlist;
764 new_songlist = tempList.remove(0, 1);
770 new_songlist = orig_songlist;
781 bool removeDuplicates,
786 QString new_songlist;
792 for (
int x = 0; x < songList.count(); x++)
794 new_songlist +=
"," + QString::number(songList.at(x));
796 new_songlist.remove(0, 1);
798 if (removeDuplicates && insertOption !=
PL_REPLACE)
801 switch (insertOption)
807 new_songlist = new_songlist +
"," + orig_songlist;
811 new_songlist = orig_songlist +
"," + new_songlist;
816 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
817 QStringList list = orig_songlist.split(
",", QString::SkipEmptyParts);
819 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
823 for (
const auto & song : qAsConst(list))
825 int an_int = song.toInt();
826 tempList +=
"," + song;
827 if (!bFound && an_int == currentTrackID)
830 tempList +=
"," + new_songlist;
835 tempList = orig_songlist +
"," + new_songlist;
837 new_songlist = tempList.remove(0, 1);
843 new_songlist = orig_songlist;
855 QString rawList =
"";
865 rawList += QString(
",%1").arg(
id);
868 rawList += QString(
",%1").arg(
id);
873 for (
int x = 0; x <
m_songs.count(); x++)
879 rawList += QString(
",%1").arg(
id);
882 rawList += QString(
",%1").arg(
id);
886 if (!rawList.isEmpty())
887 rawList = rawList.remove(0, 1);
893 bool removeDuplicates,
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;");
926 matchType = (
query.
value(1).toString() ==
"All") ?
" AND " :
" OR ";
932 LOG(VB_GENERAL, LOG_WARNING,
LOC +
933 QString(
"Cannot find smartplaylist: %1").
arg(name));
944 QString whereClause =
"WHERE ";
947 "FROM music_smartplaylist_items "
948 "WHERE smartplaylistid = :ID;");
955 QString fieldName =
query.
value(0).toString();
956 QString operatorName =
query.
value(1).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");
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";
1068 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1069 QStringList curList = orig_songlist.split(
",", QString::SkipEmptyParts);
1070 QStringList newList = new_songlist.split(
",", QString::SkipEmptyParts);
1072 QStringList curList = orig_songlist.split(
",", Qt::SkipEmptyParts);
1073 QStringList newList = new_songlist.split(
",", Qt::SkipEmptyParts);
1077 for (
const auto & song : qAsConst(newList))
1079 if (curList.indexOf(song) == -1)
1080 songlist +=
"," + song;
1082 songlist.remove(0, 1);
1108 if (pos >= 0 && pos <
m_songs.size())
1124 #ifdef CD_WRTITING_FIXED
1125 void Playlist::computeSize(
double &size_in_MB,
double &size_in_sec)
1134 for (
int x = 0; x <
m_songs.size(); x++)
1144 size_in_sec += mdata->
Length();
1146 LOG(VB_GENERAL, LOG_ERR,
"Computing track lengths. "
1149 size_in_MB += mdata->
FileSize() / 1000000;
1154 void Playlist::cdrecordData(
int fd)
1156 if (!m_progress || !m_proc)
1162 buf = m_proc->ReadAll();
1168 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1169 QStringList list = data.split(QRegExp(
"[\\r\\n]"),
1170 QString::SkipEmptyParts);
1172 QStringList list = data.split(QRegExp(
"[\\r\\n]"),
1173 Qt::SkipEmptyParts);
1176 for (
int i = 0; i < list.size(); i++)
1178 QString line = list.at(i);
1180 if (line.mid(15, 2) ==
"of")
1182 int mbdone = line.mid(10, 5).trimmed().toInt();
1183 int mbtotal = line.mid(17, 5).trimmed().toInt();
1187 m_progress->setProgress((mbdone * 100) / mbtotal);
1194 buf = m_proc->ReadAllErr();
1196 QTextStream text(buf);
1198 while (!text.atEnd())
1200 QString err = text.readLine();
1201 if (err.contains(
"Drive needs to reload the media") ||
1202 err.contains(
"Input/output error.") ||
1203 err.contains(
"No disk / Wrong disk!"))
1205 LOG(VB_GENERAL, LOG_ERR, err);
1212 void Playlist::mkisofsData(
int fd)
1214 if (!m_progress || !m_proc)
1219 buf = m_proc->ReadAll();
1222 buf = m_proc->ReadAllErr();
1224 QTextStream text(buf);
1226 while (!text.atEnd())
1228 QString line = text.readLine();
1231 line = line.mid(0, 3);
1232 m_progress->setProgress(line.trimmed().toInt());
1238 void Playlist::processExit(
uint retval)
1240 m_procExitVal = retval;
1243 void Playlist::processExit(
void)
1249 int Playlist::CreateCDMP3(
void)
1254 LOG(VB_GENERAL, LOG_ERR,
"CD Writer is not enabled.");
1259 if (scsidev.isEmpty())
1261 LOG(VB_GENERAL, LOG_ERR,
"No CD Writer device defined.");
1269 double size_in_MB = 0.0;
1271 QStringList reclist;
1284 QFileInfo testit(mdata->
Filename());
1285 if (!testit.exists())
1287 size_in_MB += testit.size() / 1000000.0;
1291 if (mdata->
Artist().length() > 0)
1292 outline += mdata->
Artist() +
"/";
1293 if (mdata->
Album().length() > 0)
1294 outline += mdata->
Album() +
"/";
1310 if (size_in_MB >= max_size)
1312 LOG(VB_GENERAL, LOG_ERR,
"MP3 CD creation aborted -- cd size too big.");
1317 QString tmptemplate(
"/tmp/mythmusicXXXXXX");
1320 if (tmprecordlist == tmptemplate)
1322 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1327 if (tmprecordisofs == tmptemplate)
1329 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1333 QFile reclistfile(tmprecordlist);
1335 if (!reclistfile.open(QIODevice::WriteOnly))
1337 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1341 QTextStream recstream(&reclistfile);
1343 QStringList::Iterator iter;
1345 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1347 recstream << *iter <<
"\n";
1350 reclistfile.close();
1352 m_progress =
new MythProgressDialog(tr(
"Creating CD File System"),
1354 m_progress->setProgress(1);
1359 command =
"mkisofs";
1360 args <<
"-graft-points";
1361 args <<
"-path-list";
1362 args << tmprecordlist;
1364 args << tmprecordisofs;
1375 Qt::DirectConnection);
1376 connect(m_proc, &MythSystemLegacy::inished,
this, qOverload<>&Playlist::processExit,
1377 Qt::DirectConnection);
1379 Qt::DirectConnection);
1387 uint retval = m_procExitVal;
1389 m_progress->Close();
1390 m_progress->deleteLater();
1391 m_proc->disconnect();
1396 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to run mkisofs: returns %1")
1401 m_progress =
new MythProgressDialog(tr(
"Burning CD"), 100);
1402 m_progress->setProgress(2);
1404 command =
"cdrecord";
1405 args = QStringList();
1408 args << QString(
"dev=%1").arg(scsidev);
1410 if (writespeed.toInt() > 0)
1417 args << tmprecordisofs;
1425 this, &Playlist::cdrecordData, Qt::DirectConnection);
1427 this, qOverload<>&Playlist::processExit, Qt::DirectConnection);
1429 this, qOverload<uint>&Playlist::processExit, Qt::DirectConnection);
1436 retval = m_procExitVal;
1438 m_progress->Close();
1439 m_progress->deleteLater();
1440 m_proc->disconnect();
1445 LOG(VB_GENERAL, LOG_ERR,
1446 QString(
"Unable to run cdrecord: returns %1") .
arg(retval));
1450 QFile::remove(tmprecordlist);
1451 QFile::remove(tmprecordisofs);
1456 int Playlist::CreateCDAudio(
void)