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.
9 #include "GUIDialogSongInfo.h"
11 #include "GUIDialogMusicInfo.h"
12 #include "GUIPassword.h"
13 #include "GUIUserMessages.h"
14 #include "ServiceBroker.h"
15 #include "TextureCache.h"
17 #include "dialogs/GUIDialogBusy.h"
18 #include "dialogs/GUIDialogFileBrowser.h"
19 #include "guilib/GUIComponent.h"
20 #include "guilib/GUIWindowManager.h"
21 #include "guilib/LocalizeStrings.h"
22 #include "input/actions/Action.h"
23 #include "input/actions/ActionIDs.h"
24 #include "music/MusicDatabase.h"
25 #include "music/MusicFileItemClassify.h"
26 #include "music/MusicUtils.h"
27 #include "music/tags/MusicInfoTag.h"
28 #include "music/windows/GUIWindowMusicBase.h"
29 #include "profiles/ProfileManager.h"
30 #include "settings/MediaSourceSettings.h"
31 #include "settings/SettingsComponent.h"
32 #include "storage/MediaManager.h"
33 #include "utils/FileUtils.h"
37 #define CONTROL_BTN_REFRESH 6
38 #define CONTROL_USERRATING 7
39 #define CONTROL_BTN_PLAY 8
40 #define CONTROL_BTN_GET_THUMB 10
41 #define CONTROL_ALBUMINFO 12
43 #define CONTROL_LIST 50
45 #define TIME_TO_BUSY_DIALOG 500
47 class CGetSongInfoJob
: public CJob
50 ~CGetSongInfoJob(void) override
= default;
52 // Fetch full song information including art types list
53 bool DoWork() override
55 CGUIDialogSongInfo
*dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSongInfo
>(WINDOW_DIALOG_SONG_INFO
);
58 if (dialog
->IsCancelled())
60 CFileItemPtr m_song
= dialog
->GetCurrentListItem();
62 // Fetch tag data from library using filename of item path, or scanning file
63 // (if item does not already have this loaded)
64 if (!m_song
->LoadMusicTag())
66 // Stop SongInfoDialog waiting
67 dialog
->FetchComplete();
70 if (dialog
->IsCancelled())
72 // Fetch album and primary song artist data from library as properties
73 // and lyrics by scanning tags from file
74 MUSIC_INFO::CMusicInfoLoader::LoadAdditionalTagInfo(m_song
.get());
75 if (dialog
->IsCancelled())
78 // Get album path (for use in browsing art selection)
79 std::string albumpath
;
82 db
.GetAlbumPath(m_song
->GetMusicInfoTag()->GetAlbumId(), albumpath
);
83 m_song
->SetProperty("album_path", albumpath
);
85 if (dialog
->IsCancelled())
89 // For songs in library this includes related album and artist(s) art.
90 // Also fetches artist art for non library songs when artist can be found
91 // uniquely by name, otherwise just embedded or cached thumb is fetched.
92 CMusicThumbLoader loader
;
93 loader
.LoadItem(m_song
.get());
94 if (dialog
->IsCancelled())
97 // For songs in library fill vector of possible art types, with current art when it exists
98 // for display on the art type selection dialog
99 CFileItemList artlist
;
100 MUSIC_UTILS::FillArtTypesList(*m_song
, artlist
);
101 dialog
->SetArtTypeList(artlist
);
102 if (dialog
->IsCancelled())
105 // Tell waiting SongInfoDialog that job is complete
106 dialog
->FetchComplete();
112 CGUIDialogSongInfo::CGUIDialogSongInfo(void)
113 : CGUIDialog(WINDOW_DIALOG_SONG_INFO
, "DialogMusicInfo.xml")
114 , m_song(new CFileItem
)
117 m_hasUpdatedUserrating
= false;
118 m_startUserrating
= -1;
119 m_artTypeList
.Clear();
120 m_loadType
= KEEP_IN_MEMORY
;
123 CGUIDialogSongInfo::~CGUIDialogSongInfo(void) = default;
125 bool CGUIDialogSongInfo::OnMessage(CGUIMessage
& message
)
127 switch (message
.GetMessage())
129 case GUI_MSG_WINDOW_DEINIT
:
131 m_artTypeList
.Clear();
132 if (m_startUserrating
!= m_song
->GetMusicInfoTag()->GetUserrating())
134 m_hasUpdatedUserrating
= true;
136 // Asynchronously update song userrating in library
137 MUSIC_UTILS::UpdateSongRatingJob(m_song
, m_song
->GetMusicInfoTag()->GetUserrating());
139 // Send a message to all windows to tell them to update the fileitem
140 // This communicates the rating change to the music lib window, current playlist and OSD.
141 // The music lib window item is updated to but changes to the rating when it is the sort
142 // do not show on screen until refresh() that fetches the list from scratch, sorts etc.
143 CGUIMessage
msg(GUI_MSG_NOTIFY_ALL
, 0, 0, GUI_MSG_UPDATE_ITEM
, 0, m_song
);
144 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
146 CGUIMessage
msg(GUI_MSG_LABEL_RESET
, GetID(), CONTROL_LIST
);
150 case GUI_MSG_WINDOW_INIT
:
151 CGUIDialog::OnMessage(message
);
156 case GUI_MSG_CLICKED
:
158 int iControl
= message
.GetSenderId();
159 if (iControl
== CONTROL_USERRATING
)
163 else if (iControl
== CONTROL_ALBUMINFO
)
165 CGUIDialogMusicInfo::ShowForAlbum(m_albumId
);
168 else if (iControl
== CONTROL_BTN_GET_THUMB
)
173 else if (iControl
== CONTROL_LIST
)
175 int iAction
= message
.GetParam1();
176 if ((ACTION_SELECT_ITEM
== iAction
|| ACTION_MOUSE_LEFT_CLICK
== iAction
))
178 CGUIMessage
msg(GUI_MSG_ITEM_SELECTED
, GetID(), iControl
);
179 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
180 int iItem
= msg
.GetParam1();
181 if (iItem
< 0 || iItem
>= static_cast<int>(m_song
->GetMusicInfoTag()->GetContributors().size()))
183 int idArtist
= m_song
->GetMusicInfoTag()->GetContributors()[iItem
].GetArtistId();
185 CGUIDialogMusicInfo::ShowForArtist(idArtist
);
189 else if (iControl
== CONTROL_BTN_PLAY
)
199 return CGUIDialog::OnMessage(message
);
202 bool CGUIDialogSongInfo::OnAction(const CAction
& action
)
204 int userrating
= m_song
->GetMusicInfoTag()->GetUserrating();
205 if (action
.GetID() == ACTION_INCREASE_RATING
)
207 SetUserrating(userrating
+ 1);
210 else if (action
.GetID() == ACTION_DECREASE_RATING
)
212 SetUserrating(userrating
- 1);
215 else if (action
.GetID() == ACTION_SHOW_INFO
)
220 return CGUIDialog::OnAction(action
);
223 bool CGUIDialogSongInfo::OnBack(int actionID
)
226 return CGUIDialog::OnBack(actionID
);
229 void CGUIDialogSongInfo::FetchComplete()
231 //Trigger the event to indicate data has been fetched
235 void CGUIDialogSongInfo::OnInitWindow()
237 // Enable album info button when we know album
238 m_albumId
= m_song
->GetMusicInfoTag()->GetAlbumId();
240 CONTROL_ENABLE_ON_CONDITION(CONTROL_ALBUMINFO
, m_albumId
> 0);
242 // Disable music user rating button for plugins as they don't have tables to save this
243 if (m_song
->IsPlugin())
244 CONTROL_DISABLE(CONTROL_USERRATING
);
246 CONTROL_ENABLE(CONTROL_USERRATING
);
248 // Disable the Choose Art button if the user isn't allowed it
249 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
250 CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB
,
251 profileManager
->GetCurrentProfile().canWriteDatabases() || g_passwordManager
.bMasterUser
);
253 SET_CONTROL_HIDDEN(CONTROL_BTN_REFRESH
);
254 SET_CONTROL_LABEL(CONTROL_USERRATING
, 38023);
255 SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB
, 13511);
256 SET_CONTROL_LABEL(CONTROL_ALBUMINFO
, 10523);
257 SET_CONTROL_LABEL(CONTROL_BTN_PLAY
, 208);
259 CGUIDialog::OnInitWindow();
262 void CGUIDialogSongInfo::Update()
265 for (const auto& contributor
: m_song
->GetMusicInfoTag()->GetContributors())
267 auto item
= std::make_shared
<CFileItem
>(contributor
.GetRoleDesc());
268 item
->SetLabel2(contributor
.GetArtist());
269 item
->GetMusicInfoTag()->SetDatabaseId(contributor
.GetArtistId(), MediaTypeArtist
);
270 items
.Add(std::move(item
));
272 CGUIMessage
message(GUI_MSG_LABEL_BIND
, GetID(), CONTROL_LIST
, 0, 0, &items
);
276 void CGUIDialogSongInfo::SetUserrating(int userrating
)
278 userrating
= std::max(userrating
, 0);
279 userrating
= std::min(userrating
, 10);
280 if (userrating
!= m_song
->GetMusicInfoTag()->GetUserrating())
282 m_song
->GetMusicInfoTag()->SetUserrating(userrating
);
286 bool CGUIDialogSongInfo::SetSong(CFileItem
* item
)
290 m_cancelled
= false; // SetSong happens before win_init
291 // In a separate job fetch song info and fill list of art types.
293 CServiceBroker::GetJobManager()->AddJob(new CGetSongInfoJob(), nullptr, CJob::PRIORITY_LOW
);
295 // Wait to get all data before show, allowing user to cancel if fetch is slow
296 if (!CGUIDialogBusy::WaitOnEvent(m_event
, TIME_TO_BUSY_DIALOG
))
298 // Cancel job still waiting in queue (unlikely)
299 CServiceBroker::GetJobManager()->CancelJob(jobid
);
300 // Flag to stop job already in progress
305 // Store initial userrating
306 m_startUserrating
= m_song
->GetMusicInfoTag()->GetUserrating();
307 m_hasUpdatedUserrating
= false;
311 void CGUIDialogSongInfo::SetArtTypeList(CFileItemList
& artlist
)
313 m_artTypeList
.Copy(artlist
);
316 CFileItemPtr
CGUIDialogSongInfo::GetCurrentListItem(int offset
)
321 std::string
CGUIDialogSongInfo::GetContent()
327 Allow user to choose artwork for the song
328 For each type of art the options are:
330 2. Local art (thumb found by filename)
331 3. Embedded art (@todo)
333 Note that songs are not scraped, hence there is no list of urls for possible remote art
335 void CGUIDialogSongInfo::OnGetArt()
337 std::string type
= MUSIC_UTILS::ShowSelectArtTypeDialog(m_artTypeList
);
342 CGUIListItem::ArtMap primeArt
= m_song
->GetArt(); // Song art without fallbacks
343 bool bHasArt
= m_song
->HasArt(type
);
344 bool bFallback(false);
347 // Check if that type of art is actually a fallback, e.g. album thumb or artist fanart
348 CGUIListItem::ArtMap::const_iterator i
= primeArt
.find(type
);
349 bFallback
= (i
== primeArt
.end());
352 // Build list of possible images of that art type
355 // Add item for current artwork, could a fallback from album/artist
356 CFileItemPtr
item(new CFileItem("thumb://Current", false));
357 item
->SetArt("thumb", m_song
->GetArt(type
));
358 item
->SetArt("icon", "DefaultPicture.png");
359 item
->SetLabel(g_localizeStrings
.Get(13512)); //! @todo: label fallback art so user knows?
362 else if (m_song
->HasArt("thumb"))
363 { // For missing art of that type add the thumb (when it exists and not a fallback)
364 CGUIListItem::ArtMap::const_iterator i
= primeArt
.find("thumb");
365 if (i
!= primeArt
.end())
367 CFileItemPtr
item(new CFileItem("thumb://Thumb", false));
368 item
->SetArt("thumb", m_song
->GetArt("thumb"));
369 item
->SetArt("icon", "DefaultAlbumCover.png");
370 item
->SetLabel(g_localizeStrings
.Get(21371));
375 std::string localThumb
;
377 { // Local thumb type art held in <filename>.tbn (for non-library items)
378 localThumb
= m_song
->GetUserMusicThumb(true);
379 if (MUSIC::IsMusicDb(*m_song
))
381 CFileItem
item(m_song
->GetMusicInfoTag()->GetURL(), false);
382 localThumb
= item
.GetUserMusicThumb(true);
384 if (CFileUtils::Exists(localThumb
))
386 CFileItemPtr
item(new CFileItem("thumb://Local", false));
387 item
->SetArt("thumb", localThumb
);
388 item
->SetLabel(g_localizeStrings
.Get(20017));
393 // Clear these local images from cache so user will see any recent
394 // local file changes immediately
395 for (auto& item
: items
)
397 std::string
thumb(item
->GetArt("thumb"));
400 CServiceBroker::GetTextureCache()->ClearCachedImage(thumb
);
401 // Remove any thumbnail of local image too (created when browsing files)
402 std::string
thumbthumb(CTextureUtils::GetWrappedThumbURL(thumb
));
403 CServiceBroker::GetTextureCache()->ClearCachedImage(thumbthumb
);
406 if (bHasArt
&& !bFallback
)
407 { // Actually has this type of art (not a fallback) so
408 // allow the user to delete it by selecting "no art".
409 CFileItemPtr
item(new CFileItem("thumb://None", false));
410 item
->SetArt("thumb", "DefaultAlbumCover.png");
411 item
->SetLabel(g_localizeStrings
.Get(13515));
415 //! @todo: Add support for extracting embedded art
417 // Show list of possible art for user selection
419 VECSOURCES
sources(*CMediaSourceSettings::GetInstance().GetSources("music"));
420 // Add album folder as source (could be disc set)
421 std::string albumpath
= m_song
->GetProperty("album_path").asString();
422 if (!albumpath
.empty())
424 CFileItem
pathItem(albumpath
, true);
425 CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources
, pathItem
);
427 else // Add parent folder of song
428 CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources
, *m_song
);
429 CServiceBroker::GetMediaManager().GetLocalDrives(sources
);
430 if (CGUIDialogFileBrowser::ShowAndGetImage(items
, sources
, g_localizeStrings
.Get(13511), result
) &&
431 result
!= "thumb://Current")
433 // User didn't choose the one they have, or the fallback image.
434 // Overwrite with the new art or clear it
436 if (result
== "thumb://Thumb")
437 newArt
= m_song
->GetArt("thumb");
438 else if (result
== "thumb://Local")
440 // else if (result == "thumb://Embedded")
441 // newArt = embeddedArt;
442 else if (CFileUtils::Exists(result
))
447 // Asynchronously update that type of art in the database
448 MUSIC_UTILS::UpdateArtJob(m_song
, type
, newArt
);
450 // Update local song with current art
453 // Remove that type of art from the song
454 primeArt
.erase(type
);
455 m_song
->SetArt(primeArt
);
458 // Add or modify the type of art
459 m_song
->SetArt(type
, newArt
);
461 // Update local art list with current art
462 // Show any fallback art when song art removed
463 if (newArt
.empty() && m_song
->HasArt(type
))
464 newArt
= m_song
->GetArt(type
);
465 for (const auto& artitem
: m_artTypeList
)
467 if (artitem
->GetProperty("artType") == type
)
469 artitem
->SetArt("thumb", newArt
);
474 // Get new artwork to show in other places e.g. on music lib window,
475 // current playlist and player OSD.
476 CGUIMessage
msg(GUI_MSG_NOTIFY_ALL
, 0, 0, GUI_MSG_UPDATE_ITEM
, 0, m_song
);
477 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
481 // Re-open the art type selection dialog as we come back from
482 // the image selection dialog
486 void CGUIDialogSongInfo::OnSetUserrating()
488 int userrating
= MUSIC_UTILS::ShowSelectRatingDialog(m_song
->GetMusicInfoTag()->GetUserrating());
489 if (userrating
< 0) // Nothing selected, so rating unchanged
492 SetUserrating(userrating
);
495 void CGUIDialogSongInfo::ShowFor(CFileItem
* pItem
)
497 if (pItem
->m_bIsFolder
)
499 if (!MUSIC::IsMusicDb(*pItem
))
500 pItem
->LoadMusicTag();
501 if (!pItem
->HasMusicInfoTag())
504 CGUIDialogSongInfo
*dialog
= CServiceBroker::GetGUI()->GetWindowManager().
505 GetWindow
<CGUIDialogSongInfo
>(WINDOW_DIALOG_SONG_INFO
);
508 if (dialog
->SetSong(pItem
)) // Fetch full song info asynchronously
511 if (dialog
->HasUpdatedUserrating())
513 auto window
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIWindowMusicBase
>(WINDOW_MUSIC_NAV
);
515 window
->RefreshContent("songs");
521 void CGUIDialogSongInfo::OnPlaySong(const std::shared_ptr
<CFileItem
>& item
)
524 MUSIC_UTILS::PlayItem(item
, "");