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++)
125 * A copy of the directory path is used because the path supplied is
126 * immediately removed from the m_pathsToScan set in DoScan(). If the
127 * reference points to the entry in the set a null reference error
137 g_infoManager.ResetLibraryBools();
143 m_handle->SetTitle(g_localizeStrings.Get(700));
144 m_handle->SetText("");
147 m_musicDatabase.CleanupOrphanedItems();
150 m_handle->SetTitle(g_localizeStrings.Get(331));
152 m_musicDatabase.Compress(false);
156 m_fileCountReader.StopThread();
158 m_musicDatabase.EmptyCache();
160 tick = XbmcThreads::SystemClockMillis() - tick;
161 CLog::Log(LOGNOTICE, "My Music: Scanning for music info using worker thread, operation took %s", StringUtils::SecondsToTimeString(tick / 1000).c_str());
163 if (m_scanType == 1) // load album info
165 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); ++it)
168 CDirectoryNode::GetDatabaseInfo(*it, params);
169 if (m_musicDatabase.HasAlbumInfo(params.GetArtistId())) // should this be here?
173 m_musicDatabase.GetAlbumInfo(params.GetAlbumId(), album, &album.songs);
176 float percentage = (float) std::distance(it, m_pathsToScan.end()) / m_pathsToScan.size();
177 m_handle->SetText(StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator) + " - " + album.strAlbum);
178 m_handle->SetPercentage(percentage);
181 CMusicAlbumInfo albumInfo;
182 UpdateDatabaseAlbumInfo(*it, albumInfo, false);
188 if (m_scanType == 2) // load artist info
190 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); ++it)
193 CDirectoryNode::GetDatabaseInfo(*it, params);
194 if (m_musicDatabase.HasArtistInfo(params.GetArtistId())) // should this be here?
198 m_musicDatabase.GetArtistInfo(params.GetArtistId(), artist);
199 m_musicDatabase.GetArtistPath(params.GetArtistId(), artist.strPath);
203 float percentage = (float) (std::distance(m_pathsToScan.begin(), it) / m_pathsToScan.size()) * 100;
204 m_handle->SetText(artist.strArtist);
205 m_handle->SetPercentage(percentage);
208 CMusicArtistInfo artistInfo;
209 UpdateDatabaseArtistInfo(*it, artistInfo, false);
219 CLog::Log(LOGERROR, "MusicInfoScanner: Exception while scanning.");
221 m_musicDatabase.Close();
222 CLog::Log(LOGDEBUG, "%s - Finished scan", __FUNCTION__);
225 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnScanFinished");
227 // we need to clear the musicdb cache and update any active lists
228 CUtil::DeleteMusicDatabaseDirectoryCache();
229 CGUIMessage msg(GUI_MSG_SCAN_FINISHED, 0, 0, 0);
230 g_windowManager.SendThreadMessage(msg);
233 m_handle->MarkFinished();
237 void CMusicInfoScanner::Start(const CStdString& strDirectory, int flags)
239 m_fileCountReader.StopThread();
241 m_pathsToScan.clear();
244 if (strDirectory.IsEmpty())
245 { // scan all paths in the database. We do this by scanning all paths in the db, and crossing them off the list as
247 m_musicDatabase.Open();
248 m_musicDatabase.GetPaths(m_pathsToScan);
249 m_musicDatabase.Close();
252 m_pathsToScan.insert(strDirectory);
259 void CMusicInfoScanner::FetchAlbumInfo(const CStdString& strDirectory,
262 m_fileCountReader.StopThread();
264 m_pathsToScan.clear();
267 if (strDirectory.IsEmpty())
269 m_musicDatabase.Open();
270 m_musicDatabase.GetAlbumsNav("musicdb://albums/", items);
271 m_musicDatabase.Close();
275 if (URIUtils::HasSlashAtEnd(strDirectory)) // directory
276 CDirectory::GetDirectory(strDirectory,items);
279 CFileItemPtr item(new CFileItem(strDirectory,false));
284 m_musicDatabase.Open();
285 for (int i=0;i<items.Size();++i)
287 if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
290 m_pathsToScan.insert(items[i]->GetPath());
293 m_musicDatabase.DeleteAlbumInfo(items[i]->GetMusicInfoTag()->GetDatabaseId());
296 m_musicDatabase.Close();
303 void CMusicInfoScanner::FetchArtistInfo(const CStdString& strDirectory,
306 m_fileCountReader.StopThread();
308 m_pathsToScan.clear();
311 if (strDirectory.IsEmpty())
313 m_musicDatabase.Open();
314 m_musicDatabase.GetArtistsNav("musicdb://artists/", items, false, -1);
315 m_musicDatabase.Close();
319 if (URIUtils::HasSlashAtEnd(strDirectory)) // directory
320 CDirectory::GetDirectory(strDirectory,items);
323 CFileItemPtr newItem(new CFileItem(strDirectory,false));
328 m_musicDatabase.Open();
329 for (int i=0;i<items.Size();++i)
331 if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
334 m_pathsToScan.insert(items[i]->GetPath());
337 m_musicDatabase.DeleteArtistInfo(items[i]->GetMusicInfoTag()->GetDatabaseId());
340 m_musicDatabase.Close();
347 bool CMusicInfoScanner::IsScanning()
352 void CMusicInfoScanner::Stop()
355 m_musicDatabase.Interupt();
360 static void OnDirectoryScanned(const CStdString& strDirectory)
362 CGUIMessage msg(GUI_MSG_DIRECTORY_SCANNED, 0, 0, 0);
363 msg.SetStringParam(strDirectory);
364 g_windowManager.SendThreadMessage(msg);
367 static CStdString Prettify(const CStdString& strDirectory)
369 CURL url(strDirectory);
370 CStdString strStrippedPath = url.GetWithoutUserDetails();
371 CURL::Decode(strStrippedPath);
373 return strStrippedPath;
376 bool CMusicInfoScanner::DoScan(const CStdString& strDirectory)
379 m_handle->SetText(Prettify(strDirectory));
381 // Discard all excluded files defined by m_musicExcludeRegExps
382 CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps;
383 if (CUtil::ExcludeFileOrFolder(strDirectory, regexps))
388 CDirectory::GetDirectory(strDirectory, items, g_advancedSettings.m_musicExtensions + "|.jpg|.tbn|.lrc|.cdg");
390 // sort and get the path hash. Note that we don't filter .cue sheet items here as we want
391 // to detect changes in the .cue sheet as well. The .cue sheet items only need filtering
392 // if we have a changed hash.
393 items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
395 GetPathHash(items, hash);
397 // check whether we need to rescan or not
399 if ((m_flags & SCAN_RESCAN) || !m_musicDatabase.GetPathHash(strDirectory, dbHash) || dbHash != hash)
400 { // path has changed - rescan
401 if (dbHash.IsEmpty())
402 CLog::Log(LOGDEBUG, "%s Scanning dir '%s' as not in the database", __FUNCTION__, strDirectory.c_str());
404 CLog::Log(LOGDEBUG, "%s Rescanning dir '%s' due to change", __FUNCTION__, strDirectory.c_str());
406 // filter items in the sub dir (for .cue sheet support)
407 items.FilterCueItems();
408 items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
410 // and then scan in the new information
411 if (RetrieveMusicInfo(strDirectory, items) > 0)
414 OnDirectoryScanned(strDirectory);
417 // save information about this folder
418 m_musicDatabase.SetPathHash(strDirectory, hash);
421 { // path is the same - no need to rescan
422 CLog::Log(LOGDEBUG, "%s Skipping dir '%s' due to no change", __FUNCTION__, strDirectory.c_str());
423 m_currentItem += CountFiles(items, false); // false for non-recursive
425 // updated the dialog with our progress
429 m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100);
430 OnDirectoryScanned(strDirectory);
434 // now scan the subfolders
435 for (int i = 0; i < items.Size(); ++i)
437 CFileItemPtr pItem = items[i];
441 // if we have a directory item (non-playlist) we then recurse into that folder
442 if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList())
444 CStdString strPath=pItem->GetPath();
445 if (!DoScan(strPath))
455 INFO_RET CMusicInfoScanner::ScanTags(const CFileItemList& items, CFileItemList& scannedItems)
457 CStdStringArray regexps = g_advancedSettings.m_audioExcludeFromScanRegExps;
459 for (int i = 0; i < items.Size(); ++i)
462 return INFO_CANCELLED;
464 CFileItemPtr pItem = items[i];
466 if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps))
469 if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || pItem->IsLyrics())
474 CMusicInfoTag& tag = *pItem->GetMusicInfoTag();
477 auto_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(pItem->GetPath()));
478 if (NULL != pLoader.get())
479 pLoader->Load(pItem->GetPath(), tag);
482 if (m_handle && m_itemCount>0)
483 m_handle->SetPercentage(m_currentItem/(float)m_itemCount*100);
487 CLog::Log(LOGDEBUG, "%s - No tag found for: %s", __FUNCTION__, pItem->GetPath().c_str());
490 scannedItems.Add(pItem);
495 void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& albums, MAPSONGS* songsMap /* = NULL */)
497 for (int i = 0; i < items.Size(); ++i)
499 CFileItemPtr pItem = items[i];
500 CMusicInfoTag& tag = *pItem->GetMusicInfoTag();
503 // keep the db-only fields intact on rescan...
504 if (songsMap != NULL)
506 MAPSONGS::iterator it = songsMap->find(pItem->GetPath());
507 if (it != songsMap->end())
509 song.iTimesPlayed = it->second.iTimesPlayed;
510 song.lastPlayed = it->second.lastPlayed;
511 song.iKaraokeNumber = it->second.iKaraokeNumber;
512 if (song.rating == '0') song.rating = it->second.rating;
513 if (song.strThumb.empty()) song.strThumb = it->second.strThumb;
517 if (!tag.GetMusicBrainzArtistID().empty())
519 for (vector<string>::const_iterator it = tag.GetMusicBrainzArtistID().begin(); it != tag.GetMusicBrainzArtistID().end(); ++it)
521 CStdString strJoinPhrase = (it == --tag.GetMusicBrainzArtistID().end() ? "" : g_advancedSettings.m_musicItemSeparator);
522 CArtistCredit mbartist(tag.GetArtist().empty() ? "" : tag.GetArtist()[0], *it, strJoinPhrase);
523 song.artistCredits.push_back(mbartist);
525 song.artist = tag.GetArtist();
529 for (vector<string>::const_iterator it = tag.GetArtist().begin(); it != tag.GetArtist().end(); ++it)
531 CStdString strJoinPhrase = (it == --tag.GetArtist().end() ? "" : g_advancedSettings.m_musicItemSeparator);
532 CArtistCredit nonmbartist(*it, strJoinPhrase);
533 song.artistCredits.push_back(nonmbartist);
535 song.artist = tag.GetArtist();
539 for (VECALBUMS::iterator it = albums.begin(); it != albums.end(); ++it)
541 if (it->strAlbum == tag.GetAlbum() && it->strMusicBrainzAlbumID == tag.GetMusicBrainzAlbumID())
543 it->songs.push_back(song);
549 CAlbum album(*pItem.get());
550 if (!tag.GetMusicBrainzAlbumArtistID().empty())
552 for (vector<string>::const_iterator it = tag.GetMusicBrainzAlbumArtistID().begin(); it != tag.GetMusicBrainzAlbumArtistID().end(); ++it)
554 // Picard always stored the display artist string in the first artist slot, no need to split it
555 CStdString strJoinPhrase = (it == --tag.GetMusicBrainzAlbumArtistID().end() ? "" : g_advancedSettings.m_musicItemSeparator);
556 CArtistCredit mbartist(tag.GetAlbumArtist().empty() ? "" : tag.GetAlbumArtist()[0], *it, strJoinPhrase);
557 album.artistCredits.push_back(mbartist);
559 album.artist = tag.GetAlbumArtist();
563 for (vector<string>::const_iterator it = tag.GetAlbumArtist().begin(); it != tag.GetAlbumArtist().end(); ++it)
565 CStdString strJoinPhrase = (it == --tag.GetAlbumArtist().end() ? "" : g_advancedSettings.m_musicItemSeparator);
566 CArtistCredit nonmbartist(*it, strJoinPhrase);
567 album.artistCredits.push_back(nonmbartist);
569 album.artist = tag.GetAlbumArtist();
571 album.songs.push_back(song);
572 albums.push_back(album);
577 int CMusicInfoScanner::RetrieveMusicInfo(const CStdString& strDirectory, CFileItemList& items)
581 // get all information for all files in current directory from database, and remove them
582 if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap))
583 m_needsCleanup = true;
585 CFileItemList scannedItems;
586 if (ScanTags(items, scannedItems) == INFO_CANCELLED)
590 FileItemsToAlbums(scannedItems, albums, &songsMap);
591 if (!(m_flags & SCAN_ONLINE))
593 FindArtForAlbums(albums, items.GetPath());
596 ADDON::AddonPtr addon;
597 ADDON::ScraperPtr albumScraper;
598 ADDON::ScraperPtr artistScraper;
599 if(ADDON::CAddonMgr::Get().GetDefault(ADDON::ADDON_SCRAPER_ALBUMS, addon))
600 albumScraper = boost::dynamic_pointer_cast<ADDON::CScraper>(addon);
602 if(ADDON::CAddonMgr::Get().GetDefault(ADDON::ADDON_SCRAPER_ARTISTS, addon))
603 artistScraper = boost::dynamic_pointer_cast<ADDON::CScraper>(addon);
606 for (VECALBUMS::iterator album = albums.begin(); album != albums.end(); ++album)
611 album->strPath = strDirectory;
612 m_musicDatabase.BeginTransaction();
614 // Check if the album has already been downloaded or failed
615 map<CAlbum, CAlbum>::iterator cachedAlbum = m_albumCache.find(*album);
616 if (cachedAlbum == m_albumCache.end())
618 // No - download the information
619 CMusicAlbumInfo albumInfo;
620 INFO_RET albumDownloadStatus = INFO_NOT_FOUND;
621 if ((m_flags & SCAN_ONLINE) && albumScraper)
622 albumDownloadStatus = DownloadAlbumInfo(*album, albumScraper, albumInfo);
624 if (albumDownloadStatus == INFO_ADDED || albumDownloadStatus == INFO_HAVE_ALREADY)
626 CAlbum &downloadedAlbum = albumInfo.GetAlbum();
627 downloadedAlbum.idAlbum = m_musicDatabase.AddAlbum(downloadedAlbum.strAlbum,
628 downloadedAlbum.strMusicBrainzAlbumID,
629 downloadedAlbum.GetArtistString(),
630 downloadedAlbum.GetGenreString(),
631 downloadedAlbum.iYear,
632 downloadedAlbum.bCompilation);
633 m_musicDatabase.SetAlbumInfo(downloadedAlbum.idAlbum,
635 downloadedAlbum.songs);
636 m_musicDatabase.SetArtForItem(downloadedAlbum.idAlbum,
637 "album", album->art);
638 GetAlbumArtwork(downloadedAlbum.idAlbum, downloadedAlbum);
639 m_albumCache.insert(make_pair(*album, albumInfo.GetAlbum()));
641 else if (albumDownloadStatus == INFO_CANCELLED)
643 else // Cache the lookup failure so we don't retry
645 album->idAlbum = m_musicDatabase.AddAlbum(album->strAlbum,
646 album->strMusicBrainzAlbumID,
647 album->GetArtistString(),
648 album->GetGenreString(),
650 album->bCompilation);
651 m_albumCache.insert(make_pair(*album, *album));
654 // Update the cache pointer with our newly created info
655 cachedAlbum = m_albumCache.find(*album);
661 // Add the album artists
662 for (VECARTISTCREDITS::iterator artistCredit = cachedAlbum->second.artistCredits.begin(); artistCredit != cachedAlbum->second.artistCredits.end(); ++artistCredit)
667 // Check if the artist has already been downloaded or failed
668 map<CArtistCredit, CArtist>::iterator cachedArtist = m_artistCache.find(*artistCredit);
669 if (cachedArtist == m_artistCache.end())
672 artistTmp.strArtist = artistCredit->GetArtist();
673 artistTmp.strMusicBrainzArtistID = artistCredit->GetMusicBrainzArtistID();
674 URIUtils::GetParentPath(album->strPath, artistTmp.strPath);
676 // No - download the information
677 CMusicArtistInfo artistInfo;
678 INFO_RET artistDownloadStatus = INFO_NOT_FOUND;
679 if ((m_flags & SCAN_ONLINE) && artistScraper)
680 artistDownloadStatus = DownloadArtistInfo(artistTmp, artistScraper, artistInfo);
682 if (artistDownloadStatus == INFO_ADDED || artistDownloadStatus == INFO_HAVE_ALREADY)
684 CArtist &downloadedArtist = artistInfo.GetArtist();
685 downloadedArtist.idArtist = m_musicDatabase.AddArtist(downloadedArtist.strArtist,
686 downloadedArtist.strMusicBrainzArtistID);
687 m_musicDatabase.SetArtistInfo(downloadedArtist.idArtist,
690 URIUtils::GetParentPath(album->strPath, downloadedArtist.strPath);
691 map<string, string> artwork = GetArtistArtwork(downloadedArtist);
693 m_musicDatabase.SetArtForItem(downloadedArtist.idArtist, "artist", artwork);
694 m_artistCache.insert(make_pair(*artistCredit, downloadedArtist));
696 else if (artistDownloadStatus == INFO_CANCELLED)
700 // Cache the lookup failure so we don't retry
701 artistTmp.idArtist = m_musicDatabase.AddArtist(artistCredit->GetArtist(), artistCredit->GetMusicBrainzArtistID());
702 m_artistCache.insert(make_pair(*artistCredit, artistTmp));
705 // Update the cache pointer with our newly created info
706 cachedArtist = m_artistCache.find(*artistCredit);
709 m_musicDatabase.AddAlbumArtist(cachedArtist->second.idArtist,
710 cachedAlbum->second.idAlbum,
711 artistCredit->GetJoinPhrase(),
712 artistCredit == album->artistCredits.begin() ? false : true,
713 std::distance(cachedAlbum->second.artistCredits.begin(), artistCredit));
719 for (VECSONGS::iterator song = album->songs.begin(); song != album->songs.end(); ++song)
721 song->idAlbum = cachedAlbum->second.idAlbum;
722 song->idSong = m_musicDatabase.AddSong(cachedAlbum->second.idAlbum,
723 song->strTitle, song->strMusicBrainzTrackID,
724 song->strFileName, song->strComment,
726 song->artist, song->genre,
727 song->iTrack, song->iDuration, song->iYear,
728 song->iTimesPlayed, song->iStartOffset,
732 song->iKaraokeNumber);
733 for (VECARTISTCREDITS::iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit)
738 // Check if the artist has already been downloaded or failed
739 map<CArtistCredit, CArtist>::iterator cachedArtist = m_artistCache.find(*artistCredit);
740 if (cachedArtist == m_artistCache.end())
743 artistTmp.strArtist = artistCredit->GetArtist();
744 artistTmp.strMusicBrainzArtistID = artistCredit->GetMusicBrainzArtistID();
745 URIUtils::GetParentPath(album->strPath, artistTmp.strPath); // FIXME
747 // No - download the information
748 CMusicArtistInfo artistInfo;
749 INFO_RET artistDownloadStatus = INFO_NOT_FOUND;
750 if ((m_flags & SCAN_ONLINE) && artistScraper)
751 artistDownloadStatus = DownloadArtistInfo(artistTmp, artistScraper, artistInfo);
753 if (artistDownloadStatus == INFO_ADDED || artistDownloadStatus == INFO_HAVE_ALREADY)
755 CArtist &downloadedArtist = artistInfo.GetArtist();
756 downloadedArtist.idArtist = m_musicDatabase.AddArtist(downloadedArtist.strArtist,
757 downloadedArtist.strMusicBrainzArtistID);
758 m_musicDatabase.SetArtistInfo(downloadedArtist.idArtist,
761 URIUtils::GetParentPath(album->strPath, downloadedArtist.strPath);
762 map<string, string> artwork = GetArtistArtwork(downloadedArtist);
763 m_musicDatabase.SetArtForItem(downloadedArtist.idArtist, "artist", artwork);
764 m_artistCache.insert(make_pair(*artistCredit, downloadedArtist));
766 else if (artistDownloadStatus == INFO_CANCELLED)
770 // Cache the lookup failure so we don't retry
771 artistTmp.idArtist = m_musicDatabase.AddArtist(artistCredit->GetArtist(), artistCredit->GetMusicBrainzArtistID());
772 m_artistCache.insert(make_pair(*artistCredit, artistTmp));
775 // Update the cache pointer with our newly created info
776 cachedArtist = m_artistCache.find(*artistCredit);
779 m_musicDatabase.AddSongArtist(cachedArtist->second.idArtist,
781 g_advancedSettings.m_musicItemSeparator, // we don't have song artist breakdowns from scrapers, yet
782 artistCredit == song->artistCredits.begin() ? false : true,
783 std::distance(song->artistCredits.begin(), artistCredit));
790 // Commit the album to the DB
791 m_musicDatabase.CommitTransaction();
792 numAdded += album->songs.size();
796 m_musicDatabase.RollbackTransaction();
799 m_handle->SetTitle(g_localizeStrings.Get(505));
804 static bool SortSongsByTrack(const CSong& song, const CSong& song2)
806 return song.iTrack < song2.iTrack;
809 void CMusicInfoScanner::FixupAlbums(VECALBUMS &albums)
812 Step 2: Split into unique albums based on album name and album artist
813 In the case where the album artist is unknown, we use the primary artist
814 (i.e. first artist from each song).
816 for (VECALBUMS::iterator album = albums.begin(); album != albums.end(); ++album)
819 * If we have a valid MusicBrainz tag for the album, assume everything
820 * is okay with our tags, as Picard should set everything up correctly.
822 if (!album->strMusicBrainzAlbumID.IsEmpty())
825 VECSONGS &songs = album->songs;
826 // sort the songs by tracknumber to identify duplicate track numbers
827 sort(songs.begin(), songs.end(), SortSongsByTrack);
829 // map the songs to their primary artists
830 bool tracksOverlap = false;
831 bool hasAlbumArtist = false;
832 bool isCompilation = true;
834 map<string, vector<CSong *> > artists;
835 for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
837 // test for song overlap
838 if (song != songs.begin() && song->iTrack == (song - 1)->iTrack)
839 tracksOverlap = true;
841 if (!song->bCompilation)
842 isCompilation = false;
844 // get primary artist
846 if (!song->albumArtist.empty())
848 primary = song->albumArtist[0];
849 hasAlbumArtist = true;
851 else if (!song->artist.empty())
852 primary = song->artist[0];
854 // add to the artist map
855 artists[primary].push_back(&(*song));
859 We have a compilation if
860 1. album name is non-empty AND
861 2a. no tracks overlap OR
862 2b. all tracks are marked as part of compilation AND
863 3a. a unique primary artist is specified as "various" or "various artists" OR
864 3b. we have at least two primary artists and no album artist specified.
866 bool compilation = !album->strAlbum.empty() && (isCompilation || !tracksOverlap); // 1+2b+2a
867 if (artists.size() == 1)
869 string artist = artists.begin()->first; StringUtils::ToLower(artist);
870 if (!StringUtils::EqualsNoCase(artist, "various") &&
871 !StringUtils::EqualsNoCase(artist, "various artists")) // 3a
874 else if (hasAlbumArtist) // 3b
879 CLog::Log(LOGDEBUG, "Album '%s' is a compilation as there's no overlapping tracks and %s", album->strAlbum.c_str(), hasAlbumArtist ? "the album artist is 'Various'" : "there is more than one unique artist");
881 std::string various = g_localizeStrings.Get(340); // Various Artists
882 vector<string> va; va.push_back(various);
883 for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
885 song->albumArtist = va;
886 artists[various].push_back(&(*song));
891 Step 3: Find the common albumartist for each song and assign
892 albumartist to those tracks that don't have it set.
894 for (map<string, vector<CSong *> >::iterator j = artists.begin(); j != artists.end(); ++j)
896 // find the common artist for these songs
897 vector<CSong *> &artistSongs = j->second;
898 vector<string> common = artistSongs.front()->albumArtist.empty() ? artistSongs.front()->artist : artistSongs.front()->albumArtist;
899 for (vector<CSong *>::iterator k = artistSongs.begin() + 1; k != artistSongs.end(); ++k)
901 unsigned int match = 0;
902 vector<string> &compare = (*k)->albumArtist.empty() ? (*k)->artist : (*k)->albumArtist;
903 for (; match < common.size() && match < compare.size(); match++)
905 if (compare[match] != common[match])
908 common.erase(common.begin() + match, common.end());
912 Step 4: Assign the album artist for each song that doesn't have it set
913 and add to the album vector
915 album->artistCredits.clear();
916 for (vector<string>::iterator it = common.begin(); it != common.end(); ++it)
918 CStdString strJoinPhrase = (it == --common.end() ? "" : g_advancedSettings.m_musicItemSeparator);
919 CArtistCredit artistCredit(*it, strJoinPhrase);
920 album->artistCredits.push_back(artistCredit);
922 album->bCompilation = compilation;
923 for (vector<CSong *>::iterator k = artistSongs.begin(); k != artistSongs.end(); ++k)
925 if ((*k)->albumArtist.empty())
926 (*k)->albumArtist = common;
927 // TODO: in future we may wish to union up the genres, for now we assume they're the same
928 if (album->genre.empty())
929 album->genre = (*k)->genre;
930 // in addition, we may want to use year as discriminating for albums
931 if (album->iYear == 0)
932 album->iYear = (*k)->iYear;
938 void CMusicInfoScanner::FindArtForAlbums(VECALBUMS &albums, const CStdString &path)
941 If there's a single album in the folder, then art can be taken from
944 std::string albumArt;
945 if (albums.size() == 1)
947 CFileItem album(path, true);
948 albumArt = album.GetUserMusicThumb(true);
949 if (!albumArt.empty())
950 albums[0].art["thumb"] = albumArt;
952 for (VECALBUMS::iterator i = albums.begin(); i != albums.end(); ++i)
956 if (albums.size() != 1)
960 Find art that is common across these items
961 If we find a single art image we treat it as the album art
962 and discard song art else we use first as album art and
963 keep everything as song art.
965 bool singleArt = true;
967 for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
972 if (art && !art->ArtMatches(song))
983 assign the first art found to the album - better than no art at all
986 if (art && albumArt.empty())
988 if (!art->strThumb.empty())
989 albumArt = art->strThumb;
991 albumArt = CTextureCache::GetWrappedImageURL(art->strFileName, "music");
994 if (!albumArt.empty())
995 album.art["thumb"] = albumArt;
998 { //if singleArt then we can clear the artwork for all songs
999 for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
1000 k->strThumb.clear();
1003 { // more than one piece of art was found for these songs, so cache per song
1004 for (VECSONGS::iterator k = album.songs.begin(); k != album.songs.end(); ++k)
1006 if (k->strThumb.empty() && !k->embeddedArt.empty())
1007 k->strThumb = CTextureCache::GetWrappedImageURL(k->strFileName, "music");
1011 if (albums.size() == 1 && !albumArt.empty())
1013 // assign to folder thumb as well
1014 CFileItem albumItem(path, true);
1015 CMusicThumbLoader::SetCachedImage(albumItem, "thumb", albumArt);
1019 int CMusicInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
1021 // Create a hash based on the filenames, filesize and filedate. Also count the number of files
1022 if (0 == items.Size()) return 0;
1023 XBMC::XBMC_MD5 md5state;
1025 for (int i = 0; i < items.Size(); ++i)
1027 const CFileItemPtr pItem = items[i];
1028 md5state.append(pItem->GetPath());
1029 md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1030 FILETIME time = pItem->m_dateTime;
1031 md5state.append((unsigned char *)&time, sizeof(FILETIME));
1032 if (pItem->IsAudio() && !pItem->IsPlayList() && !pItem->IsNFO())
1035 md5state.getDigest(hash);
1039 INFO_RET CMusicInfoScanner::UpdateDatabaseAlbumInfo(const CStdString& strPath, CMusicAlbumInfo& albumInfo, bool bAllowSelection, CGUIDialogProgress* pDialog /* = NULL */)
1041 m_musicDatabase.Open();
1042 CQueryParams params;
1043 CDirectoryNode::GetDatabaseInfo(strPath, params);
1045 if (params.GetAlbumId() == -1)
1049 m_musicDatabase.GetAlbumInfo(params.GetAlbumId(), album, &album.songs);
1052 ADDON::ScraperPtr scraper;
1053 bool result = m_musicDatabase.GetScraperForPath(strPath, scraper, ADDON::ADDON_SCRAPER_ALBUMS);
1055 m_musicDatabase.Close();
1057 if (!result || !scraper)
1061 CLog::Log(LOGDEBUG, "%s downloading info for: %s", __FUNCTION__, album.strAlbum.c_str());
1062 INFO_RET albumDownloadStatus = DownloadAlbumInfo(album, scraper, albumInfo, pDialog);
1063 if (albumDownloadStatus == INFO_NOT_FOUND)
1065 if (pDialog && bAllowSelection)
1067 if (!CGUIKeyboardFactory::ShowAndGetInput(album.strAlbum, g_localizeStrings.Get(16011), false))
1068 return INFO_CANCELLED;
1070 CStdString strTempArtist(StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1071 if (!CGUIKeyboardFactory::ShowAndGetInput(strTempArtist, g_localizeStrings.Get(16025), false))
1072 return INFO_CANCELLED;
1074 album.artist = StringUtils::Split(strTempArtist, g_advancedSettings.m_musicItemSeparator);
1078 else if (albumDownloadStatus == INFO_ADDED)
1080 m_musicDatabase.Open();
1081 m_musicDatabase.SetAlbumInfo(params.GetAlbumId(), albumInfo.GetAlbum(), albumInfo.GetAlbum().songs);
1082 GetAlbumArtwork(params.GetAlbumId(), albumInfo.GetAlbum());
1083 albumInfo.SetLoaded(true);
1084 m_musicDatabase.Close();
1086 return albumDownloadStatus;
1089 INFO_RET CMusicInfoScanner::UpdateDatabaseArtistInfo(const CStdString& strPath, CMusicArtistInfo& artistInfo, bool bAllowSelection, CGUIDialogProgress* pDialog /* = NULL */)
1091 m_musicDatabase.Open();
1092 CQueryParams params;
1093 CDirectoryNode::GetDatabaseInfo(strPath, params);
1095 if (params.GetArtistId() == -1)
1099 m_musicDatabase.GetArtistInfo(params.GetArtistId(), artist);
1102 ADDON::ScraperPtr scraper;
1103 if (!m_musicDatabase.GetScraperForPath(strPath, scraper, ADDON::ADDON_SCRAPER_ARTISTS) || !scraper)
1105 m_musicDatabase.Close();
1108 CLog::Log(LOGDEBUG, "%s downloading info for: %s", __FUNCTION__, artist.strArtist.c_str());
1109 INFO_RET artistDownloadStatus = DownloadArtistInfo(artist, scraper, artistInfo, pDialog);
1110 if (artistDownloadStatus == INFO_NOT_FOUND)
1112 if (pDialog && bAllowSelection)
1114 if (!CGUIKeyboardFactory::ShowAndGetInput(artist.strArtist, g_localizeStrings.Get(16025), false))
1115 return INFO_CANCELLED;
1119 else if (artistDownloadStatus == INFO_ADDED)
1121 m_musicDatabase.Open();
1122 m_musicDatabase.SetArtistInfo(params.GetArtistId(), artistInfo.GetArtist());
1123 m_musicDatabase.GetArtistPath(params.GetArtistId(), artist.strPath);
1124 map<string, string> artwork = GetArtistArtwork(artist);
1125 m_musicDatabase.SetArtForItem(params.GetArtistId(), "artist", artwork);
1126 artistInfo.SetLoaded();
1127 m_musicDatabase.Close();
1129 return artistDownloadStatus;
1132 #define THRESHOLD .95f
1134 INFO_RET CMusicInfoScanner::DownloadAlbumInfo(const CAlbum& album, ADDON::ScraperPtr& info, CMusicAlbumInfo& albumInfo, CGUIDialogProgress* pDialog)
1138 m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20321), info->Name().c_str()));
1139 m_handle->SetText(StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator) + " - " + album.strAlbum);
1142 // clear our scraper cache
1145 CMusicInfoScraper scraper(info);
1146 bool bMusicBrainz = false;
1147 if (!album.strMusicBrainzAlbumID.empty())
1149 CScraperUrl musicBrainzURL;
1150 if (ResolveMusicBrainz(album.strMusicBrainzAlbumID, info, scraper, musicBrainzURL))
1152 CMusicAlbumInfo albumNfo("nfo", musicBrainzURL);
1153 scraper.GetAlbums().clear();
1154 scraper.GetAlbums().push_back(albumNfo);
1155 bMusicBrainz = true;
1160 CStdString strNfo = URIUtils::AddFileToFolder(album.strPath, "album.nfo");
1161 CNfoFile::NFOResult result = CNfoFile::NO_NFO;
1163 if (XFILE::CFile::Exists(strNfo))
1165 CLog::Log(LOGDEBUG,"Found matching nfo file: %s", strNfo.c_str());
1166 result = nfoReader.Create(strNfo, info, -1, album.strPath);
1167 if (result == CNfoFile::FULL_NFO)
1169 CLog::Log(LOGDEBUG, "%s Got details from nfo", __FUNCTION__);
1170 nfoReader.GetDetails(albumInfo.GetAlbum());
1173 else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
1175 CScraperUrl scrUrl(nfoReader.ScraperUrl());
1176 CMusicAlbumInfo albumNfo("nfo",scrUrl);
1177 info = nfoReader.GetScraperInfo();
1178 CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",info->Name().c_str());
1179 CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str());
1180 scraper.SetScraperInfo(info);
1181 scraper.GetAlbums().clear();
1182 scraper.GetAlbums().push_back(albumNfo);
1185 CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str());
1188 if (!scraper.CheckValidOrFallback(CSettings::Get().GetString("musiclibrary.albumsscraper")))
1189 { // the current scraper is invalid, as is the default - bail
1190 CLog::Log(LOGERROR, "%s - current and default scrapers are invalid. Pick another one", __FUNCTION__);
1194 if (!scraper.GetAlbumCount())
1196 scraper.FindAlbumInfo(album.strAlbum, StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1198 while (!scraper.Completed())
1203 return INFO_CANCELLED;
1209 CGUIDialogSelect *pDlg = NULL;
1210 int iSelectedAlbum=0;
1211 if (result == CNfoFile::NO_NFO && !bMusicBrainz)
1213 iSelectedAlbum = -1; // set negative so that we can detect a failure
1214 if (scraper.Succeeded() && scraper.GetAlbumCount() >= 1)
1216 double bestRelevance = 0;
1217 double minRelevance = THRESHOLD;
1218 if (scraper.GetAlbumCount() > 1) // score the matches
1220 //show dialog with all albums found
1223 pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
1224 pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
1226 pDlg->EnableButton(true, 413); // manual
1229 for (int i = 0; i < scraper.GetAlbumCount(); ++i)
1231 CMusicAlbumInfo& info = scraper.GetAlbum(i);
1232 double relevance = info.GetRelevance();
1234 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));
1236 // if we're doing auto-selection (ie querying all albums at once, then allow 95->100% for perfect matches)
1237 // otherwise, perfect matches only
1238 if (relevance >= max(minRelevance, bestRelevance))
1239 { // we auto-select the best of these
1240 bestRelevance = relevance;
1245 // set the label to [relevance] album - artist
1247 strTemp.Format("[%0.2f] %s", relevance, info.GetTitle2());
1248 CFileItem item(strTemp);
1249 item.m_idepth = i; // use this to hold the index of the album in the scraper
1252 if (relevance > .99f) // we're so close, no reason to search further
1256 if (pDialog && bestRelevance < THRESHOLD)
1261 // and wait till user selects one
1262 if (pDlg->GetSelectedLabel() < 0)
1264 if (!pDlg->IsButtonPressed())
1265 return INFO_CANCELLED;
1267 // manual button pressed
1268 CStdString strNewAlbum = album.strAlbum;
1269 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewAlbum, g_localizeStrings.Get(16011), false)) return INFO_CANCELLED;
1270 if (strNewAlbum == "") return INFO_CANCELLED;
1272 CStdString strNewArtist = StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator);
1273 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, g_localizeStrings.Get(16025), false)) return INFO_CANCELLED;
1275 pDialog->SetLine(0, strNewAlbum);
1276 pDialog->SetLine(1, strNewArtist);
1277 pDialog->Progress();
1279 CAlbum newAlbum = album;
1280 newAlbum.strAlbum = strNewAlbum;
1281 newAlbum.artist = StringUtils::Split(strNewArtist, g_advancedSettings.m_musicItemSeparator);
1283 return DownloadAlbumInfo(newAlbum, info, albumInfo, pDialog);
1285 iSelectedAlbum = pDlg->GetSelectedItem()->m_idepth;
1290 CMusicAlbumInfo& info = scraper.GetAlbum(0);
1291 double relevance = info.GetRelevance();
1293 relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum,
1295 StringUtils::Join(info.GetAlbum().artist, g_advancedSettings.m_musicItemSeparator),
1296 StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
1297 if (relevance < THRESHOLD)
1298 return INFO_NOT_FOUND;
1304 if (iSelectedAlbum < 0)
1305 return INFO_NOT_FOUND;
1309 scraper.LoadAlbumInfo(iSelectedAlbum);
1310 while (!scraper.Completed())
1315 return INFO_CANCELLED;
1320 if (!scraper.Succeeded())
1323 albumInfo = scraper.GetAlbum(iSelectedAlbum);
1325 if (result == CNfoFile::COMBINED_NFO)
1326 nfoReader.GetDetails(albumInfo.GetAlbum(), NULL, true);
1331 void CMusicInfoScanner::GetAlbumArtwork(long id, const CAlbum &album)
1333 if (album.thumbURL.m_url.size())
1335 if (m_musicDatabase.GetArtForItem(id, "album", "thumb").empty())
1337 string thumb = CScraperUrl::GetThumbURL(album.thumbURL.GetFirstThumb());
1340 CTextureCache::Get().BackgroundCacheImage(thumb);
1341 m_musicDatabase.SetArtForItem(id, "album", "thumb", thumb);
1347 INFO_RET CMusicInfoScanner::DownloadArtistInfo(const CArtist& artist, ADDON::ScraperPtr& info, MUSIC_GRABBER::CMusicArtistInfo& artistInfo, CGUIDialogProgress* pDialog)
1351 m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20320), info->Name().c_str()));
1352 m_handle->SetText(artist.strArtist);
1355 // clear our scraper cache
1358 CMusicInfoScraper scraper(info);
1359 bool bMusicBrainz = false;
1360 if (!artist.strMusicBrainzArtistID.empty())
1362 CScraperUrl musicBrainzURL;
1363 if (ResolveMusicBrainz(artist.strMusicBrainzArtistID, info, scraper, musicBrainzURL))
1365 CMusicArtistInfo artistNfo("nfo", musicBrainzURL);
1366 scraper.GetArtists().clear();
1367 scraper.GetArtists().push_back(artistNfo);
1368 bMusicBrainz = true;
1373 CStdString strNfo = URIUtils::AddFileToFolder(artist.strPath, "artist.nfo");
1374 CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1376 if (XFILE::CFile::Exists(strNfo))
1378 CLog::Log(LOGDEBUG,"Found matching nfo file: %s", strNfo.c_str());
1379 result = nfoReader.Create(strNfo, info);
1380 if (result == CNfoFile::FULL_NFO)
1382 CLog::Log(LOGDEBUG, "%s Got details from nfo", __FUNCTION__);
1383 nfoReader.GetDetails(artistInfo.GetArtist());
1386 else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
1388 CScraperUrl scrUrl(nfoReader.ScraperUrl());
1389 CMusicArtistInfo artistNfo("nfo",scrUrl);
1390 info = nfoReader.GetScraperInfo();
1391 CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",info->Name().c_str());
1392 CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str());
1393 scraper.SetScraperInfo(info);
1394 scraper.GetArtists().push_back(artistNfo);
1397 CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str());
1400 if (!scraper.GetArtistCount())
1402 scraper.FindArtistInfo(artist.strArtist);
1404 while (!scraper.Completed())
1409 return INFO_CANCELLED;
1415 int iSelectedArtist = 0;
1416 if (result == CNfoFile::NO_NFO && !bMusicBrainz)
1418 if (scraper.GetArtistCount() >= 1)
1420 // now load the first match
1421 if (pDialog && scraper.GetArtistCount() > 1)
1423 // if we found more then 1 album, let user choose one
1424 CGUIDialogSelect *pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
1427 pDlg->SetHeading(g_localizeStrings.Get(21890));
1429 pDlg->EnableButton(true, 413); // manual
1431 for (int i = 0; i < scraper.GetArtistCount(); ++i)
1433 // set the label to artist
1434 CFileItem item(scraper.GetArtist(i).GetArtist());
1435 CStdString strTemp=scraper.GetArtist(i).GetArtist().strArtist;
1436 if (!scraper.GetArtist(i).GetArtist().strBorn.IsEmpty())
1437 strTemp += " ("+scraper.GetArtist(i).GetArtist().strBorn+")";
1438 if (!scraper.GetArtist(i).GetArtist().genre.empty())
1440 CStdString genres = StringUtils::Join(scraper.GetArtist(i).GetArtist().genre, g_advancedSettings.m_musicItemSeparator);
1441 if (!genres.empty())
1442 strTemp.Format("[%s] %s", genres.c_str(), strTemp.c_str());
1444 item.SetLabel(strTemp);
1445 item.m_idepth = i; // use this to hold the index of the album in the scraper
1450 // and wait till user selects one
1451 if (pDlg->GetSelectedLabel() < 0)
1453 if (!pDlg->IsButtonPressed())
1454 return INFO_CANCELLED;
1456 // manual button pressed
1457 CStdString strNewArtist = artist.strArtist;
1458 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, g_localizeStrings.Get(16025), false)) return INFO_CANCELLED;
1462 pDialog->SetLine(0, strNewArtist);
1463 pDialog->Progress();
1467 newArtist.strArtist = strNewArtist;
1468 return DownloadArtistInfo(newArtist, info, artistInfo, pDialog);
1470 iSelectedArtist = pDlg->GetSelectedItem()->m_idepth;
1475 return INFO_NOT_FOUND;
1478 scraper.LoadArtistInfo(iSelectedArtist, artist.strArtist);
1479 while (!scraper.Completed())
1484 return INFO_CANCELLED;
1489 if (!scraper.Succeeded())
1492 artistInfo = scraper.GetArtist(iSelectedArtist);
1494 if (result == CNfoFile::COMBINED_NFO)
1495 nfoReader.GetDetails(artistInfo.GetArtist(), NULL, true);
1500 bool CMusicInfoScanner::ResolveMusicBrainz(const CStdString strMusicBrainzID, ScraperPtr &preferredScraper, CMusicInfoScraper &musicInfoScraper, CScraperUrl &musicBrainzURL)
1502 // We have a MusicBrainz ID
1503 // Get a scraper that can resolve it to a MusicBrainz URL & force our
1504 // search directly to the specific album.
1505 bool bMusicBrainz = false;
1506 ADDON::TYPE type = ScraperTypeFromContent(preferredScraper->Content());
1508 CFileItemList items;
1509 ADDON::AddonPtr addon;
1510 ADDON::ScraperPtr defaultScraper;
1511 if (ADDON::CAddonMgr::Get().GetDefault(type, addon))
1512 defaultScraper = boost::dynamic_pointer_cast<CScraper>(addon);
1514 vector<ScraperPtr> vecScrapers;
1516 // add selected scraper - first proirity
1517 if (preferredScraper)
1518 vecScrapers.push_back(preferredScraper);
1520 // Add all scrapers except selected and default
1522 CAddonMgr::Get().GetAddons(type, addons);
1524 for (unsigned i = 0; i < addons.size(); ++i)
1526 ScraperPtr scraper = boost::dynamic_pointer_cast<CScraper>(addons[i]);
1528 // skip if scraper requires settings and there's nothing set yet
1529 if (!scraper || (scraper->RequiresSettings() && !scraper->HasUserSettings()))
1532 if((!preferredScraper || preferredScraper->ID() != scraper->ID()) && (!defaultScraper || defaultScraper->ID() != scraper->ID()) )
1533 vecScrapers.push_back(scraper);
1536 // add default scraper - not user selectable so it's last priority
1537 if(defaultScraper &&
1538 (!preferredScraper || preferredScraper->ID() != defaultScraper->ID()) &&
1539 (!defaultScraper->RequiresSettings() || defaultScraper->HasUserSettings()))
1540 vecScrapers.push_back(defaultScraper);
1542 for (unsigned int i=0; i < vecScrapers.size(); ++i)
1544 if (vecScrapers[i]->Type() != type)
1547 vecScrapers[i]->ClearCache();
1550 musicBrainzURL = vecScrapers[i]->ResolveIDToUrl(strMusicBrainzID);
1552 catch (const ADDON::CScraperError &sce)
1554 if (!sce.FAborted())
1557 if (!musicBrainzURL.m_url.empty())
1559 Sleep(2000); // MusicBrainz rate-limits queries to 1 p.s - once we hit the rate-limiter
1560 // they start serving up the 'you hit the rate-limiter' page fast - meaning
1561 // we will never get below the rate-limit threshold again in a specific run.
1562 // This helps us to avoidthe rate-limiter as far as possible.
1563 CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",vecScrapers[i]->Name().c_str());
1564 CLog::Log(LOGDEBUG,"-- nfo url: %s", musicBrainzURL.m_url[0].m_url.c_str());
1565 musicInfoScraper.SetScraperInfo(vecScrapers[i]);
1566 bMusicBrainz = true;
1571 return bMusicBrainz;
1574 map<string, string> CMusicInfoScanner::GetArtistArtwork(const CArtist& artist)
1576 map<string, string> artwork;
1579 CStdString strFolder;
1581 if (!artist.strPath.IsEmpty())
1583 strFolder = artist.strPath;
1584 for (int i = 0; i < 3 && thumb.IsEmpty(); ++i)
1586 CFileItem item(strFolder, true);
1587 thumb = item.GetUserMusicThumb(true);
1588 strFolder = URIUtils::GetParentPath(strFolder);
1591 if (thumb.IsEmpty())
1592 thumb = CScraperUrl::GetThumbURL(artist.thumbURL.GetFirstThumb());
1593 if (!thumb.IsEmpty())
1595 CTextureCache::Get().BackgroundCacheImage(thumb);
1596 artwork.insert(make_pair("thumb", thumb));
1601 if (!artist.strPath.IsEmpty())
1603 strFolder = artist.strPath;
1604 for (int i = 0; i < 3 && fanart.IsEmpty(); ++i)
1606 CFileItem item(strFolder, true);
1607 fanart = item.GetLocalFanart();
1608 strFolder = URIUtils::GetParentPath(strFolder);
1611 if (fanart.IsEmpty())
1612 fanart = artist.fanart.GetImageURL();
1613 if (!fanart.IsEmpty())
1615 CTextureCache::Get().BackgroundCacheImage(fanart);
1616 artwork.insert(make_pair("fanart", fanart));
1622 // This function is the Run() function of the IRunnable
1623 // CFileCountReader and runs in a separate thread.
1624 void CMusicInfoScanner::Run()
1627 for (set<std::string>::iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end() && !m_bStop; ++it)
1629 count+=CountFilesRecursively(*it);
1631 m_itemCount = count;
1634 // Recurse through all folders we scan and count files
1635 int CMusicInfoScanner::CountFilesRecursively(const CStdString& strPath)
1638 CFileItemList items;
1639 CDirectory::GetDirectory(strPath, items, g_advancedSettings.m_musicExtensions, DIR_FLAG_NO_FILE_DIRS);
1644 // true for recursive counting
1645 int count = CountFiles(items, true);
1649 int CMusicInfoScanner::CountFiles(const CFileItemList &items, bool recursive)
1652 for (int i=0; i<items.Size(); ++i)
1654 const CFileItemPtr pItem=items[i];
1656 if (recursive && pItem->m_bIsFolder)
1657 count+=CountFilesRecursively(pItem->GetPath());
1658 else if (pItem->IsAudio() && !pItem->IsPlayList() && !pItem->IsNFO())