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