|
MythTV
0.26-pre
|
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 }
1.7.6.1