00001 #include <unistd.h>
00002 #include <inttypes.h>
00003 #include <cstdlib>
00004
00005 #include <map>
00006 #include <algorithm>
00007 using namespace std;
00008
00009
00010 #include <QApplication>
00011 #include <QFileInfo>
00012 #include <QObject>
00013
00014
00015 #include "playlist.h"
00016 #include "playlistcontainer.h"
00017 #include "smartplaylist.h"
00018 #include "musicplayer.h"
00019
00020
00021 #include <mythcontext.h>
00022 #include <mythdb.h>
00023 #include <compat.h>
00024 #include <mythmediamonitor.h>
00025 #include <mythmiscutil.h>
00026 #include <mythsystem.h>
00027 #include <exitcodes.h>
00028
00029 const char *kID0err = "Song with ID of 0 in playlist, this shouldn't happen.";
00030
00032
00033
00034 #define LOC QString("Playlist: ")
00035 #define LOC_WARN QString("Playlist, Warning: ")
00036 #define LOC_ERR QString("Playlist, Error: ")
00037
00038 bool Playlist::checkTrack(int a_track_id) const
00039 {
00040 if (m_songMap.contains(a_track_id))
00041 return true;
00042
00043 return false;
00044 }
00045
00046 void Playlist::copyTracks(Playlist *to_ptr, bool update_display) const
00047 {
00048 SongList::const_iterator it = m_songs.begin();
00049 for (; it != m_songs.end(); ++it)
00050 {
00051 to_ptr->addTrack(*it, update_display);
00052 }
00053 }
00054
00055
00057 void Playlist::addTrack(int trackID, bool update_display)
00058 {
00059 Metadata *mdata = gMusicData->all_music->getMetadata(trackID);
00060 if (mdata)
00061 addTrack(mdata, update_display);
00062 else
00063 LOG(VB_GENERAL, LOG_ERR, LOC + "Can't add track, given a bad track ID");
00064 }
00065
00067 void Playlist::addTrack(Metadata *mdata, bool update_display)
00068 {
00069 m_songs.push_back(mdata);
00070 m_shuffledSongs.push_back(mdata);
00071 m_songMap.insert(mdata->ID(), mdata);
00072
00073 m_changed = true;
00074
00075 if (update_display)
00076 gPlayer->activePlaylistChanged(mdata->ID(), false);
00077 }
00078
00079 void Playlist::removeAllTracks(void)
00080 {
00081 m_songs.clear();
00082 m_songMap.clear();
00083 m_shuffledSongs.clear();
00084
00085 m_changed = true;
00086 }
00087
00088 void Playlist::removeTrack(int the_track)
00089 {
00090 QMap<int, Metadata*>::iterator it = m_songMap.find(the_track);
00091 if (it != m_songMap.end())
00092 {
00093 m_songMap.remove(the_track);
00094 m_songs.removeAll(*it);
00095 m_shuffledSongs.removeAll(*it);
00096 }
00097
00098 gPlayer->activePlaylistChanged(the_track, true);
00099 }
00100
00101 void Playlist::moveTrackUpDown(bool flag, int where_its_at)
00102 {
00103 Metadata *the_track = m_shuffledSongs.at(where_its_at);
00104
00105 if (!the_track)
00106 {
00107 LOG(VB_GENERAL, LOG_ERR, LOC +
00108 "A playlist was asked to move a track, but can't find it");
00109 return;
00110 }
00111
00112 moveTrackUpDown(flag, the_track);
00113 }
00114
00115 void Playlist::moveTrackUpDown(bool flag, Metadata* mdata)
00116 {
00117 uint insertion_point = 0;
00118 int where_its_at = m_shuffledSongs.indexOf(mdata);
00119 if (where_its_at < 0)
00120 {
00121 LOG(VB_GENERAL, LOG_ERR, LOC +
00122 "A playlist was asked to move a track, but can'd find it");
00123 return;
00124 }
00125
00126 if (flag)
00127 insertion_point = ((uint)where_its_at) - 1;
00128 else
00129 insertion_point = ((uint)where_its_at) + 1;
00130
00131 m_shuffledSongs.removeAt(where_its_at);
00132 m_shuffledSongs.insert(insertion_point, mdata);
00133
00134 m_changed = true;
00135 }
00136
00137 Playlist::Playlist(void) :
00138 m_playlistid(0),
00139 m_name(QObject::tr("oops")),
00140 m_parent(NULL),
00141 m_changed(false),
00142 m_progress(NULL),
00143 m_proc(NULL),
00144 m_procExitVal(0)
00145 {
00146 }
00147
00148 Playlist::~Playlist()
00149 {
00150 m_songs.clear();
00151 m_songMap.clear();
00152 m_shuffledSongs.clear();
00153 }
00154
00155 void Playlist::shuffleTracks(MusicPlayer::ShuffleMode shuffleMode)
00156 {
00157 m_shuffledSongs.clear();
00158
00159 switch (shuffleMode)
00160 {
00161 case MusicPlayer::SHUFFLE_RANDOM:
00162 {
00163 QMultiMap<int, Metadata*> songMap;
00164
00165 SongList::const_iterator it = m_songs.begin();
00166 for (; it != m_songs.end(); ++it)
00167 {
00168 songMap.insert(rand(), *it);
00169 }
00170
00171 QMultiMap<int, Metadata*>::const_iterator i = songMap.constBegin();
00172 while (i != songMap.constEnd())
00173 {
00174 m_shuffledSongs.append(i.value());
00175 ++i;
00176 }
00177
00178 break;
00179 }
00180
00181 case MusicPlayer::SHUFFLE_INTELLIGENT:
00182 {
00183 int RatingWeight = 2;
00184 int PlayCountWeight = 2;
00185 int LastPlayWeight = 2;
00186 int RandomWeight = 2;
00187 m_parent->FillIntelliWeights(RatingWeight, PlayCountWeight,
00188 LastPlayWeight, RandomWeight);
00189
00190
00191 int playcountMin = 0;
00192 int playcountMax = 0;
00193 double lastplayMin = 0.0;
00194 double lastplayMax = 0.0;
00195
00196 uint idx = 0;
00197 SongList::const_iterator it = m_songs.begin();
00198 for (; it != m_songs.end(); ++it, ++idx)
00199 {
00200 if (!(*it)->isCDTrack())
00201 {
00202 Metadata *mdata = (*it);
00203
00204 if (0 == idx)
00205 {
00206
00207 playcountMin = playcountMax = mdata->PlayCount();
00208 lastplayMin = lastplayMax = mdata->LastPlay().toTime_t();
00209 }
00210 else
00211 {
00212 if (mdata->PlayCount() < playcountMin)
00213 playcountMin = mdata->PlayCount();
00214 else if (mdata->PlayCount() > playcountMax)
00215 playcountMax = mdata->PlayCount();
00216
00217 if (mdata->LastPlay().toTime_t() < lastplayMin)
00218 lastplayMin = mdata->LastPlay().toTime_t();
00219 else if (mdata->LastPlay().toTime_t() > lastplayMax)
00220 lastplayMax = mdata->LastPlay().toTime_t();
00221 }
00222 }
00223 }
00224
00225
00226 std::map<int,double> weights;
00227 std::map<int,int> ratings;
00228 std::map<int,int> ratingCounts;
00229 int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
00230 for (int trackItI = 0; trackItI < m_songs.size(); ++trackItI)
00231 {
00232 Metadata *mdata = m_songs[trackItI];
00233 if (!mdata->isCDTrack())
00234 {
00235 int rating = mdata->Rating();
00236 int playcount = mdata->PlayCount();
00237 double lastplaydbl = mdata->LastPlay().toTime_t();
00238 double ratingValue = (double)(rating) / 10;
00239 double playcountValue, lastplayValue;
00240
00241 if (playcountMax == playcountMin)
00242 playcountValue = 0;
00243 else
00244 playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);
00245
00246 if (lastplayMax == lastplayMin)
00247 lastplayValue = 0;
00248 else
00249 lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);
00250
00251 double weight = (RatingWeight * ratingValue +
00252 PlayCountWeight * playcountValue +
00253 LastPlayWeight * lastplayValue) / TotalWeight;
00254 weights[mdata->ID()] = weight;
00255 ratings[mdata->ID()] = rating;
00256 ++ratingCounts[rating];
00257 }
00258 }
00259
00260
00261
00262 double totalWeights = 0;
00263 std::map<int,double>::iterator weightsIt, weightsEnd = weights.end();
00264 for (weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
00265 {
00266 weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
00267 totalWeights += weightsIt->second;
00268 }
00269
00270
00271 std::map<int,uint32_t> order;
00272 uint32_t orderCpt = 1;
00273 std::map<int,double>::iterator weightIt, weightEnd;
00274 while (!weights.empty())
00275 {
00276 double hit = totalWeights * (double)rand() / (double)RAND_MAX;
00277 weightEnd = weights.end();
00278 weightIt = weights.begin();
00279 double pos = 0;
00280 while (weightIt != weightEnd)
00281 {
00282 pos += weightIt->second;
00283 if (pos >= hit)
00284 break;
00285 ++weightIt;
00286 }
00287
00288
00289
00290
00291
00292
00293
00294
00295 if (weightIt == weightEnd)
00296 break;
00297
00298 order[weightIt->first] = orderCpt;
00299 totalWeights -= weightIt->second;
00300 weights.erase(weightIt);
00301 ++orderCpt;
00302 }
00303
00304
00305 QMultiMap<int, Metadata*> songMap;
00306 it = m_songs.begin();
00307 for (; it != m_songs.end(); ++it)
00308 songMap.insert(order[(*it)->ID()], *it);
00309
00310
00311 QMultiMap<int, Metadata*>::const_iterator i = songMap.constBegin();
00312 while (i != songMap.constEnd())
00313 {
00314 m_shuffledSongs.append(i.value());
00315 ++i;
00316 }
00317
00318 break;
00319 }
00320
00321 case MusicPlayer::SHUFFLE_ALBUM:
00322 {
00323
00324
00325 typedef map<QString, uint32_t> AlbumMap;
00326 AlbumMap album_map;
00327 AlbumMap::iterator Ialbum;
00328 QString album;
00329
00330
00331
00332 SongList::const_iterator it = m_songs.begin();
00333 for (; it != m_songs.end(); ++it)
00334 {
00335 Metadata *mdata = (*it);
00336 album = mdata->Album() + " ~ " + QString("%1").arg(mdata->getAlbumId());
00337 if ((Ialbum = album_map.find(album)) == album_map.end())
00338 album_map.insert(AlbumMap::value_type(album, 0));
00339 }
00340
00341
00342 uint32_t album_count = 1;
00343 for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
00344 {
00345 Ialbum->second = album_count;
00346 album_count++;
00347 }
00348
00349
00350 QMultiMap<int, Metadata*> songMap;
00351 it = m_songs.begin();
00352 for (; it != m_songs.end(); ++it)
00353 {
00354 uint32_t album_order;
00355 Metadata *mdata = (*it);
00356 if (mdata)
00357 {
00358 album = album = mdata->Album() + " ~ " + QString("%1").arg(mdata->getAlbumId());;
00359 if ((Ialbum = album_map.find(album)) == album_map.end())
00360 {
00361
00362
00363
00364
00365 album_order = 1;
00366 }
00367 else
00368 {
00369 album_order = Ialbum->second * 1000;
00370 }
00371 album_order += mdata->Track();
00372
00373 songMap.insert(album_order, *it);
00374 }
00375 }
00376
00377
00378 QMultiMap<int, Metadata*>::const_iterator i = songMap.constBegin();
00379 while (i != songMap.constEnd())
00380 {
00381 m_shuffledSongs.append(i.value());
00382 ++i;
00383 }
00384
00385 break;
00386 }
00387
00388 case MusicPlayer::SHUFFLE_ARTIST:
00389 {
00390
00391
00392 typedef map<QString, uint32_t> ArtistMap;
00393 ArtistMap artist_map;
00394 ArtistMap::iterator Iartist;
00395 QString artist;
00396
00397
00398
00399 SongList::const_iterator it = m_songs.begin();
00400 for (; it != m_songs.end(); ++it)
00401 {
00402 Metadata *mdata = (*it);
00403 artist = mdata->Artist() + " ~ " + mdata->Title();
00404 if ((Iartist = artist_map.find(artist)) == artist_map.end())
00405 artist_map.insert(ArtistMap::value_type(artist,0));
00406 }
00407
00408
00409 uint32_t artist_count = 1;
00410 for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
00411 {
00412 Iartist->second = artist_count;
00413 artist_count++;
00414 }
00415
00416
00417 QMultiMap<int, Metadata*> songMap;
00418 it = m_songs.begin();
00419 for (; it != m_songs.end(); ++it)
00420 {
00421 uint32_t artist_order;
00422 Metadata *mdata = (*it);
00423 if (mdata)
00424 {
00425 artist = mdata->Artist() + " ~ " + mdata->Title();
00426 if ((Iartist = artist_map.find(artist)) == artist_map.end())
00427 {
00428
00429
00430
00431
00432 artist_order = 1;
00433 }
00434 else
00435 {
00436 artist_order = Iartist->second * 1000;
00437 }
00438 artist_order += mdata->Track();
00439
00440 songMap.insert(artist_order, *it);
00441 }
00442 }
00443
00444
00445 QMultiMap<int, Metadata*>::const_iterator i = songMap.constBegin();
00446 while (i != songMap.constEnd())
00447 {
00448 m_shuffledSongs.append(i.value());
00449 ++i;
00450 }
00451
00452 break;
00453 }
00454
00455 default:
00456 {
00457
00458 SongList::const_iterator it = m_songs.begin();
00459 for (; it != m_songs.end(); ++it)
00460 {
00461 m_shuffledSongs.append(*it);
00462 }
00463
00464 break;
00465 }
00466 }
00467 }
00468
00469 void Playlist::describeYourself(void) const
00470 {
00471
00472 #if 0
00473 LOG(VB_GENERAL, LOG_DEBUG,
00474 QString("Playlist with name of \"%1\"").arg(name));
00475 LOG(VB_GENERAL, LOG_DEBUG,
00476 QString(" playlistid is %1").arg(laylistid));
00477 LOG(VB_GENERAL, LOG_DEBUG,
00478 QString(" songlist(raw) is \"%1\"").arg(raw_songlist));
00479 LOG(VB_GENERAL, LOG_DEBUG, " songlist list is ");
00480 #endif
00481
00482 QString msg;
00483 SongList::const_iterator it = m_songs.begin();
00484 for (; it != m_songs.end(); ++it)
00485 msg += (*it)->ID() + ",";
00486
00487 LOG(VB_GENERAL, LOG_INFO, LOC + msg);
00488 }
00489
00490 void Playlist::getStats(uint *trackCount, uint *totalLength, uint currenttrack, uint *playedLength) const
00491 {
00492 uint64_t total = 0, played = 0;
00493
00494 *trackCount = m_shuffledSongs.size();
00495
00496 if ((int)currenttrack >= m_shuffledSongs.size())
00497 currenttrack = 0;
00498
00499 uint track = 0;
00500 SongList::const_iterator it = m_shuffledSongs.begin();
00501 for (; it != m_shuffledSongs.end(); ++it, ++track)
00502 {
00503 Metadata *mdata = (*it);
00504 if (mdata)
00505 {
00506 total += mdata->Length();
00507 if (track < currenttrack)
00508 played += mdata->Length();
00509 }
00510 }
00511
00512 if (playedLength)
00513 *playedLength = played / 1000;
00514
00515 *totalLength = total / 1000;
00516 }
00517
00518 void Playlist::loadPlaylist(QString a_name, QString a_host)
00519 {
00520 QString thequery;
00521 QString rawSonglist;
00522
00523 if (a_host.isEmpty())
00524 {
00525 LOG(VB_GENERAL, LOG_ERR, LOC +
00526 "loadPlaylist() - We need a valid hostname");
00527 return;
00528 }
00529
00530 MSqlQuery query(MSqlQuery::InitCon());
00531
00532 if (m_name == "default_playlist_storage" || m_name == "backup_playlist_storage"
00533 || m_name == "stream_playlist")
00534 {
00535 query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
00536 "FROM music_playlists "
00537 "WHERE playlist_name = :NAME"
00538 " AND hostname = :HOST;");
00539 }
00540 else
00541 {
00542
00543
00544 query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
00545 "FROM music_playlists "
00546 "WHERE playlist_name = :NAME"
00547 " AND (hostname = '' OR hostname = :HOST);");
00548 }
00549 query.bindValue(":NAME", a_name);
00550 query.bindValue(":HOST", a_host);
00551
00552 if (query.exec() && query.size() > 0)
00553 {
00554 while (query.next())
00555 {
00556 m_playlistid = query.value(0).toInt();
00557 m_name = query.value(1).toString();
00558 rawSonglist = query.value(2).toString();
00559 }
00560 if (m_name == "default_playlist_storage")
00561 m_name = QObject::tr("Default Playlist");
00562 if (m_name == "backup_playlist_storage")
00563 m_name = "and they should **REALLY** never see this";
00564 }
00565 else
00566 {
00567
00568 m_playlistid = 0;
00569
00570 rawSonglist.clear();
00571 savePlaylist(a_name, a_host);
00572 m_changed = true;
00573 }
00574
00575 fillSongsFromSonglist(rawSonglist);
00576
00577 shuffleTracks(MusicPlayer::SHUFFLE_OFF);
00578 }
00579
00580 void Playlist::loadPlaylistByID(int id, QString a_host)
00581 {
00582 QString rawSonglist;
00583 MSqlQuery query(MSqlQuery::InitCon());
00584 query.prepare("SELECT playlist_id, playlist_name, playlist_songs "
00585 "FROM music_playlists "
00586 "WHERE playlist_id = :ID"
00587 " AND (hostname = '' OR hostname = :HOST);");
00588 query.bindValue(":ID", id);
00589 query.bindValue(":HOST", a_host);
00590
00591 if (!query.exec())
00592 MythDB::DBError("Playlist::loadPlaylistByID", query);
00593
00594 while (query.next())
00595 {
00596 m_playlistid = query.value(0).toInt();
00597 m_name = query.value(1).toString();
00598 rawSonglist = query.value(2).toString();
00599 }
00600
00601 if (m_name == "default_playlist_storage")
00602 m_name = QObject::tr("Default Playlist");
00603 if (m_name == "backup_playlist_storage")
00604 m_name = "and they should **REALLY** never see this";
00605
00606 fillSongsFromSonglist(rawSonglist);
00607 }
00608
00609 void Playlist::fillSongsFromSonglist(QString songList)
00610 {
00611 Metadata::IdType id;
00612
00613 QStringList list = songList.split(",", QString::SkipEmptyParts);
00614 QStringList::iterator it = list.begin();
00615 for (; it != list.end(); ++it)
00616 {
00617 id = (*it).toUInt();
00618
00619 if (gMusicData->all_music->isValidID(id))
00620 {
00621 Metadata *mdata = gMusicData->all_music->getMetadata(id);
00622 m_songs.push_back(mdata);
00623 m_songMap.insert(id, mdata);
00624 }
00625 else
00626 {
00627 m_changed = true;
00628
00629 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Got a bad track %1").arg(id));
00630 }
00631 }
00632
00633 if (this == gPlayer->getPlaylist())
00634 shuffleTracks(gPlayer->getShuffleMode());
00635 else
00636 shuffleTracks(MusicPlayer::SHUFFLE_OFF);
00637
00638 gPlayer->activePlaylistChanged(-1, false);
00639 }
00640
00641
00642 void Playlist::fillSonglistFromQuery(QString whereClause,
00643 bool removeDuplicates,
00644 InsertPLOption insertOption,
00645 int currentTrackID)
00646 {
00647 QString orig_songlist = toRawSonglist();
00648 QString new_songlist;
00649
00650 removeAllTracks();
00651
00652 MSqlQuery query(MSqlQuery::InitCon());
00653
00654 QString theQuery;
00655
00656 theQuery = "SELECT song_id FROM music_songs "
00657 "LEFT JOIN music_directories ON"
00658 " music_songs.directory_id=music_directories.directory_id "
00659 "LEFT JOIN music_artists ON"
00660 " music_songs.artist_id=music_artists.artist_id "
00661 "LEFT JOIN music_albums ON"
00662 " music_songs.album_id=music_albums.album_id "
00663 "LEFT JOIN music_genres ON"
00664 " music_songs.genre_id=music_genres.genre_id "
00665 "LEFT JOIN music_artists AS music_comp_artists ON "
00666 "music_albums.artist_id=music_comp_artists.artist_id ";
00667 if (whereClause.length() > 0)
00668 theQuery += whereClause;
00669
00670 if (!query.exec(theQuery))
00671 {
00672 MythDB::DBError("Load songlist from query", query);
00673 new_songlist.clear();
00674 fillSongsFromSonglist(new_songlist);
00675 return;
00676 }
00677
00678 while (query.next())
00679 {
00680 new_songlist += "," + query.value(0).toString();
00681 }
00682 new_songlist.remove(0, 1);
00683
00684 if (removeDuplicates && insertOption != PL_REPLACE)
00685 new_songlist = removeDuplicateTracks(orig_songlist, new_songlist);
00686
00687 switch (insertOption)
00688 {
00689 case PL_REPLACE:
00690 break;
00691
00692 case PL_INSERTATBEGINNING:
00693 new_songlist = new_songlist + "," + orig_songlist;
00694 break;
00695
00696 case PL_INSERTATEND:
00697 new_songlist = orig_songlist + "," + new_songlist;
00698 break;
00699
00700 case PL_INSERTAFTERCURRENT:
00701 {
00702 QStringList list = orig_songlist.split(",", QString::SkipEmptyParts);
00703 QStringList::iterator it = list.begin();
00704 bool bFound = false;
00705 QString tempList;
00706 for (; it != list.end(); ++it)
00707 {
00708 int an_int = (*it).toInt();
00709 tempList += "," + QString(*it);
00710 if (!bFound && an_int == currentTrackID)
00711 {
00712 bFound = true;
00713 tempList += "," + new_songlist;
00714 }
00715 }
00716
00717 if (!bFound)
00718 tempList = orig_songlist + "," + new_songlist;
00719
00720 new_songlist = tempList.remove(0, 1);
00721
00722 break;
00723 }
00724
00725 default:
00726 new_songlist = orig_songlist;
00727 }
00728
00729 fillSongsFromSonglist(new_songlist);
00730 }
00731
00732
00733 void Playlist::fillSonglistFromList(const QList<int> &songList,
00734 bool removeDuplicates,
00735 InsertPLOption insertOption,
00736 int currentTrackID)
00737 {
00738 QString orig_songlist = toRawSonglist();
00739 QString new_songlist;
00740
00741 removeAllTracks();
00742
00743 for (int x = 0; x < songList.count(); x++)
00744 {
00745 new_songlist += "," + QString::number(songList.at(x));
00746 }
00747 new_songlist.remove(0, 1);
00748
00749 if (removeDuplicates && insertOption != PL_REPLACE)
00750 new_songlist = removeDuplicateTracks(orig_songlist, new_songlist);
00751
00752 switch (insertOption)
00753 {
00754 case PL_REPLACE:
00755 break;
00756
00757 case PL_INSERTATBEGINNING:
00758 new_songlist = new_songlist + "," + orig_songlist;
00759 break;
00760
00761 case PL_INSERTATEND:
00762 new_songlist = orig_songlist + "," + new_songlist;
00763 break;
00764
00765 case PL_INSERTAFTERCURRENT:
00766 {
00767 QStringList list = orig_songlist.split(",", QString::SkipEmptyParts);
00768 QStringList::iterator it = list.begin();
00769 bool bFound = false;
00770 QString tempList;
00771 for (; it != list.end(); ++it)
00772 {
00773 int an_int = QString(*it).toInt();
00774 tempList += "," + QString(*it);
00775 if (!bFound && an_int == currentTrackID)
00776 {
00777 bFound = true;
00778 tempList += "," + new_songlist;
00779 }
00780 }
00781
00782 if (!bFound)
00783 tempList = orig_songlist + "," + new_songlist;
00784
00785 new_songlist = tempList.remove(0, 1);
00786
00787 break;
00788 }
00789
00790 default:
00791 new_songlist = orig_songlist;
00792 }
00793
00794 fillSongsFromSonglist(new_songlist);
00795 }
00796
00797 void Playlist::fillSongsFromCD()
00798 {
00799
00800 }
00801
00802 QString Playlist::toRawSonglist(bool shuffled)
00803 {
00804 QString rawList;
00805
00806 if (shuffled)
00807 {
00808 SongList::const_iterator it = m_shuffledSongs.begin();
00809 for (; it != m_shuffledSongs.end(); ++it)
00810 {
00811 rawList += QString(",%1").arg((*it)->ID());
00812 }
00813 }
00814 else
00815 {
00816 SongList::const_iterator it = m_songs.begin();
00817 for (; it != m_songs.end(); ++it)
00818 {
00819 rawList += QString(",%1").arg((*it)->ID());
00820 }
00821 }
00822
00823 if (!rawList.isEmpty())
00824 rawList = rawList.remove(0, 1);
00825
00826 return rawList;
00827 }
00828
00829 void Playlist::fillSonglistFromSmartPlaylist(QString category, QString name,
00830 bool removeDuplicates,
00831 InsertPLOption insertOption,
00832 int currentTrackID)
00833 {
00834 MSqlQuery query(MSqlQuery::InitCon());
00835
00836
00837 int categoryID = SmartPlaylistEditor::lookupCategoryID(category);
00838 if (categoryID == -1)
00839 {
00840 LOG(VB_GENERAL, LOG_WARNING, LOC +
00841 QString("Cannot find Smartplaylist Category: %1") .arg(category));
00842 return;
00843 }
00844
00845
00846 int ID;
00847 QString matchType;
00848 QString orderBy;
00849 int limitTo;
00850
00851 query.prepare("SELECT smartplaylistid, matchtype, orderby, limitto "
00852 "FROM music_smartplaylists "
00853 "WHERE categoryid = :CATEGORYID AND name = :NAME;");
00854 query.bindValue(":NAME", name);
00855 query.bindValue(":CATEGORYID", categoryID);
00856
00857 if (query.exec())
00858 {
00859 if (query.isActive() && query.size() > 0)
00860 {
00861 query.first();
00862 ID = query.value(0).toInt();
00863 matchType = (query.value(1).toString() == "All") ? " AND " : " OR ";
00864 orderBy = query.value(2).toString();
00865 limitTo = query.value(3).toInt();
00866 }
00867 else
00868 {
00869 LOG(VB_GENERAL, LOG_WARNING, LOC +
00870 QString("Cannot find smartplaylist: %1").arg(name));
00871 return;
00872 }
00873 }
00874 else
00875 {
00876 MythDB::DBError("Find SmartPlaylist", query);
00877 return;
00878 }
00879
00880
00881 QString whereClause = "WHERE ";
00882
00883 query.prepare("SELECT field, operator, value1, value2 "
00884 "FROM music_smartplaylist_items "
00885 "WHERE smartplaylistid = :ID;");
00886 query.bindValue(":ID", ID);
00887 if (query.exec())
00888 {
00889 bool bFirst = true;
00890 while (query.next())
00891 {
00892 QString fieldName = query.value(0).toString();
00893 QString operatorName = query.value(1).toString();
00894 QString value1 = query.value(2).toString();
00895 QString value2 = query.value(3).toString();
00896 if (!bFirst)
00897 whereClause += matchType + getCriteriaSQL(fieldName,
00898 operatorName, value1, value2);
00899 else
00900 {
00901 bFirst = false;
00902 whereClause += " " + getCriteriaSQL(fieldName, operatorName,
00903 value1, value2);
00904 }
00905 }
00906 }
00907
00908
00909 whereClause += getOrderBySQL(orderBy);
00910
00911
00912 if (limitTo > 0)
00913 whereClause += " LIMIT " + QString::number(limitTo);
00914
00915
00916
00917 fillSonglistFromQuery(whereClause, removeDuplicates,
00918 insertOption, currentTrackID);
00919 }
00920
00921 void Playlist::savePlaylist(QString a_name, QString a_host)
00922 {
00923 m_name = a_name.simplified();
00924 if (m_name.length() < 1)
00925 {
00926 LOG(VB_GENERAL, LOG_WARNING, LOC + "Not saving unnamed playlist");
00927 return;
00928 }
00929
00930 if (a_host.length() < 1)
00931 {
00932 LOG(VB_GENERAL, LOG_WARNING, LOC +
00933 "Not saving playlist without a host name");
00934 return;
00935 }
00936 if (m_name.length() < 1)
00937 return;
00938
00939 QString rawSonglist = toRawSonglist(true);
00940
00941 MSqlQuery query(MSqlQuery::InitCon());
00942 uint songcount = 0, playtime = 0;
00943
00944 getStats(&songcount, &playtime);
00945
00946 bool save_host = ("default_playlist_storage" == a_name
00947 || "backup_playlist_storage" == a_name);
00948 if (m_playlistid > 0)
00949 {
00950 QString str_query = "UPDATE music_playlists SET "
00951 "playlist_songs = :LIST, "
00952 "playlist_name = :NAME, "
00953 "songcount = :SONGCOUNT, "
00954 "length = :PLAYTIME";
00955 if (save_host)
00956 str_query += ", hostname = :HOSTNAME";
00957 str_query += " WHERE playlist_id = :ID ;";
00958
00959 query.prepare(str_query);
00960 query.bindValue(":ID", m_playlistid);
00961 }
00962 else
00963 {
00964 QString str_query = "INSERT INTO music_playlists"
00965 " (playlist_name, playlist_songs,"
00966 " songcount, length";
00967 if (save_host)
00968 str_query += ", hostname";
00969 str_query += ") VALUES(:NAME, :LIST, :SONGCOUNT, :PLAYTIME";
00970 if (save_host)
00971 str_query += ", :HOSTNAME";
00972 str_query += ");";
00973
00974 query.prepare(str_query);
00975 }
00976 query.bindValue(":LIST", rawSonglist);
00977 query.bindValue(":NAME", a_name);
00978 query.bindValue(":SONGCOUNT", songcount);
00979 query.bindValue(":PLAYTIME", qlonglong(playtime));
00980 if (save_host)
00981 query.bindValue(":HOSTNAME", a_host);
00982
00983 if (!query.exec() || (m_playlistid < 1 && query.numRowsAffected() < 1))
00984 {
00985 MythDB::DBError("Problem saving playlist", query);
00986 }
00987
00988 if (m_playlistid < 1)
00989 m_playlistid = query.lastInsertId().toInt();
00990 }
00991
00992 QString Playlist::removeDuplicateTracks(const QString &orig_songlist, const QString &new_songlist)
00993 {
00994 QStringList curList = orig_songlist.split(",", QString::SkipEmptyParts);
00995 QStringList newList = new_songlist.split(",", QString::SkipEmptyParts);
00996 QStringList::iterator it = newList.begin();
00997 QString songlist;
00998
00999 for (; it != newList.end(); ++it)
01000 {
01001 if (curList.indexOf(*it) == -1)
01002 songlist += "," + *it;
01003 }
01004 songlist.remove(0, 1);
01005 return songlist;
01006 }
01007
01008 Metadata* Playlist::getSongAt(int pos)
01009 {
01010 if (pos >= 0 && pos < m_shuffledSongs.size())
01011 return m_shuffledSongs.at(pos);
01012
01013 return NULL;
01014 }
01015
01016
01017
01018
01019
01020 void Playlist::computeSize(double &size_in_MB, double &size_in_sec)
01021 {
01022
01023
01024
01025
01026 size_in_MB = 0.0;
01027 size_in_sec = 0.0;
01028
01029 SongList::const_iterator it = m_songs.begin();
01030 for (; it != m_songs.end(); ++it)
01031 {
01032 if ((*it)->isCDTrack())
01033 continue;
01034
01035
01036 Metadata *tmpdata = (*it);
01037 if (tmpdata)
01038 {
01039 if (tmpdata->Length() > 0)
01040 size_in_sec += tmpdata->Length();
01041 else
01042 LOG(VB_GENERAL, LOG_ERR, "Computing track lengths. "
01043 "One track <=0");
01044
01045
01046 QFileInfo finfo(tmpdata->Filename());
01047
01048 size_in_MB += finfo.size() / 1000000;
01049 }
01050 }
01051 }
01052
01053 void Playlist::cdrecordData(int fd)
01054 {
01055 if (!m_progress || !m_proc)
01056 return;
01057
01058 QByteArray buf;
01059 if (fd == 1)
01060 {
01061 buf = m_proc->ReadAll();
01062
01063
01064
01065
01066 QString data(buf);
01067 QStringList list = data.split(QRegExp("[\\r\\n]"),
01068 QString::SkipEmptyParts);
01069
01070 for (int i = 0; i < list.size(); i++)
01071 {
01072 QString line = list.at(i);
01073
01074 if (line.mid(15, 2) == "of")
01075 {
01076 int mbdone = line.mid(10, 5).trimmed().toInt();
01077 int mbtotal = line.mid(17, 5).trimmed().toInt();
01078
01079 if (mbtotal > 0)
01080 {
01081 m_progress->setProgress((mbdone * 100) / mbtotal);
01082 }
01083 }
01084 }
01085 }
01086 else
01087 {
01088 buf = m_proc->ReadAllErr();
01089
01090 QTextStream text(buf);
01091
01092 while (!text.atEnd())
01093 {
01094 QString err = text.readLine();
01095 if (err.contains("Drive needs to reload the media") ||
01096 err.contains("Input/output error.") ||
01097 err.contains("No disk / Wrong disk!"))
01098 {
01099 LOG(VB_GENERAL, LOG_ERR, err);
01100 m_proc->Term();
01101 }
01102 }
01103 }
01104 }
01105
01106 void Playlist::mkisofsData(int fd)
01107 {
01108 if (!m_progress || !m_proc)
01109 return;
01110
01111 QByteArray buf;
01112 if (fd == 1)
01113 buf = m_proc->ReadAll();
01114 else
01115 {
01116 buf = m_proc->ReadAllErr();
01117
01118 QTextStream text(buf);
01119
01120 while (!text.atEnd())
01121 {
01122 QString line = text.readLine();
01123 if (line[6] == '%')
01124 {
01125 line = line.mid(0, 3);
01126 m_progress->setProgress(line.trimmed().toInt());
01127 }
01128 }
01129 }
01130 }
01131
01132 void Playlist::processExit(uint retval)
01133 {
01134 m_procExitVal = retval;
01135 }
01136
01137 int Playlist::CreateCDMP3(void)
01138 {
01139
01140 if (!gCoreContext->GetNumSetting("CDWriterEnabled"))
01141 {
01142 LOG(VB_GENERAL, LOG_ERR, "CD Writer is not enabled.");
01143 return 1;
01144 }
01145
01146 QString scsidev = MediaMonitor::defaultCDWriter();
01147 if (scsidev.isEmpty())
01148 {
01149 LOG(VB_GENERAL, LOG_ERR, "No CD Writer device defined.");
01150 return 1;
01151 }
01152
01153 int disksize = gCoreContext->GetNumSetting("CDDiskSize", 2);
01154 QString writespeed = gCoreContext->GetSetting("CDWriteSpeed", "2");
01155 bool MP3_dir_flag = gCoreContext->GetNumSetting("CDCreateDir", 1);
01156
01157 double size_in_MB = 0.0;
01158
01159 QStringList reclist;
01160
01161 SongList::const_iterator it = m_songs.begin();
01162 for (; it != m_songs.end(); ++it)
01163 {
01164 if ((*it)->isCDTrack())
01165 continue;
01166
01167
01168 Metadata *tmpdata = (*it);
01169 if (tmpdata)
01170 {
01171
01172 QFileInfo testit(tmpdata->Filename());
01173 if (!testit.exists())
01174 continue;
01175 size_in_MB += testit.size() / 1000000.0;
01176 QString outline;
01177 if (MP3_dir_flag)
01178 {
01179 if (tmpdata->Artist().length() > 0)
01180 outline += tmpdata->Artist() + "/";
01181 if (tmpdata->Album().length() > 0)
01182 outline += tmpdata->Album() + "/";
01183 }
01184
01185 outline += "=";
01186 outline += tmpdata->Filename();
01187
01188 reclist += outline;
01189 }
01190 }
01191
01192 int max_size;
01193 if (disksize == 0)
01194 max_size = 650;
01195 else
01196 max_size = 700;
01197
01198 if (size_in_MB >= max_size)
01199 {
01200 LOG(VB_GENERAL, LOG_ERR, "MP3 CD creation aborted -- cd size too big.");
01201 return 1;
01202 }
01203
01204
01205 QString tmptemplate("/tmp/mythmusicXXXXXX");
01206
01207 QString tmprecordlist = createTempFile(tmptemplate);
01208 if (tmprecordlist == tmptemplate)
01209 {
01210 LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
01211 return 1;
01212 }
01213
01214 QString tmprecordisofs = createTempFile(tmptemplate);
01215 if (tmprecordisofs == tmptemplate)
01216 {
01217 LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
01218 return 1;
01219 }
01220
01221 QFile reclistfile(tmprecordlist);
01222
01223 if (!reclistfile.open(QIODevice::WriteOnly))
01224 {
01225 LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
01226 return 1;
01227 }
01228
01229 QTextStream recstream(&reclistfile);
01230
01231 QStringList::Iterator iter;
01232
01233 for (iter = reclist.begin(); iter != reclist.end(); ++iter)
01234 {
01235 recstream << *iter << "\n";
01236 }
01237
01238 reclistfile.close();
01239
01240 m_progress = new MythProgressDialog(QObject::tr("Creating CD File System"),
01241 100);
01242 m_progress->setProgress(1);
01243
01244 QStringList args;
01245 QString command;
01246
01247 command = "mkisofs";
01248 args << "-graft-points";
01249 args << "-path-list";
01250 args << tmprecordlist;
01251 args << "-o";
01252 args << tmprecordisofs;
01253 args << "-J";
01254 args << "-R";
01255
01256 uint flags = kMSRunShell | kMSStdErr | kMSBuffered |
01257 kMSDontDisableDrawing | kMSDontBlockInputDevs |
01258 kMSRunBackground;
01259
01260 m_proc = new MythSystem(command, args, flags);
01261
01262 connect(m_proc, SIGNAL(readDataReady(int)), this, SLOT(mkisofsData(int)),
01263 Qt::DirectConnection);
01264 connect(m_proc, SIGNAL(finished()), this, SLOT(processExit()),
01265 Qt::DirectConnection);
01266 connect(m_proc, SIGNAL(error(uint)), this, SLOT(processExit(uint)),
01267 Qt::DirectConnection);
01268
01269 m_procExitVal = GENERIC_EXIT_RUNNING;
01270 m_proc->Run();
01271
01272 while( m_procExitVal == GENERIC_EXIT_RUNNING )
01273 usleep( 100000 );
01274
01275 uint retval = m_procExitVal;
01276
01277 m_progress->Close();
01278 m_progress->deleteLater();
01279 m_proc->disconnect();
01280 delete m_proc;
01281
01282 if (retval)
01283 {
01284 LOG(VB_GENERAL, LOG_ERR, QString("Unable to run mkisofs: returns %1")
01285 .arg(retval));
01286 }
01287 else
01288 {
01289 m_progress = new MythProgressDialog(QObject::tr("Burning CD"), 100);
01290 m_progress->setProgress(2);
01291
01292 command = "cdrecord";
01293 args = QStringList();
01294 args << "-v";
01295
01296 args << QString("dev=%1").arg(scsidev);
01297
01298 if (writespeed.toInt() > 0)
01299 {
01300 args << "-speed=";
01301 args << writespeed;
01302 }
01303
01304 args << "-data";
01305 args << tmprecordisofs;
01306
01307 flags = kMSRunShell | kMSStdErr | kMSStdOut | kMSBuffered |
01308 kMSDontDisableDrawing | kMSDontBlockInputDevs |
01309 kMSRunBackground;
01310
01311 m_proc = new MythSystem(command, args, flags);
01312 connect(m_proc, SIGNAL(readDataReady(int)),
01313 this, SLOT(cdrecordData(int)), Qt::DirectConnection);
01314 connect(m_proc, SIGNAL(finished()),
01315 this, SLOT(processExit()), Qt::DirectConnection);
01316 connect(m_proc, SIGNAL(error(uint)),
01317 this, SLOT(processExit(uint)), Qt::DirectConnection);
01318 m_procExitVal = GENERIC_EXIT_RUNNING;
01319 m_proc->Run();
01320
01321 while( m_procExitVal == GENERIC_EXIT_RUNNING )
01322 usleep( 100000 );
01323
01324 retval = m_procExitVal;
01325
01326 m_progress->Close();
01327 m_progress->deleteLater();
01328 m_proc->disconnect();
01329 delete m_proc;
01330
01331 if (retval)
01332 {
01333 LOG(VB_GENERAL, LOG_ERR,
01334 QString("Unable to run cdrecord: returns %1") .arg(retval));
01335 }
01336 }
01337
01338 QFile::remove(tmprecordlist);
01339 QFile::remove(tmprecordisofs);
01340
01341 return retval;
01342 }
01343
01344 int Playlist::CreateCDAudio(void)
01345 {
01346 return -1;
01347 }