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