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 
28 const char *kID0err = "Song with ID of 0 in playlist, this shouldn't happen.";
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  if ((Ialbum = album_map.find(album)) == album_map.end())
342  album_map.insert(AlbumMap::value_type(album, 0));
343  }
344  }
345 
346  // populate the sort id into the album map
347  uint32_t album_count = 1;
348  for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
349  {
350  Ialbum->second = album_count;
351  album_count++;
352  }
353 
354  // create a map of tracks sorted by the computed order
355  QMultiMap<int, MusicMetadata::IdType> songMap;
356  for (int x = 0; x < m_songs.count(); x++)
357  {
358  MusicMetadata *mdata = getRawSongAt(x);
359  if (mdata)
360  {
361  uint32_t album_order = 1;
362  album = album = mdata->Album() + " ~ " + QString("%1").arg(mdata->getAlbumId());;
363  if ((Ialbum = album_map.find(album)) == album_map.end())
364  {
365  // we didn't find this album in the map,
366  // yet we pre-loaded them all. we are broken,
367  // but we just set the track order to 1, since there
368  // is no real point in reporting an error
369  album_order = 1;
370  }
371  else
372  {
373  album_order = Ialbum->second * 10000;
374  }
375  if (mdata->DiscNumber() != -1)
376  album_order += mdata->DiscNumber()*100;
377  album_order += mdata->Track();
378 
379  songMap.insert(album_order, m_songs.at(x));
380  }
381  }
382 
383  // copy the shuffled tracks to the shuffled song list
384  QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
385  while (i != songMap.constEnd())
386  {
387  m_shuffledSongs.append(i.value());
388  ++i;
389  }
390 
391  break;
392  }
393 
395  {
396  // "intellegent/album" order
397 
398  using ArtistMap = std::map<QString, uint32_t>;
399  ArtistMap artist_map;
400  ArtistMap::iterator Iartist;
401  QString artist;
402 
403  // pre-fill the album-map with the album name.
404  // This allows us to do artist mode in artist order
405  for (int x = 0; x < m_songs.count(); x++)
406  {
407  MusicMetadata *mdata = getRawSongAt(x);
408  if (mdata)
409  {
410  artist = mdata->Artist() + " ~ " + mdata->Title();
411  if ((Iartist = artist_map.find(artist)) == artist_map.end())
412  artist_map.insert(ArtistMap::value_type(artist,0));
413  }
414  }
415 
416  // populate the sort id into the artist map
417  uint32_t artist_count = 1;
418  for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
419  {
420  Iartist->second = artist_count;
421  artist_count++;
422  }
423 
424  // create a map of tracks sorted by the computed order
425  QMultiMap<int, MusicMetadata::IdType> songMap;
426  for (int x = 0; x < m_songs.count(); x++)
427  {
428  MusicMetadata *mdata = getRawSongAt(x);
429  if (mdata)
430  {
431  uint32_t artist_order = 1;
432  artist = mdata->Artist() + " ~ " + mdata->Title();
433  if ((Iartist = artist_map.find(artist)) == artist_map.end())
434  {
435  // we didn't find this artist in the map,
436  // yet we pre-loaded them all. we are broken,
437  // but we just set the track order to 1, since there
438  // is no real point in reporting an error
439  artist_order = 1;
440  }
441  else
442  {
443  artist_order = Iartist->second * 1000;
444  }
445  artist_order += mdata->Track();
446 
447  songMap.insert(artist_order, m_songs.at(x));
448  }
449  }
450 
451  // copy the shuffled tracks to the shuffled song list
452  QMultiMap<int, MusicMetadata::IdType>::const_iterator i = songMap.constBegin();
453  while (i != songMap.constEnd())
454  {
455  m_shuffledSongs.append(i.value());
456  ++i;
457  }
458 
459  break;
460  }
461 
462  default:
463  {
464  // copy the raw song list to the shuffled track list
465  // NOLINTNEXTLINE(modernize-loop-convert)
466  for (auto it = m_songs.begin(); it != m_songs.end(); ++it)
467  m_shuffledSongs.append(*it);
468 
469  break;
470  }
471  }
472 }
473 
475 {
476  // This is for debugging
477 #if 0
478  LOG(VB_GENERAL, LOG_DEBUG,
479  QString("Playlist with name of \"%1\"").arg(name));
480  LOG(VB_GENERAL, LOG_DEBUG,
481  QString(" playlistid is %1").arg(laylistid));
482  LOG(VB_GENERAL, LOG_DEBUG,
483  QString(" songlist(raw) is \"%1\"").arg(raw_songlist));
484  LOG(VB_GENERAL, LOG_DEBUG, " songlist list is ");
485 #endif
486 
487  QString msg;
488  for (int x = 0; x < m_songs.count(); x++)
489  msg += QString("%1,").arg(m_songs.at(x));
490 
491  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
492 }
493 
494 void Playlist::getStats(uint *trackCount, uint *totalLength, uint currenttrack, uint *playedLength) const
495 {
496  uint64_t total = 0;
497  uint64_t played = 0;
498 
499  *trackCount = m_shuffledSongs.size();
500 
501  if ((int)currenttrack >= m_shuffledSongs.size())
502  currenttrack = 0;
503 
504  for (int x = 0; x < m_shuffledSongs.count(); x++)
505  {
506  MusicMetadata *mdata = getSongAt(x);
507  if (mdata)
508  {
509  total += mdata->Length();
510  if (x < (int)currenttrack)
511  played += mdata->Length();
512  }
513  }
514 
515  if (playedLength)
516  *playedLength = played / 1000;
517 
518  *totalLength = total / 1000;
519 }
520 
521 void Playlist::loadPlaylist(const QString& a_name, const QString& a_host)
522 {
523  QString rawSonglist;
524 
525  if (a_host.isEmpty())
526  {
527  LOG(VB_GENERAL, LOG_ERR, LOC +
528  "loadPlaylist() - We need a valid hostname");
529  return;
530  }
531 
533 
534  if (m_name == "default_playlist_storage" ||
535  m_name == "stream_playlist")
536  {
537  query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
538  "FROM music_playlists "
539  "WHERE playlist_name = :NAME"
540  " AND hostname = :HOST;");
541  }
542  else
543  {
544  // Technically this is never called as this function
545  // is only used to load the default playlist.
546  query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
547  "FROM music_playlists "
548  "WHERE playlist_name = :NAME"
549  " AND (hostname = '' OR hostname = :HOST);");
550  }
551  query.bindValue(":NAME", a_name);
552  query.bindValue(":HOST", a_host);
553 
554  if (query.exec() && query.size() > 0)
555  {
556  while (query.next())
557  {
558  m_playlistid = query.value(0).toInt();
559  m_name = query.value(1).toString();
560  rawSonglist = query.value(2).toString();
561  }
562  }
563  else
564  {
565  // Asked me to load a playlist I can't find so let's create a new one :)
566  m_playlistid = 0; // Be safe just in case we call load over the top
567  // of an existing playlist
568  rawSonglist.clear();
569  savePlaylist(a_name, a_host);
570  }
571 
572  fillSongsFromSonglist(rawSonglist);
573 
575 }
576 
577 void Playlist::loadPlaylistByID(int id, const QString& a_host)
578 {
579  QString rawSonglist;
581  query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
582  "FROM music_playlists "
583  "WHERE playlist_id = :ID"
584  " AND (hostname = '' OR hostname = :HOST);");
585  query.bindValue(":ID", id);
586  query.bindValue(":HOST", a_host);
587 
588  if (!query.exec())
589  MythDB::DBError("Playlist::loadPlaylistByID", query);
590 
591  while (query.next())
592  {
593  m_playlistid = query.value(0).toInt();
594  m_name = query.value(1).toString();
595  rawSonglist = query.value(2).toString();
596  }
597 
598  if (m_name == "default_playlist_storage")
599  m_name = tr("Default Playlist");
600 
601  fillSongsFromSonglist(rawSonglist);
602 }
603 
606 {
607  bool needUpdate = false;
608 
609  for (int x = 0; x < m_songs.count(); x++)
610  {
611  MusicMetadata::IdType id = m_songs.at(x);
612  MusicMetadata *mdata = getRawSongAt(x);
613  if (!mdata)
614  {
615  m_songs.removeAll(id);
616  m_shuffledSongs.removeAll(id);
617  needUpdate = true;
618  }
619  }
620 
621  if (needUpdate)
622  {
623  changed();
624 
626 
627  // TODO check we actually need this
628  if (isActivePlaylist())
629  gPlayer->activePlaylistChanged(-1, false);
630  }
631 }
632 
633 void Playlist::fillSongsFromSonglist(const QString& songList)
634 {
635  bool badTrack = false;
636 
637 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
638  QStringList list = songList.split(",", QString::SkipEmptyParts);
639 #else
640  QStringList list = songList.split(",", Qt::SkipEmptyParts);
641 #endif
642  for (const auto & song : qAsConst(list))
643  {
644  MusicMetadata::IdType id = song.toUInt();
645  int repo = ID_TO_REPO(id);
646  if (repo == RT_Radio)
647  {
648  // check this is a valid stream ID
650  m_songs.push_back(id);
651  else
652  {
653  badTrack = true;
654  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Got a bad track %1").arg(id));
655  }
656  }
657  else
658  {
659  // check this is a valid track ID
660  if (gMusicData->m_all_music->isValidID(id))
661  m_songs.push_back(id);
662  else
663  {
664  badTrack = true;
665  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Got a bad track %1").arg(id));
666  }
667  }
668  }
669 
670  if (this == gPlayer->getCurrentPlaylist())
672  else
674 
675  if (badTrack)
676  changed();
677 
678  if (isActivePlaylist())
679  gPlayer->activePlaylistChanged(-1, false);
680 }
681 
682 void Playlist::fillSonglistFromQuery(const QString& whereClause,
683  bool removeDuplicates,
684  InsertPLOption insertOption,
685  int currentTrackID)
686 {
687  QString orig_songlist = toRawSonglist();
688  QString new_songlist;
689 
690  disableSaves();
691  removeAllTracks();
692 
694 
695  QString theQuery;
696 
697  theQuery = "SELECT song_id FROM music_songs "
698  "LEFT JOIN music_directories ON"
699  " music_songs.directory_id=music_directories.directory_id "
700  "LEFT JOIN music_artists ON"
701  " music_songs.artist_id=music_artists.artist_id "
702  "LEFT JOIN music_albums ON"
703  " music_songs.album_id=music_albums.album_id "
704  "LEFT JOIN music_genres ON"
705  " music_songs.genre_id=music_genres.genre_id "
706  "LEFT JOIN music_artists AS music_comp_artists ON "
707  "music_albums.artist_id=music_comp_artists.artist_id ";
708  if (whereClause.length() > 0)
709  theQuery += whereClause;
710 
711  if (!query.exec(theQuery))
712  {
713  MythDB::DBError("Load songlist from query", query);
714  new_songlist.clear();
715  fillSongsFromSonglist(new_songlist);
716  enableSaves();
717  changed();
718  return;
719  }
720 
721  while (query.next())
722  {
723  new_songlist += "," + query.value(0).toString();
724  }
725  new_songlist.remove(0, 1);
726 
727  if (removeDuplicates && insertOption != PL_REPLACE)
728  new_songlist = removeDuplicateTracks(orig_songlist, new_songlist);
729 
730  switch (insertOption)
731  {
732  case PL_REPLACE:
733  break;
734 
736  new_songlist = new_songlist + "," + orig_songlist;
737  break;
738 
739  case PL_INSERTATEND:
740  new_songlist = orig_songlist + "," + new_songlist;
741  break;
742 
744  {
745 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
746  QStringList list = orig_songlist.split(",", QString::SkipEmptyParts);
747 #else
748  QStringList list = orig_songlist.split(",", Qt::SkipEmptyParts);
749 #endif
750  bool bFound = false;
751  QString tempList;
752  for (const auto& song : qAsConst(list))
753  {
754  int an_int = song.toInt();
755  tempList += "," + song;
756  if (!bFound && an_int == currentTrackID)
757  {
758  bFound = true;
759  tempList += "," + new_songlist;
760  }
761  }
762 
763  if (!bFound)
764  tempList = orig_songlist + "," + new_songlist;
765 
766  new_songlist = tempList.remove(0, 1);
767 
768  break;
769  }
770 
771  default:
772  new_songlist = orig_songlist;
773  }
774 
775  fillSongsFromSonglist(new_songlist);
776 
777  enableSaves();
778  changed();
779 }
780 
781 // songList is a list of trackIDs to add
782 void 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  new_songlist = removeDuplicateTracks(orig_songlist, new_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 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
819  QStringList list = orig_songlist.split(",", QString::SkipEmptyParts);
820 #else
821  QStringList list = orig_songlist.split(",", Qt::SkipEmptyParts);
822 #endif
823  bool bFound = false;
824  QString tempList;
825  for (const auto & song : qAsConst(list))
826  {
827  int an_int = song.toInt();
828  tempList += "," + song;
829  if (!bFound && an_int == currentTrackID)
830  {
831  bFound = true;
832  tempList += "," + new_songlist;
833  }
834  }
835 
836  if (!bFound)
837  tempList = orig_songlist + "," + new_songlist;
838 
839  new_songlist = tempList.remove(0, 1);
840 
841  break;
842  }
843 
844  default:
845  new_songlist = orig_songlist;
846  }
847 
848  fillSongsFromSonglist(new_songlist);
849 
850  enableSaves();
851 
852  changed();
853 }
854 
855 QString Playlist::toRawSonglist(bool shuffled, bool tracksOnly)
856 {
857  QString rawList = "";
858 
859  if (shuffled)
860  {
861  for (int x = 0; x < m_shuffledSongs.count(); x++)
862  {
864  if (tracksOnly)
865  {
866  if (ID_TO_REPO(id) == RT_Database)
867  rawList += QString(",%1").arg(id);
868  }
869  else
870  rawList += QString(",%1").arg(id);
871  }
872  }
873  else
874  {
875  for (int x = 0; x < m_songs.count(); x++)
876  {
877  MusicMetadata::IdType id = m_songs.at(x);
878  if (tracksOnly)
879  {
880  if (ID_TO_REPO(id) == RT_Database)
881  rawList += QString(",%1").arg(id);
882  }
883  else
884  rawList += QString(",%1").arg(id);
885  }
886  }
887 
888  if (!rawList.isEmpty())
889  rawList = rawList.remove(0, 1);
890 
891  return rawList;
892 }
893 
894 void Playlist::fillSonglistFromSmartPlaylist(const QString& category, const QString& name,
895  bool removeDuplicates,
896  InsertPLOption insertOption,
897  int currentTrackID)
898 {
900 
901  // find the correct categoryid
903  if (categoryID == -1)
904  {
905  LOG(VB_GENERAL, LOG_WARNING, LOC +
906  QString("Cannot find Smartplaylist Category: %1") .arg(category));
907  return;
908  }
909 
910  // find smartplaylist
911  int ID = 0;
912  QString matchType;
913  QString orderBy;
914  int limitTo = 0;
915 
916  query.prepare("SELECT smartplaylistid, matchtype, orderby, limitto "
917  "FROM music_smartplaylists "
918  "WHERE categoryid = :CATEGORYID AND name = :NAME;");
919  query.bindValue(":NAME", name);
920  query.bindValue(":CATEGORYID", categoryID);
921 
922  if (query.exec())
923  {
924  if (query.isActive() && query.size() > 0)
925  {
926  query.first();
927  ID = query.value(0).toInt();
928  matchType = (query.value(1).toString() == "All") ? " AND " : " OR ";
929  orderBy = query.value(2).toString();
930  limitTo = query.value(3).toInt();
931  }
932  else
933  {
934  LOG(VB_GENERAL, LOG_WARNING, LOC +
935  QString("Cannot find smartplaylist: %1").arg(name));
936  return;
937  }
938  }
939  else
940  {
941  MythDB::DBError("Find SmartPlaylist", query);
942  return;
943  }
944 
945  // get smartplaylist items
946  QString whereClause = "WHERE ";
947 
948  query.prepare("SELECT field, operator, value1, value2 "
949  "FROM music_smartplaylist_items "
950  "WHERE smartplaylistid = :ID;");
951  query.bindValue(":ID", ID);
952  if (query.exec())
953  {
954  bool bFirst = true;
955  while (query.next())
956  {
957  QString fieldName = query.value(0).toString();
958  QString operatorName = query.value(1).toString();
959  QString value1 = query.value(2).toString();
960  QString value2 = query.value(3).toString();
961  if (!bFirst)
962  {
963  whereClause += matchType + getCriteriaSQL(fieldName,
964  operatorName, value1, value2);
965  }
966  else
967  {
968  bFirst = false;
969  whereClause += " " + getCriteriaSQL(fieldName, operatorName,
970  value1, value2);
971  }
972  }
973  }
974 
975  // add order by clause
976  whereClause += getOrderBySQL(orderBy);
977 
978  // add limit
979  if (limitTo > 0)
980  whereClause += " LIMIT " + QString::number(limitTo);
981 
982  fillSonglistFromQuery(whereClause, removeDuplicates,
983  insertOption, currentTrackID);
984 }
985 
987 {
988  m_changed = true;
989 
990  if (m_doSave)
992 }
993 
994 void Playlist::savePlaylist(const QString& a_name, const QString& a_host)
995 {
996  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Saving playlist: " + a_name);
997 
998  m_name = a_name.simplified();
999  if (m_name.isEmpty())
1000  {
1001  LOG(VB_GENERAL, LOG_WARNING, LOC + "Not saving unnamed playlist");
1002  return;
1003  }
1004 
1005  if (a_host.isEmpty())
1006  {
1007  LOG(VB_GENERAL, LOG_WARNING, LOC +
1008  "Not saving playlist without a host name");
1009  return;
1010  }
1011 
1012  // get the shuffled list of tracks excluding any cd tracks and radio streams
1013  QString rawSonglist = toRawSonglist(true, true);
1014 
1016  uint songcount = 0;
1017  uint playtime = 0;
1018 
1019  getStats(&songcount, &playtime);
1020 
1021  bool save_host = ("default_playlist_storage" == a_name);
1022  if (m_playlistid > 0)
1023  {
1024  QString str_query = "UPDATE music_playlists SET "
1025  "playlist_songs = :LIST, "
1026  "playlist_name = :NAME, "
1027  "songcount = :SONGCOUNT, "
1028  "length = :PLAYTIME";
1029  if (save_host)
1030  str_query += ", hostname = :HOSTNAME";
1031  str_query += " WHERE playlist_id = :ID ;";
1032 
1033  query.prepare(str_query);
1034  query.bindValue(":ID", m_playlistid);
1035  }
1036  else
1037  {
1038  QString str_query = "INSERT INTO music_playlists"
1039  " (playlist_name, playlist_songs,"
1040  " songcount, length";
1041  if (save_host)
1042  str_query += ", hostname";
1043  str_query += ") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
1044  if (save_host)
1045  str_query += ", :HOSTNAME";
1046  str_query += ");";
1047 
1048  query.prepare(str_query);
1049  }
1050  query.bindValue(":LIST", rawSonglist);
1051  query.bindValue(":NAME", a_name);
1052  query.bindValue(":SONGCOUNT", songcount);
1053  query.bindValue(":PLAYTIME", qlonglong(playtime));
1054  if (save_host)
1055  query.bindValue(":HOSTNAME", a_host);
1056 
1057  if (!query.exec() || (m_playlistid < 1 && query.numRowsAffected() < 1))
1058  {
1059  MythDB::DBError("Problem saving playlist", query);
1060  }
1061 
1062  if (m_playlistid < 1)
1063  m_playlistid = query.lastInsertId().toInt();
1064 
1065  m_changed = false;
1066 }
1067 
1068 QString Playlist::removeDuplicateTracks(const QString &orig_songlist, const QString &new_songlist)
1069 {
1070 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1071  QStringList curList = orig_songlist.split(",", QString::SkipEmptyParts);
1072  QStringList newList = new_songlist.split(",", QString::SkipEmptyParts);
1073 #else
1074  QStringList curList = orig_songlist.split(",", Qt::SkipEmptyParts);
1075  QStringList newList = new_songlist.split(",", Qt::SkipEmptyParts);
1076 #endif
1077  QString songlist;
1078 
1079  for (const auto & song : qAsConst(newList))
1080  {
1081  if (curList.indexOf(song) == -1)
1082  songlist += "," + song;
1083  }
1084  songlist.remove(0, 1);
1085  return songlist;
1086 }
1087 
1089 {
1090  MusicMetadata *mdata = nullptr;
1091 
1092  if (pos >= 0 && pos < m_shuffledSongs.size())
1093  {
1095  int repo = ID_TO_REPO(id);
1096 
1097  if (repo == RT_Radio)
1098  mdata = gMusicData->m_all_streams->getMetadata(id);
1099  else
1100  mdata = gMusicData->m_all_music->getMetadata(id);
1101  }
1102 
1103  return mdata;
1104 }
1105 
1107 {
1108  MusicMetadata *mdata = nullptr;
1109 
1110  if (pos >= 0 && pos < m_songs.size())
1111  {
1112  MusicMetadata::IdType id = m_songs.at(pos);
1113  int repo = ID_TO_REPO(id);
1114 
1115  if (repo == RT_Radio)
1116  mdata = gMusicData->m_all_streams->getMetadata(id);
1117  else
1118  mdata = gMusicData->m_all_music->getMetadata(id);
1119  }
1120 
1121  return mdata;
1122 }
1123 
1124 // Here begins CD Writing things. ComputeSize, CreateCDMP3 & CreateCDAudio
1125 // FIXME none of this is currently used
1126 #ifdef CD_WRTITING_FIXED
1127 void Playlist::computeSize(double &size_in_MB, double &size_in_sec)
1128 {
1129  //double child_MB;
1130  //double child_sec;
1131 
1132  // Clear return values
1133  size_in_MB = 0.0;
1134  size_in_sec = 0.0;
1135 
1136  for (int x = 0; x < m_songs.size(); x++)
1137  {
1138  MusicMetadata *mdata = getRawSongAt(x);
1139  if (mdata)
1140  {
1141  if (mdata->isCDTrack())
1142  continue;
1143 
1144  // Normal track
1145  if (mdata->Length() > 0)
1146  size_in_sec += mdata->Length();
1147  else
1148  LOG(VB_GENERAL, LOG_ERR, "Computing track lengths. "
1149  "One track <=0");
1150 
1151  size_in_MB += mdata->FileSize() / 1000000;
1152  }
1153  }
1154 }
1155 
1156 void Playlist::cdrecordData(int fd)
1157 {
1158  if (!m_progress || !m_proc)
1159  return;
1160 
1161  QByteArray buf;
1162  if (fd == 1)
1163  {
1164  buf = m_proc->ReadAll();
1165 
1166  // I would just use the QTextStream::readLine(), but wodim uses \r
1167  // to update the same line, so I'm splitting it on \r or \n
1168  // Track 01: 6 of 147 MB written (fifo 100%) [buf 99%] 16.3x.
1169  QString data(buf);
1170 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1171  QStringList list = data.split(QRegExp("[\\r\\n]"),
1172  QString::SkipEmptyParts);
1173 #else
1174  QStringList list = data.split(QRegExp("[\\r\\n]"),
1175  Qt::SkipEmptyParts);
1176 #endif
1177 
1178  for (int i = 0; i < list.size(); i++)
1179  {
1180  QString line = list.at(i);
1181 
1182  if (line.mid(15, 2) == "of")
1183  {
1184  int mbdone = line.mid(10, 5).trimmed().toInt();
1185  int mbtotal = line.mid(17, 5).trimmed().toInt();
1186 
1187  if (mbtotal > 0)
1188  {
1189  m_progress->setProgress((mbdone * 100) / mbtotal);
1190  }
1191  }
1192  }
1193  }
1194  else
1195  {
1196  buf = m_proc->ReadAllErr();
1197 
1198  QTextStream text(buf);
1199 
1200  while (!text.atEnd())
1201  {
1202  QString err = text.readLine();
1203  if (err.contains("Drive needs to reload the media") ||
1204  err.contains("Input/output error.") ||
1205  err.contains("No disk / Wrong disk!"))
1206  {
1207  LOG(VB_GENERAL, LOG_ERR, err);
1208  m_proc->Term();
1209  }
1210  }
1211  }
1212 }
1213 
1214 void Playlist::mkisofsData(int fd)
1215 {
1216  if (!m_progress || !m_proc)
1217  return;
1218 
1219  QByteArray buf;
1220  if (fd == 1)
1221  buf = m_proc->ReadAll();
1222  else
1223  {
1224  buf = m_proc->ReadAllErr();
1225 
1226  QTextStream text(buf);
1227 
1228  while (!text.atEnd())
1229  {
1230  QString line = text.readLine();
1231  if (line[6] == '%')
1232  {
1233  line = line.mid(0, 3);
1234  m_progress->setProgress(line.trimmed().toInt());
1235  }
1236  }
1237  }
1238 }
1239 
1240 void Playlist::processExit(uint retval)
1241 {
1242  m_procExitVal = retval;
1243 }
1244 
1245 void Playlist::processExit(void)
1246 {
1247  m_procExitVal = GENERIC_EXIT_OK;
1248 }
1249 
1250 // FIXME: this needs updating to work with storage groups
1251 int Playlist::CreateCDMP3(void)
1252 {
1253  // Check & get global settings
1254  if (!gCoreContext->GetNumSetting("CDWriterEnabled"))
1255  {
1256  LOG(VB_GENERAL, LOG_ERR, "CD Writer is not enabled.");
1257  return 1;
1258  }
1259 
1260  QString scsidev = MediaMonitor::defaultCDWriter();
1261  if (scsidev.isEmpty())
1262  {
1263  LOG(VB_GENERAL, LOG_ERR, "No CD Writer device defined.");
1264  return 1;
1265  }
1266 
1267  int disksize = gCoreContext->GetNumSetting("CDDiskSize", 2);
1268  QString writespeed = gCoreContext->GetSetting("CDWriteSpeed", "2");
1269  bool MP3_dir_flag = gCoreContext->GetNumSetting("CDCreateDir", 1);
1270 
1271  double size_in_MB = 0.0;
1272 
1273  QStringList reclist;
1274 
1275  for (int x = 0; x < m_shuffledSongs.count(); x++)
1276  {
1277  MusicMetadata *mdata = getRawSongAt(x);
1278 
1279  // Normal track
1280  if (mdata)
1281  {
1282  if (mdata->isCDTrack())
1283  continue;
1284 
1285  // check filename..
1286  QFileInfo testit(mdata->Filename());
1287  if (!testit.exists())
1288  continue;
1289  size_in_MB += testit.size() / 1000000.0;
1290  QString outline;
1291  if (MP3_dir_flag)
1292  {
1293  if (mdata->Artist().length() > 0)
1294  outline += mdata->Artist() + "/";
1295  if (mdata->Album().length() > 0)
1296  outline += mdata->Album() + "/";
1297  }
1298 
1299  outline += "=";
1300  outline += mdata->Filename();
1301 
1302  reclist += outline;
1303  }
1304  }
1305 
1306  int max_size;
1307  if (disksize == 0)
1308  max_size = 650;
1309  else
1310  max_size = 700;
1311 
1312  if (size_in_MB >= max_size)
1313  {
1314  LOG(VB_GENERAL, LOG_ERR, "MP3 CD creation aborted -- cd size too big.");
1315  return 1;
1316  }
1317 
1318  // probably should tie stdout of mkisofs to stdin of cdrecord sometime
1319  QString tmptemplate("/tmp/mythmusicXXXXXX");
1320 
1321  QString tmprecordlist = createTempFile(tmptemplate);
1322  if (tmprecordlist == tmptemplate)
1323  {
1324  LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
1325  return 1;
1326  }
1327 
1328  QString tmprecordisofs = createTempFile(tmptemplate);
1329  if (tmprecordisofs == tmptemplate)
1330  {
1331  LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
1332  return 1;
1333  }
1334 
1335  QFile reclistfile(tmprecordlist);
1336 
1337  if (!reclistfile.open(QIODevice::WriteOnly))
1338  {
1339  LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
1340  return 1;
1341  }
1342 
1343  QTextStream recstream(&reclistfile);
1344 
1345  QStringList::Iterator iter;
1346 
1347  for (iter = reclist.begin(); iter != reclist.end(); ++iter)
1348  {
1349  recstream << *iter << "\n";
1350  }
1351 
1352  reclistfile.close();
1353 
1354  m_progress = new MythProgressDialog(tr("Creating CD File System"),
1355  100);
1356  m_progress->setProgress(1);
1357 
1358  QStringList args;
1359  QString command;
1360 
1361  command = "mkisofs";
1362  args << "-graft-points";
1363  args << "-path-list";
1364  args << tmprecordlist;
1365  args << "-o";
1366  args << tmprecordisofs;
1367  args << "-J";
1368  args << "-R";
1369 
1370  uint flags = kMSRunShell | kMSStdErr |
1373 
1374  m_proc = new MythSystemLegacy(command, args, flags);
1375 
1376  connect(m_proc, &MythSystemLegacy::readDataReady, this, &Playlist::mkisofsData,
1377  Qt::DirectConnection);
1378  connect(m_proc, &MythSystemLegacy::inished, this, qOverload<>&Playlist::processExit,
1379  Qt::DirectConnection);
1380  connect(m_proc, &MythSystemLegacy::error, this, qOverload<uint>&Playlist::processExit),
1381  Qt::DirectConnection);
1382 
1383  m_procExitVal = GENERIC_EXIT_RUNNING;
1384  m_proc->Run();
1385 
1386  while( m_procExitVal == GENERIC_EXIT_RUNNING )
1387  usleep( 100000 );
1388 
1389  uint retval = m_procExitVal;
1390 
1391  m_progress->Close();
1392  m_progress->deleteLater();
1393  m_proc->disconnect();
1394  delete m_proc;
1395 
1396  if (retval)
1397  {
1398  LOG(VB_GENERAL, LOG_ERR, QString("Unable to run mkisofs: returns %1")
1399  .arg(retval));
1400  }
1401  else
1402  {
1403  m_progress = new MythProgressDialog(tr("Burning CD"), 100);
1404  m_progress->setProgress(2);
1405 
1406  command = "cdrecord";
1407  args = QStringList();
1408  args << "-v";
1409  //args << "-dummy";
1410  args << QString("dev=%1").arg(scsidev);
1411 
1412  if (writespeed.toInt() > 0)
1413  {
1414  args << "-speed=";
1415  args << writespeed;
1416  }
1417 
1418  args << "-data";
1419  args << tmprecordisofs;
1420 
1421  flags = kMSRunShell | kMSStdErr | kMSStdOut |
1424 
1425  m_proc = new MythSystemLegacy(command, args, flags);
1426  connect(m_proc, &MythSystemLegacy::readDataReady,
1427  this, &Playlist::cdrecordData, Qt::DirectConnection);
1428  connect(m_proc, &MythSystemLegacy::finished,
1429  this, qOverload<>&Playlist::processExit, Qt::DirectConnection);
1430  connect(m_proc, &MythSystemLegacy::error,
1431  this, qOverload<uint>&Playlist::processExit, Qt::DirectConnection);
1432  m_procExitVal = GENERIC_EXIT_RUNNING;
1433  m_proc->Run();
1434 
1435  while( m_procExitVal == GENERIC_EXIT_RUNNING )
1436  usleep( 100000 );
1437 
1438  retval = m_procExitVal;
1439 
1440  m_progress->Close();
1441  m_progress->deleteLater();
1442  m_proc->disconnect();
1443  delete m_proc;
1444 
1445  if (retval)
1446  {
1447  LOG(VB_GENERAL, LOG_ERR,
1448  QString("Unable to run cdrecord: returns %1") .arg(retval));
1449  }
1450  }
1451 
1452  QFile::remove(tmprecordlist);
1453  QFile::remove(tmprecordisofs);
1454 
1455  return retval;
1456 }
1457 
1458 int Playlist::CreateCDAudio(void)
1459 {
1460  return -1;
1461 }
1462 #endif
kMSStdErr
@ kMSStdErr
allow access to stderr
Definition: mythsystem.h:40
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:204
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
MusicPlayer::SHUFFLE_INTELLIGENT
@ SHUFFLE_INTELLIGENT
Definition: musicplayer.h:166
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
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:203
Playlist::getSongAt
MusicMetadata * getSongAt(int pos) const
Definition: playlist.cpp:1088
MusicMetadata::Filename
QString Filename(bool find=true)
Definition: musicmetadata.cpp:966
AllStream::isValidID
bool isValidID(MusicMetadata::IdType an_id)
Definition: musicmetadata.cpp:1732
SmartPlaylistEditor::lookupCategoryID
static int lookupCategoryID(const QString &category)
Definition: smartplaylist.cpp:1164
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:34
mythdb.h
getOrderBySQL
QString getOrderBySQL(const QString &orderByFields)
Definition: smartplaylist.cpp:236
MythSystemLegacy
Definition: mythsystemlegacy.h:68
AllMusic::isValidID
bool isValidID(int an_id)
Definition: musicmetadata.cpp:1641
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:577
AllStream::getMetadata
MusicMetadata * getMetadata(MusicMetadata::IdType an_id)
Definition: musicmetadata.cpp:1743
Playlist
Definition: playlist.h:46
MSqlQuery::lastInsertId
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:888
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:198
arg
arg(title).arg(filename).arg(doDelete))
mythmediamonitor.h
MythSystemLegacy::finished
void finished(void)
Playlist::getRawSongAt
MusicMetadata * getRawSongAt(int pos) const
Definition: playlist.cpp:1106
MusicMetadata::ID
IdType ID() const
Definition: musicmetadata.h:217
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
category
QString category
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1422
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MusicMetadata
Definition: musicmetadata.h:81
MusicPlayer::getCurrentPlaylist
Playlist * getCurrentPlaylist(void)
Definition: musicplayer.cpp:1598
Playlist::fillSonglistFromSmartPlaylist
void fillSonglistFromSmartPlaylist(const QString &category, const QString &name, bool removeDuplicates=false, InsertPLOption insertOption=PL_REPLACE, int currentTrackID=0)
Definition: playlist.cpp:894
Playlist::Playlist
Playlist(void)
Definition: playlist.cpp:143
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:855
mythsystemlegacy.h
getCriteriaSQL
QString getCriteriaSQL(const QString &fieldName, const QString &operatorName, QString value1, QString value2)
Definition: smartplaylist.cpp:145
Playlist::m_shuffledSongs
SongList m_shuffledSongs
Definition: playlist.h:138
MusicPlayer::SHUFFLE_OFF
@ SHUFFLE_OFF
Definition: musicplayer.h:164
Playlist::fillSonglistFromQuery
void fillSonglistFromQuery(const QString &whereClause, bool removeDuplicates=false, InsertPLOption insertOption=PL_REPLACE, int currentTrackID=0)
Definition: playlist.cpp:682
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:986
kID0err
const char * kID0err
Definition: playlist.cpp:28
RT_Radio
@ RT_Radio
Definition: musicmetadata.h:62
Playlist::removeDuplicateTracks
static QString removeDuplicateTracks(const QString &orig_songlist, const QString &new_songlist)
Definition: playlist.cpp:1068
MSqlQuery::first
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:793
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:535
compat.h
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:178
MusicData::m_all_streams
AllStream * m_all_streams
Definition: musicdata.h:56
MusicMetadata::getAlbumId
int getAlbumId()
Definition: musicmetadata.cpp:582
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:605
createTempFile
QString createTempFile(QString name_template, bool dir)
Definition: mythmiscutil.cpp:365
MusicMetadata::isDBTrack
bool isDBTrack(void) const
Definition: musicmetadata.h:222
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:23
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:135
PL_INSERTATBEGINNING
@ PL_INSERTATBEGINNING
Definition: playlist.h:25
MusicPlayer::getShuffleMode
ShuffleMode getShuffleMode(void)
Definition: musicplayer.h:184
MusicPlayer::SHUFFLE_ARTIST
@ SHUFFLE_ARTIST
Definition: musicplayer.h:168
Playlist::checkTrack
bool checkTrack(MusicMetadata::IdType trackID) const
Definition: playlist.cpp:37
MusicPlayer::playlistChanged
void playlistChanged(int playlistID)
Definition: musicplayer.cpp:1486
AllMusic::getMetadata
MusicMetadata * getMetadata(int an_id)
Definition: musicmetadata.cpp:1633
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:56
smartplaylist.h
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:929
Playlist::loadPlaylist
void loadPlaylist(const QString &a_name, const QString &a_host)
Definition: playlist.cpp:521
musicdata.h
kMSRunShell
@ kMSRunShell
run process through shell
Definition: mythsystem.h:41
MusicPlayer::SHUFFLE_RANDOM
@ SHUFFLE_RANDOM
Definition: musicplayer.h:165
kMSRunBackground
@ kMSRunBackground
run child in the background
Definition: mythsystem.h:36
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: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:221
Playlist::isActivePlaylist
bool isActivePlaylist(void)
Definition: playlist.h:109
MusicPlayer::activePlaylistChanged
void activePlaylistChanged(int trackID, bool deleted)
Definition: musicplayer.cpp:1423
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:142
MusicMetadata::FileSize
uint64_t FileSize() const
Definition: musicmetadata.h:232
PL_INSERTAFTERCURRENT
@ PL_INSERTAFTERCURRENT
Definition: playlist.h:27
MusicPlayer::SHUFFLE_ALBUM
@ SHUFFLE_ALBUM
Definition: musicplayer.h:167
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
MusicMetadata::LastPlay
QDateTime LastPlay() const
Definition: musicmetadata.h:243
LOC
#define LOC
Definition: playlist.cpp:33
Playlist::m_parent
PlaylistContainer * m_parent
Definition: playlist.h:140
mythcontext.h
categoryID
int categoryID
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:633
Playlist::getStats
void getStats(uint *trackCount, uint *totalLength, uint currentTrack=0, uint *playedLength=nullptr) const
Definition: playlist.cpp:494
MusicMetadata::PlayCount
int PlayCount() const
Definition: musicmetadata.h:247
Playlist::m_songs
SongList m_songs
Definition: playlist.h:137
MediaMonitor::defaultCDWriter
static QString defaultCDWriter()
CDWriterDeviceLocation, user-selected drive, or /dev/cdrom.
Definition: mythmediamonitor.cpp:932
Playlist::savePlaylist
void savePlaylist(const QString &a_name, const QString &a_host)
Definition: playlist.cpp:994
MSqlQuery::numRowsAffected
int numRowsAffected() const
Definition: mythdbcon.h:206
MusicData::m_all_music
AllMusic * m_all_music
Definition: musicdata.h:55
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:855
MusicMetadata::Rating
int Rating() const
Definition: musicmetadata.h:238
Playlist::fillSongsFromSonglist
void fillSongsFromSonglist(const QString &songList)
Definition: playlist.cpp:633
Playlist::describeYourself
void describeYourself(void) const
Definition: playlist.cpp:474
Playlist::removeAllTracks
void removeAllTracks(void)
Definition: playlist.cpp:86
Playlist::m_changed
bool m_changed
Definition: playlist.h:141
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:782
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:35
exitcodes.h
ID_TO_REPO
#define ID_TO_REPO(x)
Definition: musicmetadata.h:71
query
MSqlQuery query(MSqlQuery::InitCon())
kMSStdOut
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:39
MusicMetadata::Length
int Length() const
Definition: musicmetadata.h:205
Playlist::m_playlistid
int m_playlistid
Definition: playlist.h:134
MusicPlayer::ShuffleMode
ShuffleMode
Definition: musicplayer.h:164
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:915
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
MusicMetadata::DiscNumber
int DiscNumber() const
Definition: musicmetadata.h:208
musicplayer.h