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 Ialbum = album_map.find(album);
342 if (Ialbum == album_map.end())
343 album_map.insert(AlbumMap::value_type(album, 0));
348 uint32_t album_count = 1;
349 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
351 Ialbum->second = album_count;
356 QMultiMap<int, MusicMetadata::IdType> songMap;
357 for (
int x = 0; x <
m_songs.count(); x++)
362 uint32_t album_order = 1;
363 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
364 Ialbum = album_map.find(album);
365 if (Ialbum == album_map.end())
375 album_order = Ialbum->second * 10000;
379 album_order += mdata->
Track();
381 songMap.insert(album_order,
m_songs.at(x));
386 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
387 while (i != songMap.constEnd())
400 using ArtistMap = std::map<QString, uint32_t>;
401 ArtistMap artist_map;
402 ArtistMap::iterator Iartist;
407 for (
int x = 0; x <
m_songs.count(); x++)
413 Iartist = artist_map.find(artist);
414 if (Iartist == artist_map.end())
415 artist_map.insert(ArtistMap::value_type(artist,0));
420 uint32_t artist_count = 1;
421 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
423 Iartist->second = artist_count;
428 QMultiMap<int, MusicMetadata::IdType> songMap;
429 for (
int x = 0; x <
m_songs.count(); x++)
434 uint32_t artist_order = 1;
436 Iartist = artist_map.find(artist);
437 if (Iartist == artist_map.end())
447 artist_order = Iartist->second * 1000;
449 artist_order += mdata->
Track();
451 songMap.insert(artist_order,
m_songs.at(x));
456 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
457 while (i != songMap.constEnd())
482 LOG(VB_GENERAL, LOG_DEBUG,
483 QString(
"Playlist with name of \"%1\"").arg(name));
484 LOG(VB_GENERAL, LOG_DEBUG,
485 QString(
" playlistid is %1").arg(laylistid));
486 LOG(VB_GENERAL, LOG_DEBUG,
487 QString(
" songlist(raw) is \"%1\"").arg(raw_songlist));
488 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
492 for (
int x = 0; x <
m_songs.count(); x++)
493 msg += QString(
"%1,").arg(
m_songs.at(x));
495 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
499 uint currenttrack, std::chrono::seconds *playedLength)
const
501 std::chrono::milliseconds total = 0ms;
502 std::chrono::milliseconds played = 0ms;
515 if (x < (
int)currenttrack)
516 played += mdata->
Length();
521 *playedLength = duration_cast<std::chrono::seconds>(played);
523 *totalLength = duration_cast<std::chrono::seconds>(total);
530 if (a_host.isEmpty())
532 LOG(VB_GENERAL, LOG_ERR,
LOC +
533 "loadPlaylist() - We need a valid hostname");
539 if (
m_name ==
"default_playlist_storage" ||
540 m_name ==
"stream_playlist")
542 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
543 "FROM music_playlists "
544 "WHERE playlist_name = :NAME"
545 " AND hostname = :HOST;");
551 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
552 "FROM music_playlists "
553 "WHERE playlist_name = :NAME"
554 " AND (hostname = '' OR hostname = :HOST);");
559 if (query.
exec() && query.
size() > 0)
565 rawSonglist = query.
value(2).toString();
586 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
587 "FROM music_playlists "
588 "WHERE playlist_id = :ID"
589 " AND (hostname = '' OR hostname = :HOST);");
600 rawSonglist = query.
value(2).toString();
603 if (
m_name ==
"default_playlist_storage")
604 m_name = tr(
"Default Playlist");
612 bool needUpdate =
false;
614 for (
int x = 0; x <
m_songs.count(); x++)
640 bool badTrack =
false;
642 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
643 for (
const auto & song : std::as_const(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;
699 theQuery =
"SELECT song_id FROM music_songs "
700 "LEFT JOIN music_directories ON"
701 " music_songs.directory_id=music_directories.directory_id "
702 "LEFT JOIN music_artists ON"
703 " music_songs.artist_id=music_artists.artist_id "
704 "LEFT JOIN music_albums ON"
705 " music_songs.album_id=music_albums.album_id "
706 "LEFT JOIN music_genres ON"
707 " music_songs.genre_id=music_genres.genre_id "
708 "LEFT JOIN music_artists AS music_comp_artists ON "
709 "music_albums.artist_id=music_comp_artists.artist_id ";
710 if (whereClause.length() > 0)
711 theQuery += whereClause;
713 if (!query.
exec(theQuery))
716 new_songlist.clear();
725 new_songlist +=
"," + query.
value(0).toString();
728 new_songlist.remove(0, 1);
730 if (removeDuplicates && insertOption !=
PL_REPLACE)
733 switch (insertOption)
739 new_songlist = new_songlist +
"," + orig_songlist;
743 new_songlist = orig_songlist +
"," + new_songlist;
748 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
751 for (
const auto& song : std::as_const(list))
753 int an_int = song.toInt();
754 tempList +=
"," + song;
755 if (!bFound && an_int == currentTrackID)
758 tempList +=
"," + new_songlist;
763 tempList = orig_songlist +
"," + new_songlist;
765 new_songlist = tempList.remove(0, 1);
771 new_songlist = orig_songlist;
783 bool removeDuplicates,
788 QString new_songlist;
794 for (
int x = 0; x < songList.count(); x++)
796 new_songlist +=
"," + QString::number(songList.at(x));
798 new_songlist.remove(0, 1);
800 if (removeDuplicates && insertOption !=
PL_REPLACE)
803 switch (insertOption)
809 new_songlist = new_songlist +
"," + orig_songlist;
813 new_songlist = orig_songlist +
"," + new_songlist;
818 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
821 for (
const auto & song : std::as_const(list))
823 int an_int = song.toInt();
824 tempList +=
"," + song;
825 if (!bFound && an_int == currentTrackID)
828 tempList +=
"," + new_songlist;
833 tempList = orig_songlist +
"," + new_songlist;
835 new_songlist = tempList.remove(0, 1);
841 new_songlist = orig_songlist;
849 return songList.count();
854 QString rawList =
"";
864 rawList += QString(
",%1").arg(
id);
867 rawList += QString(
",%1").arg(
id);
872 for (
int x = 0; x <
m_songs.count(); x++)
878 rawList += QString(
",%1").arg(
id);
881 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
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 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);
1210 void 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());
1236 void Playlist::processExit(
uint retval)
1238 m_procExitVal = retval;
1241 void Playlist::processExit(
void)
1247 int 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);
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);
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);
1454 int Playlist::CreateCDAudio(
void)