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