MythTV  0.26-pre
playlist.cpp
Go to the documentation of this file.
00001 #include <unistd.h>
00002 #include <inttypes.h>
00003 #include <cstdlib>
00004 
00005 #include <map>
00006 #include <algorithm>
00007 using namespace std;
00008 
00009 // qt
00010 #include <QApplication>
00011 #include <QFileInfo>
00012 #include <QObject>
00013 
00014 // mythmusic
00015 #include "playlist.h"
00016 #include "playlistcontainer.h"
00017 #include "smartplaylist.h"
00018 #include "musicplayer.h"
00019 
00020 // mythtv
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 // Playlist
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             // compute max/min playcount,lastplay for this playlist
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                         // first song
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             // next we compute all the weights
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             // then we divide weights with the number of songs in the rating class
00261             // (more songs in a class ==> lower weight, without affecting other classes)
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             // then we get a random order, balanced with relative weights of remaining songs
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                 // FIXME If we don't exit here then we'll segfault, but it
00289                 //       probably won't give us the desired randomisation
00290                 //       either - There seems to be a flaw in this code, we
00291                 //       erase items from the map but never adjust
00292                 //       'totalWeights' so at a point 'pos' will never be
00293                 //       greater or equal to 'hit' and we will always hit the
00294                 //       end of the map
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             // create a map of tracks sorted by the computed order
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             // copy the shuffled tracks to the shuffled song list
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             // "intellegent/album" order
00324 
00325             typedef map<QString, uint32_t> AlbumMap;
00326             AlbumMap                       album_map;
00327             AlbumMap::iterator             Ialbum;
00328             QString                        album;
00329 
00330             // pre-fill the album-map with the album name.
00331             // This allows us to do album mode in album order
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             // populate the sort id into the album map
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             // create a map of tracks sorted by the computed order
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                         // we didn't find this album in the map,
00362                         // yet we pre-loaded them all. we are broken,
00363                         // but we just set the track order to 1, since there
00364                         // is no real point in reporting an error
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             // copy the shuffled tracks to the shuffled song list
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             // "intellegent/album" order
00391 
00392             typedef map<QString, uint32_t> ArtistMap;
00393             ArtistMap                      artist_map;
00394             ArtistMap::iterator            Iartist;
00395             QString                        artist;
00396 
00397             // pre-fill the album-map with the album name.
00398             // This allows us to do artist mode in artist order
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             // populate the sort id into the artist map
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             // create a map of tracks sorted by the computed order
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                         // we didn't find this artist in the map,
00429                         // yet we pre-loaded them all. we are broken,
00430                         // but we just set the track order to 1, since there
00431                         // is no real point in reporting an error
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             // copy the shuffled tracks to the shuffled song list
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             // copy the raw song list to the shuffled track list
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     //  This is for debugging
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         // Technically this is never called as this function
00543         // is only used to load the default/backup playlists.
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         // Asked me to load a playlist I can't find so let's create a new one :)
00568         m_playlistid = 0; // Be safe just in case we call load over the top
00569                           // of an existing playlist
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         // check this is a valid track ID
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 //FIXME:: this needs checking
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 // songList is a list of trackIDs to add
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     //FIXME: is this still needed?
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     // find the correct categoryid
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     // find smartplaylist
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     // get smartplaylist items
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     // add order by clause
00909     whereClause += getOrderBySQL(orderBy);
00910 
00911     // add limit
00912     if (limitTo > 0)
00913         whereClause +=  " LIMIT " + QString::number(limitTo);
00914 
00915     //m_name = name; // Set Playlist name to match smart playlist name
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 // Here begins CD Writing things. ComputeSize, CreateCDMP3 & CreateCDAudio
01018 // FIXME non of this is currently used
01019 
01020 void Playlist::computeSize(double &size_in_MB, double &size_in_sec)
01021 {
01022     //double child_MB;
01023     //double child_sec;
01024 
01025     // Clear return values
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         // Normal track
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             // Check tmpdata->Filename
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         // I would just use the QTextStream::readLine(), but wodim uses \r
01064         // to update the same line, so I'm splitting it on \r or \n
01065         // Track 01:    6 of  147 MB written (fifo 100%) [buf  99%]  16.3x.
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     // Check & get global settings
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         // Normal track
01168         Metadata *tmpdata = (*it);
01169         if (tmpdata)
01170         {
01171             // check filename..
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     // probably should tie stdout of mkisofs to stdin of cdrecord sometime
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         //args << "-dummy";
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends