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