2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "threads/SystemClock.h"
22 #include "MusicInfoScanner.h"
23 #include "music/tags/MusicInfoTagLoaderFactory.h"
24 #include "MusicAlbumInfo.h"
25 #include "MusicInfoScraper.h"
26 #include "filesystem/MusicDatabaseDirectory.h"
27 #include "filesystem/MusicDatabaseDirectory/DirectoryNode.h"
29 #include "utils/md5.h"
30 #include "GUIInfoManager.h"
31 #include "utils/Variant.h"
33 #include "music/tags/MusicInfoTag.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "dialogs/GUIDialogExtendedProgressBar.h"
36 #include "dialogs/GUIDialogProgress.h"
37 #include "dialogs/GUIDialogSelect.h"
38 #include "guilib/GUIKeyboardFactory.h"
39 #include "filesystem/File.h"
40 #include "filesystem/Directory.h"
41 #include "settings/AdvancedSettings.h"
42 #include "settings/Settings.h"
44 #include "guilib/LocalizeStrings.h"
45 #include "utils/StringUtils.h"
46 #include "utils/TimeUtils.h"
47 #include "utils/log.h"
48 #include "utils/URIUtils.h"
49 #include "TextureCache.h"
50 #include "music/MusicThumbLoader.h"
51 #include "interfaces/AnnouncementManager.h"
52 #include "GUIUserMessages.h"
53 #include "addons/AddonManager.h"
54 #include "addons/Scraper.h"
59 using namespace MUSIC_INFO;
60 using namespace XFILE;
61 using namespace MUSICDATABASEDIRECTORY;
62 using namespace MUSIC_GRABBER;
63 using namespace ADDON;
65 CMusicInfoScanner::CMusicInfoScanner() : CThread("MusicInfoScanner"), m_fileCountReader(this, "MusicFileCounter")
70 m_bCanInterrupt = false;
76 CMusicInfoScanner::~CMusicInfoScanner()
80 void CMusicInfoScanner::Process()
82 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnScanStarted");
85 unsigned int tick = XbmcThreads::SystemClockMillis();
87 m_musicDatabase.Open();
89 if (m_showDialog && !CSettings::Get().GetBool("musiclibrary.backgroundupdate"))
91 CGUIDialogExtendedProgressBar* dialog =
92 (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
93 m_handle = dialog->GetHandle(g_localizeStrings.Get(314));
96 m_bCanInterrupt = true;
98 if (m_scanType == 0) // load info from files
100 CLog::Log(LOGDEBUG, "%s - Starting scan", __FUNCTION__);
103 m_handle->SetTitle(g_localizeStrings.Get(505));
105 // Reset progress vars
109 // Create the thread to count all files to be scanned
110 SetPriority( GetMinPriority() );
112 m_fileCountReader.Create();
114 // Database operations should not be canceled
115 // using Interupt() while scanning as it could
116 // result in unexpected behaviour.
117 m_bCanInterrupt = false;
118 m_needsCleanup = false;
121 bool cancelled = false;
122 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); it++)
124 if (!CDirectory::Exists(*it) && !m_bClean)
127 * Note that this will skip scanning (if m_bClean is disabled) if the directory really
128 * doesn't exist. Since the music scanner is fed with a list of existing paths from the DB
129 * and cleans out all songs under that path as its first step before re-adding files, if
130 * the entire source is offline we totally empty the music database in one go.
132 CLog::Log(LOGWARNING, "%s directory '%s' does not exist - skipping scan.", __FUNCTION__, it->c_str());
135 else if (!DoScan(*it))
142 g_infoManager.ResetLibraryBools();
148 m_handle->SetTitle(g_localizeStrings.Get(700));
149 m_handle->SetText("");
152 m_musicDatabase.CleanupOrphanedItems();
155 m_handle->SetTitle(g_localizeStrings.Get(331));
157 m_musicDatabase.Compress(false);
161 m_fileCountReader.StopThread();
163 m_musicDatabase.EmptyCache();
165 tick = XbmcThreads::SystemClockMillis() - tick;
166 CLog::Log(LOGNOTICE, "My Music: Scanning for music info using worker thread, operation took %s", StringUtils::SecondsToTimeString(tick / 1000).c_str());
168 if (m_scanType == 1) // load album info
170 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); ++it)
173 CDirectoryNode::GetDatabaseInfo(*it, params);
174 if (m_musicDatabase.HasAlbumInfo(params.GetArtistId())) // should this be here?
178 m_musicDatabase.GetAlbumInfo(params.GetAlbumId(), album, &album.songs);
181 float percentage = (float) std::distance(it, m_pathsToScan.end()) / m_pathsToScan.size();
182 m_handle->SetText(StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator) + " - " + album.strAlbum);
183 m_handle->SetPercentage(percentage);
186 CMusicAlbumInfo albumInfo;
187 UpdateDatabaseAlbumInfo(*it, albumInfo, false);
193 if (m_scanType == 2) // load artist info
195 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); ++it)
198 CDirectoryNode::GetDatabaseInfo(*it, params);
199 if (m_musicDatabase.HasArtistInfo(params.GetArtistId())) // should this be here?
203 m_musicDatabase.GetArtistInfo(params.GetArtistId(), artist);
204 m_musicDatabase.GetArtistPath(params.GetArtistId(), artist.strPath);
208 float percentage = (float) (std::distance(m_pathsToScan.begin(), it) / m_pathsToScan.size()) * 100;
209 m_handle->SetText(artist.strArtist);
210 m_handle->SetPercentage(percentage);
213 CMusicArtistInfo artistInfo;
214 UpdateDatabaseArtistInfo(*it, artistInfo, false);
224 CLog::Log(LOGERROR, "MusicInfoScanner: Exception while scanning.");
226 m_musicDatabase.Close();
227 CLog::Log(LOGDEBUG, "%s - Finished scan", __FUNCTION__);
230 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnScanFinished");
232 // we need to clear the musicdb cache and update any active lists
233 CUtil::DeleteMusicDatabaseDirectoryCache();
234 CGUIMessage msg(GUI_MSG_SCAN_FINISHED, 0, 0, 0);
235 g_windowManager.SendThreadMessage(msg);
238 m_handle->MarkFinished();
242 void CMusicInfoScanner::Start(const CStdString& strDirectory, int flags)
244 m_fileCountReader.StopThread();
246 m_pathsToScan.clear();
249 if (strDirectory.IsEmpty())
250 { // scan all paths in the database. We do this by scanning all paths in the db, and crossing them off the list as
252 m_musicDatabase.Open();
253 m_musicDatabase.GetPaths(m_pathsToScan);
254 m_musicDatabase.Close();
257 m_pathsToScan.insert(strDirectory);
258 m_bClean = g_advancedSettings.m_bMusicLibraryCleanOnUpdate;
265 void CMusicInfoScanner::FetchAlbumInfo(const CStdString& strDirectory,
268 m_fileCountReader.StopThread();
270 m_pathsToScan.clear();
273 if (strDirectory.IsEmpty())
275 m_musicDatabase.Open();
276 m_musicDatabase.GetAlbumsNav("musicdb://albums/", items);
277 m_musicDatabase.Close();
281 if (URIUtils::HasSlashAtEnd(strDirectory)) // directory
282 CDirectory::GetDirectory(strDirectory,items);
285 CFileItemPtr item(new CFileItem(strDirectory,false));
290 m_musicDatabase.Open();
291 for (int i=0;i<items.Size();++i)
293 if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
296 m_pathsToScan.insert(items[i]->GetPath());
299 m_musicDatabase.DeleteAlbumInfo(items[i]->GetMusicInfoTag()->GetDatabaseId());
302 m_musicDatabase.Close();
309 void CMusicInfoScanner::FetchArtistInfo(const CStdString& strDirectory,
312 m_fileCountReader.StopThread();
314 m_pathsToScan.clear();
317 if (strDirectory.IsEmpty())
319 m_musicDatabase.Open();
320 m_musicDatabase.GetArtistsNav("musicdb://artists/", items, false, -1);
321 m_musicDatabase.Close();
325 if (URIUtils::HasSlashAtEnd(strDirectory)) // directory
326 CDirectory::GetDirectory(strDirectory,items);
329 CFileItemPtr newItem(new CFileItem(strDirectory,false));
334 m_musicDatabase.Open();
335 for (int i=0;i<items.Size();++i)
337 if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
340 m_pathsToScan.insert(items[i]->GetPath());
343 m_musicDatabase.DeleteArtistInfo(items[i]->GetMusicInfoTag()->GetDatabaseId());
346 m_musicDatabase.Close();
353 bool CMusicInfoScanner::IsScanning()
358 void CMusicInfoScanner::Stop()
361 m_musicDatabase.Interupt();
366 static void OnDirectoryScanned(const CStdString& strDirectory)
368 CGUIMessage msg(GUI_MSG_DIRECTORY_SCANNED, 0, 0, 0);
369 msg.SetStringParam(strDirectory);
370 g_windowManager.SendThreadMessage(msg);
373 static CStdString Prettify(const CStdString& strDirectory)
375 CURL url(strDirectory);
376 CStdString strStrippedPath = url.GetWithoutUserDetails();
377 CURL::Decode(strStrippedPath);
379 return strStrippedPath;
382 bool CMusicInfoScanner::DoScan(const CStdString& strDirectory)
385 m_handle->SetText(Prettify(strDirectory));
387 // Discard all excluded files defined by m_musicExcludeRegExps
388 CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps;
389 if (CUtil::ExcludeFileOrFolder(strDirectory, regexps))
394 CDirectory::GetDirectory(strDirectory, items, g_advancedSettings.m_musicExtensions + "|.jpg|.tbn|.lrc|.cdg");
396 // sort and get the path hash. Note that we don't filter .cue sheet items here as we want
397 // to detect changes in the .cue sheet as well. The .cue sheet items only need filtering
398 // if we have a changed hash.
399 items.Sort(SortByLabel, SortOrderAscending);
401 GetPathHash(items, hash);
403 // check whether we need to rescan or not
405 if ((m_flags & SCAN_RESCAN) || !m_musicDatabase.GetPathHash(strDirectory, dbHash) || dbHash != hash)
406 { // path has changed - rescan
407 if (dbHash.IsEmpty())
408 CLog::Log(LOGDEBUG, "%s Scanning dir '%s' as not in the database", __FUNCTION__, strDirectory.c_str());
410 CLog::Log(LOGDEBUG, "%s Rescanning dir '%s' due to change", __FUNCTION__, strDirectory.c_str());
412 // filter items in the sub dir (for .cue sheet support)
413 items.FilterCueItems();
414 items.Sort(SortByLabel, SortOrderAscending);
416 // and then scan in the new information
417 if (RetrieveMusicInfo(strDirectory, items) > 0)
420 OnDirectoryScanned(strDirectory);
423 // save information about this folder
424 m_musicDatabase.SetPathHash(strDirectory, hash);
427 { // path is the same - no need to rescan
428 CLog::Log(LOGDEBUG, "%s Skipping dir '%s' due to no change", __FUNCTION__, strDirectory.c_str());
429 m_currentItem += CountFiles(items, false); // false for non-recursive
431 // updated the dialog with our progress
435 m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100);
436 OnDirectoryScanned(strDirectory);
440 // now scan the subfolders
441 for (int i = 0; i < items.Size(); ++i)
443 CFileItemPtr pItem = items[i];
447 // if we have a directory item (non-playlist) we then recurse into that folder
448 if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList())
450 CStdString strPath=pItem->GetPath();
451 if (!DoScan(strPath))
461 INFO_RET CMusicInfoScanner::ScanTags(const CFileItemList& items, CFileItemList& scannedItems)
463 CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps;
465 for (int i = 0; i < items.Size(); ++i)
468 return INFO_CANCELLED;
470 CFileItemPtr pItem = items[i];
472 if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps))
475 if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || pItem->IsLyrics())
480 CMusicInfoTag& tag = *pItem->GetMusicInfoTag();
483 auto_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(pItem->GetPath()));
484 if (NULL != pLoader.get())
485 pLoader->Load(pItem->GetPath(), tag);
488 if (m_handle && m_itemCount>0)
489 m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100);
493 CLog::Log(LOGDEBUG, "%s - No tag found for: %s", __FUNCTION__, pItem->GetPath().c_str());
496 scannedItems.Add(pItem);
501 static bool SortSongsByTrack(const CSong& song, const CSong& song2)
503 return song.iTrack < song2.iTrack;
506 void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& albums, MAPSONGS* songsMap /* = NULL */)
509 * Step 1: Convert the FileItems into Songs.
510 * If they're MB tagged, create albums directly from the FileItems.
511 * If they're non-MB tagged, index them by album name ready for step 2.
513 map<string, VECSONGS> songsByAlbumNames;
514 for (int i = 0; i < items.Size(); ++i)
516 CMusicInfoTag& tag = *items[i]->GetMusicInfoTag();
517 CSong song(*items[i]);
519 // keep the db-only fields intact on rescan...
520 if (songsMap != NULL)
522 MAPSONGS::iterator it = songsMap->find(items[i]->GetPath());
523 if (it != songsMap->end())
525 song.iTimesPlayed = it->second.iTimesPlayed;
526 song.lastPlayed = it->second.lastPlayed;
527 song.iKaraokeNumber = it->second.iKaraokeNumber;
528 if (song.rating == '0') song.rating = it->second.rating;
529 if (song.strThumb.empty()) song.strThumb = it->second.strThumb;
533 if (!tag.GetMusicBrainzAlbumID().empty())
535 VECALBUMS::iterator it;
536 for (it = albums.begin(); it != albums.end(); ++it)
537 if (it->strMusicBrainzAlbumID.Equals(tag.GetMusicBrainzAlbumID()))
540 if (it == albums.end())
542 CAlbum album(*items[i]);
543 album.songs.push_back(song);
544 albums.push_back(album);
547 it->songs.push_back(song);
550 songsByAlbumNames[tag.GetAlbum()].push_back(song);
554 Step 2: Split into unique albums based on album name and album artist
555 In the case where the album artist is unknown, we use the primary artist
556 (i.e. first artist from each song).
558 for (map<string, VECSONGS>::iterator songsByAlbumName = songsByAlbumNames.begin(); songsByAlbumName != songsByAlbumNames.end(); ++songsByAlbumName)
560 VECSONGS &songs = songsByAlbumName->second;
561 // sort the songs by tracknumber to identify duplicate track numbers
562 sort(songs.begin(), songs.end(), SortSongsByTrack);
564 // map the songs to their primary artists
565 bool tracksOverlap = false;
566 bool hasAlbumArtist = false;
567 bool isCompilation = true;
569 map<string, vector<CSong *> > artists;
570 for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
572 // test for song overlap
573 if (song != songs.begin() && song->iTrack == (song - 1)->iTrack)
574 tracksOverlap = true;
576 if (!song->bCompilation)
577 isCompilation = false;
579 // get primary artist
581 if (!song->albumArtist.empty())
583 primary = song->albumArtist[0];
584 hasAlbumArtist = true;
586 else if (!song->artist.empty())
587 primary = song->artist[0];
589 // add to the artist map
590 artists[primary].push_back(&(*song));
594 We have a compilation if
595 1. album name is non-empty AND
596 2a. no tracks overlap OR
597 2b. all tracks are marked as part of compilation AND
598 3a. a unique primary artist is specified as "various" or "various artists" OR
599 3b. we have at least two primary artists and no album artist specified.
601 bool compilation = !songsByAlbumName->first.empty() && (isCompilation || !tracksOverlap); // 1+2b+2a
602 if (artists.size() == 1)
604 string artist = artists.begin()->first; StringUtils::ToLower(artist);
605 if (!StringUtils::EqualsNoCase(artist, "various") &&
606 !StringUtils::EqualsNoCase(artist, "various artists")) // 3a
609 else if (hasAlbumArtist) // 3b
614 CLog::Log(LOGDEBUG, "Album '%s' is a compilation as there's no overlapping tracks and %s", songsByAlbumName->first.c_str(), hasAlbumArtist ? "the album artist is 'Various'" : "there is more than one unique artist");
616 std::string various = g_localizeStrings.Get(340); // Various Artists
617 vector<string> va; va.push_back(various);
618 for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
620 song->albumArtist = va;
621 artists[various].push_back(&(*song));
626 Step 3: Find the common albumartist for each song and assign
627 albumartist to those tracks that don't have it set.
629 for (map<string, vector<CSong *> >::iterator j = artists.begin(); j != artists.end(); ++j)
631 // find the common artist for these songs
632 vector<CSong *> &artistSongs = j->second;
633 vector<string> common = artistSongs.front()->albumArtist.empty() ? artistSongs.front()->artist : artistSongs.front()->albumArtist;
634 for (vector<CSong *>::iterator k = artistSongs.begin() + 1; k != artistSongs.end(); ++k)
636 unsigned int match = 0;
637 vector<string> &compare = (*k)->albumArtist.empty() ? (*k)->artist : (*k)->albumArtist;
638 for (; match < common.size() && match < compare.size(); match++)
640 if (compare[match] != common[match])
643 common.erase(common.begin() + match, common.end());
647 Step 4: Assign the album artist for each song that doesn't have it set
648 and add to the album vector
651 album.strAlbum = songsByAlbumName->first;
652 album.artist = common;
653 for (vector<string>::iterator it = common.begin(); it != common.end(); ++it)
655 CStdString strJoinPhrase = (it == --common.end() ? "" : g_advancedSettings.m_musicItemSeparator);
656 CArtistCredit artistCredit(*it, strJoinPhrase);
657 album.artistCredits.push_back(artistCredit);
659 album.bCompilation = compilation;
660 for (vector<CSong *>::iterator k = artistSongs.begin(); k != artistSongs.end(); ++k)
662 if ((*k)->albumArtist.empty())
663 (*k)->albumArtist = common;
664 // TODO: in future we may wish to union up the genres, for now we assume they're the same
665 album.genre = (*k)->genre;
666 // in addition, we may want to use year as discriminating for albums
667 album.iYear = (*k)->iYear;
668 album.songs.push_back(**k);
670 albums.push_back(album);
675 int CMusicInfoScanner::RetrieveMusicInfo(const CStdString& strDirectory, CFileItemList& items)
679 // get all information for all files in current directory from database, and remove them
680 if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap))
681 m_needsCleanup = true;
683 CFileItemList scannedItems;
684 if (ScanTags(items, scannedItems) == INFO_CANCELLED)
688 FileItemsToAlbums(scannedItems, albums, &songsMap);
689 FindArtForAlbums(albums, items.GetPath());
692 ADDON::AddonPtr addon;
693 ADDON::ScraperPtr albumScraper;
694 ADDON::ScraperPtr artistScraper;
695 if(ADDON::CAddonMgr::Get().GetDefault(ADDON::ADDON_SCRAPER_ALBUMS, addon))
696 albumScraper = boost::dynamic_pointer_cast<ADDON::CScraper>(addon);
698 if(ADDON::CAddonMgr::Get().GetDefault(ADDON::ADDON_SCRAPER_ARTISTS, addon))
699 artistScraper = boost::dynamic_pointer_cast<ADDON::CScraper>(addon);
702 for (VECALBUMS::iterator album = albums.begin(); album != albums.end(); ++album)
707 album->strPath = strDirectory;
708 m_musicDatabase.BeginTransaction();
710 // Check if the album has already been downloaded or failed
711 map<CAlbum, CAlbum>::iterator cachedAlbum = m_albumCache.find(*album);
712 if (cachedAlbum == m_albumCache.end())
714 // No - download the information
715 CMusicAlbumInfo albumInfo;
716 INFO_RET albumDownloadStatus = INFO_NOT_FOUND;
717 if ((m_flags & SCAN_ONLINE) && albumScraper)
718 albumDownloadStatus = DownloadAlbumInfo(*album, albumScraper, albumInfo);
720 if (albumDownloadStatus == INFO_ADDED || albumDownloadStatus == INFO_HAVE_ALREADY)
722 CAlbum &downloadedAlbum = albumInfo.GetAlbum();
723 downloadedAlbum.idAlbum = m_musicDatabase.AddAlbum(downloadedAlbum.strAlbum,
724 downloadedAlbum.strMusicBrainzAlbumID,
725 downloadedAlbum.GetArtistString(),
726 downloadedAlbum.GetGenreString(),
727 downloadedAlbum.iYear,
728 downloadedAlbum.bCompilation);
729 m_musicDatabase.SetAlbumInfo(downloadedAlbum.idAlbum,
731 downloadedAlbum.songs);
732 m_musicDatabase.SetArtForItem(downloadedAlbum.idAlbum,
733 "album", album->art);
734 GetAlbumArtwork(downloadedAlbum.idAlbum, downloadedAlbum);
735 m_albumCache.insert(make_pair(*album, albumInfo.GetAlbum()));
737 else if (albumDownloadStatus == INFO_CANCELLED)
741 // No download info, fallback to already gathered (eg. local) information/art (if any)
742 album->idAlbum = m_musicDatabase.AddAlbum(album->strAlbum,
743 album->strMusicBrainzAlbumID,
744 album->GetArtistString(),
745 album->GetGenreString(),
747 album->bCompilation);
748 if (!album->art.empty())
749 m_musicDatabase.SetArtForItem(album->idAlbum,
750 "album", album->art);
751 m_albumCache.insert(make_pair(*album, *album));
754 // Update the cache pointer with our newly created info
755 cachedAlbum = m_albumCache.find(*album);
761 // Add the album artists
762 for (VECARTISTCREDITS::iterator artistCredit = cachedAlbum->second.artistCredits.begin(); artistCredit != cachedAlbum->second.artistCredits.end(); ++artistCredit)
767 // Check if the artist has already been downloaded or failed
768 map<CArtistCredit, CArtist>::iterator cachedArtist = m_artistCache.find(*artistCredit);
769 if (cachedArtist == m_artistCache.end())
772 artistTmp.strArtist = artistCredit->GetArtist();
773 artistTmp.strMusicBrainzArtistID = artistCredit->GetMusicBrainzArtistID();
774 URIUtils::GetParentPath(album->strPath, artistTmp.strPath);
776 // No - download the information
777 CMusicArtistInfo artistInfo;
778 INFO_RET artistDownloadStatus = INFO_NOT_FOUND;
779 if ((m_flags & SCAN_ONLINE) && artistScraper)
780 artistDownloadStatus = DownloadArtistInfo(artistTmp, artistScraper, artistInfo);
782 if (artistDownloadStatus == INFO_ADDED || artistDownloadStatus == INFO_HAVE_ALREADY)
784 CArtist &downloadedArtist = artistInfo.GetArtist();
785 downloadedArtist.idArtist = m_musicDatabase.AddArtist(downloadedArtist.strArtist,
786 downloadedArtist.strMusicBrainzArtistID);
787 m_musicDatabase.SetArtistInfo(downloadedArtist.idArtist,
790 URIUtils::GetParentPath(album->strPath, downloadedArtist.strPath);
791 map<string, string> artwork = GetArtistArtwork(downloadedArtist);
793 m_musicDatabase.SetArtForItem(downloadedArtist.idArtist, "artist", artwork);
794 m_artistCache.insert(make_pair(*artistCredit, downloadedArtist));
796 else if (artistDownloadStatus == INFO_CANCELLED)
800 // Cache the lookup failure so we don't retry
801 artistTmp.idArtist = m_musicDatabase.AddArtist(artistCredit->GetArtist(), artistCredit->GetMusicBrainzArtistID());
802 m_artistCache.insert(make_pair(*artistCredit, artistTmp));
805 // Update the cache pointer with our newly created info
806 cachedArtist = m_artistCache.find(*artistCredit);
809 m_musicDatabase.AddAlbumArtist(cachedArtist->second.idArtist,
810 cachedAlbum->second.idAlbum,
811 artistCredit->GetJoinPhrase(),
812 artistCredit == cachedAlbum->second.artistCredits.begin() ? false : true,
813 std::distance(cachedAlbum->second.artistCredits.begin(), artistCredit));
819 for (VECSONGS::iterator song = album->songs.begin(); song != album->songs.end(); ++song)
821 song->idAlbum = cachedAlbum->second.idAlbum;
822 song->idSong = m_musicDatabase.AddSong(cachedAlbum->second.idAlbum,
823 song->strTitle, song->strMusicBrainzTrackID,
824 song->strFileName, song->strComment,
826 song->artist, song->genre,
827 song->iTrack, song->iDuration, song->iYear,
828 song->iTimesPlayed, song->iStartOffset,
832 song->iKaraokeNumber);
833 for (VECARTISTCREDITS::iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit)
838 // Check if the artist has already been downloaded or failed
839 map<CArtistCredit, CArtist>::iterator cachedArtist = m_artistCache.find(*artistCredit);
840 if (cachedArtist == m_artistCache.end())
843 artistTmp.strArtist = artistCredit->GetArtist();
844 artistTmp.strMusicBrainzArtistID = artistCredit->GetMusicBrainzArtistID();
845 URIUtils::GetParentPath(album->strPath, artistTmp.strPath); // FIXME
847 // No - download the information
848 CMusicArtistInfo artistInfo;
849 INFO_RET artistDownloadStatus = INFO_NOT_FOUND;
850 if ((m_flags & SCAN_ONLINE) && artistScraper)
851 artistDownloadStatus = DownloadArtistInfo(artistTmp, artistScraper, artistInfo);
853 if (artistDownloadStatus == INFO_ADDED || artistDownloadStatus == INFO_HAVE_ALREADY)
855 CArtist &downloadedArtist = artistInfo.GetArtist();
856 downloadedArtist.idArtist = m_musicDatabase.AddArtist(downloadedArtist.strArtist,
857 downloadedArtist.strMusicBrainzArtistID);
858 m_musicDatabase.SetArtistInfo(downloadedArtist.idArtist,
861 URIUtils::GetParentPath(album->strPath, downloadedArtist.strPath);
862 map<string, string> artwork = GetArtistArtwork(downloadedArtist);
863 m_musicDatabase.SetArtForItem(downloadedArtist.idArtist, "artist", artwork);
864 m_artistCache.insert(make_pair(*artistCredit, downloadedArtist));
866 else if (artistDownloadStatus == INFO_CANCELLED)
870 // Cache the lookup failure so we don't retry
871 artistTmp.idArtist = m_musicDatabase.AddArtist(artistCredit->GetArtist(), artistCredit->GetMusicBrainzArtistID());
872 m_artistCache.insert(make_pair(*artistCredit, artistTmp));
875 // Update the cache pointer with our newly created info
876 cachedArtist = m_artistCache.find(*artistCredit);
879 m_musicDatabase.AddSongArtist(cachedArtist->second.idArtist,
881 g_advancedSettings.m_musicItemSeparator, // we don't have song artist breakdowns from scrapers, yet
882 artistCredit == song->artistCredits.begin() ? false : true,
883 std::distance(song->artistCredits.begin(), artistCredit));
890 // Commit the album to the DB
891 m_musicDatabase.CommitTransaction();
892 numAdded += album->songs.size();
896 m_musicDatabase.RollbackTransaction();
899 m_handle->SetTitle(g_localizeStrings.Get(505));
904 void CMusicInfoScanner::FindArtForAlbums(VECALBUMS &albums, const CStdString &path)
907 If there's a single album in the folder, then art can be taken from
910 std::string albumArt;
911 if (albums.size() == 1)
913 CFileItem album(path, true);
914 albumArt = album.GetUserMusicThumb(true);
915 if (!albumArt.empty())
916 albums[0].art["thumb"] = albumArt;
918 for (VECALBUMS::iterator i = albums.begin(); i != albums.end(); ++i)
922 if (albums.size() != 1)
926 Find art that is common across these items
927 If we find a single art image we treat it as the album art
928 and discard song art else we use first as album art and
929 keep everything as song art.
931 bool singleArt = true;
933 for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
938 if (art && !art->ArtMatches(song))
949 assign the first art found to the album - better than no art at all
952 if (art && albumArt.empty())
954 if (!art->strThumb.empty())
955 albumArt = art->strThumb;
957 albumArt = CTextureUtils::GetWrappedImageURL(art->strFileName, "music");
960 if (!albumArt.empty())
961 album.art["thumb"] = albumArt;
964 { //if singleArt then we can clear the artwork for all songs
965 for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
969 { // more than one piece of art was found for these songs, so cache per song
970 for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
972 if (k->strThumb.empty() && !k->embeddedArt.empty())
973 k->strThumb = CTextureUtils::GetWrappedImageURL(k->strFileName, "music");
977 if (albums.size() == 1 && !albumArt.empty())
979 // assign to folder thumb as well
980 CFileItem albumItem(path, true);
981 CMusicThumbLoader loader;
982 loader.SetCachedImage(albumItem, "thumb", albumArt);
986 int CMusicInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
988 // Create a hash based on the filenames, filesize and filedate. Also count the number of files
989 if (0 == items.Size()) return 0;
990 XBMC::XBMC_MD5 md5state;
992 for (int i = 0; i < items.Size(); ++i)
994 const CFileItemPtr pItem = items[i];
995 md5state.append(pItem->GetPath());
996 md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
997 FILETIME time = pItem->m_dateTime;
998 md5state.append((unsigned char *)&time, sizeof(FILETIME));
999 if (pItem->IsAudio() && !pItem->IsPlayList() && !pItem->IsNFO())
1002 md5state.getDigest(hash);
1006 INFO_RET CMusicInfoScanner::UpdateDatabaseAlbumInfo(const CStdString& strPath, CMusicAlbumInfo& albumInfo, bool bAllowSelection, CGUIDialogProgress* pDialog /* = NULL */)
1008 m_musicDatabase.Open();
1009 CQueryParams params;
1010 CDirectoryNode::GetDatabaseInfo(strPath, params);
1012 if (params.GetAlbumId() == -1)
1016 m_musicDatabase.GetAlbumInfo(params.GetAlbumId(), album, &album.songs);
1019 ADDON::ScraperPtr scraper;
1020 bool result = m_musicDatabase.GetScraperForPath(strPath, scraper, ADDON::ADDON_SCRAPER_ALBUMS);
1022 m_musicDatabase.Close();
1024 if (!result || !scraper)
1028 CLog::Log(LOGDEBUG, "%s downloading info for: %s", __FUNCTION__, album.strAlbum.c_str());
1029 INFO_RET albumDownloadStatus = DownloadAlbumInfo(album, scraper, albumInfo, pDialog);
1030 if (albumDownloadStatus == INFO_NOT_FOUND)
1032 if (pDialog && bAllowSelection)
1034 if (!CGUIKeyboardFactory::ShowAndGetInput(album.strAlbum, g_localizeStrings.Get(16011), false))
1035 return INFO_CANCELLED;
1037 CStdString strTempArtist(StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1038 if (!CGUIKeyboardFactory::ShowAndGetInput(strTempArtist, g_localizeStrings.Get(16025), false))
1039 return INFO_CANCELLED;
1041 album.artist = StringUtils::Split(strTempArtist, g_advancedSettings.m_musicItemSeparator);
1045 else if (albumDownloadStatus == INFO_ADDED)
1047 m_musicDatabase.Open();
1048 m_musicDatabase.SetAlbumInfo(params.GetAlbumId(), albumInfo.GetAlbum(), albumInfo.GetAlbum().songs);
1049 GetAlbumArtwork(params.GetAlbumId(), albumInfo.GetAlbum());
1050 albumInfo.SetLoaded(true);
1051 m_musicDatabase.Close();
1053 return albumDownloadStatus;
1056 INFO_RET CMusicInfoScanner::UpdateDatabaseArtistInfo(const CStdString& strPath, CMusicArtistInfo& artistInfo, bool bAllowSelection, CGUIDialogProgress* pDialog /* = NULL */)
1058 m_musicDatabase.Open();
1059 CQueryParams params;
1060 CDirectoryNode::GetDatabaseInfo(strPath, params);
1062 if (params.GetArtistId() == -1)
1066 m_musicDatabase.GetArtistInfo(params.GetArtistId(), artist);
1069 ADDON::ScraperPtr scraper;
1070 if (!m_musicDatabase.GetScraperForPath(strPath, scraper, ADDON::ADDON_SCRAPER_ARTISTS) || !scraper)
1072 m_musicDatabase.Close();
1075 CLog::Log(LOGDEBUG, "%s downloading info for: %s", __FUNCTION__, artist.strArtist.c_str());
1076 INFO_RET artistDownloadStatus = DownloadArtistInfo(artist, scraper, artistInfo, pDialog);
1077 if (artistDownloadStatus == INFO_NOT_FOUND)
1079 if (pDialog && bAllowSelection)
1081 if (!CGUIKeyboardFactory::ShowAndGetInput(artist.strArtist, g_localizeStrings.Get(16025), false))
1082 return INFO_CANCELLED;
1086 else if (artistDownloadStatus == INFO_ADDED)
1088 m_musicDatabase.Open();
1089 m_musicDatabase.SetArtistInfo(params.GetArtistId(), artistInfo.GetArtist());
1090 m_musicDatabase.GetArtistPath(params.GetArtistId(), artist.strPath);
1091 map<string, string> artwork = GetArtistArtwork(artist);
1092 m_musicDatabase.SetArtForItem(params.GetArtistId(), "artist", artwork);
1093 artistInfo.SetLoaded();
1094 m_musicDatabase.Close();
1096 return artistDownloadStatus;
1099 #define THRESHOLD .95f
1101 INFO_RET CMusicInfoScanner::DownloadAlbumInfo(const CAlbum& album, ADDON::ScraperPtr& info, CMusicAlbumInfo& albumInfo, CGUIDialogProgress* pDialog)
1105 m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20321), info->Name().c_str()));
1106 m_handle->SetText(StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator) + " - " + album.strAlbum);
1109 // clear our scraper cache
1112 CMusicInfoScraper scraper(info);
1113 bool bMusicBrainz = false;
1114 if (!album.strMusicBrainzAlbumID.empty())
1116 CScraperUrl musicBrainzURL;
1117 if (ResolveMusicBrainz(album.strMusicBrainzAlbumID, info, scraper, musicBrainzURL))
1119 CMusicAlbumInfo albumNfo("nfo", musicBrainzURL);
1120 scraper.GetAlbums().clear();
1121 scraper.GetAlbums().push_back(albumNfo);
1122 bMusicBrainz = true;
1127 CStdString strNfo = URIUtils::AddFileToFolder(album.strPath, "album.nfo");
1128 CNfoFile::NFOResult result = CNfoFile::NO_NFO;
1130 if (XFILE::CFile::Exists(strNfo))
1132 CLog::Log(LOGDEBUG,"Found matching nfo file: %s", strNfo.c_str());
1133 result = nfoReader.Create(strNfo, info, -1, album.strPath);
1134 if (result == CNfoFile::FULL_NFO)
1136 CLog::Log(LOGDEBUG, "%s Got details from nfo", __FUNCTION__);
1137 nfoReader.GetDetails(albumInfo.GetAlbum());
1140 else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
1142 CScraperUrl scrUrl(nfoReader.ScraperUrl());
1143 CMusicAlbumInfo albumNfo("nfo",scrUrl);
1144 info = nfoReader.GetScraperInfo();
1145 CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",info->Name().c_str());
1146 CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str());
1147 scraper.SetScraperInfo(info);
1148 scraper.GetAlbums().clear();
1149 scraper.GetAlbums().push_back(albumNfo);
1152 CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str());
1155 if (!scraper.CheckValidOrFallback(CSettings::Get().GetString("musiclibrary.albumsscraper")))
1156 { // the current scraper is invalid, as is the default - bail
1157 CLog::Log(LOGERROR, "%s - current and default scrapers are invalid. Pick another one", __FUNCTION__);
1161 if (!scraper.GetAlbumCount())
1163 scraper.FindAlbumInfo(album.strAlbum, StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1165 while (!scraper.Completed())
1170 return INFO_CANCELLED;
1176 CGUIDialogSelect *pDlg = NULL;
1177 int iSelectedAlbum=0;
1178 if (result == CNfoFile::NO_NFO && !bMusicBrainz)
1180 iSelectedAlbum = -1; // set negative so that we can detect a failure
1181 if (scraper.Succeeded() && scraper.GetAlbumCount() >= 1)
1183 double bestRelevance = 0;
1184 double minRelevance = THRESHOLD;
1185 if (scraper.GetAlbumCount() > 1) // score the matches
1187 //show dialog with all albums found
1190 pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
1191 pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
1193 pDlg->EnableButton(true, 413); // manual
1196 for (int i = 0; i < scraper.GetAlbumCount(); ++i)
1198 CMusicAlbumInfo& info = scraper.GetAlbum(i);
1199 double relevance = info.GetRelevance();
1201 relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum, album.strAlbum, StringUtils::Join(info.GetAlbum().artist, g_advancedSettings.m_musicItemSeparator), StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1203 // if we're doing auto-selection (ie querying all albums at once, then allow 95->100% for perfect matches)
1204 // otherwise, perfect matches only
1205 if (relevance >= max(minRelevance, bestRelevance))
1206 { // we auto-select the best of these
1207 bestRelevance = relevance;
1212 // set the label to [relevance] album - artist
1214 strTemp.Format("[%0.2f] %s", relevance, info.GetTitle2());
1215 CFileItem item(strTemp);
1216 item.m_idepth = i; // use this to hold the index of the album in the scraper
1219 if (relevance > .99f) // we're so close, no reason to search further
1223 if (pDialog && bestRelevance < THRESHOLD)
1228 // and wait till user selects one
1229 if (pDlg->GetSelectedLabel() < 0)
1231 if (!pDlg->IsButtonPressed())
1232 return INFO_CANCELLED;
1234 // manual button pressed
1235 CStdString strNewAlbum = album.strAlbum;
1236 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewAlbum, g_localizeStrings.Get(16011), false)) return INFO_CANCELLED;
1237 if (strNewAlbum == "") return INFO_CANCELLED;
1239 CStdString strNewArtist = StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator);
1240 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, g_localizeStrings.Get(16025), false)) return INFO_CANCELLED;
1242 pDialog->SetLine(0, strNewAlbum);
1243 pDialog->SetLine(1, strNewArtist);
1244 pDialog->Progress();
1246 CAlbum newAlbum = album;
1247 newAlbum.strAlbum = strNewAlbum;
1248 newAlbum.artist = StringUtils::Split(strNewArtist, g_advancedSettings.m_musicItemSeparator);
1250 return DownloadAlbumInfo(newAlbum, info, albumInfo, pDialog);
1252 iSelectedAlbum = pDlg->GetSelectedItem()->m_idepth;
1257 CMusicAlbumInfo& info = scraper.GetAlbum(0);
1258 double relevance = info.GetRelevance();
1260 relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum,
1262 StringUtils::Join(info.GetAlbum().artist, g_advancedSettings.m_musicItemSeparator),
1263 StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1264 if (relevance < THRESHOLD)
1265 return INFO_NOT_FOUND;
1271 if (iSelectedAlbum < 0)
1272 return INFO_NOT_FOUND;
1276 scraper.LoadAlbumInfo(iSelectedAlbum);
1277 while (!scraper.Completed())
1282 return INFO_CANCELLED;
1287 if (!scraper.Succeeded())
1290 albumInfo = scraper.GetAlbum(iSelectedAlbum);
1292 if (result == CNfoFile::COMBINED_NFO)
1293 nfoReader.GetDetails(albumInfo.GetAlbum(), NULL, true);
1298 void CMusicInfoScanner::GetAlbumArtwork(long id, const CAlbum &album)
1300 if (album.thumbURL.m_url.size())
1302 if (m_musicDatabase.GetArtForItem(id, "album", "thumb").empty())
1304 string thumb = CScraperUrl::GetThumbURL(album.thumbURL.GetFirstThumb());
1307 CTextureCache::Get().BackgroundCacheImage(thumb);
1308 m_musicDatabase.SetArtForItem(id, "album", "thumb", thumb);
1314 INFO_RET CMusicInfoScanner::DownloadArtistInfo(const CArtist& artist, ADDON::ScraperPtr& info, MUSIC_GRABBER::CMusicArtistInfo& artistInfo, CGUIDialogProgress* pDialog)
1318 m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20320), info->Name().c_str()));
1319 m_handle->SetText(artist.strArtist);
1322 // clear our scraper cache
1325 CMusicInfoScraper scraper(info);
1326 bool bMusicBrainz = false;
1327 if (!artist.strMusicBrainzArtistID.empty())
1329 CScraperUrl musicBrainzURL;
1330 if (ResolveMusicBrainz(artist.strMusicBrainzArtistID, info, scraper, musicBrainzURL))
1332 CMusicArtistInfo artistNfo("nfo", musicBrainzURL);
1333 scraper.GetArtists().clear();
1334 scraper.GetArtists().push_back(artistNfo);
1335 bMusicBrainz = true;
1340 CStdString strNfo = URIUtils::AddFileToFolder(artist.strPath, "artist.nfo");
1341 CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1343 if (XFILE::CFile::Exists(strNfo))
1345 CLog::Log(LOGDEBUG,"Found matching nfo file: %s", strNfo.c_str());
1346 result = nfoReader.Create(strNfo, info);
1347 if (result == CNfoFile::FULL_NFO)
1349 CLog::Log(LOGDEBUG, "%s Got details from nfo", __FUNCTION__);
1350 nfoReader.GetDetails(artistInfo.GetArtist());
1353 else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
1355 CScraperUrl scrUrl(nfoReader.ScraperUrl());
1356 CMusicArtistInfo artistNfo("nfo",scrUrl);
1357 info = nfoReader.GetScraperInfo();
1358 CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",info->Name().c_str());
1359 CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str());
1360 scraper.SetScraperInfo(info);
1361 scraper.GetArtists().push_back(artistNfo);
1364 CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str());
1367 if (!scraper.GetArtistCount())
1369 scraper.FindArtistInfo(artist.strArtist);
1371 while (!scraper.Completed())
1376 return INFO_CANCELLED;
1382 int iSelectedArtist = 0;
1383 if (result == CNfoFile::NO_NFO && !bMusicBrainz)
1385 if (scraper.GetArtistCount() >= 1)
1387 // now load the first match
1388 if (pDialog && scraper.GetArtistCount() > 1)
1390 // if we found more then 1 album, let user choose one
1391 CGUIDialogSelect *pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
1394 pDlg->SetHeading(g_localizeStrings.Get(21890));
1396 pDlg->EnableButton(true, 413); // manual
1398 for (int i = 0; i < scraper.GetArtistCount(); ++i)
1400 // set the label to artist
1401 CFileItem item(scraper.GetArtist(i).GetArtist());
1402 CStdString strTemp=scraper.GetArtist(i).GetArtist().strArtist;
1403 if (!scraper.GetArtist(i).GetArtist().strBorn.IsEmpty())
1404 strTemp += " ("+scraper.GetArtist(i).GetArtist().strBorn+")";
1405 if (!scraper.GetArtist(i).GetArtist().genre.empty())
1407 CStdString genres = StringUtils::Join(scraper.GetArtist(i).GetArtist().genre, g_advancedSettings.m_musicItemSeparator);
1408 if (!genres.empty())
1409 strTemp.Format("[%s] %s", genres.c_str(), strTemp.c_str());
1411 item.SetLabel(strTemp);
1412 item.m_idepth = i; // use this to hold the index of the album in the scraper
1417 // and wait till user selects one
1418 if (pDlg->GetSelectedLabel() < 0)
1420 if (!pDlg->IsButtonPressed())
1421 return INFO_CANCELLED;
1423 // manual button pressed
1424 CStdString strNewArtist = artist.strArtist;
1425 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, g_localizeStrings.Get(16025), false)) return INFO_CANCELLED;
1429 pDialog->SetLine(0, strNewArtist);
1430 pDialog->Progress();
1434 newArtist.strArtist = strNewArtist;
1435 return DownloadArtistInfo(newArtist, info, artistInfo, pDialog);
1437 iSelectedArtist = pDlg->GetSelectedItem()->m_idepth;
1442 return INFO_NOT_FOUND;
1445 scraper.LoadArtistInfo(iSelectedArtist, artist.strArtist);
1446 while (!scraper.Completed())
1451 return INFO_CANCELLED;
1456 if (!scraper.Succeeded())
1459 artistInfo = scraper.GetArtist(iSelectedArtist);
1461 if (result == CNfoFile::COMBINED_NFO)
1462 nfoReader.GetDetails(artistInfo.GetArtist(), NULL, true);
1467 bool CMusicInfoScanner::ResolveMusicBrainz(const CStdString strMusicBrainzID, ScraperPtr &preferredScraper, CMusicInfoScraper &musicInfoScraper, CScraperUrl &musicBrainzURL)
1469 // We have a MusicBrainz ID
1470 // Get a scraper that can resolve it to a MusicBrainz URL & force our
1471 // search directly to the specific album.
1472 bool bMusicBrainz = false;
1473 ADDON::TYPE type = ScraperTypeFromContent(preferredScraper->Content());
1475 CFileItemList items;
1476 ADDON::AddonPtr addon;
1477 ADDON::ScraperPtr defaultScraper;
1478 if (ADDON::CAddonMgr::Get().GetDefault(type, addon))
1479 defaultScraper = boost::dynamic_pointer_cast<CScraper>(addon);
1481 vector<ScraperPtr> vecScrapers;
1483 // add selected scraper - first proirity
1484 if (preferredScraper)
1485 vecScrapers.push_back(preferredScraper);
1487 // Add all scrapers except selected and default
1489 CAddonMgr::Get().GetAddons(type, addons);
1491 for (unsigned i = 0; i < addons.size(); ++i)
1493 ScraperPtr scraper = boost::dynamic_pointer_cast<CScraper>(addons[i]);
1495 // skip if scraper requires settings and there's nothing set yet
1496 if (!scraper || (scraper->RequiresSettings() && !scraper->HasUserSettings()))
1499 if((!preferredScraper || preferredScraper->ID() != scraper->ID()) && (!defaultScraper || defaultScraper->ID() != scraper->ID()) )
1500 vecScrapers.push_back(scraper);
1503 // add default scraper - not user selectable so it's last priority
1504 if(defaultScraper &&
1505 (!preferredScraper || preferredScraper->ID() != defaultScraper->ID()) &&
1506 (!defaultScraper->RequiresSettings() || defaultScraper->HasUserSettings()))
1507 vecScrapers.push_back(defaultScraper);
1509 for (unsigned int i=0; i < vecScrapers.size(); ++i)
1511 if (vecScrapers[i]->Type() != type)
1514 vecScrapers[i]->ClearCache();
1517 musicBrainzURL = vecScrapers[i]->ResolveIDToUrl(strMusicBrainzID);
1519 catch (const ADDON::CScraperError &sce)
1521 if (!sce.FAborted())
1524 if (!musicBrainzURL.m_url.empty())
1526 Sleep(2000); // MusicBrainz rate-limits queries to 1 p.s - once we hit the rate-limiter
1527 // they start serving up the 'you hit the rate-limiter' page fast - meaning
1528 // we will never get below the rate-limit threshold again in a specific run.
1529 // This helps us to avoidthe rate-limiter as far as possible.
1530 CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",vecScrapers[i]->Name().c_str());
1531 CLog::Log(LOGDEBUG,"-- nfo url: %s", musicBrainzURL.m_url[0].m_url.c_str());
1532 musicInfoScraper.SetScraperInfo(vecScrapers[i]);
1533 bMusicBrainz = true;
1538 return bMusicBrainz;
1541 map<string, string> CMusicInfoScanner::GetArtistArtwork(const CArtist& artist)
1543 map<string, string> artwork;
1546 CStdString strFolder;
1548 if (!artist.strPath.IsEmpty())
1550 strFolder = artist.strPath;
1551 for (int i = 0; i < 3 && thumb.IsEmpty(); ++i)
1553 CFileItem item(strFolder, true);
1554 thumb = item.GetUserMusicThumb(true);
1555 strFolder = URIUtils::GetParentPath(strFolder);
1558 if (thumb.IsEmpty())
1559 thumb = CScraperUrl::GetThumbURL(artist.thumbURL.GetFirstThumb());
1560 if (!thumb.IsEmpty())
1562 CTextureCache::Get().BackgroundCacheImage(thumb);
1563 artwork.insert(make_pair("thumb", thumb));
1568 if (!artist.strPath.IsEmpty())
1570 strFolder = artist.strPath;
1571 for (int i = 0; i < 3 && fanart.IsEmpty(); ++i)
1573 CFileItem item(strFolder, true);
1574 fanart = item.GetLocalFanart();
1575 strFolder = URIUtils::GetParentPath(strFolder);
1578 if (fanart.IsEmpty())
1579 fanart = artist.fanart.GetImageURL();
1580 if (!fanart.IsEmpty())
1582 CTextureCache::Get().BackgroundCacheImage(fanart);
1583 artwork.insert(make_pair("fanart", fanart));
1589 // This function is the Run() function of the IRunnable
1590 // CFileCountReader and runs in a separate thread.
1591 void CMusicInfoScanner::Run()
1594 for (set<std::string>::iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end() && !m_bStop; ++it)
1596 count+=CountFilesRecursively(*it);
1598 m_itemCount = count;
1601 // Recurse through all folders we scan and count files
1602 int CMusicInfoScanner::CountFilesRecursively(const CStdString& strPath)
1605 CFileItemList items;
1606 CDirectory::GetDirectory(strPath, items, g_advancedSettings.m_musicExtensions, DIR_FLAG_NO_FILE_DIRS);
1611 // true for recursive counting
1612 int count = CountFiles(items, true);
1616 int CMusicInfoScanner::CountFiles(const CFileItemList &items, bool recursive)
1619 for (int i=0; i<items.Size(); ++i)
1621 const CFileItemPtr pItem=items[i];
1623 if (recursive && pItem->m_bIsFolder)
1624 count+=CountFilesRecursively(pItem->GetPath());
1625 else if (pItem->IsAudio() && !pItem->IsPlayList() && !pItem->IsNFO())