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