[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / music / infoscanner / MusicInfoScanner.cpp
blob1e57245c5c7dab54b7bd7732870b83f42c385222
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "MusicInfoScanner.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUIUserMessages.h"
15 #include "MusicAlbumInfo.h"
16 #include "MusicInfoScraper.h"
17 #include "NfoFile.h"
18 #include "ServiceBroker.h"
19 #include "TextureCache.h"
20 #include "URL.h"
21 #include "Util.h"
22 #include "addons/AddonSystemSettings.h"
23 #include "addons/Scraper.h"
24 #include "addons/addoninfo/AddonType.h"
25 #include "dialogs/GUIDialogExtendedProgressBar.h"
26 #include "dialogs/GUIDialogProgress.h"
27 #include "dialogs/GUIDialogSelect.h"
28 #include "dialogs/GUIDialogYesNo.h"
29 #include "events/EventLog.h"
30 #include "events/MediaLibraryEvent.h"
31 #include "filesystem/Directory.h"
32 #include "filesystem/MusicDatabaseDirectory.h"
33 #include "filesystem/MusicDatabaseDirectory/DirectoryNode.h"
34 #include "filesystem/SmartPlaylistDirectory.h"
35 #include "guilib/GUIComponent.h"
36 #include "guilib/GUIKeyboardFactory.h"
37 #include "guilib/GUIWindowManager.h"
38 #include "guilib/LocalizeStrings.h"
39 #include "interfaces/AnnouncementManager.h"
40 #include "music/MusicFileItemClassify.h"
41 #include "music/MusicLibraryQueue.h"
42 #include "music/MusicThumbLoader.h"
43 #include "music/MusicUtils.h"
44 #include "music/tags/MusicInfoTag.h"
45 #include "music/tags/MusicInfoTagLoaderFactory.h"
46 #include "settings/AdvancedSettings.h"
47 #include "settings/Settings.h"
48 #include "settings/SettingsComponent.h"
49 #include "utils/Digest.h"
50 #include "utils/FileExtensionProvider.h"
51 #include "utils/FileUtils.h"
52 #include "utils/StringUtils.h"
53 #include "utils/URIUtils.h"
54 #include "utils/Variant.h"
55 #include "utils/log.h"
57 #include <algorithm>
58 #include <utility>
60 using namespace KODI;
61 using namespace MUSIC_INFO;
62 using namespace XFILE;
63 using namespace MUSICDATABASEDIRECTORY;
64 using namespace MUSIC_GRABBER;
65 using namespace ADDON;
66 using KODI::UTILITY::CDigest;
68 CMusicInfoScanner::CMusicInfoScanner()
69 : m_fileCountReader(this, "MusicFileCounter")
71 m_bStop = false;
72 m_currentItem=0;
73 m_itemCount=0;
74 m_flags = 0;
77 CMusicInfoScanner::~CMusicInfoScanner() = default;
79 void CMusicInfoScanner::Process()
81 m_bStop = false;
82 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::AudioLibrary, "OnScanStarted");
83 try
85 if (m_showDialog && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE))
87 CGUIDialogExtendedProgressBar* dialog =
88 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS);
89 if (dialog)
90 m_handle = dialog->GetHandle(g_localizeStrings.Get(314));
93 // check if we only need to perform a cleaning
94 if (m_bClean && m_pathsToScan.empty())
96 CMusicLibraryQueue::GetInstance().CleanLibrary(false);
97 m_handle = NULL;
98 m_bRunning = false;
100 return;
103 auto tick = std::chrono::steady_clock::now();
104 m_musicDatabase.Open();
105 m_bCanInterrupt = true;
107 if (m_scanType == 0) // load info from files
109 CLog::Log(LOGDEBUG, "{} - Starting scan", __FUNCTION__);
111 if (m_handle)
112 m_handle->SetTitle(g_localizeStrings.Get(505));
114 // Reset progress vars
115 m_currentItem=0;
116 m_itemCount=-1;
118 // Create the thread to count all files to be scanned
119 if (m_handle)
120 m_fileCountReader.Create();
122 // Database operations should not be canceled
123 // using Interrupt() while scanning as it could
124 // result in unexpected behaviour.
125 m_bCanInterrupt = false;
126 m_needsCleanup = false;
128 bool commit = true;
129 for (const auto& it : m_pathsToScan)
131 if (!CDirectory::Exists(it) && !m_bClean)
134 * Note that this will skip scanning (if m_bClean is disabled) if the directory really
135 * doesn't exist. Since the music scanner is fed with a list of existing paths from the DB
136 * and cleans out all songs under that path as its first step before re-adding files, if
137 * the entire source is offline we totally empty the music database in one go.
139 CLog::Log(LOGWARNING, "{} directory '{}' does not exist - skipping scan.", __FUNCTION__,
140 it);
141 m_seenPaths.insert(it);
142 continue;
145 // Clear list of albums added by this scan
146 m_albumsAdded.clear();
147 bool scancomplete = DoScan(it);
148 if (scancomplete)
150 if (m_albumsAdded.size() > 0)
152 // Set local art for added album disc sets and primary album artists
153 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
154 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL) !=
155 CSettings::MUSICLIBRARY_ARTWORK_LEVEL_NONE)
156 RetrieveLocalArt();
158 if (m_flags & SCAN_ONLINE)
159 // Download additional album and artist information for the recently added albums.
160 // This also identifies any local artist art if it exists, and gives it priority,
161 // otherwise it is set to the first available from the remote art that was scraped.
162 ScrapeInfoAddedAlbums();
165 else
167 commit = false;
168 break;
172 if (commit)
174 CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider().ResetLibraryBools();
176 if (m_needsCleanup)
178 if (m_handle)
180 m_handle->SetTitle(g_localizeStrings.Get(700));
181 m_handle->SetText("");
184 m_musicDatabase.CleanupOrphanedItems();
185 m_musicDatabase.CheckArtistLinksChanged();
187 if (m_handle)
188 m_handle->SetTitle(g_localizeStrings.Get(331));
190 m_musicDatabase.Compress(false);
194 m_fileCountReader.StopThread();
196 m_musicDatabase.EmptyCache();
198 auto elapsed =
199 std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - tick);
200 CLog::Log(LOGINFO,
201 "My Music: Scanning for music info using worker thread, operation took {}s",
202 elapsed.count());
204 if (m_scanType == 1) // load album info
206 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); ++it)
208 CQueryParams params;
209 CDirectoryNode::GetDatabaseInfo(*it, params);
210 // Only scrape information for albums that have not been scraped before
211 // For refresh of information the lastscraped date is optionally clearered elsewhere
212 if (m_musicDatabase.HasAlbumBeenScraped(params.GetAlbumId()))
213 continue;
215 CAlbum album;
216 m_musicDatabase.GetAlbum(params.GetAlbumId(), album);
217 if (m_handle)
219 float percentage = static_cast<float>(std::distance(m_pathsToScan.begin(), it) * 100) / static_cast<float>(m_pathsToScan.size());
220 m_handle->SetText(album.GetAlbumArtistString() + " - " + album.strAlbum);
221 m_handle->SetPercentage(percentage);
224 // find album info
225 ADDON::ScraperPtr scraper;
226 if (!m_musicDatabase.GetScraper(album.idAlbum, CONTENT_ALBUMS, scraper))
227 continue;
229 UpdateDatabaseAlbumInfo(album, scraper, false);
231 if (m_bStop)
232 break;
235 if (m_scanType == 2) // load artist info
237 for (std::set<std::string>::const_iterator it = m_pathsToScan.begin(); it != m_pathsToScan.end(); ++it)
239 CQueryParams params;
240 CDirectoryNode::GetDatabaseInfo(*it, params);
241 // Only scrape information for artists that have not been scraped before
242 // For refresh of information the lastscraped date is optionally clearered elsewhere
243 if (m_musicDatabase.HasArtistBeenScraped(params.GetArtistId()))
244 continue;
246 CArtist artist;
247 m_musicDatabase.GetArtist(params.GetArtistId(), artist);
248 m_musicDatabase.GetArtistPath(artist, artist.strPath);
250 if (m_handle)
252 float percentage = static_cast<float>(std::distance(m_pathsToScan.begin(), it) * 100) / static_cast<float>(m_pathsToScan.size());
253 m_handle->SetText(artist.strArtist);
254 m_handle->SetPercentage(percentage);
257 // find album info
258 ADDON::ScraperPtr scraper;
259 if (!m_musicDatabase.GetScraper(artist.idArtist, CONTENT_ARTISTS, scraper) || !scraper)
260 continue;
262 UpdateDatabaseArtistInfo(artist, scraper, false);
264 if (m_bStop)
265 break;
268 //propagate artist sort names to albums and songs
269 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryArtistSortOnUpdate)
270 m_musicDatabase.UpdateArtistSortNames();
272 catch (...)
274 CLog::Log(LOGERROR, "MusicInfoScanner: Exception while scanning.");
276 m_musicDatabase.Close();
277 CLog::Log(LOGDEBUG, "{} - Finished scan", __FUNCTION__);
279 m_bRunning = false;
280 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::AudioLibrary, "OnScanFinished");
282 // we need to clear the musicdb cache and update any active lists
283 CUtil::DeleteMusicDatabaseDirectoryCache();
284 CGUIMessage msg(GUI_MSG_SCAN_FINISHED, 0, 0, 0);
285 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
287 if (m_handle)
288 m_handle->MarkFinished();
289 m_handle = NULL;
292 void CMusicInfoScanner::Start(const std::string& strDirectory, int flags)
294 m_fileCountReader.StopThread();
296 m_pathsToScan.clear();
297 m_seenPaths.clear();
298 m_albumsAdded.clear();
299 m_flags = flags;
301 m_musicDatabase.Open();
302 // Check db sources match xml file and update if they don't
303 m_musicDatabase.UpdateSources();
305 if (strDirectory.empty())
306 { // Scan all paths in the database. We do this by scanning all paths in the
307 // db, and crossing them off the list as we go.
308 m_musicDatabase.GetPaths(m_pathsToScan);
309 m_idSourcePath = -1;
311 else
313 m_pathsToScan.insert(strDirectory);
314 m_idSourcePath = m_musicDatabase.GetSourceFromPath(strDirectory);
316 m_musicDatabase.Close();
318 m_bClean = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryCleanOnUpdate;
320 m_scanType = 0;
321 m_bRunning = true;
322 Process();
325 void CMusicInfoScanner::FetchAlbumInfo(const std::string& strDirectory,
326 bool refresh)
328 m_fileCountReader.StopThread();
329 m_pathsToScan.clear();
331 CFileItemList items;
332 if (strDirectory.empty())
334 m_musicDatabase.Open();
335 m_musicDatabase.GetAlbumsNav("musicdb://albums/", items);
336 m_musicDatabase.Close();
338 else
340 CURL pathToUrl(strDirectory);
342 if (pathToUrl.IsProtocol("musicdb"))
344 CQueryParams params;
345 CDirectoryNode::GetDatabaseInfo(strDirectory, params);
346 if (params.GetAlbumId() != -1)
348 //Add single album (id and path) as item to scan
349 CFileItemPtr item(new CFileItem(strDirectory, false));
350 item->GetMusicInfoTag()->SetDatabaseId(params.GetAlbumId(), MediaTypeAlbum);
351 items.Add(item);
353 else
355 CMusicDatabaseDirectory dir;
356 NODE_TYPE childtype = dir.GetDirectoryChildType(strDirectory);
357 if (childtype == NODE_TYPE_ALBUM)
358 dir.GetDirectory(pathToUrl, items);
361 else if (StringUtils::EndsWith(strDirectory, ".xsp"))
363 CSmartPlaylistDirectory dir;
364 dir.GetDirectory(pathToUrl, items);
368 m_musicDatabase.Open();
369 for (int i=0;i<items.Size();++i)
371 if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
372 continue;
374 m_pathsToScan.insert(items[i]->GetPath());
375 if (refresh)
377 m_musicDatabase.ClearAlbumLastScrapedTime(items[i]->GetMusicInfoTag()->GetDatabaseId());
380 m_musicDatabase.Close();
382 m_scanType = 1;
383 m_bRunning = true;
384 Process();
387 void CMusicInfoScanner::FetchArtistInfo(const std::string& strDirectory,
388 bool refresh)
390 m_fileCountReader.StopThread();
391 m_pathsToScan.clear();
392 CFileItemList items;
394 if (strDirectory.empty())
396 m_musicDatabase.Open();
397 m_musicDatabase.GetArtistsNav("musicdb://artists/", items, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS), -1);
398 m_musicDatabase.Close();
400 else
402 CURL pathToUrl(strDirectory);
404 if (pathToUrl.IsProtocol("musicdb"))
406 CQueryParams params;
407 CDirectoryNode::GetDatabaseInfo(strDirectory, params);
408 if (params.GetArtistId() != -1)
410 //Add single artist (id and path) as item to scan
411 CFileItemPtr item(new CFileItem(strDirectory, false));
412 item->GetMusicInfoTag()->SetDatabaseId(params.GetAlbumId(), MediaTypeArtist);
413 items.Add(item);
415 else
417 CMusicDatabaseDirectory dir;
418 NODE_TYPE childtype = dir.GetDirectoryChildType(strDirectory);
419 if (childtype == NODE_TYPE_ARTIST)
420 dir.GetDirectory(pathToUrl, items);
423 else if (StringUtils::EndsWith(strDirectory, ".xsp"))
425 CSmartPlaylistDirectory dir;
426 dir.GetDirectory(pathToUrl, items);
430 m_musicDatabase.Open();
431 for (int i=0;i<items.Size();++i)
433 if (CMusicDatabaseDirectory::IsAllItem(items[i]->GetPath()) || items[i]->IsParentFolder())
434 continue;
436 m_pathsToScan.insert(items[i]->GetPath());
437 if (refresh)
439 m_musicDatabase.ClearArtistLastScrapedTime(items[i]->GetMusicInfoTag()->GetDatabaseId());
442 m_musicDatabase.Close();
444 m_scanType = 2;
445 m_bRunning = true;
446 Process();
449 void CMusicInfoScanner::Stop()
451 if (m_bCanInterrupt)
452 m_musicDatabase.Interrupt();
454 m_bStop = true;
457 static void OnDirectoryScanned(const std::string& strDirectory)
459 CGUIMessage msg(GUI_MSG_DIRECTORY_SCANNED, 0, 0, 0);
460 msg.SetStringParam(strDirectory);
461 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
464 static std::string Prettify(const std::string& strDirectory)
466 CURL url(strDirectory);
468 return CURL::Decode(url.GetWithoutUserDetails());
471 bool CMusicInfoScanner::DoScan(const std::string& strDirectory)
473 if (m_handle)
475 m_handle->SetTitle(g_localizeStrings.Get(506)); //"Checking media files..."
476 m_handle->SetText(Prettify(strDirectory));
479 std::set<std::string>::const_iterator it = m_seenPaths.find(strDirectory);
480 if (it != m_seenPaths.end())
481 return true;
483 m_seenPaths.insert(strDirectory);
485 // Discard all excluded files defined by m_musicExcludeRegExps
486 const std::vector<std::string> &regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromScanRegExps;
488 if (CUtil::ExcludeFileOrFolder(strDirectory, regexps))
489 return true;
491 if (HasNoMedia(strDirectory))
492 return true;
494 // load subfolder
495 CFileItemList items;
496 CDirectory::GetDirectory(strDirectory, items, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions() + "|.jpg|.tbn|.lrc|.cdg", DIR_FLAG_DEFAULTS);
498 // sort and get the path hash. Note that we don't filter .cue sheet items here as we want
499 // to detect changes in the .cue sheet as well. The .cue sheet items only need filtering
500 // if we have a changed hash.
501 items.Sort(SortByLabel, SortOrderAscending);
502 std::string hash;
503 GetPathHash(items, hash);
505 // check whether we need to rescan or not
506 std::string dbHash;
507 if ((m_flags & SCAN_RESCAN) || !m_musicDatabase.GetPathHash(strDirectory, dbHash) || !StringUtils::EqualsNoCase(dbHash, hash))
508 { // path has changed - rescan
509 if (dbHash.empty())
510 CLog::Log(LOGDEBUG, "{} Scanning dir '{}' as not in the database", __FUNCTION__,
511 CURL::GetRedacted(strDirectory));
512 else
513 CLog::Log(LOGDEBUG, "{} Rescanning dir '{}' due to change", __FUNCTION__,
514 CURL::GetRedacted(strDirectory));
516 if (m_handle)
517 m_handle->SetTitle(g_localizeStrings.Get(505)); //"Loading media information from files..."
519 // filter items in the sub dir (for .cue sheet support)
520 items.FilterCueItems();
521 items.Sort(SortByLabel, SortOrderAscending);
523 // and then scan in the new information from tags
524 if (RetrieveMusicInfo(strDirectory, items) > 0)
526 if (m_handle)
527 OnDirectoryScanned(strDirectory);
530 // save information about this folder
531 m_musicDatabase.SetPathHash(strDirectory, hash);
533 else
534 { // path is the same - no need to rescan
535 CLog::Log(LOGDEBUG, "{} Skipping dir '{}' due to no change", __FUNCTION__,
536 CURL::GetRedacted(strDirectory));
537 m_currentItem += CountFiles(items, false); // false for non-recursive
539 // updated the dialog with our progress
540 if (m_handle)
542 if (m_itemCount>0)
543 m_handle->SetPercentage(static_cast<float>(m_currentItem * 100) / static_cast<float>(m_itemCount));
544 OnDirectoryScanned(strDirectory);
548 // now scan the subfolders
549 for (int i = 0; i < items.Size(); ++i)
551 CFileItemPtr pItem = items[i];
553 if (m_bStop)
554 break;
555 // if we have a directory item (non-playlist) we then recurse into that folder
556 if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList())
558 std::string strPath=pItem->GetPath();
559 if (!DoScan(strPath))
561 m_bStop = true;
565 return !m_bStop;
568 CInfoScanner::INFO_RET CMusicInfoScanner::ScanTags(const CFileItemList& items,
569 CFileItemList& scannedItems)
571 std::vector<std::string> regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromScanRegExps;
573 for (int i = 0; i < items.Size(); ++i)
575 if (m_bStop)
576 return INFO_CANCELLED;
578 CFileItemPtr pItem = items[i];
580 if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps))
581 continue;
583 if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || MUSIC::IsLyrics(*pItem))
584 continue;
586 m_currentItem++;
588 CMusicInfoTag& tag = *pItem->GetMusicInfoTag();
589 if (!tag.Loaded())
591 std::unique_ptr<IMusicInfoTagLoader> pLoader (CMusicInfoTagLoaderFactory::CreateLoader(*pItem));
592 if (nullptr != pLoader)
593 pLoader->Load(pItem->GetPath(), tag);
596 if (m_handle && m_itemCount>0)
597 m_handle->SetPercentage(static_cast<float>(m_currentItem * 100) / static_cast<float>(m_itemCount));
599 if (!tag.Loaded() && !pItem->HasCueDocument())
601 CLog::Log(LOGDEBUG, "{} - No tag found for: {}", __FUNCTION__, pItem->GetPath());
602 continue;
604 else
606 if (!tag.GetCueSheet().empty())
607 pItem->LoadEmbeddedCue();
610 if (pItem->HasCueDocument())
611 pItem->LoadTracksFromCueDocument(scannedItems);
612 else
613 scannedItems.Add(pItem);
615 return INFO_ADDED;
618 static bool SortSongsByTrack(const CSong& song, const CSong& song2)
620 return song.iTrack < song2.iTrack;
623 void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& albums, MAPSONGS* songsMap /* = NULL */)
626 * Step 1: Convert the FileItems into Songs.
627 * If they're MB tagged, create albums directly from the FileItems.
628 * If they're non-MB tagged, index them by album name ready for step 2.
630 std::map<std::string, VECSONGS> songsByAlbumNames;
631 for (int i = 0; i < items.Size(); ++i)
633 CMusicInfoTag& tag = *items[i]->GetMusicInfoTag();
634 CSong song(*items[i]);
636 // keep the db-only fields intact on rescan...
637 if (songsMap != NULL)
639 // Match up item to songs in library previously scanned with this path
640 MAPSONGS::iterator songlist = songsMap->find(items[i]->GetPath());
641 if (songlist != songsMap->end())
643 VECSONGS::iterator foundsong;
644 if (songlist->second.size() == 1)
645 foundsong = songlist->second.begin();
646 else
648 // When filename mapped to multiple songs it is from cuesheet, match on disc/track number
649 int disctrack = tag.GetTrackAndDiscNumber();
650 foundsong = std::find_if(songlist->second.begin(), songlist->second.end(),
651 [&](const CSong& song) { return disctrack == song.iTrack; });
653 if (foundsong != songlist->second.end())
655 song.idSong = foundsong->idSong; // Reuse ID
656 song.dateNew = foundsong->dateNew; // Keep date originally created
657 song.iTimesPlayed = foundsong->iTimesPlayed;
658 song.lastPlayed = foundsong->lastPlayed;
659 if (song.rating == 0)
660 song.rating = foundsong->rating;
661 if (song.userrating == 0)
662 song.userrating = foundsong->userrating;
663 if (song.strThumb.empty())
664 song.strThumb = foundsong->strThumb;
669 if (!tag.GetMusicBrainzAlbumID().empty())
671 VECALBUMS::iterator it;
672 for (it = albums.begin(); it != albums.end(); ++it)
673 if (it->strMusicBrainzAlbumID == tag.GetMusicBrainzAlbumID())
674 break;
676 if (it == albums.end())
678 CAlbum album(*items[i]);
679 album.songs.push_back(song);
680 albums.push_back(album);
682 else
683 it->songs.push_back(song);
685 else
686 songsByAlbumNames[tag.GetAlbum()].push_back(song);
690 Step 2: Split into unique albums based on album name and album artist
691 In the case where the album artist is unknown, we use the primary artist
692 (i.e. first artist from each song).
694 for (auto& songsByAlbumName : songsByAlbumNames)
696 VECSONGS& songs = songsByAlbumName.second;
697 // sort the songs by tracknumber to identify duplicate track numbers
698 sort(songs.begin(), songs.end(), SortSongsByTrack);
700 // map the songs to their primary artists
701 bool tracksOverlap = false;
702 bool hasAlbumArtist = false;
703 bool isCompilation = true;
704 std::string old_DiscSubtitle;
706 std::map<std::string, std::vector<CSong *> > artists;
707 for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
709 // test for song overlap
710 if (song != songs.begin() && song->iTrack == (song - 1)->iTrack)
711 tracksOverlap = true;
713 if (!song->bCompilation)
714 isCompilation = false;
716 if (song->strDiscSubtitle != old_DiscSubtitle)
717 old_DiscSubtitle = song->strDiscSubtitle;
719 // get primary artist
720 std::string primary;
721 if (!song->GetAlbumArtist().empty())
723 primary = song->GetAlbumArtist()[0];
724 hasAlbumArtist = true;
726 else if (!song->artistCredits.empty())
727 primary = song->artistCredits.begin()->GetArtist();
729 // add to the artist map
730 artists[primary].push_back(&(*song));
734 We have a Various Artists compilation if
735 1. album name is non-empty AND
736 2a. no tracks overlap OR
737 2b. all tracks are marked as part of compilation AND
738 3a. a unique primary artist is specified as "various", "various artists" or the localized value
740 3b. we have at least two primary artists and no album artist specified.
742 std::string various = g_localizeStrings.Get(340); // Various Artists
743 bool compilation =
744 !songsByAlbumName.first.empty() && (isCompilation || !tracksOverlap); // 1+2b+2a
745 if (artists.size() == 1)
747 std::string artist = artists.begin()->first; StringUtils::ToLower(artist);
748 if (!StringUtils::EqualsNoCase(artist, "various") &&
749 !StringUtils::EqualsNoCase(artist, "various artists") &&
750 !StringUtils::EqualsNoCase(artist, various)) // 3a
751 compilation = false;
752 else
753 // Grab name for use in "various artist" artist
754 various = artists.begin()->first;
756 else if (hasAlbumArtist) // 3b
757 compilation = false;
759 // Such a compilation album is stored under a unique artist that matches on Musicbrainz ID
760 // the "various artists" artist for music tagged with mbids.
761 if (compilation)
763 CLog::Log(LOGDEBUG, "Album '{}' is a compilation as there's no overlapping tracks and {}",
764 songsByAlbumName.first,
765 hasAlbumArtist ? "the album artist is 'Various'"
766 : "there is more than one unique artist");
767 // Clear song artists from artists map, put songs under "various artists" mbid entry
768 artists.clear();
769 for (auto& song : songs)
770 artists[VARIOUSARTISTS_MBID].push_back(&song);
774 We also have a compilation album when album name is non-empty and ALL tracks are marked as part of
775 a compilation even if an album artist is given, or all songs have the same primary artist. For
776 example an anthology - a collection of recordings from various old sources
777 combined together such as a "best of", retrospective or rarities type release.
779 Such an anthology compilation will not have been caught by the previous tests as it fails 3a and 3b.
780 The album artist can be determined just like any normal album.
782 if (!compilation && !songsByAlbumName.first.empty() && isCompilation)
784 compilation = true;
785 CLog::Log(LOGDEBUG,
786 "Album '{}' is a compilation as all songs are marked as part of a compilation",
787 songsByAlbumName.first);
791 Step 3: Find the common albumartist for each song and assign
792 albumartist to those tracks that don't have it set.
794 for (auto& j : artists)
796 /* Find the common artist(s) for these songs (grouped under primary artist).
797 Various artist compilations already under the unique "various artists" mbid.
798 Take from albumartist tag when present, or use artist tag.
799 When from albumartist tag also check albumartistsort tag and take first non-empty value
801 std::vector<CSong*>& artistSongs = j.second;
802 std::vector<std::string> common;
803 std::string albumartistsort;
804 if (artistSongs.front()->GetAlbumArtist().empty())
805 common = artistSongs.front()->GetArtist();
806 else
808 common = artistSongs.front()->GetAlbumArtist();
809 albumartistsort = artistSongs.front()->GetAlbumArtistSort();
811 for (std::vector<CSong *>::iterator k = artistSongs.begin() + 1; k != artistSongs.end(); ++k)
813 unsigned int match = 0;
814 std::vector<std::string> compare;
815 if ((*k)->GetAlbumArtist().empty())
816 compare = (*k)->GetArtist();
817 else
819 compare = (*k)->GetAlbumArtist();
820 if (albumartistsort.empty())
821 albumartistsort = (*k)->GetAlbumArtistSort();
823 for (; match < common.size() && match < compare.size(); match++)
825 if (compare[match] != common[match])
826 break;
828 common.erase(common.begin() + match, common.end());
830 if (j.first == VARIOUSARTISTS_MBID)
832 common.clear();
833 common.emplace_back(VARIOUSARTISTS_MBID);
837 Step 4: Assign the album artist for each song that doesn't have it set
838 and add to the album vector
840 CAlbum album;
841 album.strAlbum = songsByAlbumName.first;
843 //Split the albumartist sort string to try and get sort names for individual artists
844 std::vector<std::string> sortnames = StringUtils::Split(albumartistsort, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
845 if (sortnames.size() != common.size())
846 // Split artist sort names further using multiple possible delimiters, over single separator applied in Tag loader
847 sortnames = StringUtils::SplitMulti(sortnames, { ";", ":", "|", "#" });
849 for (size_t i = 0; i < common.size(); i++)
851 if (common[i] == VARIOUSARTISTS_MBID)
852 /* Treat "various", "various artists" and the localized equivalent name as the same
853 album artist as the artist with Musicbrainz ID 89ad4ac3-39f7-470e-963a-56509c546377.
854 If adding this artist for the first time then the name will be set to either the primary
855 artist read from tags when 3a, or the localized value for "various artists" when not 3a.
856 This means that tag values are no longer translated into the current language.
858 album.artistCredits.emplace_back(various, VARIOUSARTISTS_MBID);
859 else
861 album.artistCredits.emplace_back(StringUtils::Trim(common[i]));
862 // Set artist sort name providing we have as many as we have artists,
863 // otherwise something is wrong with them so ignore rather than guess.
864 if (sortnames.size() == common.size())
865 album.artistCredits.back().SetSortName(StringUtils::Trim(sortnames[i]));
868 album.bCompilation = compilation;
869 for (auto& k : artistSongs)
871 if (k->GetAlbumArtist().empty())
872 k->SetAlbumArtist(common);
873 //! @todo in future we may wish to union up the genres, for now we assume they're the same
874 album.genre = k->genre;
875 album.strArtistSort = k->GetAlbumArtistSort();
876 // in addition, we may want to use release date as discriminating between albums
877 album.strReleaseDate = k->strReleaseDate,
878 album.strLabel = k->strRecordLabel;
879 album.strType = k->strAlbumType;
880 album.songs.push_back(*k);
882 albums.push_back(album);
887 CInfoScanner::INFO_RET
888 CMusicInfoScanner::UpdateAlbumInfo(CAlbum& album,
889 const ADDON::ScraperPtr& scraper,
890 bool bAllowSelection,
891 CGUIDialogProgress* pDialog)
893 m_musicDatabase.Open();
894 INFO_RET result = UpdateDatabaseAlbumInfo(album, scraper, bAllowSelection, pDialog);
895 m_musicDatabase.Close();
896 return result;
899 CInfoScanner::INFO_RET
900 CMusicInfoScanner::UpdateArtistInfo(CArtist& artist,
901 const ADDON::ScraperPtr& scraper,
902 bool bAllowSelection,
903 CGUIDialogProgress* pDialog)
905 m_musicDatabase.Open();
906 INFO_RET result = UpdateDatabaseArtistInfo(artist, scraper, bAllowSelection, pDialog);
907 m_musicDatabase.Close();
908 return result;
911 int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileItemList& items)
913 MAPSONGS songsMap;
915 // get all information for all files in current directory from database, and remove them
916 if (m_musicDatabase.RemoveSongsFromPath(strDirectory, songsMap))
917 m_needsCleanup = true;
919 CFileItemList scannedItems;
920 if (ScanTags(items, scannedItems) == INFO_CANCELLED || scannedItems.Size() == 0)
921 return 0;
923 VECALBUMS albums;
924 FileItemsToAlbums(scannedItems, albums, &songsMap);
927 Set thumb for songs and, if only one album in folder, store the thumb for
928 the album (music db) and the folder path (in Textures db) too.
929 The album and path thumb is either set to the folder art, or failing that to
930 the art embedded in the first music file.
931 Song thumb is only set when it varies, otherwise it is cleared so that it will
932 fallback to the album art (that may be from the first file, or that of the
933 folder or set later by scraping from NFO files or remote sources). Clearing
934 saves caching repeats of the same image.
936 However even if all songs are from one album this may not be the album
937 folder. It could be just a subfolder containing some of the songs from a disc
938 set e.g. CD1, CD2 etc., or the album could spread across many folders. In
939 this case the album art gets reset every time a folder with songs from just
940 that album is processed, and needs to be corrected later once all the parts
941 of the album have been scanned.
943 FindArtForAlbums(albums, items.GetPath());
945 /* Strategy: Having scanned tags and made a list of albums, add them to the library. Only then try
946 to scrape additional album and artist information. Music is often tagged to a mixed standard
947 - some albums have mbid tags, some don't. Once all the music files have been added to the library,
948 the mbid for an artist will be known even if it was only tagged on one song. The artist is best
949 scraped with an mbid, so scrape after all the files that may provide that tag have been scanned.
950 That artist mbid can then be used to improve the accuracy of scraping other albums by that artist
951 even when it was not in the tagging for that album.
953 Doing scraping, generally the slower activity, in the background after scanning has fully populated
954 the library also means that the user can use their library to select music to play sooner.
957 int numAdded = 0;
959 // Add all albums to the library, and hence any new song or album artists or other contributors
960 for (auto& album : albums)
962 if (m_bStop)
963 break;
965 // mark albums without a title as singles
966 if (album.strAlbum.empty())
967 album.releaseType = CAlbum::Single;
969 album.strPath = strDirectory;
970 m_musicDatabase.AddAlbum(album, m_idSourcePath);
971 m_albumsAdded.insert(album.idAlbum);
973 numAdded += static_cast<int>(album.songs.size());
975 return numAdded;
978 void MUSIC_INFO::CMusicInfoScanner::ScrapeInfoAddedAlbums()
980 /* Strategy: Having scanned tags, make a list of albums and add them to the library, only then try
981 to scrape additional album and artist information. Music is often tagged to a mixed standard
982 - some albums have mbid tags, some don't. Once all the music files have been added to the library,
983 the mbid for an artist will be known even if it was only tagged on one song. The artist is best
984 scraped with an mbid, so scrape after all the files that may provide that tag have been scanned.
985 That artist mbid can then be used to improve the accuracy of scraping other albums by that artist
986 even when it was not in the tagging for that album.
988 Doing scraping, generally the slower activity, in the background after scanning has fully populated
989 the library also means that the user can use their library to select music to play sooner.
992 /* Scrape additional album and artist data.
993 For albums and artists without mbids, matching on album-artist pair can
994 be used to identify artist with greater accuracy than artist name alone.
995 Artist mbid returned by album scraper is used if we do not already have it.
996 Hence scrape album then related artists.
998 ADDON::AddonPtr addon;
1000 ADDON::ScraperPtr albumScraper;
1001 ADDON::ScraperPtr artistScraper;
1002 if (ADDON::CAddonSystemSettings::GetInstance().GetActive(ADDON::AddonType::SCRAPER_ALBUMS, addon))
1003 albumScraper = std::dynamic_pointer_cast<ADDON::CScraper>(addon);
1005 if (ADDON::CAddonSystemSettings::GetInstance().GetActive(ADDON::AddonType::SCRAPER_ARTISTS,
1006 addon))
1007 artistScraper = std::dynamic_pointer_cast<ADDON::CScraper>(addon);
1009 bool albumartistsonly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS);
1011 if (!albumScraper || !artistScraper)
1012 return;
1014 int i = 0;
1015 std::set<int> artists;
1016 for (auto albumId : m_albumsAdded)
1018 i++;
1019 if (m_bStop)
1020 break;
1021 // Scrape album data
1022 CAlbum album;
1023 if (!m_musicDatabase.HasAlbumBeenScraped(albumId))
1025 if (m_handle)
1027 m_handle->SetText(album.GetAlbumArtistString() + " - " + album.strAlbum);
1028 m_handle->SetProgress(i, static_cast<int>(m_albumsAdded.size()));
1031 // Fetch any artist mbids for album artist(s) and song artists when scraping those too.
1032 m_musicDatabase.GetAlbum(albumId, album, !albumartistsonly);
1033 UpdateDatabaseAlbumInfo(album, albumScraper, false);
1035 // Scrape information for artists that have not been scraped before, avoiding repeating
1036 // unsuccessful attempts for every album and song.
1037 for (const auto &artistCredit : album.artistCredits)
1039 if (m_bStop)
1040 break;
1042 if (!m_musicDatabase.HasArtistBeenScraped(artistCredit.GetArtistId()) &&
1043 artists.find(artistCredit.GetArtistId()) == artists.end())
1045 artists.insert(artistCredit.GetArtistId()); // Artist scraping attempted
1046 CArtist artist;
1047 m_musicDatabase.GetArtist(artistCredit.GetArtistId(), artist);
1048 UpdateDatabaseArtistInfo(artist, artistScraper, false);
1051 // Only scrape song artists if they are being displayed in artists node by default
1052 if (!albumartistsonly)
1054 for (auto &song : album.songs)
1056 if (m_bStop)
1057 break;
1058 for (const auto &artistCredit : song.artistCredits)
1060 if (m_bStop)
1061 break;
1063 CMusicArtistInfo musicArtistInfo;
1064 if (!m_musicDatabase.HasArtistBeenScraped(artistCredit.GetArtistId()) &&
1065 artists.find(artistCredit.GetArtistId()) == artists.end())
1067 artists.insert(artistCredit.GetArtistId()); // Artist scraping attempted
1068 CArtist artist;
1069 m_musicDatabase.GetArtist(artistCredit.GetArtistId(), artist);
1070 UpdateDatabaseArtistInfo(artist, artistScraper, false);
1080 Set thumb for songs and the album(if only one album in folder).
1081 The album thumb is either set to the folder art, or failing that to the art
1082 embedded in the first music file. However this does not allow for there being
1083 other folders with more songs from the album e.g. this was a subfolder CD1
1084 and there is CD2 etc. yet to be processed
1085 Song thumb is only set when it varies, otherwise it is cleared so that it will
1086 fallback to the album art(that may be from the first file, or that of the
1087 folder or set later by scraping from NFO files or remote sources).Clearing
1088 saves caching repeats of the same image.
1090 void CMusicInfoScanner::FindArtForAlbums(VECALBUMS &albums, const std::string &path)
1093 If there's a single album in the folder, then art can be taken from
1094 the folder art.
1096 std::string albumArt;
1097 if (albums.size() == 1)
1099 CFileItem album(path, true);
1101 If we are scanning a directory served over http(s) the root directory for an album will set
1102 IsInternetStream to true which prevents scanning it for art. As we can't reach this point
1103 without having read some tags (and tags are not read from streams) we can safely check for
1104 that case and set the IsHTTPDirectory property to enable scanning for art.
1106 if (StringUtils::StartsWithNoCase(path, "http") && StringUtils::EndsWith(path, "/"))
1107 album.SetProperty("IsHTTPDirectory", true);
1108 albumArt = album.GetUserMusicThumb(true);
1109 if (!albumArt.empty())
1110 albums[0].art["thumb"] = albumArt;
1112 for (auto& album : albums)
1114 if (albums.size() != 1)
1115 albumArt = "";
1118 Find art that is common across these items
1119 If we find a single art image we treat it as the album art
1120 and discard song art else we use first as album art and
1121 keep everything as song art.
1123 bool singleArt = true;
1124 CSong *art = NULL;
1125 for (auto& song : album.songs)
1127 if (song.HasArt())
1129 if (art && !art->ArtMatches(song))
1131 singleArt = false;
1132 break;
1134 if (!art)
1135 art = &song;
1140 assign the first art found to the album - better than no art at all
1143 if (art && albumArt.empty())
1145 if (!art->strThumb.empty())
1146 albumArt = art->strThumb;
1147 else
1148 albumArt = CTextureUtils::GetWrappedImageURL(art->strFileName, "music");
1151 if (!albumArt.empty())
1152 album.art["thumb"] = albumArt;
1154 if (singleArt)
1155 { //if singleArt then we can clear the artwork for all songs
1156 for (auto& k : album.songs)
1157 k.strThumb.clear();
1159 else
1160 { // more than one piece of art was found for these songs, so cache per song
1161 for (auto& k : album.songs)
1163 if (k.strThumb.empty() && !k.embeddedArt.Empty())
1164 k.strThumb = CTextureUtils::GetWrappedImageURL(k.strFileName, "music");
1168 if (albums.size() == 1 && !albumArt.empty())
1170 // assign to folder thumb as well
1171 CFileItem albumItem(path, true);
1172 CMusicThumbLoader loader;
1173 loader.SetCachedImage(albumItem, "thumb", albumArt);
1177 void MUSIC_INFO::CMusicInfoScanner::RetrieveLocalArt()
1179 if (m_handle)
1181 m_handle->SetTitle(g_localizeStrings.Get(506)); //"Checking media files..."
1182 //!@todo: title = Checking for local art
1185 std::set<int> artistsArtDone; // artists processed to avoid unsuccessful repeats
1186 int count = 0;
1187 for (auto albumId : m_albumsAdded)
1189 count++;
1190 if (m_bStop)
1191 break;
1192 CAlbum album;
1193 m_musicDatabase.GetAlbum(albumId, album, false);
1194 if (m_handle)
1196 m_handle->SetText(album.GetAlbumArtistString() + " - " + album.strAlbum);
1197 m_handle->SetProgress(count, static_cast<int>(m_albumsAdded.size()));
1201 Automatically fetch local art from album folder and any disc sets subfolders
1203 Providing all songs from an album are are under a unique common album
1204 folder (no songs from other albums) then thumb has been set to local art,
1205 or failing that to embedded art, during scanning by FindArtForAlbums().
1206 But when songs are also spread over multiple subfolders within it e.g. disc
1207 sets, it will have been set to either the art of the last subfolder that was
1208 processed (if there is any), or from the first song in that subfolder with
1209 embedded art (if there is any). To correct this and find any thumb in the
1210 (common) album folder add "thumb" to those missing.
1212 AddAlbumArtwork(album);
1215 Local album artist art
1217 Look in the nominated "Artist Information Folder" for thumbs and fanart.
1218 Failing that, for backward compatibility, fallback to the folder immediately
1219 above the album folder.
1220 It can only fallback if the album has a unique folder, and can only do so
1221 for the first album artist if the album is a collaboration e.g. composer,
1222 conductor, orchestra, or by several pop artists in their own right.
1223 Avoids repeatedly processing the same artist by maintaining a set.
1225 Adding the album may have added new artists, or provide art for an existing
1226 (song) artist, but does not replace any artwork already set. Hence once art
1227 has been found for an album artist, art is not searched for in other folders.
1229 It will find art for "various artists", if artwork is located above the
1230 folder containing compilatons.
1232 for (auto artistCredit = album.artistCredits.begin(); artistCredit != album.artistCredits.end(); ++artistCredit)
1234 if (m_bStop)
1235 break;
1236 int idArtist = artistCredit->GetArtistId();
1237 if (artistsArtDone.find(idArtist) == artistsArtDone.end())
1239 artistsArtDone.insert(idArtist); // Artist processed
1241 // Get artist and subfolder within the Artist Information Folder
1242 CArtist artist;
1243 m_musicDatabase.GetArtist(idArtist, artist);
1244 m_musicDatabase.GetArtistPath(artist, artist.strPath);
1245 // Location of local art
1246 std::string artfolder;
1247 if (CDirectory::Exists(artist.strPath))
1248 // When subfolder exists that is only place we look for local art
1249 artfolder = artist.strPath;
1250 else if (!album.strPath.empty() && artistCredit == album.artistCredits.begin())
1252 // If no individual artist subfolder has been found, for primary
1253 // album artist only look in the folder immediately above the album
1254 // folder. Not using GetOldArtistPath here because may not have not
1255 // have scanned all the albums yet.
1256 artfolder = URIUtils::GetParentPath(album.strPath);
1258 AddArtistArtwork(artist, artfolder);
1264 int CMusicInfoScanner::GetPathHash(const CFileItemList &items, std::string &hash)
1266 // Create a hash based on the filenames, filesize and filedate. Also count the number of files
1267 if (0 == items.Size()) return 0;
1268 CDigest digest{CDigest::Type::MD5};
1269 int count = 0;
1270 for (int i = 0; i < items.Size(); ++i)
1272 const CFileItemPtr pItem = items[i];
1273 digest.Update(pItem->GetPath());
1274 digest.Update((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1275 KODI::TIME::FileTime time = pItem->m_dateTime;
1276 digest.Update((unsigned char*)&time, sizeof(KODI::TIME::FileTime));
1277 if (MUSIC::IsAudio(*pItem) && !pItem->IsPlayList() && !pItem->IsNFO())
1278 count++;
1280 hash = digest.Finalize();
1281 return count;
1284 CInfoScanner::INFO_RET
1285 CMusicInfoScanner::UpdateDatabaseAlbumInfo(CAlbum& album,
1286 const ADDON::ScraperPtr& scraper,
1287 bool bAllowSelection,
1288 CGUIDialogProgress* pDialog /* = NULL */)
1290 if (!scraper)
1291 return INFO_ERROR;
1293 CMusicAlbumInfo albumInfo;
1294 INFO_RET albumDownloadStatus(INFO_CANCELLED);
1295 std::string origArtist(album.GetAlbumArtistString());
1296 std::string origAlbum(album.strAlbum);
1298 bool stop(false);
1299 while (!stop)
1301 stop = true;
1302 CLog::Log(LOGDEBUG, "{} downloading info for: {}", __FUNCTION__, album.strAlbum);
1303 albumDownloadStatus = DownloadAlbumInfo(album, scraper, albumInfo, !bAllowSelection, pDialog);
1304 if (albumDownloadStatus == INFO_NOT_FOUND)
1306 if (pDialog && bAllowSelection)
1308 std::string strTempAlbum(album.strAlbum);
1309 if (!CGUIKeyboardFactory::ShowAndGetInput(strTempAlbum, CVariant{ g_localizeStrings.Get(16011) }, false))
1310 albumDownloadStatus = INFO_CANCELLED;
1311 else
1313 std::string strTempArtist(album.GetAlbumArtistString());
1314 if (!CGUIKeyboardFactory::ShowAndGetInput(strTempArtist, CVariant{ g_localizeStrings.Get(16025) }, false))
1315 albumDownloadStatus = INFO_CANCELLED;
1316 else
1318 album.strAlbum = strTempAlbum;
1319 album.strArtistDesc = strTempArtist;
1320 stop = false;
1324 else
1326 auto eventLog = CServiceBroker::GetEventLog();
1327 if (eventLog)
1328 eventLog->Add(EventPtr(new CMediaLibraryEvent(
1329 MediaTypeAlbum, album.strPath, 24146,
1330 StringUtils::Format(g_localizeStrings.Get(24147), MediaTypeAlbum, album.strAlbum),
1331 CScraperUrl::GetThumbUrl(album.thumbURL.GetFirstUrlByType()),
1332 CURL::GetRedacted(album.strPath), EventLevel::Warning)));
1337 // Restore original album and artist name, possibly changed by manual entry
1338 // to get info but should not change outside merge
1339 album.strAlbum = origAlbum;
1340 album.strArtistDesc = origArtist;
1342 if (albumDownloadStatus == INFO_ADDED)
1344 bool overridetags = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_OVERRIDETAGS);
1345 // Remove art accidentally set by the Python scraper, it only provides URLs of possible artwork
1346 // Art is selected later applying whitelist and other art preferences
1347 albumInfo.GetAlbum().art.clear();
1348 album.MergeScrapedAlbum(albumInfo.GetAlbum(), overridetags);
1349 m_musicDatabase.UpdateAlbum(album);
1350 albumInfo.SetLoaded(true);
1353 // Check album art.
1354 // Fill any gaps with local art files or use first available from scraped URL list (when it has
1355 // been successfully scraped) as controlled by whitelist. Do this even when no info added
1356 // (cancelled, not found or error), there may be new local art files.
1357 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
1358 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL) !=
1359 CSettings::MUSICLIBRARY_ARTWORK_LEVEL_NONE &&
1360 AddAlbumArtwork(album))
1361 albumDownloadStatus = INFO_ADDED; // Local art added
1363 return albumDownloadStatus;
1366 CInfoScanner::INFO_RET
1367 CMusicInfoScanner::UpdateDatabaseArtistInfo(CArtist& artist,
1368 const ADDON::ScraperPtr& scraper,
1369 bool bAllowSelection,
1370 CGUIDialogProgress* pDialog /* = NULL */)
1372 if (!scraper)
1373 return INFO_ERROR;
1375 CMusicArtistInfo artistInfo;
1376 INFO_RET artistDownloadStatus(INFO_CANCELLED);
1377 std::string origArtist(artist.strArtist);
1379 bool stop(false);
1380 while (!stop)
1382 stop = true;
1383 CLog::Log(LOGDEBUG, "{} downloading info for: {}", __FUNCTION__, artist.strArtist);
1384 artistDownloadStatus = DownloadArtistInfo(artist, scraper, artistInfo, !bAllowSelection, pDialog);
1385 if (artistDownloadStatus == INFO_NOT_FOUND)
1387 if (pDialog && bAllowSelection)
1389 if (!CGUIKeyboardFactory::ShowAndGetInput(artist.strArtist, CVariant{ g_localizeStrings.Get(16025) }, false))
1390 artistDownloadStatus = INFO_CANCELLED;
1391 else
1392 stop = false;
1394 else
1396 auto eventLog = CServiceBroker::GetEventLog();
1397 if (eventLog)
1398 eventLog->Add(EventPtr(new CMediaLibraryEvent(
1399 MediaTypeArtist, artist.strPath, 24146,
1400 StringUtils::Format(g_localizeStrings.Get(24147), MediaTypeArtist, artist.strArtist),
1401 CScraperUrl::GetThumbUrl(artist.thumbURL.GetFirstUrlByType()),
1402 CURL::GetRedacted(artist.strPath), EventLevel::Warning)));
1407 // Restore original artist name, possibly changed by manual entry to get info
1408 // but should not change outside merge
1409 artist.strArtist = origArtist;
1411 if (artistDownloadStatus == INFO_ADDED)
1413 artist.MergeScrapedArtist(artistInfo.GetArtist(), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_OVERRIDETAGS));
1414 m_musicDatabase.UpdateArtist(artist);
1415 artistInfo.SetLoaded();
1418 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
1419 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL) ==
1420 CSettings::MUSICLIBRARY_ARTWORK_LEVEL_NONE)
1421 return artistDownloadStatus;
1423 // Check artist art.
1424 // Fill any gaps with local art files, or use first available from scraped
1425 // list (when it has been successfully scraped). Do this even when no info
1426 // added (cancelled, not found or error), there may be new local art files.
1427 // Get individual artist subfolder within the Artist Information Folder
1428 m_musicDatabase.GetArtistPath(artist, artist.strPath);
1429 // Location of local art
1430 std::string artfolder;
1431 if (CDirectory::Exists(artist.strPath))
1432 // When subfolder exists that is only place we look for art
1433 artfolder = artist.strPath;
1434 else
1436 // Fallback to the old location local to music files (when there is a
1437 // unique folder). If there is no folder for the artist, and *only* the
1438 // artist, this will be blank
1439 m_musicDatabase.GetOldArtistPath(artist.idArtist, artfolder);
1441 if (AddArtistArtwork(artist, artfolder))
1442 artistDownloadStatus = INFO_ADDED; // Local art added
1444 return artistDownloadStatus; // Added, cancelled or not found
1447 #define THRESHOLD .95f
1449 CInfoScanner::INFO_RET
1450 CMusicInfoScanner::DownloadAlbumInfo(const CAlbum& album,
1451 const ADDON::ScraperPtr& info,
1452 CMusicAlbumInfo& albumInfo,
1453 bool bUseScrapedMBID,
1454 CGUIDialogProgress* pDialog)
1456 if (m_handle)
1458 m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20321), info->Name()));
1459 m_handle->SetText(album.GetAlbumArtistString() + " - " + album.strAlbum);
1462 // clear our scraper cache
1463 info->ClearCache();
1465 CMusicInfoScraper scraper(info);
1466 bool bMusicBrainz = false;
1468 When the mbid is derived from tags scraping of album information is done directly
1469 using that ID, otherwise the lookup is based on album and artist names and can mis-identify the
1470 album (i.e. classical music has many "Symphony No. 5"). To be able to correct any mistakes a
1471 manual refresh of artist information uses either the mbid if derived from tags or the album
1472 and artist names, not any previously scraped mbid.
1474 if (!album.strMusicBrainzAlbumID.empty() && (!album.bScrapedMBID || bUseScrapedMBID))
1476 CScraperUrl musicBrainzURL;
1477 if (ResolveMusicBrainz(album.strMusicBrainzAlbumID, info, musicBrainzURL))
1479 CMusicAlbumInfo albumNfo("nfo", musicBrainzURL);
1480 scraper.GetAlbums().clear();
1481 scraper.GetAlbums().push_back(albumNfo);
1482 bMusicBrainz = true;
1486 // handle nfo files
1487 bool existsNFO = false;
1488 std::string path = album.strPath;
1489 if (path.empty())
1490 m_musicDatabase.GetAlbumPath(album.idAlbum, path);
1492 std::string strNfo = URIUtils::AddFileToFolder(path, "album.nfo");
1493 CInfoScanner::INFO_TYPE result = CInfoScanner::NO_NFO;
1494 CNfoFile nfoReader;
1495 existsNFO = CFileUtils::Exists(strNfo);
1496 // When on GUI ask user if they want to ignore nfo and refresh from Internet
1497 if (existsNFO && pDialog && CGUIDialogYesNo::ShowAndGetInput(10523, 20446))
1499 existsNFO = false;
1500 CLog::Log(LOGDEBUG, "Ignoring nfo file: {}", CURL::GetRedacted(strNfo));
1502 if (existsNFO)
1504 CLog::Log(LOGDEBUG, "Found matching nfo file: {}", CURL::GetRedacted(strNfo));
1505 result = nfoReader.Create(strNfo, info);
1506 if (result == CInfoScanner::FULL_NFO)
1508 CLog::Log(LOGDEBUG, "{} Got details from nfo", __FUNCTION__);
1509 nfoReader.GetDetails(albumInfo.GetAlbum());
1510 return INFO_ADDED;
1512 else if (result == CInfoScanner::URL_NFO ||
1513 result == CInfoScanner::COMBINED_NFO)
1515 CScraperUrl scrUrl(nfoReader.ScraperUrl());
1516 CMusicAlbumInfo albumNfo("nfo",scrUrl);
1517 ADDON::ScraperPtr nfoReaderScraper = nfoReader.GetScraperInfo();
1518 CLog::Log(LOGDEBUG, "-- nfo-scraper: {}", nfoReaderScraper->Name());
1519 CLog::Log(LOGDEBUG, "-- nfo url: {}", scrUrl.GetFirstThumbUrl());
1520 scraper.SetScraperInfo(nfoReaderScraper);
1521 scraper.GetAlbums().clear();
1522 scraper.GetAlbums().push_back(albumNfo);
1524 else if (result != CInfoScanner::OVERRIDE_NFO)
1525 CLog::Log(LOGERROR, "Unable to find an url in nfo file: {}", strNfo);
1528 if (!scraper.CheckValidOrFallback(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER)))
1529 { // the current scraper is invalid, as is the default - bail
1530 CLog::Log(LOGERROR, "{} - current and default scrapers are invalid. Pick another one",
1531 __FUNCTION__);
1532 return INFO_ERROR;
1535 if (!scraper.GetAlbumCount())
1537 scraper.FindAlbumInfo(album.strAlbum, album.GetAlbumArtistString());
1539 while (!scraper.Completed())
1541 if (m_bStop)
1543 scraper.Cancel();
1544 return INFO_CANCELLED;
1546 ScannerWait(1);
1549 Finding album using xml scraper may request data from Musicbrainz.
1550 MusicBrainz rate-limits queries to 1 per sec, once we hit the rate-limiter the server
1551 returns 503 errors for all calls from that IP address.
1552 To stay below the rate-limit threshold wait 1s before proceeding
1554 if (!info->IsPython())
1555 ScannerWait(1000);
1558 CGUIDialogSelect *pDlg = NULL;
1559 int iSelectedAlbum=0;
1560 if ((result == CInfoScanner::NO_NFO || result == CInfoScanner::OVERRIDE_NFO)
1561 && !bMusicBrainz)
1563 iSelectedAlbum = -1; // set negative so that we can detect a failure
1564 if (scraper.Succeeded() && scraper.GetAlbumCount() >= 1)
1566 double bestRelevance = 0;
1567 double minRelevance = static_cast<double>(THRESHOLD);
1568 if (pDialog || scraper.GetAlbumCount() > 1) // score the matches
1570 //show dialog with all albums found
1571 if (pDialog)
1573 pDlg = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
1574 pDlg->SetHeading(CVariant{g_localizeStrings.Get(181)});
1575 pDlg->Reset();
1576 pDlg->EnableButton(true, 413); // manual
1577 pDlg->SetUseDetails(true);
1580 CFileItemList items;
1581 for (int i = 0; i < scraper.GetAlbumCount(); ++i)
1583 CMusicAlbumInfo& info = scraper.GetAlbum(i);
1584 double relevance = static_cast<double>(info.GetRelevance());
1585 if (relevance < 0)
1586 relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum, album.strAlbum,
1587 info.GetAlbum().GetAlbumArtistString(),
1588 album.GetAlbumArtistString());
1590 // if we're doing auto-selection (ie querying all albums at once, then allow 95->100% for perfect matches)
1591 // otherwise, perfect matches only
1592 if (relevance >= std::max(minRelevance, bestRelevance))
1593 { // we auto-select the best of these
1594 bestRelevance = relevance;
1595 iSelectedAlbum = i;
1597 if (pDialog)
1599 // set the label to [relevance] album - artist
1600 std::string strTemp = StringUtils::Format("[{:0.2f}] {}", relevance, info.GetTitle2());
1601 CFileItemPtr item(new CFileItem("", false));
1602 item->SetLabel(strTemp);
1604 std::string strTemp2;
1605 if (!scraper.GetAlbum(i).GetAlbum().strType.empty())
1606 strTemp2 += scraper.GetAlbum(i).GetAlbum().strType;
1607 if (!scraper.GetAlbum(i).GetAlbum().strReleaseDate.empty())
1608 strTemp2 += " - " + scraper.GetAlbum(i).GetAlbum().strReleaseDate;
1609 if (!scraper.GetAlbum(i).GetAlbum().strReleaseStatus.empty())
1610 strTemp2 += " - " + scraper.GetAlbum(i).GetAlbum().strReleaseStatus;
1611 if (!scraper.GetAlbum(i).GetAlbum().strLabel.empty())
1612 strTemp2 += " - " + scraper.GetAlbum(i).GetAlbum().strLabel;
1613 item->SetLabel2(strTemp2);
1615 item->SetArt(scraper.GetAlbum(i).GetAlbum().art);
1617 items.Add(item);
1619 if (!pDialog && relevance > 0.999) // we're so close, no reason to search further
1620 break;
1623 if (pDialog)
1625 pDlg->Sort(false);
1626 pDlg->SetItems(items);
1627 pDlg->Open();
1629 // and wait till user selects one
1630 if (pDlg->GetSelectedItem() < 0)
1631 { // none chosen
1632 if (!pDlg->IsButtonPressed())
1633 return INFO_CANCELLED;
1635 // manual button pressed
1636 std::string strNewAlbum = album.strAlbum;
1637 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewAlbum, CVariant{g_localizeStrings.Get(16011)}, false))
1638 return INFO_CANCELLED;
1639 if (strNewAlbum == "")
1640 return INFO_CANCELLED;
1642 std::string strNewArtist = album.GetAlbumArtistString();
1643 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, CVariant{g_localizeStrings.Get(16025)}, false))
1644 return INFO_CANCELLED;
1646 pDialog->SetLine(0, CVariant{strNewAlbum});
1647 pDialog->SetLine(1, CVariant{strNewArtist});
1648 pDialog->Progress();
1650 CAlbum newAlbum = album;
1651 newAlbum.strAlbum = strNewAlbum;
1652 newAlbum.strArtistDesc = strNewArtist;
1654 return DownloadAlbumInfo(newAlbum, info, albumInfo, bUseScrapedMBID, pDialog);
1656 iSelectedAlbum = pDlg->GetSelectedItem();
1659 else
1661 CMusicAlbumInfo& info = scraper.GetAlbum(0);
1662 double relevance = static_cast<double>(info.GetRelevance());
1663 if (relevance < 0)
1664 relevance = CUtil::AlbumRelevance(info.GetAlbum().strAlbum,
1665 album.strAlbum,
1666 info.GetAlbum().GetAlbumArtistString(),
1667 album.GetAlbumArtistString());
1668 if (relevance < static_cast<double>(THRESHOLD))
1669 return INFO_NOT_FOUND;
1671 iSelectedAlbum = 0;
1675 if (iSelectedAlbum < 0)
1676 return INFO_NOT_FOUND;
1680 scraper.LoadAlbumInfo(iSelectedAlbum);
1681 while (!scraper.Completed())
1683 if (m_bStop)
1685 scraper.Cancel();
1686 return INFO_CANCELLED;
1688 ScannerWait(1);
1690 if (!scraper.Succeeded())
1691 return INFO_ERROR;
1693 Fetching album details using xml scraper may makes requests for data from Musicbrainz.
1694 MusicBrainz rate-limits queries to 1 per sec, once we hit the rate-limiter the server
1695 returns 503 errors for all calls from that IP address.
1696 To stay below the rate-limit threshold wait 1s before proceeding incase next action is
1697 to scrape another album or artist
1699 if (!info->IsPython())
1700 ScannerWait(1000);
1702 albumInfo = scraper.GetAlbum(iSelectedAlbum);
1704 if (result == CInfoScanner::COMBINED_NFO || result == CInfoScanner::OVERRIDE_NFO)
1705 nfoReader.GetDetails(albumInfo.GetAlbum(), NULL, true);
1707 return INFO_ADDED;
1710 CInfoScanner::INFO_RET
1711 CMusicInfoScanner::DownloadArtistInfo(const CArtist& artist,
1712 const ADDON::ScraperPtr& info,
1713 MUSIC_GRABBER::CMusicArtistInfo& artistInfo,
1714 bool bUseScrapedMBID,
1715 CGUIDialogProgress* pDialog)
1717 if (m_handle)
1719 m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20320), info->Name()));
1720 m_handle->SetText(artist.strArtist);
1723 // clear our scraper cache
1724 info->ClearCache();
1726 CMusicInfoScraper scraper(info);
1727 bool bMusicBrainz = false;
1729 When the mbid is derived from tags scraping of artist information is done directly
1730 using that ID, otherwise the lookup is based on name and can mis-identify the artist
1731 (many have same name). To be able to correct any mistakes a manual refresh of artist
1732 information uses either the mbid if derived from tags or the artist name, not any previously
1733 scraped mbid.
1735 if (!artist.strMusicBrainzArtistID.empty() && (!artist.bScrapedMBID || bUseScrapedMBID))
1737 CScraperUrl musicBrainzURL;
1738 if (ResolveMusicBrainz(artist.strMusicBrainzArtistID, info, musicBrainzURL))
1740 CMusicArtistInfo artistNfo("nfo", musicBrainzURL);
1741 scraper.GetArtists().clear();
1742 scraper.GetArtists().push_back(artistNfo);
1743 bMusicBrainz = true;
1747 // Handle nfo files
1748 CInfoScanner::INFO_TYPE result = CInfoScanner::NO_NFO;
1749 CNfoFile nfoReader;
1750 std::string strNfo;
1751 std::string path;
1752 bool existsNFO = false;
1753 // First look for nfo in the artists folder, the primary location
1754 path = artist.strPath;
1755 // Get path when don't already have it.
1756 bool artistpathfound = !path.empty();
1757 if (!artistpathfound)
1758 artistpathfound = m_musicDatabase.GetArtistPath(artist, path);
1759 if (artistpathfound)
1761 strNfo = URIUtils::AddFileToFolder(path, "artist.nfo");
1762 existsNFO = CFileUtils::Exists(strNfo);
1765 // If not there fall back local to music files (historic location for those album artists with a unique folder)
1766 if (!existsNFO)
1768 artistpathfound = m_musicDatabase.GetOldArtistPath(artist.idArtist, path);
1769 if (artistpathfound)
1771 strNfo = URIUtils::AddFileToFolder(path, "artist.nfo");
1772 existsNFO = CFileUtils::Exists(strNfo);
1774 else
1775 CLog::Log(LOGDEBUG, "{} not have path, nfo file not possible", artist.strArtist);
1778 // When on GUI ask user if they want to ignore nfo and refresh from Internet
1779 if (existsNFO && pDialog && CGUIDialogYesNo::ShowAndGetInput(21891, 20446))
1781 existsNFO = false;
1782 CLog::Log(LOGDEBUG, "Ignoring nfo file: {}", CURL::GetRedacted(strNfo));
1785 if (existsNFO)
1787 CLog::Log(LOGDEBUG, "Found matching nfo file: {}", CURL::GetRedacted(strNfo));
1788 result = nfoReader.Create(strNfo, info);
1789 if (result == CInfoScanner::FULL_NFO)
1791 CLog::Log(LOGDEBUG, "{} Got details from nfo", __FUNCTION__);
1792 nfoReader.GetDetails(artistInfo.GetArtist());
1793 return INFO_ADDED;
1795 else if (result == CInfoScanner::URL_NFO || result == CInfoScanner::COMBINED_NFO)
1797 CScraperUrl scrUrl(nfoReader.ScraperUrl());
1798 CMusicArtistInfo artistNfo("nfo", scrUrl);
1799 ADDON::ScraperPtr nfoReaderScraper = nfoReader.GetScraperInfo();
1800 CLog::Log(LOGDEBUG, "-- nfo-scraper: {}", nfoReaderScraper->Name());
1801 CLog::Log(LOGDEBUG, "-- nfo url: {}", scrUrl.GetFirstThumbUrl());
1802 scraper.SetScraperInfo(nfoReaderScraper);
1803 scraper.GetArtists().push_back(artistNfo);
1805 else
1806 CLog::Log(LOGERROR, "Unable to find an url in nfo file: {}", strNfo);
1809 if (!scraper.GetArtistCount())
1811 scraper.FindArtistInfo(artist.strArtist);
1813 while (!scraper.Completed())
1815 if (m_bStop)
1817 scraper.Cancel();
1818 return INFO_CANCELLED;
1820 ScannerWait(1);
1823 Finding artist using xml scraper makes a request for data from Musicbrainz.
1824 MusicBrainz rate-limits queries to 1 per sec, once we hit the rate-limiter
1825 the server returns 503 errors for all calls from that IP address. To stay
1826 below the rate-limit threshold wait 1s before proceeding
1828 if (!info->IsPython())
1829 ScannerWait(1000);
1832 int iSelectedArtist = 0;
1833 if (result == CInfoScanner::NO_NFO && !bMusicBrainz)
1835 if (scraper.GetArtistCount() >= 1)
1837 // now load the first match
1838 if (pDialog && scraper.GetArtistCount() > 1)
1840 // if we found more then 1 album, let user choose one
1841 CGUIDialogSelect *pDlg = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
1842 if (pDlg)
1844 pDlg->SetHeading(CVariant{g_localizeStrings.Get(21890)});
1845 pDlg->Reset();
1846 pDlg->EnableButton(true, 413); // manual
1848 for (int i = 0; i < scraper.GetArtistCount(); ++i)
1850 // set the label to artist
1851 CFileItem item(scraper.GetArtist(i).GetArtist());
1852 std::string strTemp = scraper.GetArtist(i).GetArtist().strArtist;
1853 if (!scraper.GetArtist(i).GetArtist().strBorn.empty())
1854 strTemp += " ("+scraper.GetArtist(i).GetArtist().strBorn+")";
1855 if (!scraper.GetArtist(i).GetArtist().strDisambiguation.empty())
1856 strTemp += " - " + scraper.GetArtist(i).GetArtist().strDisambiguation;
1857 if (!scraper.GetArtist(i).GetArtist().genre.empty())
1859 std::string genres = StringUtils::Join(scraper.GetArtist(i).GetArtist().genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
1860 if (!genres.empty())
1861 strTemp = StringUtils::Format("[{}] {}", genres, strTemp);
1863 item.SetLabel(strTemp);
1864 item.m_idepth = i; // use this to hold the index of the album in the scraper
1865 pDlg->Add(item);
1867 pDlg->Open();
1869 // and wait till user selects one
1870 if (pDlg->GetSelectedItem() < 0)
1871 { // none chosen
1872 if (!pDlg->IsButtonPressed())
1873 return INFO_CANCELLED;
1875 // manual button pressed
1876 std::string strNewArtist = artist.strArtist;
1877 if (!CGUIKeyboardFactory::ShowAndGetInput(strNewArtist, CVariant{g_localizeStrings.Get(16025)}, false))
1878 return INFO_CANCELLED;
1880 if (pDialog)
1882 pDialog->SetLine(0, CVariant{strNewArtist});
1883 pDialog->Progress();
1886 CArtist newArtist;
1887 newArtist.strArtist = strNewArtist;
1888 return DownloadArtistInfo(newArtist, info, artistInfo, bUseScrapedMBID, pDialog);
1890 iSelectedArtist = pDlg->GetSelectedFileItem()->m_idepth;
1894 else
1895 return INFO_NOT_FOUND;
1898 Fetching artist details using xml scraper makes requests for data from Musicbrainz.
1899 MusicBrainz rate-limits queries to 1 per sec, once we hit the rate-limiter the server
1900 returns 503 errors for all calls from that IP address.
1901 To stay below the rate-limit threshold wait 1s before proceeding incase next action is
1902 to scrape another album or artist
1904 if (!info->IsPython())
1905 ScannerWait(1000);
1907 scraper.LoadArtistInfo(iSelectedArtist, artist.strArtist);
1908 while (!scraper.Completed())
1910 if (m_bStop)
1912 scraper.Cancel();
1913 return INFO_CANCELLED;
1915 ScannerWait(1);
1918 if (!scraper.Succeeded())
1919 return INFO_ERROR;
1921 artistInfo = scraper.GetArtist(iSelectedArtist);
1923 if (result == CInfoScanner::COMBINED_NFO)
1924 nfoReader.GetDetails(artistInfo.GetArtist(), NULL, true);
1926 return INFO_ADDED;
1929 bool CMusicInfoScanner::ResolveMusicBrainz(const std::string &strMusicBrainzID, const ScraperPtr &preferredScraper, CScraperUrl &musicBrainzURL)
1931 // We have a MusicBrainz ID
1932 // Get a scraper that can resolve it to a MusicBrainz URL & force our
1933 // search directly to the specific album.
1934 bool bMusicBrainz = false;
1937 musicBrainzURL = preferredScraper->ResolveIDToUrl(strMusicBrainzID);
1939 catch (const ADDON::CScraperError &sce)
1941 if (sce.FAborted())
1942 return false;
1945 if (musicBrainzURL.HasUrls())
1947 CLog::Log(LOGDEBUG, "-- nfo-scraper: {}", preferredScraper->Name());
1948 CLog::Log(LOGDEBUG, "-- nfo url: {}", musicBrainzURL.GetFirstThumbUrl());
1949 bMusicBrainz = true;
1952 return bMusicBrainz;
1955 void CMusicInfoScanner::ScannerWait(unsigned int milliseconds)
1957 if (milliseconds > 10)
1959 CEvent m_StopEvent;
1960 m_StopEvent.Wait(std::chrono::milliseconds(milliseconds));
1962 else
1963 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
1966 bool CMusicInfoScanner::AddArtistArtwork(CArtist& artist, const std::string& artfolder)
1968 if (!artist.thumbURL.HasUrls() && artfolder.empty())
1969 return false; // No local or scraped possible art to process
1971 if (artist.art.empty())
1972 m_musicDatabase.GetArtForItem(artist.idArtist, MediaTypeArtist, artist.art);
1974 std::map<std::string, std::string> addedart;
1975 std::string strArt;
1977 // Handle thumb separately, can be from multiple confgurable file names
1978 if (artist.art.find("thumb") == artist.art.end())
1980 if (!artfolder.empty())
1981 { // Local music thumbnail images named by "musiclibrary.musicthumbs"
1982 CFileItem item(artfolder, true);
1983 strArt = item.GetUserMusicThumb(true);
1985 if (strArt.empty())
1986 strArt = CScraperUrl::GetThumbUrl(artist.thumbURL.GetFirstUrlByType("thumb"));
1987 if (!strArt.empty())
1988 addedart.insert(std::make_pair("thumb", strArt));
1991 // Process additional art types in artist folder
1992 AddLocalArtwork(addedart, MediaTypeArtist, artist.strArtist, artfolder);
1994 // Process remote artist art filling gaps with first of scraped art URLs
1995 AddRemoteArtwork(addedart, MediaTypeArtist, artist.thumbURL);
1997 int iArtLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
1998 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL);
2000 for (const auto& it : addedart)
2002 // Cache thumb, fanart and other whitelisted artwork immediately
2003 // (other art types will be cached when first displayed)
2004 if (iArtLevel != CSettings::MUSICLIBRARY_ARTWORK_LEVEL_ALL || it.first == "thumb" ||
2005 it.first == "fanart")
2006 CServiceBroker::GetTextureCache()->BackgroundCacheImage(it.second);
2007 auto ret = artist.art.insert(it);
2008 if (ret.second)
2009 m_musicDatabase.SetArtForItem(artist.idArtist, MediaTypeArtist, it.first, it.second);
2011 return addedart.size() > 0;
2014 bool CMusicInfoScanner::AddAlbumArtwork(CAlbum& album)
2016 // Fetch album path and any subfolders (disc sets).
2017 // No paths found when songs from different albums are in one folder
2018 std::vector<std::pair<std::string, int>> paths;
2019 m_musicDatabase.GetAlbumPaths(album.idAlbum, paths);
2020 for (const auto& pathpair : paths)
2022 if (album.strPath.empty())
2023 album.strPath = pathpair.first.c_str();
2024 else
2025 // When more than one album path is the common path
2026 URIUtils::GetCommonPath(album.strPath, pathpair.first.c_str());
2029 if (!album.thumbURL.HasUrls() && album.strPath.empty())
2030 return false; // No local or scraped possible art to process
2032 if (album.art.empty())
2033 m_musicDatabase.GetArtForItem(album.idAlbum, MediaTypeAlbum, album.art);
2034 auto thumb = album.art.find("thumb"); // Find "thumb", may want to replace it
2036 bool replaceThumb = paths.size() > 1;
2037 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
2038 CSettings::SETTING_MUSICLIBRARY_PREFERONLINEALBUMART))
2040 // When "prefer online album art" enabled and we have a thumb as embedded art
2041 // then replace it if we find a scraped cover
2042 if (thumb != album.art.end() && StringUtils::StartsWith(thumb->second, "image://"))
2043 replaceThumb = true;
2046 std::map<std::string, std::string> addedart;
2047 std::string strArt;
2049 // Fetch local art from album folder
2050 // Handle thumbs separately, can be from multiple confgurable file names
2051 if (replaceThumb || thumb == album.art.end())
2053 if (!album.strPath.empty())
2054 { // Local music thumbnail images named by "musiclibrary.musicthumbs"
2055 CFileItem item(album.strPath, true);
2056 strArt = item.GetUserMusicThumb(true);
2058 if (strArt.empty())
2059 strArt = CScraperUrl::GetThumbUrl(album.thumbURL.GetFirstUrlByType("thumb"));
2060 if (!strArt.empty())
2062 if (thumb != album.art.end())
2063 album.art.erase(thumb);
2064 addedart.insert(std::make_pair("thumb", strArt));
2067 // Process additional art types in album folder
2068 AddLocalArtwork(addedart, MediaTypeAlbum, album.strAlbum, album.strPath);
2070 // Fetch local art from disc subfolders
2071 if (paths.size() > 1)
2073 CMusicThumbLoader loader;
2074 std::string firstDiscThumb;
2075 int iDiscThumb = 10000;
2076 for (const auto& pathpair : paths)
2078 strArt.clear();
2080 int discnum = m_musicDatabase.GetDiscnumberForPathID(pathpair.second);
2081 if (discnum > 0)
2083 // Handle thumbs separately. Get thumb for path from textures db cached during scan
2084 // (could be embedded or local file from multiple confgurable file names)
2085 CFileItem item(pathpair.first.c_str(), true);
2086 std::string strArtType = StringUtils::Format("{}{}", "thumb", discnum);
2087 strArt = loader.GetCachedImage(item, "thumb");
2088 if (strArt.empty())
2089 strArt = CScraperUrl::GetThumbUrl(album.thumbURL.GetFirstUrlByType(strArtType));
2090 if (!strArt.empty())
2092 addedart.insert(std::make_pair(strArtType, strArt));
2093 // Store thumb of first disc with a thumb
2094 if (discnum < iDiscThumb)
2096 iDiscThumb = discnum;
2097 firstDiscThumb = strArt;
2101 // Process additional art types in disc subfolder
2102 AddLocalArtwork(addedart, MediaTypeAlbum, album.strAlbum, pathpair.first, discnum);
2104 // Finally if we still don't have album thumb then use the art from the
2105 // first disc in the set with a thumb
2106 if (!firstDiscThumb.empty() && album.art.find("thumb") == album.art.end())
2108 m_musicDatabase.SetArtForItem(album.idAlbum, MediaTypeAlbum, "thumb", firstDiscThumb);
2109 // Assign art as folder thumb (in textures db) as well
2111 CFileItem albumItem(album.strPath, true);
2112 loader.SetCachedImage(albumItem, "thumb", firstDiscThumb);
2116 // Process remote album art filling gaps with first of scraped art URLs
2117 AddRemoteArtwork(addedart, MediaTypeAlbum, album.thumbURL);
2119 int iArtLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
2120 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL);
2121 for (const auto& it : addedart)
2123 // Cache thumb, fanart and whitelisted artwork immediately
2124 // (other art types will be cached when first displayed)
2125 if (iArtLevel != CSettings::MUSICLIBRARY_ARTWORK_LEVEL_ALL || it.first == "thumb" ||
2126 it.first == "fanart")
2127 CServiceBroker::GetTextureCache()->BackgroundCacheImage(it.second);
2129 auto ret = album.art.insert(it);
2130 if (ret.second)
2131 m_musicDatabase.SetArtForItem(album.idAlbum, MediaTypeAlbum, it.first, it.second);
2133 return addedart.size() > 0;
2136 std::vector<CVariant> CMusicInfoScanner::GetArtWhitelist(const MediaType& mediaType, int iArtLevel)
2138 std::vector<CVariant> whitelistarttypes;
2139 if (iArtLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_BASIC)
2141 // Basic artist artwork = thumb + fanart (but not "family" fanart1, fanart2 etc.)
2142 // Basic album artwork = thumb only, thumb handled separately not in whitelist
2143 if (mediaType == MediaTypeArtist)
2144 whitelistarttypes.emplace_back("fanart");
2146 else
2148 if (mediaType == MediaTypeArtist)
2149 whitelistarttypes = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
2150 CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST);
2151 else
2152 whitelistarttypes = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
2153 CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST);
2156 return whitelistarttypes;
2159 bool CMusicInfoScanner::AddLocalArtwork(std::map<std::string, std::string>& art,
2160 const std::string& mediaType,
2161 const std::string& mediaName,
2162 const std::string& artfolder,
2163 int discnum)
2165 if (artfolder.empty())
2166 return false;
2168 int iArtLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
2169 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL);
2171 std::vector<CVariant> whitelistarttypes = GetArtWhitelist(mediaType, iArtLevel);
2172 bool bUseAll = (iArtLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_ALL) ||
2173 ((iArtLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_CUSTOM) &&
2174 CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
2175 CSettings::SETTING_MUSICLIBRARY_USEALLLOCALART));
2177 // Not useall and empty whitelist means no extra art is picked up from either place
2178 if (!bUseAll && whitelistarttypes.empty())
2179 return false;
2181 // Image files used as thumbs
2182 std::vector<CVariant> thumbs = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
2183 CSettings::SETTING_MUSICLIBRARY_MUSICTHUMBS);
2185 // Find local art
2186 CFileItemList availableArtFiles;
2187 CDirectory::GetDirectory(artfolder, availableArtFiles,
2188 CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(),
2189 DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
2191 for (const auto& artFile : availableArtFiles)
2193 if (artFile->m_bIsFolder)
2194 continue;
2195 std::string strCandidate = URIUtils::GetFileName(artFile->GetPath());
2196 // Strip media name
2197 if (!mediaName.empty() && StringUtils::StartsWith(strCandidate, mediaName))
2198 strCandidate.erase(0, mediaName.length());
2199 StringUtils::ToLower(strCandidate);
2200 // Skip files already used as "thumb"
2201 // Typically folder.jpg but can be from multiple confgurable file names
2202 if (std::find(thumbs.begin(), thumbs.end(), strCandidate) != thumbs.end())
2203 continue;
2204 // Grab and strip file extension
2205 std::string strExt;
2206 size_t period = strCandidate.find_last_of("./\\");
2207 if (period != std::string::npos && strCandidate[period] == '.')
2209 strExt = strCandidate.substr(period); // ".jpg", ".png" etc.
2210 strCandidate.erase(period); // "abc16" for file Abc16.jpg
2212 if (strCandidate.empty())
2213 continue;
2214 // Validate art type name
2215 size_t last_index = strCandidate.find_last_not_of("0123456789");
2216 std::string strDigits = strCandidate.substr(last_index + 1);
2217 std::string strFamily = strCandidate.substr(0, last_index + 1); // "abc" of "abc16"
2218 if (strFamily.empty())
2219 continue;
2220 if (!MUSIC_UTILS::IsValidArtType(strCandidate))
2221 continue;
2222 // Disc specific art from disc subfolder
2223 // Skip art where digits of filename do not match disc number
2224 if (discnum > 0 && !strDigits.empty() && (atoi(strDigits.c_str()) != discnum))
2225 continue;
2227 // Use all art, or check for basic level art in whitelist exactly allowing for disc number,
2228 // or for custom art check whitelist contains art type family (strip trailing digits)
2229 // e.g. 'fanart', 'fanart1', 'fanart2' etc. all match whitelist entry 'fanart'
2230 std::string strCheck = strCandidate;
2231 if (discnum > 0 || iArtLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_CUSTOM)
2232 strCheck = strFamily;
2233 if (bUseAll || std::find(whitelistarttypes.begin(), whitelistarttypes.end(), strCheck) !=
2234 whitelistarttypes.end())
2236 if (!strDigits.empty())
2238 // Catch any variants of music thumbs e.g. folder2.jpg as "thumb2"
2239 // Used for disc sets when files all in one album folder
2240 if (std::find(thumbs.begin(), thumbs.end(), strFamily + strExt) != thumbs.end())
2241 strCandidate = "thumb" + strDigits;
2243 else if (discnum > 0)
2244 // Append disc number when candidate art type (and file) not have it
2245 strCandidate += std::to_string(discnum);
2247 if (art.find(strCandidate) == art.end())
2248 art.insert(std::make_pair(strCandidate, artFile->GetPath()));
2252 return art.size() > 0;
2255 bool CMusicInfoScanner::AddRemoteArtwork(std::map<std::string, std::string>& art,
2256 const std::string& mediaType,
2257 const CScraperUrl& thumbURL)
2259 int iArtLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
2260 CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL);
2262 std::vector<CVariant> whitelistarttypes = GetArtWhitelist(mediaType, iArtLevel);
2263 bool bUseAll = (iArtLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_ALL) ||
2264 ((iArtLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_CUSTOM) &&
2265 CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
2266 CSettings::SETTING_MUSICLIBRARY_USEALLREMOTEART));
2268 // not useall and empty whitelist means no extra art is picked up from either place
2269 if (!bUseAll && whitelistarttypes.empty())
2270 return false;
2272 // Add online art
2273 // Done for artists and albums, so not need repeating at disc level
2274 for (const auto& url : thumbURL.GetUrls())
2276 // Art type is encoded into the scraper XML held in thumbURL as optional "aspect=" field.
2277 // Those URL without aspect are also returned for all other type values.
2278 // Loop through all the first URLS of each type except "thumb" and add if art missing
2279 if (url.m_aspect.empty() || url.m_aspect == "thumb")
2280 continue;
2281 if (!bUseAll)
2282 { // Check whitelist for art type family e.g. "discart" for aspect="discart2"
2283 std::string strName = url.m_aspect;
2284 if (iArtLevel != CSettings::MUSICLIBRARY_ARTWORK_LEVEL_BASIC)
2285 StringUtils::TrimRight(strName, "0123456789");
2286 if (std::find(whitelistarttypes.begin(), whitelistarttypes.end(), strName) ==
2287 whitelistarttypes.end())
2288 continue;
2290 if (art.find(url.m_aspect) == art.end())
2292 std::string strArt = CScraperUrl::GetThumbUrl(url);
2293 if (!strArt.empty())
2294 art.insert(std::make_pair(url.m_aspect, strArt));
2298 return art.size() > 0;
2301 // This function is the Run() function of the IRunnable
2302 // CFileCountReader and runs in a separate thread.
2303 void CMusicInfoScanner::Run()
2305 int count = 0;
2306 for (auto& it : m_pathsToScan)
2308 count += CountFilesRecursively(it);
2310 m_itemCount = count;
2313 // Recurse through all folders we scan and count files
2314 int CMusicInfoScanner::CountFilesRecursively(const std::string& strPath)
2316 // load subfolder
2317 CFileItemList items;
2318 CDirectory::GetDirectory(strPath, items, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), DIR_FLAG_NO_FILE_DIRS);
2320 if (m_bStop)
2321 return 0;
2323 // true for recursive counting
2324 int count = CountFiles(items, true);
2325 return count;
2328 int CMusicInfoScanner::CountFiles(const CFileItemList &items, bool recursive)
2330 int count = 0;
2331 for (int i=0; i<items.Size(); ++i)
2333 const CFileItemPtr pItem=items[i];
2335 if (recursive && pItem->m_bIsFolder)
2336 count+=CountFilesRecursively(pItem->GetPath());
2337 else if (MUSIC::IsAudio(*pItem) && !pItem->IsPlayList() && !pItem->IsNFO())
2338 count++;
2340 return count;