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++)
84 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Can't add track, given a bad track ID");
100 for (
int x = 0; x <
m_songs.count(); x++)
105 cdTracks.append(
m_songs.at(x));
109 for (
int x = 0; x < cdTracks.count(); x++)
111 m_songs.removeAll(cdTracks.at(x));
131 uint insertion_point = 0;
135 insertion_point = ((
uint)where_its_at) - 1;
137 insertion_point = ((
uint)where_its_at) + 1;
164 QMultiMap<int, MusicMetadata::IdType> songMap;
166 for (
int x = 0; x <
m_songs.count(); x++)
170 songMap.insert(rand(),
m_songs.at(x));
173 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
174 while (i != songMap.constEnd())
185 int RatingWeight = 2;
186 int PlayCountWeight = 2;
187 int LastPlayWeight = 2;
188 int RandomWeight = 2;
190 LastPlayWeight, RandomWeight);
193 int playcountMin = 0;
194 int playcountMax = 0;
195 double lastplayMin = 0.0;
196 double lastplayMax = 0.0;
198 for (
int x = 0; x <
m_songs.count(); x++)
210 playcountMin = playcountMax = mdata->
PlayCount();
211 lastplayMin = lastplayMax = mdata->
LastPlay().toSecsSinceEpoch();
217 else if (mdata->
PlayCount() > playcountMax)
220 double lastplaysecs = mdata->
LastPlay().toSecsSinceEpoch();
221 if (lastplaysecs < lastplayMin)
222 lastplayMin = lastplaysecs;
223 else if (lastplaysecs > lastplayMax)
224 lastplayMax = lastplaysecs;
230 std::map<int,double> weights;
231 std::map<int,int> ratings;
232 std::map<int,int> ratingCounts;
233 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
234 for (
int x = 0; x <
m_songs.size(); x++)
241 double lastplaydbl = mdata->
LastPlay().toSecsSinceEpoch();
242 double ratingValue = (double)(
rating) / 10;
243 double playcountValue = __builtin_nan(
"");
244 double lastplayValue = __builtin_nan(
"");
246 if (playcountMax == playcountMin)
249 playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);
251 if (lastplayMax == lastplayMin)
254 lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);
256 double weight = (RatingWeight * ratingValue +
257 PlayCountWeight * playcountValue +
258 LastPlayWeight * lastplayValue) / TotalWeight;
259 weights[mdata->
ID()] = weight;
267 double totalWeights = 0;
268 auto weightsEnd = weights.end();
269 for (
auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
271 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
272 totalWeights += weightsIt->second;
276 std::map<int,uint32_t> order;
277 uint32_t orderCpt = 1;
278 while (!weights.empty())
282 double hit = totalWeights * (double)rand() / (double)RAND_MAX;
283 auto weightEnd = weights.end();
284 auto weightIt = weights.begin();
286 while (weightIt != weightEnd)
288 pos += weightIt->second;
301 if (weightIt == weightEnd)
304 order[weightIt->first] = orderCpt;
305 totalWeights -= weightIt->second;
306 weights.erase(weightIt);
311 QMultiMap<int, MusicMetadata::IdType> songMap;
312 for (
int x = 0; x <
m_songs.count(); x++)
316 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
317 while (i != songMap.constEnd())
330 using AlbumMap = std::map<QString, uint32_t>;
332 AlbumMap::iterator Ialbum;
337 for (
int x = 0; x <
m_songs.count(); x++)
342 album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());
343 Ialbum = album_map.find(album);
344 if (Ialbum == album_map.end())
345 album_map.insert(AlbumMap::value_type(album, 0));
350 uint32_t album_count = 1;
351 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
353 Ialbum->second = album_count;
358 QMultiMap<int, MusicMetadata::IdType> songMap;
359 for (
int x = 0; x <
m_songs.count(); x++)
364 uint32_t album_order = 1;
365 album = album = mdata->
Album() +
" ~ " + QString(
"%1").arg(mdata->
getAlbumId());;
366 Ialbum = album_map.find(album);
367 if (Ialbum == album_map.end())
377 album_order = Ialbum->second * 10000;
381 album_order += mdata->
Track();
383 songMap.insert(album_order,
m_songs.at(x));
388 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
389 while (i != songMap.constEnd())
402 using ArtistMap = std::map<QString, uint32_t>;
403 ArtistMap artist_map;
404 ArtistMap::iterator Iartist;
409 for (
int x = 0; x <
m_songs.count(); x++)
415 Iartist = artist_map.find(artist);
416 if (Iartist == artist_map.end())
417 artist_map.insert(ArtistMap::value_type(artist,0));
422 uint32_t artist_count = 1;
423 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
425 Iartist->second = artist_count;
430 QMultiMap<int, MusicMetadata::IdType> songMap;
431 for (
int x = 0; x <
m_songs.count(); x++)
436 uint32_t artist_order = 1;
438 Iartist = artist_map.find(artist);
439 if (Iartist == artist_map.end())
449 artist_order = Iartist->second * 1000;
451 artist_order += mdata->
Track();
453 songMap.insert(artist_order,
m_songs.at(x));
458 QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
459 while (i != songMap.constEnd())
484 LOG(VB_GENERAL, LOG_DEBUG,
485 QString(
"Playlist with name of \"%1\"").arg(name));
486 LOG(VB_GENERAL, LOG_DEBUG,
487 QString(
" playlistid is %1").arg(laylistid));
488 LOG(VB_GENERAL, LOG_DEBUG,
489 QString(
" songlist(raw) is \"%1\"").arg(raw_songlist));
490 LOG(VB_GENERAL, LOG_DEBUG,
" songlist list is ");
494 for (
int x = 0; x <
m_songs.count(); x++)
495 msg += QString(
"%1,").arg(
m_songs.at(x));
497 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
501 uint currenttrack, std::chrono::seconds *playedLength)
const
503 std::chrono::milliseconds total = 0ms;
504 std::chrono::milliseconds played = 0ms;
517 if (x < (
int)currenttrack)
518 played += mdata->
Length();
523 *playedLength = duration_cast<std::chrono::seconds>(played);
525 *totalLength = duration_cast<std::chrono::seconds>(total);
532 if (a_host.isEmpty())
534 LOG(VB_GENERAL, LOG_ERR,
LOC +
535 "loadPlaylist() - We need a valid hostname");
541 if (
m_name ==
"default_playlist_storage" ||
542 m_name ==
"stream_playlist")
544 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
545 "FROM music_playlists "
546 "WHERE playlist_name = :NAME"
547 " AND hostname = :HOST;");
553 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
554 "FROM music_playlists "
555 "WHERE playlist_name = :NAME"
556 " AND (hostname = '' OR hostname = :HOST);");
561 if (query.
exec() && query.
size() > 0)
567 rawSonglist = query.
value(2).toString();
588 query.
prepare(
"SELECT playlist_id, playlist_name, playlist_songs "
589 "FROM music_playlists "
590 "WHERE playlist_id = :ID"
591 " AND (hostname = '' OR hostname = :HOST);");
602 rawSonglist = query.
value(2).toString();
605 if (
m_name ==
"default_playlist_storage")
606 m_name = tr(
"Default Playlist");
614 bool needUpdate =
false;
616 for (
int x = 0; x <
m_songs.count(); x++)
642 bool badTrack =
false;
644 QStringList list = songList.split(
",", Qt::SkipEmptyParts);
645 for (
const auto & song : std::as_const(list))
657 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
668 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Got a bad track %1").arg(
id));
686 bool removeDuplicates,
691 QString new_songlist;
701 theQuery =
"SELECT song_id FROM music_songs "
702 "LEFT JOIN music_directories ON"
703 " music_songs.directory_id=music_directories.directory_id "
704 "LEFT JOIN music_artists ON"
705 " music_songs.artist_id=music_artists.artist_id "
706 "LEFT JOIN music_albums ON"
707 " music_songs.album_id=music_albums.album_id "
708 "LEFT JOIN music_genres ON"
709 " music_songs.genre_id=music_genres.genre_id "
710 "LEFT JOIN music_artists AS music_comp_artists ON "
711 "music_albums.artist_id=music_comp_artists.artist_id ";
712 if (whereClause.length() > 0)
713 theQuery += whereClause;
715 if (!query.
exec(theQuery))
718 new_songlist.clear();
727 new_songlist +=
"," + query.
value(0).toString();
730 new_songlist.remove(0, 1);
732 if (removeDuplicates && insertOption !=
PL_REPLACE)
735 switch (insertOption)
741 new_songlist = new_songlist +
"," + orig_songlist;
745 new_songlist = orig_songlist +
"," + new_songlist;
750 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
753 for (
const auto& song : std::as_const(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;
785 bool removeDuplicates,
790 QString new_songlist;
796 for (
int x = 0; x < songList.count(); x++)
798 new_songlist +=
"," + QString::number(songList.at(x));
800 new_songlist.remove(0, 1);
802 if (removeDuplicates && insertOption !=
PL_REPLACE)
805 switch (insertOption)
811 new_songlist = new_songlist +
"," + orig_songlist;
815 new_songlist = orig_songlist +
"," + new_songlist;
820 QStringList list = orig_songlist.split(
",", Qt::SkipEmptyParts);
823 for (
const auto & song : std::as_const(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;
851 return songList.count();
856 QString rawList =
"";
866 rawList += QString(
",%1").arg(
id);
870 rawList += QString(
",%1").arg(
id);
876 for (
int x = 0; x <
m_songs.count(); x++)
882 rawList += QString(
",%1").arg(
id);
886 rawList += QString(
",%1").arg(
id);
891 if (!rawList.isEmpty())
892 rawList = rawList.remove(0, 1);
898 bool removeDuplicates,
906 if (categoryID == -1)
908 LOG(VB_GENERAL, LOG_WARNING,
LOC +
909 QString(
"Cannot find Smartplaylist Category: %1") .arg(category));
919 query.
prepare(
"SELECT smartplaylistid, matchtype, orderby, limitto "
920 "FROM music_smartplaylists "
921 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
923 query.
bindValue(
":CATEGORYID", categoryID);
930 ID = query.
value(0).toInt();
931 matchType = (query.
value(1).toString() ==
"All") ?
" AND " :
" OR ";
932 orderBy = query.
value(2).toString();
933 limitTo = query.
value(3).toInt();
937 LOG(VB_GENERAL, LOG_WARNING,
LOC +
938 QString(
"Cannot find smartplaylist: %1").arg(name));
949 QString whereClause =
"WHERE ";
951 query.
prepare(
"SELECT field, operator, value1, value2 "
952 "FROM music_smartplaylist_items "
953 "WHERE smartplaylistid = :ID;");
960 QString fieldName = query.
value(0).toString();
961 QString operatorName = query.
value(1).toString();
962 QString value1 = query.
value(2).toString();
963 QString value2 = query.
value(3).toString();
967 operatorName, value1, value2);
983 whereClause +=
" LIMIT " + QString::number(limitTo);
986 insertOption, currentTrackID);
999 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Saving playlist: " + a_name);
1001 m_name = a_name.simplified();
1004 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Not saving unnamed playlist");
1008 if (a_host.isEmpty())
1010 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1011 "Not saving playlist without a host name");
1020 std::chrono::seconds playtime = 0s;
1024 bool save_host = (
"default_playlist_storage" == a_name);
1027 QString str_query =
"UPDATE music_playlists SET "
1028 "playlist_songs = :LIST, "
1029 "playlist_name = :NAME, "
1030 "songcount = :SONGCOUNT, "
1031 "length = :PLAYTIME";
1033 str_query +=
", hostname = :HOSTNAME";
1034 str_query +=
" WHERE playlist_id = :ID ;";
1041 QString str_query =
"INSERT INTO music_playlists"
1042 " (playlist_name, playlist_songs,"
1043 " songcount, length";
1045 str_query +=
", hostname";
1046 str_query +=
") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1048 str_query +=
", :HOSTNAME";
1055 query.
bindValue(
":SONGCOUNT", songcount);
1056 query.
bindValue(
":PLAYTIME", qlonglong(playtime.count()));
1082 QStringList removeList = remove_list.split(
",", Qt::SkipEmptyParts);
1083 QStringList sourceList = source_list.split(
",", Qt::SkipEmptyParts);
1086 for (
const auto & song : std::as_const(sourceList))
1088 if (removeList.indexOf(song) == -1)
1089 songlist +=
"," + song;
1091 songlist.remove(0, 1);
1117 if (pos >= 0 && pos <
m_songs.size())
1133 #ifdef CD_WRTITING_FIXED
1134 void Playlist::computeSize(
double &size_in_MB,
double &size_in_sec)
1143 for (
int x = 0; x <
m_songs.size(); x++)
1152 if (mdata->
Length() > 0ms)
1153 size_in_sec += duration_cast<floatsecs>(mdata->
Length()).count();
1155 LOG(VB_GENERAL, LOG_ERR,
"Computing track lengths. "
1158 size_in_MB += mdata->
FileSize() / 1000000;
1163 void Playlist::cdrecordData(
int fd)
1165 if (!m_progress || !m_proc)
1171 buf = m_proc->ReadAll();
1177 static const QRegularExpression newline {
"\\R" };
1178 QStringList list = data.split(newline, Qt::SkipEmptyParts);
1180 for (
int i = 0; i < list.size(); i++)
1182 QString line = list.at(i);
1184 if (line.mid(15, 2) ==
"of")
1186 int mbdone = line.mid(10, 5).trimmed().toInt();
1187 int mbtotal = line.mid(17, 5).trimmed().toInt();
1191 m_progress->setProgress((mbdone * 100) / mbtotal);
1198 buf = m_proc->ReadAllErr();
1200 QTextStream text(buf);
1202 while (!text.atEnd())
1204 QString err = text.readLine();
1205 if (err.contains(
"Drive needs to reload the media") ||
1206 err.contains(
"Input/output error.") ||
1207 err.contains(
"No disk / Wrong disk!"))
1209 LOG(VB_GENERAL, LOG_ERR, err);
1216 void Playlist::mkisofsData(
int fd)
1218 if (!m_progress || !m_proc)
1223 buf = m_proc->ReadAll();
1226 buf = m_proc->ReadAllErr();
1228 QTextStream text(buf);
1230 while (!text.atEnd())
1232 QString line = text.readLine();
1235 line = line.mid(0, 3);
1236 m_progress->setProgress(line.trimmed().toInt());
1242 void Playlist::processExit(
uint retval)
1244 m_procExitVal = retval;
1247 void Playlist::processExit(
void)
1253 int Playlist::CreateCDMP3(
void)
1258 LOG(VB_GENERAL, LOG_ERR,
"CD Writer is not enabled.");
1263 if (scsidev.isEmpty())
1265 LOG(VB_GENERAL, LOG_ERR,
"No CD Writer device defined.");
1273 double size_in_MB = 0.0;
1275 QStringList reclist;
1288 QFileInfo testit(mdata->
Filename());
1289 if (!testit.exists())
1291 size_in_MB += testit.size() / 1000000.0;
1295 if (mdata->
Artist().length() > 0)
1296 outline += mdata->
Artist() +
"/";
1297 if (mdata->
Album().length() > 0)
1298 outline += mdata->
Album() +
"/";
1314 if (size_in_MB >= max_size)
1316 LOG(VB_GENERAL, LOG_ERR,
"MP3 CD creation aborted -- cd size too big.");
1321 QString tmptemplate(
"/tmp/mythmusicXXXXXX");
1324 if (tmprecordlist == tmptemplate)
1326 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1331 if (tmprecordisofs == tmptemplate)
1333 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1337 QFile reclistfile(tmprecordlist);
1339 if (!reclistfile.open(QIODevice::WriteOnly))
1341 LOG(VB_GENERAL, LOG_ERR,
"Unable to open temporary file");
1345 QTextStream recstream(&reclistfile);
1347 QStringList::Iterator iter;
1349 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1351 recstream << *iter <<
"\n";
1354 reclistfile.close();
1356 m_progress =
new MythProgressDialog(tr(
"Creating CD File System"),
1358 m_progress->setProgress(1);
1363 command =
"mkisofs";
1364 args <<
"-graft-points";
1365 args <<
"-path-list";
1366 args << tmprecordlist;
1368 args << tmprecordisofs;
1379 Qt::DirectConnection);
1381 Qt::DirectConnection);
1383 Qt::DirectConnection);
1391 uint retval = m_procExitVal;
1393 m_progress->Close();
1394 m_progress->deleteLater();
1395 m_proc->disconnect();
1400 LOG(VB_GENERAL, LOG_ERR, QString(
"Unable to run mkisofs: returns %1")
1405 m_progress =
new MythProgressDialog(tr(
"Burning CD"), 100);
1406 m_progress->setProgress(2);
1408 command =
"cdrecord";
1409 args = QStringList();
1412 args << QString(
"dev=%1").arg(scsidev);
1414 if (writespeed.toInt() > 0)
1421 args << tmprecordisofs;
1429 this, &Playlist::cdrecordData, Qt::DirectConnection);
1431 this, qOverload<>(&Playlist::processExit), Qt::DirectConnection);
1433 this, qOverload<uint>(&Playlist::processExit), Qt::DirectConnection);
1440 retval = m_procExitVal;
1442 m_progress->Close();
1443 m_progress->deleteLater();
1444 m_proc->disconnect();
1449 LOG(VB_GENERAL, LOG_ERR,
1450 QString(
"Unable to run cdrecord: returns %1") .arg(retval));
1454 QFile::remove(tmprecordlist);
1455 QFile::remove(tmprecordisofs);
1460 int Playlist::CreateCDAudio(
void)