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