9 #include <QApplication>
12 #include <QRegularExpression>
33 #define LOC QString("Playlist: ")
34 #define LOC_WARN QString("Playlist, Warning: ")
35 #define LOC_ERR QString("Playlist, Error: ")
39 return m_songs.contains(trackID);
46 for (
int x = 0; x <
m_songs.size(); x++)
83 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Can't add track, given a bad track ID");
98 for (
int x = 0; x <
m_songs.count(); x++)
103 cdTracks.append(
m_songs.at(x));
107 for (
int x = 0; x < cdTracks.count(); x++)
109 m_songs.removeAll(cdTracks.at(x));
129 uint insertion_point = 0;
133 insertion_point = ((
uint)where_its_at) - 1;
135 insertion_point = ((
uint)where_its_at) + 1;
162 QMultiMap<int, MusicMetadata::IdType> songMap;
164 for (
int x = 0; x <
m_songs.count(); x++)
168 songMap.insert(rand(),
m_songs.at(x));
171 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
172 while (i != songMap.constEnd())
183 int RatingWeight = 2;
184 int PlayCountWeight = 2;
185 int LastPlayWeight = 2;
186 int RandomWeight = 2;
188 LastPlayWeight, RandomWeight);
191 int playcountMin = 0;
192 int playcountMax = 0;
193 double lastplayMin = 0.0;
194 double lastplayMax = 0.0;
196 for (
int x = 0; x <
m_songs.count(); x++)
208 playcountMin = playcountMax = mdata->
PlayCount();
209 lastplayMin = lastplayMax = mdata->
LastPlay().toSecsSinceEpoch();
215 else if (mdata->
PlayCount() > playcountMax)
218 double lastplaysecs = mdata->
LastPlay().toSecsSinceEpoch();
219 if (lastplaysecs < lastplayMin)
220 lastplayMin = lastplaysecs;
221 else if (lastplaysecs > lastplayMax)
222 lastplayMax = lastplaysecs;
228 std::map<int,double> weights;
229 std::map<int,int> ratings;
230 std::map<int,int> ratingCounts;
231 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
232 for (
int x = 0; x <
m_songs.size(); x++)
239 double lastplaydbl = mdata->
LastPlay().toSecsSinceEpoch();
240 double ratingValue = (double)(
rating) / 10;
241 double playcountValue = NAN;
242 double lastplayValue = NAN;
244 if (playcountMax == playcountMin)
247 playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);
249 if (lastplayMax == lastplayMin)
252 lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);
254 double weight = (RatingWeight * ratingValue +
255 PlayCountWeight * playcountValue +
256 LastPlayWeight * lastplayValue) / TotalWeight;
257 weights[mdata->
ID()] = weight;
265 double totalWeights = 0;
266 auto weightsEnd = weights.end();
267 for (
auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
269 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
270 totalWeights += weightsIt->second;
274 std::map<int,uint32_t> order;
275 uint32_t orderCpt = 1;
276 while (!weights.empty())
280 double hit = totalWeights * (double)rand() / (double)RAND_MAX;
281 auto weightEnd = weights.end();
282 auto weightIt = weights.begin();
284 while (weightIt != weightEnd)
286 pos += weightIt->second;
299 if (weightIt == weightEnd)
302 order[weightIt->first] = orderCpt;
303 totalWeights -= weightIt->second;
304 weights.erase(weightIt);
309 QMultiMap<int, MusicMetadata::IdType> songMap;
310 for (
int x = 0; x <
m_songs.count(); x++)
314 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
315 while (i != songMap.constEnd())
328 using AlbumMap = std::map<QString, uint32_t>;
330 AlbumMap::iterator Ialbum;
335 for (
int x = 0; x <
m_songs.count(); x++)
340 album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());
341 if ((Ialbum = album_map.find(album)) == album_map.end())
342 album_map.insert(AlbumMap::value_type(album, 0));
347 uint32_t album_count = 1;
348 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
350 Ialbum->second = album_count;
355 QMultiMap<int, MusicMetadata::IdType> songMap;
356 for (
int x = 0; x <
m_songs.count(); x++)
361 uint32_t album_order = 1;
362 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
363 if ((Ialbum = album_map.find(album)) == album_map.end())
373 album_order = Ialbum->second * 10000;
377 album_order += mdata->
Track();
379 songMap.insert(album_order,
m_songs.at(x));
384 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
385 while (i != songMap.constEnd())
398 using ArtistMap = std::map<QString, uint32_t>;
399 ArtistMap artist_map;
400 ArtistMap::iterator Iartist;
405 for (
int x = 0; x <
m_songs.count(); x++)
411 if ((Iartist = artist_map.find(artist)) == artist_map.end())
412 artist_map.insert(ArtistMap::value_type(artist,0));
417 uint32_t artist_count = 1;
418 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
420 Iartist->second = artist_count;
425 QMultiMap<int, MusicMetadata::IdType> songMap;
426 for (
int x = 0; x <
m_songs.count(); x++)
431 uint32_t artist_order = 1;
433 if ((Iartist = artist_map.find(artist)) == artist_map.end())
443 artist_order = Iartist->second * 1000;
445 artist_order += mdata->
Track();
447 songMap.insert(artist_order,
m_songs.at(x));
452 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
453 while (i != songMap.constEnd())
478 LOG(VB_GENERAL, LOG_DEBUG,
479 QString(
"Playlist with name of \"%1\"").arg(name));
480 LOG(VB_GENERAL, LOG_DEBUG,
481 QString(
" playlistid is %1").arg(laylistid));
482 LOG(VB_GENERAL, LOG_DEBUG,
483 QString(
" songlist(raw) is \"%1\"").arg(raw_songlist));
484 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
488 for (
int x = 0; x <
m_songs.count(); x++)
489 msg += QString(
"%1,").arg(
m_songs.at(x));
491 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
495 uint currenttrack, std::chrono::seconds *playedLength)
const
497 std::chrono::milliseconds total = 0ms;
498 std::chrono::milliseconds played = 0ms;
511 if (x < (
int)currenttrack)
512 played += mdata->
Length();
517 *playedLength = duration_cast<std::chrono::seconds>(played);
519 *totalLength = duration_cast<std::chrono::seconds>(total);
526 if (a_host.isEmpty())
528 LOG(VB_GENERAL, LOG_ERR,
LOC +
529 "loadPlaylist() - We need a valid hostname");
535 if (
m_name ==
"default_playlist_storage" ||
536 m_name ==
"stream_playlist")
538 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
539 "FROM music_playlists "
540 "WHERE playlist_name = :NAME"
541 " AND hostname = :HOST;");
547 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
548 "FROM music_playlists "
549 "WHERE playlist_name = :NAME"
550 " AND (hostname = '' OR hostname = :HOST);");
555 if (query.
exec() && query.
size() > 0)
561 rawSonglist = query.
value(2).toString();
582 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
583 "FROM music_playlists "
584 "WHERE playlist_id = :ID"
585 " AND (hostname = '' OR hostname = :HOST);");
596 rawSonglist = query.
value(2).toString();
599 if (
m_name ==
"default_playlist_storage")
600 m_name = tr(
"Default Playlist");
608 bool needUpdate =
false;
610 for (
int x = 0; x <
m_songs.count(); x++)
636 bool badTrack =
false;
638 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
639 QStringList list = songList.split(
",", QString::SkipEmptyParts);
641 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
643 for (
const auto & song : qAsConst(list))
655 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
666 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
684 bool removeDuplicates,
689 QString new_songlist;
698 theQuery =
"SELECT song_id FROM music_songs "
699 "LEFT JOIN music_directories ON"
700 " music_songs.directory_id=music_directories.directory_id "
701 "LEFT JOIN music_artists ON"
702 " music_songs.artist_id=music_artists.artist_id "
703 "LEFT JOIN music_albums ON"
704 " music_songs.album_id=music_albums.album_id "
705 "LEFT JOIN music_genres ON"
706 " music_songs.genre_id=music_genres.genre_id "
707 "LEFT JOIN music_artists AS music_comp_artists ON "
708 "music_albums.artist_id=music_comp_artists.artist_id ";
709 if (whereClause.length() > 0)
710 theQuery += whereClause;
712 if (!query.
exec(theQuery))
715 new_songlist.clear();
724 new_songlist +=
"," + query.
value(0).toString();
726 new_songlist.remove(0, 1);
728 if (removeDuplicates && insertOption !=
PL_REPLACE)
731 switch (insertOption)
737 new_songlist = new_songlist +
"," + orig_songlist;
741 new_songlist = orig_songlist +
"," + new_songlist;
746 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
747 QStringList list = orig_songlist.split(
",", QString::SkipEmptyParts);
749 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
753 for (
const auto& song : qAsConst(list))
755 int an_int = song.toInt();
756 tempList +=
"," + song;
757 if (!bFound && an_int == currentTrackID)
760 tempList +=
"," + new_songlist;
765 tempList = orig_songlist +
"," + new_songlist;
767 new_songlist = tempList.remove(0, 1);
773 new_songlist = orig_songlist;
784 bool removeDuplicates,
789 QString new_songlist;
795 for (
int x = 0; x < songList.count(); x++)
797 new_songlist +=
"," + QString::number(songList.at(x));
799 new_songlist.remove(0, 1);
801 if (removeDuplicates && insertOption !=
PL_REPLACE)
804 switch (insertOption)
810 new_songlist = new_songlist +
"," + orig_songlist;
814 new_songlist = orig_songlist +
"," + new_songlist;
819 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
820 QStringList list = orig_songlist.split(
",", QString::SkipEmptyParts);
822 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
826 for (
const auto & song : qAsConst(list))
828 int an_int = song.toInt();
829 tempList +=
"," + song;
830 if (!bFound && an_int == currentTrackID)
833 tempList +=
"," + new_songlist;
838 tempList = orig_songlist +
"," + new_songlist;
840 new_songlist = tempList.remove(0, 1);
846 new_songlist = orig_songlist;
858 QString rawList =
"";
868 rawList += QString(
",%1").arg(
id);
871 rawList += QString(
",%1").arg(
id);
876 for (
int x = 0; x <
m_songs.count(); x++)
882 rawList += QString(
",%1").arg(
id);
885 rawList += QString(
",%1").arg(
id);
889 if (!rawList.isEmpty())
890 rawList = rawList.remove(0, 1);
896 bool removeDuplicates,
904 if (categoryID == -1)
906 LOG(VB_GENERAL, LOG_WARNING,
LOC +
907 QString(
"Cannot find Smartplaylist Category: %1") .arg(category));
917 query.
prepare(
"SELECT smartplaylistid, matchtype, orderby, limitto "
918 "FROM music_smartplaylists "
919 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
921 query.
bindValue(
":CATEGORYID", categoryID);
928 ID = query.
value(0).toInt();
929 matchType = (query.
value(1).toString() ==
"All") ?
" AND " :
" OR ";
930 orderBy = query.
value(2).toString();
931 limitTo = query.
value(3).toInt();
935 LOG(VB_GENERAL, LOG_WARNING,
LOC +
936 QString(
"Cannot find smartplaylist: %1").arg(name));
947 QString whereClause =
"WHERE ";
949 query.
prepare(
"SELECT field, operator, value1, value2 "
950 "FROM music_smartplaylist_items "
951 "WHERE smartplaylistid = :ID;");
958 QString fieldName = query.
value(0).toString();
959 QString operatorName = query.
value(1).toString();
960 QString value1 = query.
value(2).toString();
961 QString value2 = query.
value(3).toString();
965 operatorName, value1, value2);
981 whereClause +=
" LIMIT " + QString::number(limitTo);
984 insertOption, currentTrackID);
997 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Saving playlist: " + a_name);
999 m_name = a_name.simplified();
1002 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Not saving unnamed playlist");
1006 if (a_host.isEmpty())
1008 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1009 "Not saving playlist without a host name");
1018 std::chrono::seconds playtime = 0s;
1022 bool save_host = (
"default_playlist_storage" == a_name);
1025 QString str_query =
"UPDATE music_playlists SET "
1026 "playlist_songs = :LIST, "
1027 "playlist_name = :NAME, "
1028 "songcount = :SONGCOUNT, "
1029 "length = :PLAYTIME";
1031 str_query +=
", hostname = :HOSTNAME";
1032 str_query +=
" WHERE playlist_id = :ID ;";
1039 QString str_query =
"INSERT INTO music_playlists"
1040 " (playlist_name, playlist_songs,"
1041 " songcount, length";
1043 str_query +=
", hostname";
1044 str_query +=
") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1046 str_query +=
", :HOSTNAME";
1053 query.
bindValue(
":SONGCOUNT", songcount);
1054 query.
bindValue(
":PLAYTIME", qlonglong(playtime.count()));
1071 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1072 QStringList curList = orig_songlist.split(
",", QString::SkipEmptyParts);
1073 QStringList newList = new_songlist.split(
",", QString::SkipEmptyParts);
1075 QStringList curList = orig_songlist.split(
",", Qt::SkipEmptyParts);
1076 QStringList newList = new_songlist.split(
",", Qt::SkipEmptyParts);
1080 for (
const auto & song : qAsConst(newList))
1082 if (curList.indexOf(song) == -1)
1083 songlist +=
"," + song;
1085 songlist.remove(0, 1);
1111 if (pos >= 0 && pos <
m_songs.size())
1127 #ifdef CD_WRTITING_FIXED
1128 void 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;
1157 void Playlist::cdrecordData(
int fd)
1159 if (!m_progress || !m_proc)
1165 buf = m_proc->ReadAll();
1171 static const QRegularExpression newline {
"\\R" };
1172 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1173 QStringList list = data.split(newline, QString::SkipEmptyParts);
1175 QStringList list = data.split(newline, Qt::SkipEmptyParts);
1178 for (
int i = 0; i < list.size(); i++)
1180 QString line = list.at(i);
1182 if (line.mid(15, 2) ==
"of")
1184 int mbdone = line.mid(10, 5).trimmed().toInt();
1185 int mbtotal = line.mid(17, 5).trimmed().toInt();
1189 m_progress->setProgress((mbdone * 100) / mbtotal);
1196 buf = m_proc->ReadAllErr();
1198 QTextStream text(buf);
1200 while (!text.atEnd())
1202 QString err = text.readLine();
1203 if (err.contains(
"Drive needs to reload the media") ||
1204 err.contains(
"Input/output error.") ||
1205 err.contains(
"No disk / Wrong disk!"))
1207 LOG(VB_GENERAL, LOG_ERR, err);
1214 void Playlist::mkisofsData(
int fd)
1216 if (!m_progress || !m_proc)
1221 buf = m_proc->ReadAll();
1224 buf = m_proc->ReadAllErr();
1226 QTextStream text(buf);
1228 while (!text.atEnd())
1230 QString line = text.readLine();
1233 line = line.mid(0, 3);
1234 m_progress->setProgress(line.trimmed().toInt());
1240 void Playlist::processExit(
uint retval)
1242 m_procExitVal = retval;
1245 void Playlist::processExit(
void)
1251 int Playlist::CreateCDMP3(
void)
1256 LOG(VB_GENERAL, LOG_ERR,
"CD Writer is not enabled.");
1261 if (scsidev.isEmpty())
1263 LOG(VB_GENERAL, LOG_ERR,
"No CD Writer device defined.");
1271 double size_in_MB = 0.0;
1273 QStringList reclist;
1286 QFileInfo testit(mdata->
Filename());
1287 if (!testit.exists())
1289 size_in_MB += testit.size() / 1000000.0;
1293 if (mdata->
Artist().length() > 0)
1294 outline += mdata->
Artist() +
"/";
1295 if (mdata->
Album().length() > 0)
1296 outline += mdata->
Album() +
"/";
1312 if (size_in_MB >= max_size)
1314 LOG(VB_GENERAL, LOG_ERR,
"MP3 CD creation aborted -- cd size too big.");
1319 QString tmptemplate(
"/tmp/mythmusicXXXXXX");
1322 if (tmprecordlist == tmptemplate)
1324 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1329 if (tmprecordisofs == tmptemplate)
1331 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1335 QFile reclistfile(tmprecordlist);
1337 if (!reclistfile.open(QIODevice::WriteOnly))
1339 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1343 QTextStream recstream(&reclistfile);
1345 QStringList::Iterator iter;
1347 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1349 recstream << *iter <<
"\n";
1352 reclistfile.close();
1354 m_progress =
new MythProgressDialog(tr(
"Creating CD File System"),
1356 m_progress->setProgress(1);
1361 command =
"mkisofs";
1362 args <<
"-graft-points";
1363 args <<
"-path-list";
1364 args << tmprecordlist;
1366 args << tmprecordisofs;
1377 Qt::DirectConnection);
1379 Qt::DirectConnection);
1381 Qt::DirectConnection);
1389 uint retval = m_procExitVal;
1391 m_progress->Close();
1392 m_progress->deleteLater();
1393 m_proc->disconnect();
1398 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to run mkisofs: returns %1")
1403 m_progress =
new MythProgressDialog(tr(
"Burning CD"), 100);
1404 m_progress->setProgress(2);
1406 command =
"cdrecord";
1407 args = QStringList();
1410 args << QString(
"dev=%1").arg(scsidev);
1412 if (writespeed.toInt() > 0)
1419 args << tmprecordisofs;
1427 this, &Playlist::cdrecordData, Qt::DirectConnection);
1429 this, qOverload<>(&Playlist::processExit), Qt::DirectConnection);
1431 this, qOverload<uint>(&Playlist::processExit), Qt::DirectConnection);
1438 retval = m_procExitVal;
1440 m_progress->Close();
1441 m_progress->deleteLater();
1442 m_proc->disconnect();
1447 LOG(VB_GENERAL, LOG_ERR,
1448 QString(
"Unable to run cdrecord: returns %1") .arg(retval));
1452 QFile::remove(tmprecordlist);
1453 QFile::remove(tmprecordisofs);
1458 int Playlist::CreateCDAudio(
void)