9 #include <QApplication>
12 #include <QRegularExpression>
34 #define LOC QString("Playlist: ")
35 #define LOC_WARN QString("Playlist, Warning: ")
36 #define LOC_ERR QString("Playlist, Error: ")
40 return m_songs.contains(trackID);
47 for (
int x = 0; x <
m_songs.size(); x++)
85 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Can't add track, given a bad track ID");
101 for (
int x = 0; x <
m_songs.count(); x++)
106 cdTracks.append(
m_songs.at(x));
110 for (
int x = 0; x < cdTracks.count(); x++)
112 m_songs.removeAll(cdTracks.at(x));
132 uint insertion_point = 0;
136 insertion_point = ((
uint)where_its_at) - 1;
138 insertion_point = ((
uint)where_its_at) + 1;
165 QMultiMap<int, MusicMetadata::IdType> songMap;
167 for (
int x = 0; x <
m_songs.count(); x++)
171 songMap.insert(rand(),
m_songs.at(x));
174 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
175 while (i != songMap.constEnd())
186 int RatingWeight = 2;
187 int PlayCountWeight = 2;
188 int LastPlayWeight = 2;
189 int RandomWeight = 2;
191 LastPlayWeight, RandomWeight);
194 int playcountMin = 0;
195 int playcountMax = 0;
196 double lastplayMin = 0.0;
197 double lastplayMax = 0.0;
199 for (
int x = 0; x <
m_songs.count(); x++)
211 playcountMin = playcountMax = mdata->
PlayCount();
212 lastplayMin = lastplayMax = mdata->
LastPlay().toSecsSinceEpoch();
218 else if (mdata->
PlayCount() > playcountMax)
221 double lastplaysecs = mdata->
LastPlay().toSecsSinceEpoch();
222 if (lastplaysecs < lastplayMin)
223 lastplayMin = lastplaysecs;
224 else if (lastplaysecs > lastplayMax)
225 lastplayMax = lastplaysecs;
231 std::map<int,double> weights;
232 std::map<int,int> ratings;
233 std::map<int,int> ratingCounts;
234 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
235 for (
int x = 0; x <
m_songs.size(); x++)
242 double lastplaydbl = mdata->
LastPlay().toSecsSinceEpoch();
243 double ratingValue = (double)(
rating) / 10;
244 double playcountValue = __builtin_nan(
"");
245 double lastplayValue = __builtin_nan(
"");
247 if (playcountMax == playcountMin)
250 playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);
252 if (lastplayMax == lastplayMin)
255 lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);
257 double weight = (RatingWeight * ratingValue +
258 PlayCountWeight * playcountValue +
259 LastPlayWeight * lastplayValue) / TotalWeight;
260 weights[mdata->
ID()] = weight;
268 double totalWeights = 0;
269 auto weightsEnd = weights.end();
270 for (
auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
272 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
273 totalWeights += weightsIt->second;
277 std::map<int,uint32_t> order;
278 uint32_t orderCpt = 1;
279 while (!weights.empty())
283 double hit = totalWeights * (double)rand() / (double)RAND_MAX;
284 auto weightEnd = weights.end();
285 auto weightIt = weights.begin();
287 while (weightIt != weightEnd)
289 pos += weightIt->second;
302 if (weightIt == weightEnd)
305 order[weightIt->first] = orderCpt;
306 totalWeights -= weightIt->second;
307 weights.erase(weightIt);
312 QMultiMap<int, MusicMetadata::IdType> songMap;
313 for (
int x = 0; x <
m_songs.count(); x++)
317 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
318 while (i != songMap.constEnd())
331 using AlbumMap = std::map<QString, uint32_t>;
333 AlbumMap::iterator Ialbum;
338 for (
int x = 0; x <
m_songs.count(); x++)
343 album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());
344 Ialbum = album_map.find(album);
345 if (Ialbum == album_map.end())
346 album_map.insert(AlbumMap::value_type(album, 0));
351 uint32_t album_count = 1;
352 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
354 Ialbum->second = album_count;
359 QMultiMap<int, MusicMetadata::IdType> songMap;
360 for (
int x = 0; x <
m_songs.count(); x++)
365 uint32_t album_order = 1;
366 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
367 Ialbum = album_map.find(album);
368 if (Ialbum == album_map.end())
378 album_order = Ialbum->second * 10000;
382 album_order += mdata->
Track();
384 songMap.insert(album_order,
m_songs.at(x));
389 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
390 while (i != songMap.constEnd())
403 using ArtistMap = std::map<QString, uint32_t>;
404 ArtistMap artist_map;
405 ArtistMap::iterator Iartist;
410 for (
int x = 0; x <
m_songs.count(); x++)
416 Iartist = artist_map.find(artist);
417 if (Iartist == artist_map.end())
418 artist_map.insert(ArtistMap::value_type(artist,0));
423 uint32_t artist_count = 1;
424 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
426 Iartist->second = artist_count;
431 QMultiMap<int, MusicMetadata::IdType> songMap;
432 for (
int x = 0; x <
m_songs.count(); x++)
437 uint32_t artist_order = 1;
439 Iartist = artist_map.find(artist);
440 if (Iartist == artist_map.end())
450 artist_order = Iartist->second * 1000;
452 artist_order += mdata->
Track();
454 songMap.insert(artist_order,
m_songs.at(x));
459 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
460 while (i != songMap.constEnd())
485 LOG(VB_GENERAL, LOG_DEBUG,
486 QString(
"Playlist with name of \"%1\"").arg(name));
487 LOG(VB_GENERAL, LOG_DEBUG,
488 QString(
" playlistid is %1").arg(laylistid));
489 LOG(VB_GENERAL, LOG_DEBUG,
490 QString(
" songlist(raw) is \"%1\"").arg(raw_songlist));
491 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
495 for (
int x = 0; x <
m_songs.count(); x++)
496 msg += QString(
"%1,").arg(
m_songs.at(x));
498 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
502 uint currenttrack, std::chrono::seconds *playedLength)
const
504 std::chrono::milliseconds total = 0ms;
505 std::chrono::milliseconds played = 0ms;
518 if (x < (
int)currenttrack)
519 played += mdata->
Length();
524 *playedLength = duration_cast<std::chrono::seconds>(played);
526 *totalLength = duration_cast<std::chrono::seconds>(total);
533 if (a_host.isEmpty())
535 LOG(VB_GENERAL, LOG_ERR,
LOC +
536 "loadPlaylist() - We need a valid hostname");
542 if (
m_name ==
"default_playlist_storage" ||
543 m_name ==
"stream_playlist")
545 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
546 "FROM music_playlists "
547 "WHERE playlist_name = :NAME"
548 " AND hostname = :HOST;");
554 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
555 "FROM music_playlists "
556 "WHERE playlist_name = :NAME"
557 " AND (hostname = '' OR hostname = :HOST);");
562 if (query.
exec() && query.
size() > 0)
568 rawSonglist = query.
value(2).toString();
589 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
590 "FROM music_playlists "
591 "WHERE playlist_id = :ID"
592 " AND (hostname = '' OR hostname = :HOST);");
603 rawSonglist = query.
value(2).toString();
606 if (
m_name ==
"default_playlist_storage")
607 m_name = tr(
"Default Playlist");
615 bool needUpdate =
false;
617 for (
int x = 0; x <
m_songs.count(); x++)
643 bool badTrack =
false;
645 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
646 for (
const auto & song : std::as_const(list))
658 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
669 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
687 bool removeDuplicates,
692 QString new_songlist;
702 theQuery =
"SELECT song_id FROM music_songs "
703 "LEFT JOIN music_directories ON"
704 " music_songs.directory_id=music_directories.directory_id "
705 "LEFT JOIN music_artists ON"
706 " music_songs.artist_id=music_artists.artist_id "
707 "LEFT JOIN music_albums ON"
708 " music_songs.album_id=music_albums.album_id "
709 "LEFT JOIN music_genres ON"
710 " music_songs.genre_id=music_genres.genre_id "
711 "LEFT JOIN music_artists AS music_comp_artists ON "
712 "music_albums.artist_id=music_comp_artists.artist_id ";
713 if (whereClause.length() > 0)
714 theQuery += whereClause;
716 if (!query.
exec(theQuery))
719 new_songlist.clear();
728 new_songlist +=
"," + query.
value(0).toString();
731 new_songlist.remove(0, 1);
733 if (removeDuplicates && insertOption !=
PL_REPLACE)
736 switch (insertOption)
742 new_songlist = new_songlist +
"," + orig_songlist;
746 new_songlist = orig_songlist +
"," + new_songlist;
751 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
754 for (
const auto& song : std::as_const(list))
756 int an_int = song.toInt();
757 tempList +=
"," + song;
758 if (!bFound && an_int == currentTrackID)
761 tempList +=
"," + new_songlist;
766 tempList = orig_songlist +
"," + new_songlist;
768 new_songlist = tempList.remove(0, 1);
774 new_songlist = orig_songlist;
786 bool removeDuplicates,
791 QString new_songlist;
797 for (
int x = 0; x < songList.count(); x++)
799 new_songlist +=
"," + QString::number(songList.at(x));
801 new_songlist.remove(0, 1);
803 if (removeDuplicates && insertOption !=
PL_REPLACE)
806 switch (insertOption)
812 new_songlist = new_songlist +
"," + orig_songlist;
816 new_songlist = orig_songlist +
"," + new_songlist;
821 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
824 for (
const auto & song : std::as_const(list))
826 int an_int = song.toInt();
827 tempList +=
"," + song;
828 if (!bFound && an_int == currentTrackID)
831 tempList +=
"," + new_songlist;
836 tempList = orig_songlist +
"," + new_songlist;
838 new_songlist = tempList.remove(0, 1);
844 new_songlist = orig_songlist;
852 return songList.count();
857 QString rawList =
"";
867 rawList += QString(
",%1").arg(
id);
871 rawList += QString(
",%1").arg(
id);
877 for (
int x = 0; x <
m_songs.count(); x++)
883 rawList += QString(
",%1").arg(
id);
887 rawList += QString(
",%1").arg(
id);
892 if (!rawList.isEmpty())
893 rawList = rawList.remove(0, 1);
899 bool removeDuplicates,
907 if (categoryID == -1)
909 LOG(VB_GENERAL, LOG_WARNING,
LOC +
910 QString(
"Cannot find Smartplaylist Category: %1") .arg(category));
920 query.
prepare(
"SELECT smartplaylistid, matchtype, orderby, limitto "
921 "FROM music_smartplaylists "
922 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
924 query.
bindValue(
":CATEGORYID", categoryID);
931 ID = query.
value(0).toInt();
932 matchType = (query.
value(1).toString() ==
"All") ?
" AND " :
" OR ";
933 orderBy = query.
value(2).toString();
934 limitTo = query.
value(3).toInt();
938 LOG(VB_GENERAL, LOG_WARNING,
LOC +
939 QString(
"Cannot find smartplaylist: %1").arg(name));
950 QString whereClause =
"WHERE ";
952 query.
prepare(
"SELECT field, operator, value1, value2 "
953 "FROM music_smartplaylist_items "
954 "WHERE smartplaylistid = :ID;");
961 QString fieldName = query.
value(0).toString();
962 QString operatorName = query.
value(1).toString();
963 QString value1 = query.
value(2).toString();
964 QString value2 = query.
value(3).toString();
968 operatorName, value1, value2);
984 whereClause +=
" LIMIT " + QString::number(limitTo);
987 insertOption, currentTrackID);
1000 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Saving playlist: " + a_name);
1002 m_name = a_name.simplified();
1005 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Not saving unnamed playlist");
1009 if (a_host.isEmpty())
1011 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1012 "Not saving playlist without a host name");
1021 std::chrono::seconds playtime = 0s;
1025 bool save_host = (
"default_playlist_storage" == a_name);
1028 QString str_query =
"UPDATE music_playlists SET "
1029 "playlist_songs = :LIST, "
1030 "playlist_name = :NAME, "
1031 "songcount = :SONGCOUNT, "
1032 "length = :PLAYTIME";
1034 str_query +=
", hostname = :HOSTNAME";
1035 str_query +=
" WHERE playlist_id = :ID ;";
1042 QString str_query =
"INSERT INTO music_playlists"
1043 " (playlist_name, playlist_songs,"
1044 " songcount, length";
1046 str_query +=
", hostname";
1047 str_query +=
") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1049 str_query +=
", :HOSTNAME";
1056 query.
bindValue(
":SONGCOUNT", songcount);
1057 query.
bindValue(
":PLAYTIME", qlonglong(playtime.count()));
1083 QStringList removeList = remove_list.split(
",", Qt::SkipEmptyParts);
1084 QStringList sourceList = source_list.split(
",", Qt::SkipEmptyParts);
1087 for (
const auto & song : std::as_const(sourceList))
1089 if (removeList.indexOf(song) == -1)
1090 songlist +=
"," + song;
1092 songlist.remove(0, 1);
1118 if (pos >= 0 && pos <
m_songs.size())
1134 #ifdef CD_WRTITING_FIXED
1135 void Playlist::computeSize(
double &size_in_MB,
double &size_in_sec)
1144 for (
int x = 0; x <
m_songs.size(); x++)
1153 if (mdata->
Length() > 0ms)
1154 size_in_sec += duration_cast<floatsecs>(mdata->
Length()).count();
1156 LOG(VB_GENERAL, LOG_ERR,
"Computing track lengths. "
1159 size_in_MB += mdata->
FileSize() / 1000000;
1164 void Playlist::cdrecordData(
int fd)
1166 if (!m_progress || !m_proc)
1172 buf = m_proc->ReadAll();
1178 static const QRegularExpression newline {
"\\R" };
1179 QStringList list = data.split(newline, Qt::SkipEmptyParts);
1181 for (
int i = 0; i < list.size(); i++)
1183 QString line = list.at(i);
1185 if (line.mid(15, 2) ==
"of")
1187 int mbdone = line.mid(10, 5).trimmed().toInt();
1188 int mbtotal = line.mid(17, 5).trimmed().toInt();
1192 m_progress->setProgress((mbdone * 100) / mbtotal);
1199 buf = m_proc->ReadAllErr();
1201 QTextStream text(buf);
1203 while (!text.atEnd())
1205 QString err = text.readLine();
1206 if (err.contains(
"Drive needs to reload the media") ||
1207 err.contains(
"Input/output error.") ||
1208 err.contains(
"No disk / Wrong disk!"))
1210 LOG(VB_GENERAL, LOG_ERR, err);
1217 void Playlist::mkisofsData(
int fd)
1219 if (!m_progress || !m_proc)
1224 buf = m_proc->ReadAll();
1227 buf = m_proc->ReadAllErr();
1229 QTextStream text(buf);
1231 while (!text.atEnd())
1233 QString line = text.readLine();
1236 line = line.mid(0, 3);
1237 m_progress->setProgress(line.trimmed().toInt());
1243 void Playlist::processExit(
uint retval)
1245 m_procExitVal = retval;
1248 void Playlist::processExit(
void)
1254 int Playlist::CreateCDMP3(
void)
1259 LOG(VB_GENERAL, LOG_ERR,
"CD Writer is not enabled.");
1264 if (scsidev.isEmpty())
1266 LOG(VB_GENERAL, LOG_ERR,
"No CD Writer device defined.");
1274 double size_in_MB = 0.0;
1276 QStringList reclist;
1289 QFileInfo testit(mdata->
Filename());
1290 if (!testit.exists())
1292 size_in_MB += testit.size() / 1000000.0;
1296 if (mdata->
Artist().length() > 0)
1297 outline += mdata->
Artist() +
"/";
1298 if (mdata->
Album().length() > 0)
1299 outline += mdata->
Album() +
"/";
1315 if (size_in_MB >= max_size)
1317 LOG(VB_GENERAL, LOG_ERR,
"MP3 CD creation aborted -- cd size too big.");
1322 QString tmptemplate(
"/tmp/mythmusicXXXXXX");
1325 if (tmprecordlist == tmptemplate)
1327 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1332 if (tmprecordisofs == tmptemplate)
1334 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1338 QFile reclistfile(tmprecordlist);
1340 if (!reclistfile.open(QIODevice::WriteOnly))
1342 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1346 QTextStream recstream(&reclistfile);
1348 QStringList::Iterator iter;
1350 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1352 recstream << *iter <<
"\n";
1355 reclistfile.close();
1357 m_progress =
new MythProgressDialog(tr(
"Creating CD File System"),
1359 m_progress->setProgress(1);
1364 command =
"mkisofs";
1365 args <<
"-graft-points";
1366 args <<
"-path-list";
1367 args << tmprecordlist;
1369 args << tmprecordisofs;
1380 Qt::DirectConnection);
1382 Qt::DirectConnection);
1384 Qt::DirectConnection);
1392 uint retval = m_procExitVal;
1394 m_progress->Close();
1395 m_progress->deleteLater();
1396 m_proc->disconnect();
1401 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to run mkisofs: returns %1")
1406 m_progress =
new MythProgressDialog(tr(
"Burning CD"), 100);
1407 m_progress->setProgress(2);
1409 command =
"cdrecord";
1410 args = QStringList();
1413 args << QString(
"dev=%1").arg(scsidev);
1415 if (writespeed.toInt() > 0)
1422 args << tmprecordisofs;
1430 this, &Playlist::cdrecordData, Qt::DirectConnection);
1432 this, qOverload<>(&Playlist::processExit), Qt::DirectConnection);
1434 this, qOverload<uint>(&Playlist::processExit), Qt::DirectConnection);
1441 retval = m_procExitVal;
1443 m_progress->Close();
1444 m_progress->deleteLater();
1445 m_proc->disconnect();
1450 LOG(VB_GENERAL, LOG_ERR,
1451 QString(
"Unable to run cdrecord: returns %1") .arg(retval));
1455 QFile::remove(tmprecordlist);
1456 QFile::remove(tmprecordisofs);
1461 int Playlist::CreateCDAudio(
void)