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