void CMusicInfoLoader::OnLoaderFinish() { // cleanup last loaded songs from database m_songsMap.Clear(); // cleanup cache loaded from HD m_mapFileItems->Clear(); if (!m_bStop) { // check for art VECSONGS songs; songs.reserve(m_pVecItems->Size()); for (int i = 0; i < m_pVecItems->Size(); ++i) { CFileItemPtr pItem = m_pVecItems->Get(i); if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsNFO() || pItem->IsInternetStream()) continue; if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->Loaded()) { CSong song(*pItem->GetMusicInfoTag()); song.strThumb = pItem->GetArt("thumb"); song.idSong = i; // for the lookup below songs.push_back(song); } } VECALBUMS albums; CMusicInfoScanner::CategoriseAlbums(songs, albums); CMusicInfoScanner::FindArtForAlbums(albums, m_pVecItems->GetPath()); for (VECALBUMS::iterator i = albums.begin(); i != albums.end(); ++i) { string albumArt = i->art["thumb"]; for (VECSONGS::iterator j = i->songs.begin(); j != i->songs.end(); ++j) { if (!j->strThumb.empty()) m_pVecItems->Get(j->idSong)->SetArt("thumb", j->strThumb); else m_pVecItems->Get(j->idSong)->SetArt("thumb", albumArt); } } } // Save loaded items to HD if (!m_strCacheFileName.IsEmpty()) SaveCache(m_strCacheFileName, *m_pVecItems); else if (!m_bStop && (m_databaseHits > 1 || m_tagReads > 0)) m_pVecItems->Save(); m_musicDatabase.Close(); }
////////////////////////////////////////////////////////////////////////////////// // Function:GetNextItem() // Returns the track information from the next item in the cuelist ////////////////////////////////////////////////////////////////////////////////// void CCueDocument::GetSongs(VECSONGS &songs) { for (int i = 0; i < m_iTotalTracks; i++) { CSong song; if ((m_Track[i].strArtist.length() == 0) && (m_strArtist.length() > 0)) song.strArtist = m_strArtist; else song.strArtist = m_Track[i].strArtist; song.strAlbumArtist = m_strArtist; song.strAlbum = m_strAlbum; song.strGenre = m_strGenre; song.iYear = m_iYear; song.iTrack = m_Track[i].iTrackNumber; if (m_Track[i].strTitle.length() == 0) // No track information for this track! song.strTitle.Format("Track %2d", i + 1); else song.strTitle = m_Track[i].strTitle; song.strFileName = m_Track[i].strFile; song.iStartOffset = m_Track[i].iStartTime; song.iEndOffset = m_Track[i].iEndTime; if (song.iEndOffset) song.iDuration = (song.iEndOffset - song.iStartOffset + 37) / 75; else song.iDuration = 0; // TODO: replayGain goes here songs.push_back(song); } }
////////////////////////////////////////////////////////////////////////////////// // Function:GetNextItem() // Returns the track information from the next item in the cuelist ////////////////////////////////////////////////////////////////////////////////// void CCueDocument::GetSongs(VECSONGS &songs) { for (int i = 0; i < m_iTotalTracks; i++) { CSong song; if ((m_Track[i].strArtist.length() == 0) && (m_strArtist.length() > 0)) song.artist = StringUtils::Split(m_strArtist, g_advancedSettings.m_musicItemSeparator); else song.artist = StringUtils::Split(m_Track[i].strArtist, g_advancedSettings.m_musicItemSeparator); song.albumArtist = StringUtils::Split(m_strArtist, g_advancedSettings.m_musicItemSeparator); song.strAlbum = m_strAlbum; song.genre = StringUtils::Split(m_strGenre, g_advancedSettings.m_musicItemSeparator); song.iYear = m_iYear; song.iTrack = m_Track[i].iTrackNumber; if ( m_iDiscNumber > 0 ) song.iTrack |= (m_iDiscNumber << 16); // see CMusicInfoTag::GetDiscNumber() if (m_Track[i].strTitle.length() == 0) // No track information for this track! song.strTitle = StringUtils::Format("Track %2d", i + 1); else song.strTitle = m_Track[i].strTitle; song.strFileName = m_Track[i].strFile; song.iStartOffset = m_Track[i].iStartTime; song.iEndOffset = m_Track[i].iEndTime; if (song.iEndOffset) song.iDuration = (song.iEndOffset - song.iStartOffset + 37) / 75; else song.iDuration = 0; // TODO: replayGain goes here songs.push_back(song); } }
////////////////////////////////////////////////////////////////////////////////// // Function:GetSongs() // Store track information into songs list. ////////////////////////////////////////////////////////////////////////////////// void CCueDocument::GetSongs(VECSONGS &songs) { for (size_t i = 0; i < m_tracks.size(); ++i) { CSong song; GetSong(i, song); songs.push_back(song); } }
void CGUIDialogMusicInfo::SetSongs(const VECSONGS &songs) const { m_albumSongs->Clear(); for (unsigned int i = 0; i < songs.size(); i++) { const CSong& song = songs[i]; CFileItemPtr item(new CFileItem(song)); m_albumSongs->Add(item); } }
void CGUIWindowMusicInfo::SetSongs(const VECSONGS &songs) { m_albumSongs->Clear(); for (unsigned int i = 0; i < songs.size(); i++) { const CSong& song = songs[i]; CFileItem *item = new CFileItem(song); m_albumSongs->Add(item); } }
void CGUIDialogMusicInfo::SetSongs(const VECSONGS &songs) const { m_albumSongs->Clear(); CMusicThumbLoader loader; for (unsigned int i = 0; i < songs.size(); i++) { const CSong& song = songs[i]; CFileItemPtr item(new CFileItem(song)); // Load the song art and related artist(s) (that may be different from album artist) art loader.LoadItem(item.get()); m_albumSongs->Add(item); } }
bool CMusicInfoScanner::HasSingleAlbum(const VECSONGS &songs, CStdString &album, CStdString &artist) { // check how many unique albums are in this path, and if there's only one, and it has a thumb // then cache the thumb as the folder thumb for (unsigned int i = 0; i < songs.size(); i++) { const CSong &song = songs[i]; // don't bother with empty album tags - they're unlikely to have an embedded thumb anyway, // and if one is not tagged correctly, how can we know whether there is only one album? if (song.strAlbum.IsEmpty()) return false; CStdString albumArtist = song.strAlbumArtist.IsEmpty() ? song.strArtist : song.strAlbumArtist; if (!album.IsEmpty() && (album != song.strAlbum || artist != albumArtist)) return false; // have more than one album album = song.strAlbum; artist = albumArtist; } return !album.IsEmpty(); }
bool CMusicInfoScanner::HasSingleAlbum(const VECSONGS &songs, CStdString &album, CStdString &artist) { // check how many unique albums are in this path, and if there's only one, and it has a thumb // then cache the thumb as the folder thumb for (unsigned int i = 0; i < songs.size(); i++) { const CSong &song = songs[i]; // don't bother with empty album tags - they're treated as singles, and there's no way to determine // whether more than one track in the folder is supposed to mean they belong to an "album" if (song.strAlbum.IsEmpty()) return false; CStdString albumArtist = song.strAlbumArtist.IsEmpty() ? song.strArtist : song.strAlbumArtist; if (!album.IsEmpty() && (album != song.strAlbum || artist != albumArtist)) return false; // have more than one album album = song.strAlbum; artist = albumArtist; } return !album.IsEmpty(); }
void CGUIWindowMusicBase::UpdateThumb(const CAlbum &album, const CStdString &path) { // check user permissions bool saveDb = album.idAlbum != -1; bool saveDirThumb = true; if (!g_settings.GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser) { saveDb = false; saveDirThumb = false; } CStdString albumThumb = m_musicdatabase.GetArtForItem(album.idAlbum, "album", "thumb"); // Update the thumb in the music database (songs + albums) CStdString albumPath(path); if (saveDb && CFile::Exists(albumThumb)) m_musicdatabase.SaveAlbumThumb(album.idAlbum, albumThumb); // Update currently playing song if it's from the same album. This is necessary as when the album // first gets it's cover, the info manager's item doesn't have the updated information (so will be // sending a blank thumb to the skin.) if (g_application.IsPlayingAudio()) { const CMusicInfoTag* tag=g_infoManager.GetCurrentSongTag(); if (tag) { // really, this may not be enough as it is to reliably update this item. eg think of various artists albums // that aren't tagged as such (and aren't yet scanned). But we probably can't do anything better than this // in that case if (album.strAlbum == tag->GetAlbum() && (album.artist == tag->GetAlbumArtist() || album.artist == tag->GetArtist())) { g_infoManager.SetCurrentAlbumThumb(albumThumb); } } } // Save this thumb as the directory thumb if it's the only album in the folder (files view nicety) // We do this by grabbing all the songs in the folder, and checking to see whether they come // from the same album. if (saveDirThumb && CFile::Exists(albumThumb) && !albumPath.IsEmpty() && !URIUtils::IsCDDA(albumPath)) { CFileItemList items; GetDirectory(albumPath, items); OnRetrieveMusicInfo(items); VECSONGS songs; for (int i = 0; i < items.Size(); i++) { CFileItemPtr item = items[i]; if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->Loaded()) { CSong song(*item->GetMusicInfoTag()); songs.push_back(song); } } VECALBUMS albums; CMusicInfoScanner::CategoriseAlbums(songs, albums); if (albums.size() == 1) { // set as folder thumb as well CThumbLoader::SetCachedImage(items, "thumb", albumPath); } } // update the file listing - we have to update the whole lot, as it's likely that // more than just our thumbnaias changed // TODO: Ideally this would only be done when needed - at the moment we appear to be // doing this for every lookup, possibly twice (see ShowAlbumInfo) m_vecItems->RemoveDiscCache(GetID()); Update(m_vecItems->GetPath()); // Do we have to autoswitch to the thumb control? m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems)); UpdateButtons(); }
void CMusicInfoScanner::CategoriseAlbums(VECSONGS &songsToCheck, VECALBUMS &albums) { /* Step 1: categorise on the album name */ map<string, vector<CSong *> > albumNames; for (VECSONGS::iterator i = songsToCheck.begin(); i != songsToCheck.end(); ++i) albumNames[i->strAlbum].push_back(&(*i)); /* Step 2: Split into unique albums based on album name and album artist In the case where the album artist is unknown, we use the primary artist (i.e. first artist from each song). */ albums.clear(); for (map<string, vector<CSong *> >::iterator i = albumNames.begin(); i != albumNames.end(); ++i) { // sort the songs by tracknumber to identify duplicate track numbers vector<CSong *> &songs = i->second; sort(songs.begin(), songs.end(), SortSongsByTrack); // map the songs to their primary artists bool tracksOverlap = false; bool hasAlbumArtist = false; map<string, vector<CSong *> > artists; for (vector<CSong *>::iterator j = songs.begin(); j != songs.end(); ++j) { CSong *song = *j; // test for song overlap if (j != songs.begin() && song->iTrack == (*(j-1))->iTrack) tracksOverlap = true; // get primary artist string primary; if (!song->albumArtist.empty()) { primary = song->albumArtist[0]; hasAlbumArtist = true; } else if (!song->artist.empty()) primary = song->artist[0]; // add to the artist map artists[primary].push_back(song); } /* We have a compilation if 1. album name is non-empty AND 2. no tracks overlap AND 3a. a unique primary artist is specified as "various" or "various artists" OR 3b. we have at least two primary artists and no album artist specified. */ bool compilation = !i->first.empty() && !tracksOverlap; // 1+2 if (artists.size() == 1) { string artist = artists.begin()->first; StringUtils::ToLower(artist); if (!StringUtils::EqualsNoCase(artist, "various") && !StringUtils::EqualsNoCase(artist, "various artists")) // 3a compilation = false; } else if (hasAlbumArtist) // 3b compilation = false; if (compilation) { CLog::Log(LOGDEBUG, "Album '%s' is a compilation as there's no overlapping tracks and %s", i->first.c_str(), hasAlbumArtist ? "the album artist is 'Various'" : "there is more than one unique artist"); artists.clear(); std::string various = g_localizeStrings.Get(340); // Various Artists vector<string> va; va.push_back(various); for (vector<CSong *>::iterator j = songs.begin(); j != songs.end(); ++j) (*j)->albumArtist = va; artists.insert(make_pair(various, songs)); } /* Step 3: Find the common albumartist for each song and assign albumartist to those tracks that don't have it set. */ for (map<string, vector<CSong *> >::iterator j = artists.begin(); j != artists.end(); ++j) { // find the common artist for these songs vector<CSong *> &artistSongs = j->second; vector<string> common = artistSongs.front()->albumArtist.empty() ? artistSongs.front()->artist : artistSongs.front()->albumArtist; for (vector<CSong *>::iterator k = artistSongs.begin() + 1; k != artistSongs.end(); ++k) { unsigned int match = 0; vector<string> &compare = (*k)->albumArtist.empty() ? (*k)->artist : (*k)->albumArtist; for (; match < common.size() && match < compare.size(); match++) { if (compare[match] != common[match]) break; } common.erase(common.begin() + match, common.end()); } /* Step 4: Assign the album artist for each song that doesn't have it set and add to the album vector */ CAlbum album; album.strAlbum = i->first; album.artist = common; album.bCompilation = compilation; for (vector<CSong *>::iterator k = artistSongs.begin(); k != artistSongs.end(); ++k) { if ((*k)->albumArtist.empty()) (*k)->albumArtist = common; album.songs.push_back(*(*k)); // TODO: in future we may wish to union up the genres, for now we assume they're the same if (album.genre.empty()) album.genre = (*k)->genre; // in addition, we may want to use year as discriminating for albums if (album.iYear == 0) album.iYear = (*k)->iYear; } albums.push_back(album); } } }
int CMusicInfoScanner::RetrieveMusicInfo(CFileItemList& items, const CStdString& strDirectory) { CSongMap songsMap; // get all information for all files in current directory from database, and remove them if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap)) m_needsCleanup = true; VECSONGS songsToAdd; CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps; // for every file found, but skip folder for (int i = 0; i < items.Size(); ++i) { CFileItemPtr pItem = items[i]; CStdString strExtension; URIUtils::GetExtension(pItem->GetPath(), strExtension); if (m_bStop) return 0; // Discard all excluded files defined by m_musicExcludeRegExps if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps)) continue; // dont try reading id3tags for folders, playlists or shoutcast streams if (!pItem->m_bIsFolder && !pItem->IsPlayList() && !pItem->IsPicture() && !pItem->IsLyrics() ) { m_currentItem++; // CLog::Log(LOGDEBUG, "%s - Reading tag for: %s", __FUNCTION__, pItem->GetPath().c_str()); // grab info from the song CSong *dbSong = songsMap.Find(pItem->GetPath()); CMusicInfoTag& tag = *pItem->GetMusicInfoTag(); if (!tag.Loaded() ) { // read the tag from a file auto_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(pItem->GetPath())); if (NULL != pLoader.get()) pLoader->Load(pItem->GetPath(), tag); } // if we have the itemcount, update our // dialog with the progress we made if (m_handle && m_itemCount>0) m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100); if (tag.Loaded()) { CSong song(tag); // ensure our song has a valid filename or else it will assert in AddSong() if (song.strFileName.IsEmpty()) { // copy filename from path in case UPnP or other tag loaders didn't specify one (FIXME?) song.strFileName = pItem->GetPath(); // if we still don't have a valid filename, skip the song if (song.strFileName.IsEmpty()) { // this shouldn't ideally happen! CLog::Log(LOGERROR, "Skipping song since it doesn't seem to have a filename"); continue; } } song.iStartOffset = pItem->m_lStartOffset; song.iEndOffset = pItem->m_lEndOffset; song.strThumb = pItem->GetUserMusicThumb(true); if (dbSong) { // keep the db-only fields intact on rescan... song.iTimesPlayed = dbSong->iTimesPlayed; song.lastPlayed = dbSong->lastPlayed; song.iKaraokeNumber = dbSong->iKaraokeNumber; if (song.rating == '0') song.rating = dbSong->rating; if (song.strThumb.empty()) song.strThumb = dbSong->strThumb; } songsToAdd.push_back(song); // CLog::Log(LOGDEBUG, "%s - Tag loaded for: %s", __FUNCTION__, pItem->GetPath().c_str()); } else CLog::Log(LOGDEBUG, "%s - No tag found for: %s", __FUNCTION__, pItem->GetPath().c_str()); } } VECALBUMS albums; CategoriseAlbums(songsToAdd, albums); FindArtForAlbums(albums, items.GetPath()); // finally, add these to the database m_musicDatabase.BeginTransaction(); int numAdded = 0; set<int> albumsToScan; set<int> artistsToScan; for (VECALBUMS::iterator i = albums.begin(); i != albums.end(); ++i) { vector<int> songIDs; int idAlbum = m_musicDatabase.AddAlbum(*i, songIDs); numAdded += i->songs.size(); if (m_bStop) { m_musicDatabase.RollbackTransaction(); return numAdded; } // Build the artist & album sets albumsToScan.insert(idAlbum); for (vector<int>::iterator j = songIDs.begin(); j != songIDs.end(); ++j) { vector<int> songArtists; m_musicDatabase.GetArtistsBySong(*j, false, songArtists); artistsToScan.insert(songArtists.begin(), songArtists.end()); } std::vector<int> albumArtists; m_musicDatabase.GetArtistsByAlbum(idAlbum, false, albumArtists); artistsToScan.insert(albumArtists.begin(), albumArtists.end()); } m_musicDatabase.CommitTransaction(); // Download info & artwork bool bCanceled; for (set<int>::iterator it = artistsToScan.begin(); it != artistsToScan.end(); ++it) { bCanceled = false; if (find(m_artistsScanned.begin(),m_artistsScanned.end(), *it) == m_artistsScanned.end()) { CStdString strArtist = m_musicDatabase.GetArtistById(*it); m_artistsScanned.push_back(*it); if (!m_bStop && (m_flags & SCAN_ONLINE)) { CStdString strPath; strPath.Format("musicdb://2/%u/", *it); if (!DownloadArtistInfo(strPath, strArtist, bCanceled)) // assume we want to retry m_artistsScanned.pop_back(); } else { map<string, string> artwork = GetArtistArtwork(*it); m_musicDatabase.SetArtForItem(*it, "artist", artwork); } } } if (m_flags & SCAN_ONLINE) { for (set<int>::iterator it = albumsToScan.begin(); it != albumsToScan.end(); ++it) { if (m_bStop) return songsToAdd.size(); CStdString strPath; strPath.Format("musicdb://3/%u/",*it); CAlbum album; m_musicDatabase.GetAlbumInfo(*it, album, NULL); bCanceled = false; if (find(m_albumsScanned.begin(), m_albumsScanned.end(), *it) == m_albumsScanned.end()) { CMusicAlbumInfo albumInfo; if (DownloadAlbumInfo(strPath, StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator), album.strAlbum, bCanceled, albumInfo)) m_albumsScanned.push_back(*it); } } } if (m_handle) m_handle->SetTitle(g_localizeStrings.Get(505)); return songsToAdd.size(); }
int CMusicInfoScanner::RetrieveMusicInfo(CFileItemList& items, const CStdString& strDirectory) { CSongMap songsMap; // get all information for all files in current directory from database, and remove them if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap, false)) m_needsCleanup = true; VECSONGS songsToAdd; CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps; // for every file found, but skip folder for (int i = 0; i < items.Size(); ++i) { CFileItemPtr pItem = items[i]; CStdString strExtension; URIUtils::GetExtension(pItem->GetPath(), strExtension); if (m_bStop) return 0; // Discard all excluded files defined by m_musicExcludeRegExps if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps)) continue; if (pItem.get()->IsPFC()) { m_currentItem++; // if we have the itemcount, notify our // observer with the progress we made if (m_pObserver && m_itemCount>0) m_pObserver->OnSetProgress(m_currentItem, m_itemCount); int got = RetrievePFCSongs(pItem, songsToAdd); if (got > 0) CLog::Log(LOGDEBUG, "%s: Got songs from PFC: '%s'", __FUNCTION__, pItem.get()->GetPath().c_str()); } } // Laureon: From here! // CheckForVariousArtists(songsToAdd); // if (!items.HasThumbnail()) // UpdateFolderThumb(songsToAdd, items.GetPath()); // // // finally, add these to the database // set<CStdString> artistsToScan; // set< pair<CStdString, CStdString> > albumsToScan; // m_musicDatabase.BeginTransaction(); // for (unsigned int i = 0; i < songsToAdd.size(); ++i) // { // if (m_bStop) // { // m_musicDatabase.RollbackTransaction(); // return i; // } // CSong &song = songsToAdd[i]; // m_musicDatabase.AddSong(song, true); // // artistsToScan.insert(song.strArtist); // albumsToScan.insert(make_pair(song.strAlbum, song.strArtist)); // } // m_musicDatabase.CommitTransaction(); // // bool bCanceled; // for (set<CStdString>::iterator i = artistsToScan.begin(); i != artistsToScan.end(); ++i) // { // bCanceled = false; // long iArtist = m_musicDatabase.GetArtistByName(*i); // if (find(m_artistsScanned.begin(),m_artistsScanned.end(),iArtist) == m_artistsScanned.end()) // { // m_artistsScanned.push_back(iArtist); // if (!m_bStop)// && g_guiSettings.GetBool("musiclibrary.downloadinfo")) // { // CStdString strPath; // strPath.Format("musicdb://2/%u/",iArtist); // if (!DownloadArtistInfo(strPath,*i, bCanceled)) // assume we want to retry // m_artistsScanned.pop_back(); // } // else // GetArtistArtwork(iArtist, *i); // } // } // // for (set< pair<CStdString, CStdString> >::iterator i = albumsToScan.begin(); i != albumsToScan.end(); ++i) // { // if (m_bStop) // return songsToAdd.size(); // // long iAlbum = m_musicDatabase.GetAlbumByName(i->first, i->second); // CStdString strPath; // strPath.Format("musicdb://3/%u/",iAlbum); // // bCanceled = false; // if (find(m_albumsScanned.begin(), m_albumsScanned.end(), iAlbum) == m_albumsScanned.end()) // { // CMusicAlbumInfo albumInfo; // if (DownloadAlbumInfo(strPath, i->second, i->first, bCanceled, albumInfo)) // m_albumsScanned.push_back(iAlbum); // } // } if (m_pObserver) m_pObserver->OnStateChanged(READING_MUSIC_INFO); return songsToAdd.size(); }
void CGUIWindowMusicBase::UpdateThumb(const CAlbum &album, const CStdString &path) { // check user permissions bool saveDb = album.idAlbum != -1; bool saveDirThumb = true; if (!g_settings.m_vecProfiles[g_settings.m_iLastLoadedProfileIndex].canWriteDatabases() && !g_passwordManager.bMasterUser) { saveDb = false; saveDirThumb = false; } CStdString albumThumb(CUtil::GetCachedAlbumThumb(album.strAlbum, album.strArtist)); // Update the thumb in the music database (songs + albums) CStdString albumPath(path); if (saveDb && CFile::Exists(albumThumb)) m_musicdatabase.SaveAlbumThumb(album.idAlbum, albumThumb); // Update currently playing song if it's from the same album. This is necessary as when the album // first gets it's cover, the info manager's item doesn't have the updated information (so will be // sending a blank thumb to the skin.) if (g_application.IsPlayingAudio()) { CStdString strSongFolder; const CMusicInfoTag* tag=g_infoManager.GetCurrentSongTag(); if (tag) { // really, this may not be enough as it is to reliably update this item. eg think of various artists albums // that aren't tagged as such (and aren't yet scanned). But we probably can't do anything better than this // in that case if (album.strAlbum == tag->GetAlbum() && (album.strArtist == tag->GetAlbumArtist() || album.strArtist == tag->GetArtist())) { g_infoManager.SetCurrentAlbumThumb(albumThumb); } } } // Save this thumb as the directory thumb if it's the only album in the folder (files view nicety) // We do this by grabbing all the songs in the folder, and checking to see whether they come // from the same album. if (saveDirThumb && CFile::Exists(albumThumb) && !albumPath.IsEmpty() && !CUtil::IsCDDA(albumPath)) { CFileItemList items; GetDirectory(albumPath, items); OnRetrieveMusicInfo(items); VECSONGS songs; for (int i = 0; i < items.Size(); i++) { CFileItem *item = items[i]; if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->Loaded()) { CSong song(*item->GetMusicInfoTag()); songs.push_back(song); } } CMusicInfoScanner::CheckForVariousArtists(songs); CStdString album, artist; if (CMusicInfoScanner::HasSingleAlbum(songs, album, artist)) { // can cache as the folder thumb CStdString folderThumb(CUtil::GetCachedMusicThumb(albumPath)); ::CopyFile(albumThumb, folderThumb, false); } } // update the file listing int iSelectedItem = m_viewControl.GetSelectedItem(); if (iSelectedItem >= 0 && m_vecItems->Get(iSelectedItem)) { CFileItem* pSelectedItem=m_vecItems->Get(iSelectedItem); if (pSelectedItem->m_bIsFolder) { // refresh only the icon of // the current folder pSelectedItem->FreeMemory(); if (!pSelectedItem->HasThumbnail()) pSelectedItem->SetThumbnailImage(albumThumb); pSelectedItem->FillInDefaultIcon(); } else { // Refresh all items m_vecItems->RemoveDiscCache(); Update(m_vecItems->m_strPath); } // Do we have to autoswitch to the thumb control? m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems)); UpdateButtons(); } }
void CMusicInfoScanner::CheckForVariousArtists(VECSONGS &songsToCheck) { // first, find all the album names for these songs map<CStdString, vector<CSong *> > albumsToAdd; map<CStdString, vector<CSong *> >::iterator it; for (unsigned int i = 0; i < songsToCheck.size(); ++i) { CSong &song = songsToCheck[i]; if (!song.strAlbumArtist.IsEmpty()) // albumartist specified, so assume the user knows what they're doing continue; it = albumsToAdd.find(song.strAlbum); if (it == albumsToAdd.end()) { vector<CSong *> songs; songs.push_back(&song); albumsToAdd.insert(make_pair(song.strAlbum, songs)); } else it->second.push_back(&song); } // as an additional check for those that have multiple albums in the same folder, ignore albums // that have overlapping track numbers for (it = albumsToAdd.begin(); it != albumsToAdd.end();) { vector<CSong *> &songs = it->second; bool overlappingTrackNumbers(false); if (songs.size() > 1) { sort(songs.begin(), songs.end(), SortSongsByTrack); for (unsigned int i = 0; i < songs.size() - 1; i++) { CSong *song = songs[i]; CSong *song2 = songs[i+1]; if (song->iTrack == song2->iTrack) { overlappingTrackNumbers = true; break; } } } if (overlappingTrackNumbers) { // remove this album albumsToAdd.erase(it++); } else it++; } // ok, now run through these albums, and check whether they qualify as a "various artist" album // an album is considered a various artists album if the songs' primary artist differs // it qualifies as a "single artist with featured artists" album if the primary artist is the same, but secondary artists differ for (it = albumsToAdd.begin(); it != albumsToAdd.end(); it++) { const CStdString &album = it->first; vector<CSong *> &songs = it->second; if (!album.IsEmpty() && songs.size() > 1) { bool variousArtists(false); bool singleArtistWithFeaturedArtists(false); for (unsigned int i = 0; i < songs.size() - 1; i++) { CSong *song1 = songs[i]; CSong *song2 = songs[i+1]; CStdStringArray vecArtists1, vecArtists2; StringUtils::SplitString(song1->strArtist, g_advancedSettings.m_musicItemSeparator, vecArtists1); StringUtils::SplitString(song2->strArtist, g_advancedSettings.m_musicItemSeparator, vecArtists2); CStdString primaryArtist1 = vecArtists1[0]; primaryArtist1.TrimRight(); CStdString primaryArtist2 = vecArtists2[0]; primaryArtist2.TrimRight(); if (primaryArtist1 != primaryArtist2) { // primary artist differs -> a various artists album variousArtists = true; break; } else if (song1->strArtist != song2->strArtist) { // have more than one artist, the first artist(s) agree, but the full artist name doesn't // so this is likely a single-artist compilation (ie with other artists featured on some tracks) album singleArtistWithFeaturedArtists = true; } } if (variousArtists) { // have a various artists album - update all songs to be the various artist for (unsigned int i = 0; i < songs.size(); i++) { CSong *song = songs[i]; song->strAlbumArtist = g_localizeStrings.Get(340); // Various Artists } } else if (singleArtistWithFeaturedArtists) { // have an album where all the first artists agree - make this the album artist CStdStringArray vecArtists; StringUtils::SplitString(songs[0]->strArtist, g_advancedSettings.m_musicItemSeparator, vecArtists); CStdString albumArtist(vecArtists[0]); for (unsigned int i = 0; i < songs.size(); i++) { CSong *song = songs[i]; song->strAlbumArtist = albumArtist; // first artist of all tracks } } } } }
int CMusicInfoScanner::RetrieveMusicInfo(CFileItemList& items, const CStdString& strDirectory) { CSongMap songsMap; // get all information for all files in current directory from database, and remove them if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap)) m_needsCleanup = true; VECSONGS songsToAdd; CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps; // for every file found, but skip folder for (int i = 0; i < items.Size(); ++i) { CFileItemPtr pItem = items[i]; CStdString strExtension; URIUtils::GetExtension(pItem->GetPath(), strExtension); if (m_bStop) return 0; // Discard all excluded files defined by m_musicExcludeRegExps if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps)) continue; // dont try reading id3tags for folders, playlists or shoutcast streams if (!pItem->m_bIsFolder && !pItem->IsPlayList() && !pItem->IsPicture() && !pItem->IsLyrics() ) { m_currentItem++; // CLog::Log(LOGDEBUG, "%s - Reading tag for: %s", __FUNCTION__, pItem->GetPath().c_str()); // grab info from the song CSong *dbSong = songsMap.Find(pItem->GetPath()); CMusicInfoTag& tag = *pItem->GetMusicInfoTag(); if (!tag.Loaded() ) { // read the tag from a file auto_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(pItem->GetPath())); if (NULL != pLoader.get()) pLoader->Load(pItem->GetPath(), tag); } // if we have the itemcount, notify our // observer with the progress we made if (m_pObserver && m_itemCount>0) m_pObserver->OnSetProgress(m_currentItem, m_itemCount); if (tag.Loaded()) { CSong song(tag); // ensure our song has a valid filename or else it will assert in AddSong() if (song.strFileName.IsEmpty()) { // copy filename from path in case UPnP or other tag loaders didn't specify one (FIXME?) song.strFileName = pItem->GetPath(); // if we still don't have a valid filename, skip the song if (song.strFileName.IsEmpty()) { // this shouldn't ideally happen! CLog::Log(LOGERROR, "Skipping song since it doesn't seem to have a filename"); continue; } } song.iStartOffset = pItem->m_lStartOffset; song.iEndOffset = pItem->m_lEndOffset; if (dbSong) { // keep the db-only fields intact on rescan... song.iTimesPlayed = dbSong->iTimesPlayed; song.lastPlayed = dbSong->lastPlayed; song.iKaraokeNumber = dbSong->iKaraokeNumber; if (song.rating == '0') song.rating = dbSong->rating; } pItem->SetMusicThumb(); song.strThumb = pItem->GetThumbnailImage(); songsToAdd.push_back(song); // CLog::Log(LOGDEBUG, "%s - Tag loaded for: %s", __FUNCTION__, pItem->GetPath().c_str()); } else CLog::Log(LOGDEBUG, "%s - No tag found for: %s", __FUNCTION__, pItem->GetPath().c_str()); } } CheckForVariousArtists(songsToAdd); if (!items.HasThumbnail()) UpdateFolderThumb(songsToAdd, items.GetPath()); // finally, add these to the database set<CStdString> artistsToScan; set< pair<CStdString, CStdString> > albumsToScan; m_musicDatabase.BeginTransaction(); for (unsigned int i = 0; i < songsToAdd.size(); ++i) { if (m_bStop) { m_musicDatabase.RollbackTransaction(); return i; } CSong &song = songsToAdd[i]; m_musicDatabase.AddSong(song, false); artistsToScan.insert(song.strArtist); albumsToScan.insert(make_pair(song.strAlbum, song.strArtist)); } m_musicDatabase.CommitTransaction(); bool bCanceled; for (set<CStdString>::iterator i = artistsToScan.begin(); i != artistsToScan.end(); ++i) { bCanceled = false; long iArtist = m_musicDatabase.GetArtistByName(*i); if (find(m_artistsScanned.begin(),m_artistsScanned.end(),iArtist) == m_artistsScanned.end()) { m_artistsScanned.push_back(iArtist); if (!m_bStop && g_guiSettings.GetBool("musiclibrary.downloadinfo")) { CStdString strPath; strPath.Format("musicdb://2/%u/",iArtist); if (!DownloadArtistInfo(strPath,*i, bCanceled)) // assume we want to retry m_artistsScanned.pop_back(); } else GetArtistArtwork(iArtist, *i); } } if (g_guiSettings.GetBool("musiclibrary.downloadinfo")) { for (set< pair<CStdString, CStdString> >::iterator i = albumsToScan.begin(); i != albumsToScan.end(); ++i) { if (m_bStop) return songsToAdd.size(); long iAlbum = m_musicDatabase.GetAlbumByName(i->first, i->second); CStdString strPath; strPath.Format("musicdb://3/%u/",iAlbum); bCanceled = false; if (find(m_albumsScanned.begin(), m_albumsScanned.end(), iAlbum) == m_albumsScanned.end()) { CMusicAlbumInfo albumInfo; if (DownloadAlbumInfo(strPath, i->second, i->first, bCanceled, albumInfo)) m_albumsScanned.push_back(iAlbum); } } } if (m_pObserver) m_pObserver->OnStateChanged(READING_MUSIC_INFO); return songsToAdd.size(); }
int CMusicInfoScanner::RetrieveMusicInfo(CFileItemList& items, const CStdString& strDirectory) { CSongMap songsMap; // get all information for all files in current directory from database, and remove them if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap)) m_needsCleanup = true; VECSONGS songsToAdd; // for every file found, but skip folder for (int i = 0; i < items.Size(); ++i) { CFileItem* pItem = items[i]; CStdString strExtension; CUtil::GetExtension(pItem->m_strPath, strExtension); if (m_bStop) return 0; // dont try reading id3tags for folders, playlists or shoutcast streams if (!pItem->m_bIsFolder && !pItem->IsPlayList() && !pItem->IsShoutCast() && !pItem->IsPicture()) { m_currentItem++; // CLog::Log(LOGDEBUG, "%s - Reading tag for: %s", __FUNCTION__, pItem->m_strPath.c_str()); // grab info from the song CSong *dbSong = songsMap.Find(pItem->m_strPath); CMusicInfoTag& tag = *pItem->GetMusicInfoTag(); if (!tag.Loaded() ) { // read the tag from a file auto_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(pItem->m_strPath)); if (NULL != pLoader.get()) pLoader->Load(pItem->m_strPath, tag); } // if we have the itemcount, notify our // observer with the progress we made if (m_pObserver && m_itemCount>0) m_pObserver->OnSetProgress(m_currentItem, m_itemCount); if (tag.Loaded()) { CSong song(tag); song.iStartOffset = pItem->m_lStartOffset; song.iEndOffset = pItem->m_lEndOffset; if (dbSong) { // keep the db-only fields intact on rescan... song.iTimesPlayed = dbSong->iTimesPlayed; song.lastPlayed = dbSong->lastPlayed; if (song.rating == '0') song.rating = dbSong->rating; } pItem->SetMusicThumb(); song.strThumb = pItem->GetThumbnailImage(); songsToAdd.push_back(song); // CLog::Log(LOGDEBUG, "%s - Tag loaded for: %s", __FUNCTION__, spItem->m_strPath.c_str()); } else CLog::Log(LOGDEBUG, "%s - No tag found for: %s", __FUNCTION__, pItem->m_strPath.c_str()); } } CheckForVariousArtists(songsToAdd); if (!items.HasThumbnail()) UpdateFolderThumb(songsToAdd, items.m_strPath); // finally, add these to the database for (unsigned int i = 0; i < songsToAdd.size(); ++i) { if (m_bStop) return i; CSong &song = songsToAdd[i]; m_musicDatabase.AddSong(song, false); if (!m_bStop && g_guiSettings.GetBool("musiclibrary.autoartistinfo")) { long iArtist = m_musicDatabase.GetArtistByName(song.strArtist); CStdString strPath; strPath.Format("musicdb://2/%u/",iArtist); if (find(m_artistsScanned.begin(),m_artistsScanned.end(),iArtist) == m_artistsScanned.end()) if (DownloadArtistInfo(strPath,song.strArtist)) m_artistsScanned.push_back(iArtist); if (m_pObserver) m_pObserver->OnStateChanged(READING_MUSIC_INFO); } if (!m_bStop && g_guiSettings.GetBool("musiclibrary.autoalbuminfo")) { long iAlbum = m_musicDatabase.GetAlbumByName(song.strAlbum,song.strArtist); CStdString strPath; strPath.Format("musicdb://3/%u/",iAlbum); CMusicAlbumInfo albumInfo; bool bCanceled; if (find(m_albumsScanned.begin(),m_albumsScanned.end(),iAlbum) == m_albumsScanned.end()) if (DownloadAlbumInfo(strPath,song.strArtist,song.strAlbum,bCanceled,albumInfo)) m_albumsScanned.push_back(iAlbum); if (m_pObserver) m_pObserver->OnStateChanged(READING_MUSIC_INFO); } } return songsToAdd.size(); }