MythTV  master
playlist.cpp
Go to the documentation of this file.
1 // C++
2 #include <algorithm>
3 #include <cinttypes>
4 #include <cstdlib>
5 #include <map>
6 #include <unistd.h>
7 
8 // qt
9 #include <QApplication>
10 #include <QFileInfo>
11 #include <QObject>
12 #include <QRegularExpression>
13 
14 // MythTV
15 #include <libmyth/mythcontext.h>
17 #include <libmythbase/compat.h>
18 #include <libmythbase/exitcodes.h>
19 #include <libmythbase/mythdb.h>
22 
23 // mythmusic
24 #include "musicdata.h"
25 #include "musicplayer.h"
26 #include "playlist.h"
27 #include "playlistcontainer.h"
28 #include "smartplaylist.h"
29 
31 // Playlist
32 
33 #define LOC QString("Playlist: ")
34 #define LOC_WARN QString("Playlist, Warning: ")
35 #define LOC_ERR QString("Playlist, Error: ")
36 
38 {
39  return m_songs.contains(trackID);
40 }
41 
42 void Playlist::copyTracks(Playlist *to_ptr, bool update_display)
43 {
44  disableSaves();
45 
46  for (int x = 0; x < m_songs.size(); x++)
47  {
48  MusicMetadata *mdata = getRawSongAt(x);
49  if (mdata)
50  {
51  if (mdata->isDBTrack())
52  to_ptr->addTrack(mdata->ID(), update_display);
53  }
54  }
55 
56  enableSaves();
57 
58  changed();
59 }
60 
62 void Playlist::addTrack(MusicMetadata::IdType trackID, bool update_display)
63 {
64  int repo = ID_TO_REPO(trackID);
65  MusicMetadata *mdata = nullptr;
66 
67  if (repo == RT_Radio)
68  mdata = gMusicData->m_all_streams->getMetadata(trackID);
69  else
70  mdata = gMusicData->m_all_music->getMetadata(trackID);
71 
72  if (mdata)
73  {
74  m_songs.push_back(trackID);
75  m_shuffledSongs.push_back(trackID);
76 
77  changed();
78 
79  if (update_display && isActivePlaylist())
80  gPlayer->activePlaylistChanged(trackID, false);
81  }
82  else
83  LOG(VB_GENERAL, LOG_ERR, LOC + "Can't add track, given a bad track ID");
84 }
85 
87 {
88  m_songs.clear();
89  m_shuffledSongs.clear();
90 
91  changed();
92 }
93 
95 {
96  // find the cd tracks
97  SongList cdTracks;
98  for (int x = 0; x < m_songs.count(); x++)
99  {
100  MusicMetadata *mdata = getRawSongAt(x);
101 
102  if (mdata && mdata->isCDTrack())
103  cdTracks.append(m_songs.at(x));
104  }
105 
106  // remove the tracks from our lists
107  for (int x = 0; x < cdTracks.count(); x++)
108  {
109  m_songs.removeAll(cdTracks.at(x));
110  m_shuffledSongs.removeAll(cdTracks.at(x));;
111  }
112 
113  changed();
114 }
115 
117 {
118  m_songs.removeAll(trackID);
119  m_shuffledSongs.removeAll(trackID);
120 
121  changed();
122 
123  if (isActivePlaylist())
124  gPlayer->activePlaylistChanged(trackID, true);
125 }
126 
127 void Playlist::moveTrackUpDown(bool flag, int where_its_at)
128 {
129  uint insertion_point = 0;
130  MusicMetadata::IdType id = m_shuffledSongs.at(where_its_at);
131 
132  if (flag)
133  insertion_point = ((uint)where_its_at) - 1;
134  else
135  insertion_point = ((uint)where_its_at) + 1;
136 
137  m_shuffledSongs.removeAt(where_its_at);
138  m_shuffledSongs.insert(insertion_point, id);
139 
140  changed();
141 }
142 
144  m_name(tr("oops"))
145 {
146 }
147 
149 {
150  m_songs.clear();
151  m_shuffledSongs.clear();
152 }
153 
155 {
156  m_shuffledSongs.clear();
157 
158  switch (shuffleMode)
159  {
161  {
162  QMultiMap<int, MusicMetadata::IdType> songMap;
163 
164  for (int x = 0; x < m_songs.count(); x++)
165  {
166  // Pseudo-random is good enough. Don't need a true random.
167  // NOLINTNEXTLINE(cert-msc30-c,cert-msc50-cpp)
168  songMap.insert(rand(), m_songs.at(x));
169  }
170 
171  QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
172  while (i != songMap.constEnd())
173  {
174  m_shuffledSongs.append(i.value());
175  ++i;
176  }
177 
178  break;
179  }
180 
182  {
183  int RatingWeight = 2;
184  int PlayCountWeight = 2;
185  int LastPlayWeight = 2;
186  int RandomWeight = 2;
187  m_parent->FillIntelliWeights(RatingWeight, PlayCountWeight,
188  LastPlayWeight, RandomWeight);
189 
190  // compute max/min playcount,lastplay for this playlist
191  int playcountMin = 0;
192  int playcountMax = 0;
193  double lastplayMin = 0.0;
194  double lastplayMax = 0.0;
195 
196  for (int x = 0; x < m_songs.count(); x++)
197  {
198  MusicMetadata *mdata = getRawSongAt(x);
199  if (!mdata)
200  continue;
201 
202  if (!mdata->isCDTrack())
203  {
204 
205  if (0 == x)
206  {
207  // first song
208  playcountMin = playcountMax = mdata->PlayCount();
209  lastplayMin = lastplayMax = mdata->LastPlay().toSecsSinceEpoch();
210  }
211  else
212  {
213  if (mdata->PlayCount() < playcountMin)
214  playcountMin = mdata->PlayCount();
215  else if (mdata->PlayCount() > playcountMax)
216  playcountMax = mdata->PlayCount();
217 
218  double lastplaysecs = mdata->LastPlay().toSecsSinceEpoch();
219  if (lastplaysecs < lastplayMin)
220  lastplayMin = lastplaysecs;
221  else if (lastplaysecs > lastplayMax)
222  lastplayMax = lastplaysecs;
223  }
224  }
225  }
226 
227  // next we compute all the weights
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++)
233  {
234  MusicMetadata *mdata = getRawSongAt(x);
235  if (mdata && !mdata->isCDTrack())
236  {
237  int rating = mdata->Rating();
238  int playcount = mdata->PlayCount();
239  double lastplaydbl = mdata->LastPlay().toSecsSinceEpoch();
240  double ratingValue = (double)(rating) / 10;
241  double playcountValue = NAN;
242  double lastplayValue = NAN;
243 
244  if (playcountMax == playcountMin)
245  playcountValue = 0;
246  else
247  playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);
248 
249  if (lastplayMax == lastplayMin)
250  lastplayValue = 0;
251  else
252  lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);
253 
254  double weight = (RatingWeight * ratingValue +
255  PlayCountWeight * playcountValue +
256  LastPlayWeight * lastplayValue) / TotalWeight;
257  weights[mdata->ID()] = weight;
258  ratings[mdata->ID()] = rating;
259  ++ratingCounts[rating];
260  }
261  }
262 
263  // then we divide weights with the number of songs in the rating class
264  // (more songs in a class ==> lower weight, without affecting other classes)
265  double totalWeights = 0;
266  auto weightsEnd = weights.end();
267  for (auto weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
268  {
269  weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
270  totalWeights += weightsIt->second;
271  }
272 
273  // then we get a random order, balanced with relative weights of remaining songs
274  std::map<int,uint32_t> order;
275  uint32_t orderCpt = 1;
276  while (!weights.empty())
277  {
278  // Pseudo-random is good enough. Don't need a true random.
279  // NOLINTNEXTLINE(cert-msc30-c,cert-msc50-cpp)
280  double hit = totalWeights * (double)rand() / (double)RAND_MAX;
281  auto weightEnd = weights.end();
282  auto weightIt = weights.begin();
283  double pos = 0;
284  while (weightIt != weightEnd)
285  {
286  pos += weightIt->second;
287  if (pos >= hit)
288  break;
289  ++weightIt;
290  }
291 
292  // FIXME If we don't exit here then we'll segfault, but it
293  // probably won't give us the desired randomisation
294  // either - There seems to be a flaw in this code, we
295  // erase items from the map but never adjust
296  // 'totalWeights' so at a point 'pos' will never be
297  // greater or equal to 'hit' and we will always hit the
298  // end of the map
299  if (weightIt == weightEnd)
300  break;
301 
302  order[weightIt->first] = orderCpt;
303  totalWeights -= weightIt->second;
304  weights.erase(weightIt);
305  ++orderCpt;
306  }
307 
308  // create a map of tracks sorted by the computed order
309  QMultiMap<int, MusicMetadata::IdType> songMap;
310  for (int x = 0; x < m_songs.count(); x++)
311  songMap.insert(order[m_songs.at(x)], m_songs.at(x));
312 
313  // copy the shuffled tracks to the shuffled song list
314  QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
315  while (i != songMap.constEnd())
316  {
317  m_shuffledSongs.append(i.value());
318  ++i;
319  }
320 
321  break;
322  }
323 
325  {
326  // "intellegent/album" order
327 
328  using AlbumMap = std::map<QString, uint32_t>;
329  AlbumMap album_map;
330  AlbumMap::iterator Ialbum;
331  QString album;
332 
333  // pre-fill the album-map with the album name.
334  // This allows us to do album mode in album order
335  for (int x = 0; x < m_songs.count(); x++)
336  {
337  MusicMetadata *mdata = getRawSongAt(x);
338  if (mdata)
339  {
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));
344  }
345  }
346 
347  // populate the sort id into the album map
348  uint32_t album_count = 1;
349  for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
350  {
351  Ialbum->second = album_count;
352  album_count++;
353  }
354 
355  // create a map of tracks sorted by the computed order
356  QMultiMap<int, MusicMetadata::IdType> songMap;
357  for (int x = 0; x < m_songs.count(); x++)
358  {
359  MusicMetadata *mdata = getRawSongAt(x);
360  if (mdata)
361  {
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())
366  {
367  // we didn't find this album in the map,
368  // yet we pre-loaded them all. we are broken,
369  // but we just set the track order to 1, since there
370  // is no real point in reporting an error
371  album_order = 1;
372  }
373  else
374  {
375  album_order = Ialbum->second * 10000;
376  }
377  if (mdata->DiscNumber() != -1)
378  album_order += mdata->DiscNumber()*100;
379  album_order += mdata->Track();
380 
381  songMap.insert(album_order, m_songs.at(x));
382  }
383  }
384 
385  // copy the shuffled tracks to the shuffled song list
386  QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
387  while (i != songMap.constEnd())
388  {
389  m_shuffledSongs.append(i.value());
390  ++i;
391  }
392 
393  break;
394  }
395 
397  {
398  // "intellegent/album" order
399 
400  using ArtistMap = std::map<QString, uint32_t>;
401  ArtistMap artist_map;
402  ArtistMap::iterator Iartist;
403  QString artist;
404 
405  // pre-fill the album-map with the album name.
406  // This allows us to do artist mode in artist order
407  for (int x = 0; x < m_songs.count(); x++)
408  {
409  MusicMetadata *mdata = getRawSongAt(x);
410  if (mdata)
411  {
412  artist = mdata->Artist() + " ~ " + mdata->Title();
413  Iartist = artist_map.find(artist);
414  if (Iartist == artist_map.end())
415  artist_map.insert(ArtistMap::value_type(artist,0));
416  }
417  }
418 
419  // populate the sort id into the artist map
420  uint32_t artist_count = 1;
421  for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
422  {
423  Iartist->second = artist_count;
424  artist_count++;
425  }
426 
427  // create a map of tracks sorted by the computed order
428  QMultiMap<int, MusicMetadata::IdType> songMap;
429  for (int x = 0; x < m_songs.count(); x++)
430  {
431  MusicMetadata *mdata = getRawSongAt(x);
432  if (mdata)
433  {
434  uint32_t artist_order = 1;
435  artist = mdata->Artist() + " ~ " + mdata->Title();
436  Iartist = artist_map.find(artist);
437  if (Iartist == artist_map.end())
438  {
439  // we didn't find this artist in the map,
440  // yet we pre-loaded them all. we are broken,
441  // but we just set the track order to 1, since there
442  // is no real point in reporting an error
443  artist_order = 1;
444  }
445  else
446  {
447  artist_order = Iartist->second * 1000;
448  }
449  artist_order += mdata->Track();
450 
451  songMap.insert(artist_order, m_songs.at(x));
452  }
453  }
454 
455  // copy the shuffled tracks to the shuffled song list
456  QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
457  while (i != songMap.constEnd())
458  {
459  m_shuffledSongs.append(i.value());
460  ++i;
461  }
462 
463  break;
464  }
465 
466  default:
467  {
468  // copy the raw song list to the shuffled track list
469  // NOLINTNEXTLINE(modernize-loop-convert)
470  for (auto it = m_songs.begin(); it != m_songs.end(); ++it)
471  m_shuffledSongs.append(*it);
472 
473  break;
474  }
475  }
476 }
477 
479 {
480  // This is for debugging
481 #if 0
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 ");
489 #endif
490 
491  QString msg;
492  for (int x = 0; x < m_songs.count(); x++)
493  msg += QString("%1,").arg(m_songs.at(x));
494 
495  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
496 }
497 
498 void Playlist::getStats(uint *trackCount, std::chrono::seconds *totalLength,
499  uint currenttrack, std::chrono::seconds *playedLength) const
500 {
501  std::chrono::milliseconds total = 0ms;
502  std::chrono::milliseconds played = 0ms;
503 
504  *trackCount = m_shuffledSongs.size();
505 
506  if ((int)currenttrack >= m_shuffledSongs.size())
507  currenttrack = 0;
508 
509  for (int x = 0; x < m_shuffledSongs.count(); x++)
510  {
511  MusicMetadata *mdata = getSongAt(x);
512  if (mdata)
513  {
514  total += mdata->Length();
515  if (x < (int)currenttrack)
516  played += mdata->Length();
517  }
518  }
519 
520  if (playedLength)
521  *playedLength = duration_cast<std::chrono::seconds>(played);
522 
523  *totalLength = duration_cast<std::chrono::seconds>(total);
524 }
525 
526 void Playlist::loadPlaylist(const QString& a_name, const QString& a_host)
527 {
528  QString rawSonglist;
529 
530  if (a_host.isEmpty())
531  {
532  LOG(VB_GENERAL, LOG_ERR, LOC +
533  "loadPlaylist() - We need a valid hostname");
534  return;
535  }
536 
537  MSqlQuery query(MSqlQuery::InitCon());
538 
539  if (m_name == "default_playlist_storage" ||
540  m_name == "stream_playlist")
541  {
542  query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
543  "FROM music_playlists "
544  "WHERE playlist_name = :NAME"
545  " AND hostname = :HOST;");
546  }
547  else
548  {
549  // Technically this is never called as this function
550  // is only used to load the default playlist.
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);");
555  }
556  query.bindValue(":NAME", a_name);
557  query.bindValue(":HOST", a_host);
558 
559  if (query.exec() && query.size() > 0)
560  {
561  while (query.next())
562  {
563  m_playlistid = query.value(0).toInt();
564  m_name = query.value(1).toString();
565  rawSonglist = query.value(2).toString();
566  }
567  }
568  else
569  {
570  // Asked me to load a playlist I can't find so let's create a new one :)
571  m_playlistid = 0; // Be safe just in case we call load over the top
572  // of an existing playlist
573  rawSonglist.clear();
574  savePlaylist(a_name, a_host);
575  }
576 
577  fillSongsFromSonglist(rawSonglist);
578 
580 }
581 
582 void Playlist::loadPlaylistByID(int id, const QString& a_host)
583 {
584  QString rawSonglist;
585  MSqlQuery query(MSqlQuery::InitCon());
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);");
590  query.bindValue(":ID", id);
591  query.bindValue(":HOST", a_host);
592 
593  if (!query.exec())
594  MythDB::DBError("Playlist::loadPlaylistByID", query);
595 
596  while (query.next())
597  {
598  m_playlistid = query.value(0).toInt();
599  m_name = query.value(1).toString();
600  rawSonglist = query.value(2).toString();
601  }
602 
603  if (m_name == "default_playlist_storage")
604  m_name = tr("Default Playlist");
605 
606  fillSongsFromSonglist(rawSonglist);
607 }
608 
611 {
612  bool needUpdate = false;
613 
614  for (int x = 0; x < m_songs.count(); x++)
615  {
616  MusicMetadata::IdType id = m_songs.at(x);
617  MusicMetadata *mdata = getRawSongAt(x);
618  if (!mdata)
619  {
620  m_songs.removeAll(id);
621  m_shuffledSongs.removeAll(id);
622  needUpdate = true;
623  }
624  }
625 
626  if (needUpdate)
627  {
628  changed();
629 
631 
632  // TODO check we actually need this
633  if (isActivePlaylist())
634  gPlayer->activePlaylistChanged(-1, false);
635  }
636 }
637 
638 void Playlist::fillSongsFromSonglist(const QString& songList)
639 {
640  bool badTrack = false;
641 
642  QStringList list = songList.split(",", Qt::SkipEmptyParts);
643  for (const auto & song : std::as_const(list))
644  {
645  MusicMetadata::IdType id = song.toUInt();
646  int repo = ID_TO_REPO(id);
647  if (repo == RT_Radio)
648  {
649  // check this is a valid stream ID
651  m_songs.push_back(id);
652  else
653  {
654  badTrack = true;
655  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Got a bad track %1").arg(id));
656  }
657  }
658  else
659  {
660  // check this is a valid track ID
661  if (gMusicData->m_all_music->isValidID(id))
662  m_songs.push_back(id);
663  else
664  {
665  badTrack = true;
666  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Got a bad track %1").arg(id));
667  }
668  }
669  }
670 
671  if (this == gPlayer->getCurrentPlaylist())
673  else
675 
676  if (badTrack)
677  changed();
678 
679  if (isActivePlaylist())
680  gPlayer->activePlaylistChanged(-1, false);
681 }
682 
683 int Playlist::fillSonglistFromQuery(const QString& whereClause,
684  bool removeDuplicates,
685  InsertPLOption insertOption,
686  int currentTrackID)
687 {
688  QString orig_songlist = toRawSonglist();
689  QString new_songlist;
690  int added = 0;
691 
692  disableSaves();
693  removeAllTracks();
694 
695  MSqlQuery query(MSqlQuery::InitCon());
696 
697  QString theQuery;
698 
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;
712 
713  if (!query.exec(theQuery))
714  {
715  MythDB::DBError("Load songlist from query", query);
716  new_songlist.clear();
717  fillSongsFromSonglist(new_songlist);
718  enableSaves();
719  changed();
720  return 0;
721  }
722 
723  while (query.next())
724  {
725  new_songlist += "," + query.value(0).toString();
726  added++;
727  }
728  new_songlist.remove(0, 1);
729 
730  if (removeDuplicates && insertOption != PL_REPLACE)
731  orig_songlist = removeItemsFromList(new_songlist, orig_songlist);
732 
733  switch (insertOption)
734  {
735  case PL_REPLACE:
736  break;
737 
739  new_songlist = new_songlist + "," + orig_songlist;
740  break;
741 
742  case PL_INSERTATEND:
743  new_songlist = orig_songlist + "," + new_songlist;
744  break;
745 
747  {
748  QStringList list = orig_songlist.split(",", Qt::SkipEmptyParts);
749  bool bFound = false;
750  QString tempList;
751  for (const auto& song : std::as_const(list))
752  {
753  int an_int = song.toInt();
754  tempList += "," + song;
755  if (!bFound && an_int == currentTrackID)
756  {
757  bFound = true;
758  tempList += "," + new_songlist;
759  }
760  }
761 
762  if (!bFound)
763  tempList = orig_songlist + "," + new_songlist;
764 
765  new_songlist = tempList.remove(0, 1);
766 
767  break;
768  }
769 
770  default:
771  new_songlist = orig_songlist;
772  }
773 
774  fillSongsFromSonglist(new_songlist);
775 
776  enableSaves();
777  changed();
778  return added;
779 }
780 
781 // songList is a list of trackIDs to add
782 int Playlist::fillSonglistFromList(const QList<int> &songList,
783  bool removeDuplicates,
784  InsertPLOption insertOption,
785  int currentTrackID)
786 {
787  QString orig_songlist = toRawSonglist();
788  QString new_songlist;
789 
790  disableSaves();
791 
792  removeAllTracks();
793 
794  for (int x = 0; x < songList.count(); x++)
795  {
796  new_songlist += "," + QString::number(songList.at(x));
797  }
798  new_songlist.remove(0, 1);
799 
800  if (removeDuplicates && insertOption != PL_REPLACE)
801  orig_songlist = removeItemsFromList(new_songlist, orig_songlist);
802 
803  switch (insertOption)
804  {
805  case PL_REPLACE:
806  break;
807 
809  new_songlist = new_songlist + "," + orig_songlist;
810  break;
811 
812  case PL_INSERTATEND:
813  new_songlist = orig_songlist + "," + new_songlist;
814  break;
815 
817  {
818  QStringList list = orig_songlist.split(",", Qt::SkipEmptyParts);
819  bool bFound = false;
820  QString tempList;
821  for (const auto & song : std::as_const(list))
822  {
823  int an_int = song.toInt();
824  tempList += "," + song;
825  if (!bFound && an_int == currentTrackID)
826  {
827  bFound = true;
828  tempList += "," + new_songlist;
829  }
830  }
831 
832  if (!bFound)
833  tempList = orig_songlist + "," + new_songlist;
834 
835  new_songlist = tempList.remove(0, 1);
836 
837  break;
838  }
839 
840  default:
841  new_songlist = orig_songlist;
842  }
843 
844  fillSongsFromSonglist(new_songlist);
845 
846  enableSaves();
847 
848  changed();
849  return songList.count();
850 }
851 
852 QString Playlist::toRawSonglist(bool shuffled, bool tracksOnly)
853 {
854  QString rawList = "";
855 
856  if (shuffled)
857  {
858  for (int x = 0; x < m_shuffledSongs.count(); x++)
859  {
861  if (tracksOnly)
862  {
863  if (ID_TO_REPO(id) == RT_Database)
864  rawList += QString(",%1").arg(id);
865  }
866  else
867  rawList += QString(",%1").arg(id);
868  }
869  }
870  else
871  {
872  for (int x = 0; x < m_songs.count(); x++)
873  {
874  MusicMetadata::IdType id = m_songs.at(x);
875  if (tracksOnly)
876  {
877  if (ID_TO_REPO(id) == RT_Database)
878  rawList += QString(",%1").arg(id);
879  }
880  else
881  rawList += QString(",%1").arg(id);
882  }
883  }
884 
885  if (!rawList.isEmpty())
886  rawList = rawList.remove(0, 1);
887 
888  return rawList;
889 }
890 
891 int Playlist::fillSonglistFromSmartPlaylist(const QString& category, const QString& name,
892  bool removeDuplicates,
893  InsertPLOption insertOption,
894  int currentTrackID)
895 {
896  MSqlQuery query(MSqlQuery::InitCon());
897 
898  // find the correct categoryid
899  int categoryID = SmartPlaylistEditor::lookupCategoryID(category);
900  if (categoryID == -1)
901  {
902  LOG(VB_GENERAL, LOG_WARNING, LOC +
903  QString("Cannot find Smartplaylist Category: %1") .arg(category));
904  return 0;
905  }
906 
907  // find smartplaylist
908  int ID = 0;
909  QString matchType;
910  QString orderBy;
911  int limitTo = 0;
912 
913  query.prepare("SELECT smartplaylistid, matchtype, orderby, limitto "
914  "FROM music_smartplaylists "
915  "WHERE categoryid = :CATEGORYID AND name = :NAME;");
916  query.bindValue(":NAME", name);
917  query.bindValue(":CATEGORYID", categoryID);
918 
919  if (query.exec())
920  {
921  if (query.isActive() && query.size() > 0)
922  {
923  query.first();
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();
928  }
929  else
930  {
931  LOG(VB_GENERAL, LOG_WARNING, LOC +
932  QString("Cannot find smartplaylist: %1").arg(name));
933  return 0;
934  }
935  }
936  else
937  {
938  MythDB::DBError("Find SmartPlaylist", query);
939  return 0;
940  }
941 
942  // get smartplaylist items
943  QString whereClause = "WHERE ";
944 
945  query.prepare("SELECT field, operator, value1, value2 "
946  "FROM music_smartplaylist_items "
947  "WHERE smartplaylistid = :ID;");
948  query.bindValue(":ID", ID);
949  if (query.exec())
950  {
951  bool bFirst = true;
952  while (query.next())
953  {
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();
958  if (!bFirst)
959  {
960  whereClause += matchType + getCriteriaSQL(fieldName,
961  operatorName, value1, value2);
962  }
963  else
964  {
965  bFirst = false;
966  whereClause += " " + getCriteriaSQL(fieldName, operatorName,
967  value1, value2);
968  }
969  }
970  }
971 
972  // add order by clause
973  whereClause += getOrderBySQL(orderBy);
974 
975  // add limit
976  if (limitTo > 0)
977  whereClause += " LIMIT " + QString::number(limitTo);
978 
979  return fillSonglistFromQuery(whereClause, removeDuplicates,
980  insertOption, currentTrackID);
981 }
982 
984 {
985  m_changed = true;
986 
987  if (m_doSave)
989 }
990 
991 void Playlist::savePlaylist(const QString& a_name, const QString& a_host)
992 {
993  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Saving playlist: " + a_name);
994 
995  m_name = a_name.simplified();
996  if (m_name.isEmpty())
997  {
998  LOG(VB_GENERAL, LOG_WARNING, LOC + "Not saving unnamed playlist");
999  return;
1000  }
1001 
1002  if (a_host.isEmpty())
1003  {
1004  LOG(VB_GENERAL, LOG_WARNING, LOC +
1005  "Not saving playlist without a host name");
1006  return;
1007  }
1008 
1009  // get the shuffled list of tracks excluding any cd tracks and radio streams
1010  QString rawSonglist = toRawSonglist(true, true);
1011 
1012  MSqlQuery query(MSqlQuery::InitCon());
1013  uint songcount = 0;
1014  std::chrono::seconds playtime = 0s;
1015 
1016  getStats(&songcount, &playtime);
1017 
1018  bool save_host = ("default_playlist_storage" == a_name);
1019  if (m_playlistid > 0)
1020  {
1021  QString str_query = "UPDATE music_playlists SET "
1022  "playlist_songs = :LIST, "
1023  "playlist_name = :NAME, "
1024  "songcount = :SONGCOUNT, "
1025  "length = :PLAYTIME";
1026  if (save_host)
1027  str_query += ", hostname = :HOSTNAME";
1028  str_query += " WHERE playlist_id = :ID ;";
1029 
1030  query.prepare(str_query);
1031  query.bindValue(":ID", m_playlistid);
1032  }
1033  else
1034  {
1035  QString str_query = "INSERT INTO music_playlists"
1036  " (playlist_name, playlist_songs,"
1037  " songcount, length";
1038  if (save_host)
1039  str_query += ", hostname";
1040  str_query += ") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1041  if (save_host)
1042  str_query += ", :HOSTNAME";
1043  str_query += ");";
1044 
1045  query.prepare(str_query);
1046  }
1047  query.bindValue(":LIST", rawSonglist);
1048  query.bindValue(":NAME", a_name);
1049  query.bindValue(":SONGCOUNT", songcount);
1050  query.bindValue(":PLAYTIME", qlonglong(playtime.count()));
1051  if (save_host)
1052  query.bindValue(":HOSTNAME", a_host);
1053 
1054  if (!query.exec() || (m_playlistid < 1 && query.numRowsAffected() < 1))
1055  {
1056  MythDB::DBError("Problem saving playlist", query);
1057  }
1058 
1059  if (m_playlistid < 1)
1060  m_playlistid = query.lastInsertId().toInt();
1061 
1062  m_changed = false;
1063 }
1064 
1065 // Return a copy of the second list, having removed any item that
1066 // also appears in the first list.
1067 //
1068 // @param remove_list A comma separated list of strings to be
1069 // removed from the source list.
1070 // @param source_list A comma separated list of strings to be
1071 // processed.
1072 // @return A comma separated list of the strings remaining after
1073 // processing.
1074 QString Playlist::removeItemsFromList(const QString &remove_list, const QString &source_list)
1075 {
1076  QStringList removeList = remove_list.split(",", Qt::SkipEmptyParts);
1077  QStringList sourceList = source_list.split(",", Qt::SkipEmptyParts);
1078  QString songlist;
1079 
1080  for (const auto & song : std::as_const(sourceList))
1081  {
1082  if (removeList.indexOf(song) == -1)
1083  songlist += "," + song;
1084  }
1085  songlist.remove(0, 1);
1086  return songlist;
1087 }
1088 
1090 {
1091  MusicMetadata *mdata = nullptr;
1092 
1093  if (pos >= 0 && pos < m_shuffledSongs.size())
1094  {
1096  int repo = ID_TO_REPO(id);
1097 
1098  if (repo == RT_Radio)
1099  mdata = gMusicData->m_all_streams->getMetadata(id);
1100  else
1101  mdata = gMusicData->m_all_music->getMetadata(id);
1102  }
1103 
1104  return mdata;
1105 }
1106 
1108 {
1109  MusicMetadata *mdata = nullptr;
1110 
1111  if (pos >= 0 && pos < m_songs.size())
1112  {
1113  MusicMetadata::IdType id = m_songs.at(pos);
1114  int repo = ID_TO_REPO(id);
1115 
1116  if (repo == RT_Radio)
1117  mdata = gMusicData->m_all_streams->getMetadata(id);
1118  else
1119  mdata = gMusicData->m_all_music->getMetadata(id);
1120  }
1121 
1122  return mdata;
1123 }
1124 
1125 // Here begins CD Writing things. ComputeSize, CreateCDMP3 & CreateCDAudio
1126 // FIXME none of this is currently used
1127 #ifdef CD_WRTITING_FIXED
1128 void Playlist::computeSize(double &size_in_MB, double &size_in_sec)
1129 {
1130  //double child_MB;
1131  //double child_sec;
1132 
1133  // Clear return values
1134  size_in_MB = 0.0;
1135  size_in_sec = 0.0;
1136 
1137  for (int x = 0; x < m_songs.size(); x++)
1138  {
1139  MusicMetadata *mdata = getRawSongAt(x);
1140  if (mdata)
1141  {
1142  if (mdata->isCDTrack())
1143  continue;
1144 
1145  // Normal track
1146  if (mdata->Length() > 0ms)
1147  size_in_sec += duration_cast<floatsecs>(mdata->Length()).count();
1148  else
1149  LOG(VB_GENERAL, LOG_ERR, "Computing track lengths. "
1150  "One track <=0");
1151 
1152  size_in_MB += mdata->FileSize() / 1000000;
1153  }
1154  }
1155 }
1156 
1157 void Playlist::cdrecordData(int fd)
1158 {
1159  if (!m_progress || !m_proc)
1160  return;
1161 
1162  QByteArray buf;
1163  if (fd == 1)
1164  {
1165  buf = m_proc->ReadAll();
1166 
1167  // I would just use the QTextStream::readLine(), but wodim uses \r
1168  // to update the same line, so I'm splitting it on \r or \n
1169  // Track 01: 6 of 147 MB written (fifo 100%) [buf 99%] 16.3x.
1170  QString data(buf);
1171  static const QRegularExpression newline { "\\R" }; // Any unicode newline
1172  QStringList list = data.split(newline, Qt::SkipEmptyParts);
1173 
1174  for (int i = 0; i < list.size(); i++)
1175  {
1176  QString line = list.at(i);
1177 
1178  if (line.mid(15, 2) == "of")
1179  {
1180  int mbdone = line.mid(10, 5).trimmed().toInt();
1181  int mbtotal = line.mid(17, 5).trimmed().toInt();
1182 
1183  if (mbtotal > 0)
1184  {
1185  m_progress->setProgress((mbdone * 100) / mbtotal);
1186  }
1187  }
1188  }
1189  }
1190  else
1191  {
1192  buf = m_proc->ReadAllErr();
1193 
1194  QTextStream text(buf);
1195 
1196  while (!text.atEnd())
1197  {
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!"))
1202  {
1203  LOG(VB_GENERAL, LOG_ERR, err);
1204  m_proc->Term();
1205  }
1206  }
1207  }
1208 }
1209 
1210 void Playlist::mkisofsData(int fd)
1211 {
1212  if (!m_progress || !m_proc)
1213  return;
1214 
1215  QByteArray buf;
1216  if (fd == 1)
1217  buf = m_proc->ReadAll();
1218  else
1219  {
1220  buf = m_proc->ReadAllErr();
1221 
1222  QTextStream text(buf);
1223 
1224  while (!text.atEnd())
1225  {
1226  QString line = text.readLine();
1227  if (line[6] == '%')
1228  {
1229  line = line.mid(0, 3);
1230  m_progress->setProgress(line.trimmed().toInt());
1231  }
1232  }
1233  }
1234 }
1235 
1236 void Playlist::processExit(uint retval)
1237 {
1238  m_procExitVal = retval;
1239 }
1240 
1241 void Playlist::processExit(void)
1242 {
1243  m_procExitVal = GENERIC_EXIT_OK;
1244 }
1245 
1246 // FIXME: this needs updating to work with storage groups
1247 int Playlist::CreateCDMP3(void)
1248 {
1249  // Check & get global settings
1250  if (!gCoreContext->GetNumSetting("CDWriterEnabled"))
1251  {
1252  LOG(VB_GENERAL, LOG_ERR, "CD Writer is not enabled.");
1253  return 1;
1254  }
1255 
1256  QString scsidev = MediaMonitor::defaultCDWriter();
1257  if (scsidev.isEmpty())
1258  {
1259  LOG(VB_GENERAL, LOG_ERR, "No CD Writer device defined.");
1260  return 1;
1261  }
1262 
1263  int disksize = gCoreContext->GetNumSetting("CDDiskSize", 2);
1264  QString writespeed = gCoreContext->GetSetting("CDWriteSpeed", "2");
1265  bool MP3_dir_flag = gCoreContext->GetNumSetting("CDCreateDir", 1);
1266 
1267  double size_in_MB = 0.0;
1268 
1269  QStringList reclist;
1270 
1271  for (int x = 0; x < m_shuffledSongs.count(); x++)
1272  {
1273  MusicMetadata *mdata = getRawSongAt(x);
1274 
1275  // Normal track
1276  if (mdata)
1277  {
1278  if (mdata->isCDTrack())
1279  continue;
1280 
1281  // check filename..
1282  QFileInfo testit(mdata->Filename());
1283  if (!testit.exists())
1284  continue;
1285  size_in_MB += testit.size() / 1000000.0;
1286  QString outline;
1287  if (MP3_dir_flag)
1288  {
1289  if (mdata->Artist().length() > 0)
1290  outline += mdata->Artist() + "/";
1291  if (mdata->Album().length() > 0)
1292  outline += mdata->Album() + "/";
1293  }
1294 
1295  outline += "=";
1296  outline += mdata->Filename();
1297 
1298  reclist += outline;
1299  }
1300  }
1301 
1302  int max_size;
1303  if (disksize == 0)
1304  max_size = 650;
1305  else
1306  max_size = 700;
1307 
1308  if (size_in_MB >= max_size)
1309  {
1310  LOG(VB_GENERAL, LOG_ERR, "MP3 CD creation aborted -- cd size too big.");
1311  return 1;
1312  }
1313 
1314  // probably should tie stdout of mkisofs to stdin of cdrecord sometime
1315  QString tmptemplate("/tmp/mythmusicXXXXXX");
1316 
1317  QString tmprecordlist = createTempFile(tmptemplate);
1318  if (tmprecordlist == tmptemplate)
1319  {
1320  LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
1321  return 1;
1322  }
1323 
1324  QString tmprecordisofs = createTempFile(tmptemplate);
1325  if (tmprecordisofs == tmptemplate)
1326  {
1327  LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
1328  return 1;
1329  }
1330 
1331  QFile reclistfile(tmprecordlist);
1332 
1333  if (!reclistfile.open(QIODevice::WriteOnly))
1334  {
1335  LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
1336  return 1;
1337  }
1338 
1339  QTextStream recstream(&reclistfile);
1340 
1341  QStringList::Iterator iter;
1342 
1343  for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1344  {
1345  recstream << *iter << "\n";
1346  }
1347 
1348  reclistfile.close();
1349 
1350  m_progress = new MythProgressDialog(tr("Creating CD File System"),
1351  100);
1352  m_progress->setProgress(1);
1353 
1354  QStringList args;
1355  QString command;
1356 
1357  command = "mkisofs";
1358  args << "-graft-points";
1359  args << "-path-list";
1360  args << tmprecordlist;
1361  args << "-o";
1362  args << tmprecordisofs;
1363  args << "-J";
1364  args << "-R";
1365 
1366  uint flags = kMSRunShell | kMSStdErr |
1369 
1370  m_proc = new MythSystemLegacy(command, args, flags);
1371 
1372  connect(m_proc, &MythSystemLegacy::readDataReady, this, &Playlist::mkisofsData,
1373  Qt::DirectConnection);
1374  connect(m_proc, &MythSystemLegacy::finished, this, qOverload<>(&Playlist::processExit),
1375  Qt::DirectConnection);
1376  connect(m_proc, &MythSystemLegacy::error, this, qOverload<uint>(&Playlist::processExit),
1377  Qt::DirectConnection);
1378 
1379  m_procExitVal = GENERIC_EXIT_RUNNING;
1380  m_proc->Run();
1381 
1382  while( m_procExitVal == GENERIC_EXIT_RUNNING )
1383  usleep( 100000 );
1384 
1385  uint retval = m_procExitVal;
1386 
1387  m_progress->Close();
1388  m_progress->deleteLater();
1389  m_proc->disconnect();
1390  delete m_proc;
1391 
1392  if (retval)
1393  {
1394  LOG(VB_GENERAL, LOG_ERR, QString("Unable to run mkisofs: returns %1")
1395  .arg(retval));
1396  }
1397  else
1398  {
1399  m_progress = new MythProgressDialog(tr("Burning CD"), 100);
1400  m_progress->setProgress(2);
1401 
1402  command = "cdrecord";
1403  args = QStringList();
1404  args << "-v";
1405  //args << "-dummy";
1406  args << QString("dev=%1").arg(scsidev);
1407 
1408  if (writespeed.toInt() > 0)
1409  {
1410  args << "-speed=";
1411  args << writespeed;
1412  }
1413 
1414  args << "-data";
1415  args << tmprecordisofs;
1416 
1417  flags = kMSRunShell | kMSStdErr | kMSStdOut |
1420 
1421  m_proc = new MythSystemLegacy(command, args, flags);
1422  connect(m_proc, &MythSystemLegacy::readDataReady,
1423  this, &Playlist::cdrecordData, Qt::DirectConnection);
1424  connect(m_proc, &MythSystemLegacy::finished,
1425  this, qOverload<>(&Playlist::processExit), Qt::DirectConnection);
1426  connect(m_proc, &MythSystemLegacy::error,
1427  this, qOverload<uint>(&Playlist::processExit), Qt::DirectConnection);
1428  m_procExitVal = GENERIC_EXIT_RUNNING;
1429  m_proc->Run();
1430 
1431  while( m_procExitVal == GENERIC_EXIT_RUNNING )
1432  usleep( 100000 );
1433 
1434  retval = m_procExitVal;
1435 
1436  m_progress->Close();
1437  m_progress->deleteLater();
1438  m_proc->disconnect();
1439  delete m_proc;
1440 
1441  if (retval)
1442  {
1443  LOG(VB_GENERAL, LOG_ERR,
1444  QString("Unable to run cdrecord: returns %1") .arg(retval));
1445  }
1446  }
1447 
1448  QFile::remove(tmprecordlist);
1449  QFile::remove(tmprecordisofs);
1450 
1451  return retval;
1452 }
1453 
1454 int Playlist::CreateCDAudio(void)
1455 {
1456  return -1;
1457 }
1458 #endif
kMSStdErr
@ kMSStdErr
allow access to stderr
Definition: mythsystem.h:42
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:215
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
MusicPlayer::SHUFFLE_INTELLIGENT
@ SHUFFLE_INTELLIGENT
Definition: musicplayer.h:177
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
build_compdb.args
args
Definition: build_compdb.py:11
playlistcontainer.h
PL_REPLACE
@ PL_REPLACE
Definition: playlist.h:24
MusicMetadata::Title
QString Title() const
Definition: musicmetadata.h:162
gPlayer
MusicPlayer * gPlayer
Definition: musicplayer.cpp:37
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:214
Playlist::getSongAt
MusicMetadata * getSongAt(int pos) const
Definition: playlist.cpp:1089
MusicMetadata::Filename
QString Filename(bool find=true)
Definition: musicmetadata.cpp:962
AllStream::isValidID
bool isValidID(MusicMetadata::IdType an_id)
Definition: musicmetadata.cpp:1731
SmartPlaylistEditor::lookupCategoryID
static int lookupCategoryID(const QString &category)
Definition: smartplaylist.cpp:1168
kMSDontBlockInputDevs
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
mythdb.h
getOrderBySQL
QString getOrderBySQL(const QString &orderByFields)
Definition: smartplaylist.cpp:240
MythSystemLegacy
Definition: mythsystemlegacy.h:67
AllMusic::isValidID
bool isValidID(int an_id)
Definition: musicmetadata.cpp:1640
Playlist::enableSaves
void enableSaves(void)
Definition: playlist.h:103
Playlist::shuffleTracks
void shuffleTracks(MusicPlayer::ShuffleMode mode)
Definition: playlist.cpp:154
Playlist::loadPlaylistByID
void loadPlaylistByID(int id, const QString &a_host)
Definition: playlist.cpp:582
AllStream::getMetadata
MusicMetadata * getMetadata(MusicMetadata::IdType an_id)
Definition: musicmetadata.cpp:1742
Playlist
Definition: playlist.h:45
MSqlQuery::lastInsertId
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:936
Playlist::~Playlist
~Playlist() override
Definition: playlist.cpp:148
Playlist::removeAllCDTracks
void removeAllCDTracks(void)
Definition: playlist.cpp:94
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
mythmediamonitor.h
MythSystemLegacy::finished
void finished(void)
Playlist::getRawSongAt
MusicMetadata * getRawSongAt(int pos) const
Definition: playlist.cpp:1107
MusicMetadata::ID
IdType ID() const
Definition: musicmetadata.h:218
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
SongList
QList< MusicMetadata::IdType > SongList
Definition: playlist.h:43
MusicMetadata::Track
int Track() const
Definition: musicmetadata.h:199
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MusicMetadata
Definition: musicmetadata.h:80
MusicMetadata::Length
std::chrono::milliseconds Length() const
Definition: musicmetadata.h:205
MusicPlayer::getCurrentPlaylist
Playlist * getCurrentPlaylist(void)
Definition: musicplayer.cpp:1611
Playlist::Playlist
Playlist(void)
Definition: playlist.cpp:143
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:11
MusicMetadata::Artist
QString Artist() const
Definition: musicmetadata.h:126
RT_Database
@ RT_Database
Definition: musicmetadata.h:60
Playlist::toRawSonglist
QString toRawSonglist(bool shuffled=false, bool tracksOnly=false)
Definition: playlist.cpp:852
ID_TO_REPO
static constexpr uint32_t ID_TO_REPO(uint32_t x)
Definition: musicmetadata.h:71
Playlist::fillSonglistFromQuery
int fillSonglistFromQuery(const QString &whereClause, bool removeDuplicates=false, InsertPLOption insertOption=PL_REPLACE, int currentTrackID=0)
Definition: playlist.cpp:683
mythsystemlegacy.h
getCriteriaSQL
QString getCriteriaSQL(const QString &fieldName, const QString &operatorName, QString value1, QString value2)
Definition: smartplaylist.cpp:149
Playlist::m_shuffledSongs
SongList m_shuffledSongs
Definition: playlist.h:139
MusicPlayer::SHUFFLE_OFF
@ SHUFFLE_OFF
Definition: musicplayer.h:175
PlaylistContainer::FillIntelliWeights
void FillIntelliWeights(int &rating, int &playcount, int &lastplay, int &random) const
Definition: playlistcontainer.cpp:60
Playlist::changed
void changed(void)
Definition: playlist.cpp:983
RT_Radio
@ RT_Radio
Definition: musicmetadata.h:62
MSqlQuery::first
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:823
Playlist::removeTrack
void removeTrack(MusicMetadata::IdType trackID)
Definition: playlist.cpp:116
MythSystemLegacy::error
void error(uint status)
MythSystemLegacy::readDataReady
void readDataReady(int fd)
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
compat.h
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
MusicData::m_all_streams
AllStream * m_all_streams
Definition: musicdata.h:57
MusicMetadata::getAlbumId
int getAlbumId()
Definition: musicmetadata.cpp:578
hardwareprofile.scan.rating
def rating(profile, smoonURL, gate)
Definition: scan.py:37
Playlist::resync
void resync(void)
make sure all tracks are still valid after a scan
Definition: playlist.cpp:610
createTempFile
QString createTempFile(QString name_template, bool dir)
Definition: mythmiscutil.cpp:323
MusicMetadata::isDBTrack
bool isDBTrack(void) const
Definition: musicmetadata.h:223
Playlist::addTrack
void addTrack(MusicMetadata::IdType trackID, bool update_display)
Given a tracks ID, add that track to this playlist.
Definition: playlist.cpp:62
InsertPLOption
InsertPLOption
Definition: playlist.h:22
Playlist::disableSaves
void disableSaves(void)
whether any changes should be saved to the DB
Definition: playlist.h:102
Playlist::m_name
QString m_name
Definition: playlist.h:136
PL_INSERTATBEGINNING
@ PL_INSERTATBEGINNING
Definition: playlist.h:25
MusicPlayer::getShuffleMode
ShuffleMode getShuffleMode(void)
Definition: musicplayer.h:195
MusicPlayer::SHUFFLE_ARTIST
@ SHUFFLE_ARTIST
Definition: musicplayer.h:179
Playlist::checkTrack
bool checkTrack(MusicMetadata::IdType trackID) const
Definition: playlist.cpp:37
MusicPlayer::playlistChanged
void playlistChanged(int playlistID)
Definition: musicplayer.cpp:1499
AllMusic::getMetadata
MusicMetadata * getMetadata(int an_id)
Definition: musicmetadata.cpp:1632
uint
unsigned int uint
Definition: compat.h:81
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
smartplaylist.h
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:912
Playlist::loadPlaylist
void loadPlaylist(const QString &a_name, const QString &a_host)
Definition: playlist.cpp:526
musicdata.h
kMSRunShell
@ kMSRunShell
run process through shell
Definition: mythsystem.h:43
MusicPlayer::SHUFFLE_RANDOM
@ SHUFFLE_RANDOM
Definition: musicplayer.h:176
kMSRunBackground
@ kMSRunBackground
run child in the background
Definition: mythsystem.h:38
MusicMetadata::Album
QString Album() const
Definition: musicmetadata.h:150
gMusicData
MusicData * gMusicData
Definition: musicdata.cpp:21
Playlist::moveTrackUpDown
void moveTrackUpDown(bool flag, int where_its_at)
Definition: playlist.cpp:127
MusicMetadata::IdType
uint32_t IdType
Definition: musicmetadata.h:86
PL_INSERTATEND
@ PL_INSERTATEND
Definition: playlist.h:26
MusicMetadata::isCDTrack
bool isCDTrack(void) const
Definition: musicmetadata.h:222
Playlist::isActivePlaylist
bool isActivePlaylist(void)
Definition: playlist.h:109
MusicPlayer::activePlaylistChanged
void activePlaylistChanged(int trackID, bool deleted)
Definition: musicplayer.cpp:1436
mythmiscutil.h
Playlist::copyTracks
void copyTracks(Playlist *to_ptr, bool update_display)
Definition: playlist.cpp:42
Playlist::m_doSave
bool m_doSave
Definition: playlist.h:143
MusicMetadata::FileSize
uint64_t FileSize() const
Definition: musicmetadata.h:233
Playlist::removeItemsFromList
static QString removeItemsFromList(const QString &remove_list, const QString &source_list)
Definition: playlist.cpp:1074
PL_INSERTAFTERCURRENT
@ PL_INSERTAFTERCURRENT
Definition: playlist.h:27
MusicPlayer::SHUFFLE_ALBUM
@ SHUFFLE_ALBUM
Definition: musicplayer.h:178
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
MusicMetadata::LastPlay
QDateTime LastPlay() const
Definition: musicmetadata.h:244
LOC
#define LOC
Definition: playlist.cpp:33
Playlist::m_parent
PlaylistContainer * m_parent
Definition: playlist.h:141
Playlist::getStats
void getStats(uint *trackCount, std::chrono::seconds *totalLength, uint currentTrack=0, std::chrono::seconds *playedLength=nullptr) const
Definition: playlist.cpp:498
mythcontext.h
MusicMetadata::PlayCount
int PlayCount() const
Definition: musicmetadata.h:248
Playlist::m_songs
SongList m_songs
Definition: playlist.h:138
MediaMonitor::defaultCDWriter
static QString defaultCDWriter()
CDWriterDeviceLocation, user-selected drive, or /dev/cdrom.
Definition: mythmediamonitor.cpp:912
Playlist::savePlaylist
void savePlaylist(const QString &a_name, const QString &a_host)
Definition: playlist.cpp:991
MSqlQuery::numRowsAffected
int numRowsAffected() const
Definition: mythdbcon.h:217
MusicData::m_all_music
AllMusic * m_all_music
Definition: musicdata.h:56
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:838
MusicMetadata::Rating
int Rating() const
Definition: musicmetadata.h:239
Playlist::fillSonglistFromSmartPlaylist
int fillSonglistFromSmartPlaylist(const QString &category, const QString &name, bool removeDuplicates=false, InsertPLOption insertOption=PL_REPLACE, int currentTrackID=0)
Definition: playlist.cpp:891
Playlist::fillSongsFromSonglist
void fillSongsFromSonglist(const QString &songList)
Definition: playlist.cpp:638
Playlist::describeYourself
void describeYourself(void) const
Definition: playlist.cpp:478
Playlist::removeAllTracks
void removeAllTracks(void)
Definition: playlist.cpp:86
Playlist::m_changed
bool m_changed
Definition: playlist.h:142
playlist.h
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
exitcodes.h
Playlist::fillSonglistFromList
int fillSonglistFromList(const QList< int > &songList, bool removeDuplicates, InsertPLOption insertOption, int currentTrackID)
Definition: playlist.cpp:782
GENERIC_EXIT_RUNNING
@ GENERIC_EXIT_RUNNING
Process is running.
Definition: exitcodes.h:26
kMSStdOut
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:41
Playlist::m_playlistid
int m_playlistid
Definition: playlist.h:135
MusicPlayer::ShuffleMode
ShuffleMode
Definition: musicplayer.h:174
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:898
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
MusicMetadata::DiscNumber
int DiscNumber() const
Definition: musicmetadata.h:209
musicplayer.h