diff options
42 files changed, 2482 insertions, 329 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index d7d3ec71aa..28462c83b6 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -820,6 +820,7 @@ msgstr "" #: xbmc/addons/GUIDialogAddonInfo.cpp #: xbmc/dialogs/GUIDialogSelect.cpp #: addons/skin.estuary/xml/DialogPVRGroupManager.xml +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp msgctxt "#186" msgid "OK" msgstr "" @@ -1018,6 +1019,15 @@ msgctxt "#221" msgid "Network is not connected" msgstr "" +#: xbmc/addons/settings/GUIDialogAddonSettings.cpp +#: xbmc/network/GUIDialogNetworkSetup.cpp +#: xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp +#: xbmc/profiles/dialogs/GUIDialogLockSettings.cpp +#: xbmc/profiles/dialogs/GUIDialogProfileSettings.cpp +#: xbmc/pvr/dialogs/GUIDialogPVRRecordingSettings.cpp +#: xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +#: xbmc/settings/dialogs/GUIDialogContentSettings.cpp +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp #: addons/skin.estuary/xml/DialogKeyboard.xml msgctxt "#222" msgid "Cancel" @@ -3020,6 +3030,8 @@ msgctxt "#660" msgid "Volume amplification" msgstr "" +#: xbmc/interfaces/builtins/LibraryBuiltins.cpp +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp msgctxt "#661" msgid "Choose export folder" msgstr "" @@ -4728,6 +4740,7 @@ msgstr "" #: xbmc/guilib/WindowIDs.h #: addons/skin.estuary/xml/AddonBrowser.xml #: xbmc/settings/dialogs/GUIDialogContentSettings.cpp +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp msgctxt "#10004" msgid "Settings" msgstr "" @@ -12544,7 +12557,13 @@ msgctxt "#20222" msgid "Look for external subtitles" msgstr "" -#empty strings from id 20223 to 20239 +#: system/settings/settings.xml +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#20223" +msgid "Artist information folder" +msgstr "" + +#empty strings from id 20224 to 20239 #: xbmc/dialogs/GUIDialogMediaSource.cpp msgctxt "#20240" @@ -12741,6 +12760,7 @@ msgctxt "#20332" msgid "Use file or folder names in lookups?" msgstr "" +#: xbmc/dialogs/GUIDialogContentSettings.cpp msgctxt "#20333" msgid "Set content" msgstr "" @@ -12809,6 +12829,7 @@ msgctxt "#20343" msgid "TV shows" msgstr "" +#. xbmc/settings/dialogs/GUIDialogContentSettings.cpp #: addons/skin.estuary/xml/DialogContentSettings.xml msgctxt "#20344" msgid "This directory contains" @@ -12983,6 +13004,7 @@ msgctxt "#20378" msgid "Refresh information for all episodes?" msgstr "" +#. xbmc/settings/dialogs/GUIDialogContentSettings.cpp msgctxt "#20379" msgid "Selected folder contains a single TV show" msgstr "" @@ -13210,6 +13232,7 @@ msgctxt "#20425" msgid "Fanart slideshow" msgstr "" +#. xbmc/interfaces/builtins/LibraryBuiltins.cpp msgctxt "#20426" msgid "Export to a single file or separate files per entry?" msgstr "" @@ -13219,14 +13242,17 @@ msgctxt "#20427" msgid "Choose rule type" msgstr "" +#. xbmc/interfaces/builtins/LibraryBuiltins.cpp msgctxt "#20428" msgid "Single file" msgstr "" +#. xbmc/interfaces/builtins/LibraryBuiltins.cpp msgctxt "#20429" msgid "Separate" msgstr "" +#. xbmc/interfaces/builtins/LibraryBuiltins.cpp msgctxt "#20430" msgid "Export thumbnails and fanart?" msgstr "" @@ -13235,6 +13261,7 @@ msgctxt "#20431" msgid "Overwrite old files?" msgstr "" +#. xbmc/settings/dialogs/GUIDialogContentSettings.cpp msgctxt "#20432" msgid "Exclude path from library updates" msgstr "" @@ -14881,7 +14908,7 @@ msgctxt "#24040" msgid "(Clear the current setting)" msgstr "" -#: xbmc/addons/GUIViewStateAddonBrowser.cpp +#: xbmc/addons/GUIWindowAddonBrowser.cpp msgctxt "#24041" msgid "Install from zip file" msgstr "" @@ -18040,7 +18067,7 @@ msgstr "" #. Description of setting with label #648 "Import video library" #: system/settings/settings.xml msgctxt "#36150" -msgid "Import a XML file into the video library database." +msgid "Import an XML file into the video library database." msgstr "" #. Description of settings category with label #14086 "Playback" @@ -18633,13 +18660,13 @@ msgstr "" #. Description of setting with label #20196 "Export music library" #: system/settings/settings.xml msgctxt "#36262" -msgid "Export the music library database to XML files. This will optionally overwrite your current XML files." +msgid "Export parts of the music library to an XML file or NFO files. This will optionally overwrite your current NFO and artwork files." msgstr "" #. Description of setting with label #20197 "Import music library" #: system/settings/settings.xml msgctxt "#36263" -msgid "Import a XML file into the music library database." +msgid "Import an XML file into the music library database." msgstr "" #. Description of settings category with label #14086 "Playback" @@ -18816,7 +18843,13 @@ msgctxt "#36292" msgid "This category contains startup settings." msgstr "" -#empty strings from id 36293 to 36301 +#. Description of setting with label #20223 "Artist information folder" +#: system/settings/settings.xml +msgctxt "#36293" +msgid "Select the folder where artist information (nfo files and images) should be saved in." +msgstr "" + +#empty strings from id 36294 to 36301 #: system/settings/settings.xml msgctxt "#36302" @@ -20987,7 +21020,111 @@ msgctxt "#38209" msgid "Reset resume position" msgstr "" -#empty strings from id 38210 to 38999 +#empty strings from id 38210 to 38299 + +#. Dialog header for settings for how to export specific music library data +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38300" +msgid "Export Music Library" +msgstr "" + +#. Kind of export output, see #38304 +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38301" +msgid "Single file" +msgstr "" + +#. Kind of export output, see #38304 +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38302" +msgid "Separate files for each item" +msgstr "" + +#. Kind of export output, see #38304 +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38303" +msgid "To library folders" +msgstr "" + +#. Selection of export to a single file, many separate files or the library folder(s) +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38304" +msgid "Choose kind of export output" +msgstr "" + +#. Folder path for export output file or files +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38305" +msgid "Destination folder" +msgstr "" + +#. Section of music items to export - albums, album artists, song artists etc. +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38306" +msgid "Items to export" +msgstr "" + +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38307" +msgid "Include art work such as thumbnails and fanart" +msgstr "" + +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38308" +msgid "Include items that have not been scraped (to create template NFO files)" +msgstr "" + +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38309" +msgid "Output only artwork, not NFO files" +msgstr "" + +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38310" +msgid "Overwrite existing files" +msgstr "" + +#empty string id 38311 + +#. Music item to export output, see #38093 +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38312" +msgid "Song artists" +msgstr "" + +#. Music item to export output, see #38093 +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38313" +msgid "Other artists" +msgstr "" + +#empty strings from id 38314 to 38316 + +#. Message when trying to do a kind #38303 of export and setting #20223 is empty +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38317" +msgid "Unable to export to library folders as the system artist information folder setting is empty" +msgstr "" + +#. Message when trying to do export and destination folder #38305 does not exist +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38318" +msgid "Unable to export data as the destination folder does not exist" +msgstr "" + +#. Button text to start export +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38319" +msgid "Export" +msgstr "" + +#. Message when adding music source and setting #20223 is empty +#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +msgctxt "#38320" +msgid "Do you have local artist information (NFO) and art files that you want to fetch? Set the location of these artist folders now" +msgstr "" + +#empty strings from id 38321 to 38999 #: system/settings/settings.xml msgctxt "#39000" diff --git a/cmake/treedata/common/music.txt b/cmake/treedata/common/music.txt index 71f30e16fa..29aae7b135 100644 --- a/cmake/treedata/common/music.txt +++ b/cmake/treedata/common/music.txt @@ -1,5 +1,6 @@ xbmc/music music xbmc/music/dialogs music/dialogs xbmc/music/infoscanner music/infoscanner +xbmc/music/jobs music/jobs xbmc/music/tags music/tags xbmc/music/windows music/windows diff --git a/system/settings/settings.xml b/system/settings/settings.xml index bcaf14e315..2d276e3d95 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -893,6 +893,38 @@ <level>2</level> <control type="button" format="action" /> </setting> + <!-- Hidden settings edited using CGUIDialogMusicExportSettings --> + <setting id="musiclibrary.exportfiletype" type="integer" label="38304" help=""> + <level>4</level> + <default>0</default> + </setting> + <setting id="musiclibrary.exportfolder" type="string" label="38305" help=""> + <level>4</level> + <default></default> + <constraints> + <allowempty>true</allowempty> + </constraints> + </setting> + <setting id="musiclibrary.exportitems" type="integer" label="" help=""> + <level>4</level> + <default>48</default> <!-- Albums + Album Artists --> + </setting> + <setting id="musiclibrary.exportunscraped" type="boolean" label="" help=""> + <level>4</level> + <default>false</default> + </setting> + <setting id="musiclibrary.exportoverwrite" type="boolean" label="" help=""> + <level>4</level> + <default>false</default> + </setting> + <setting id="musiclibrary.exportartwork" type="boolean" label="" help=""> + <level>4</level> + <default>false</default> + </setting> + <setting id="musiclibrary.exportskipnfo" type="boolean" label="" help=""> + <level>4</level> + <default>false</default> + </setting> <setting id="musiclibrary.import" type="action" label="14249" help="36263"> <level>2</level> <control type="button" format="action" /> @@ -1135,6 +1167,16 @@ <default>false</default> <control type="toggle" /> </setting> + <setting id="musiclibrary.artistsfolder" type="path" label="20223" help="36293"> + <level>1</level> + <default></default> + <constraints> + <allowempty>true</allowempty> + </constraints> + <control type="button" format="path"> + <heading>657</heading> + </control> + </setting> <setting id="musiclibrary.albumsscraper" type="addon" label="20193" help="36257"> <level>1</level> <default>metadata.album.universal</default> diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index 87230f7cba..0e25a9498c 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -5040,18 +5040,15 @@ void CApplication::StartMusicScan(const std::string &strDirectory, bool userInit if (m_musicInfoScanner->IsScanning()) return; + // Setup default flags if (!flags) - { // setup default flags + { // Online scraping of additional info during scanning if (m_ServiceManager->GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO)) flags |= CMusicInfoScanner::SCAN_ONLINE; - if (!userInitiated || m_ServiceManager->GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE)) - flags |= CMusicInfoScanner::SCAN_BACKGROUND; - // Ask for full rescan of music files - //! @todo replace with a music library setting in UI - if (g_advancedSettings.m_bMusicLibraryPromptFullTagScan) - if (CGUIDialogYesNo::ShowAndGetInput(CVariant{ 799 }, CVariant{ 38062 })) - flags |= CMusicInfoScanner::SCAN_RESCAN; } + if (!userInitiated || m_ServiceManager->GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE)) + flags |= CMusicInfoScanner::SCAN_BACKGROUND; + if (!(flags & CMusicInfoScanner::SCAN_BACKGROUND)) m_musicInfoScanner->ShowDialog(true); diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index abefb31c84..c962e0d801 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -90,6 +90,7 @@ #include "profiles/dialogs/GUIDialogProfileSettings.h" #include "profiles/dialogs/GUIDialogLockSettings.h" #include "settings/dialogs/GUIDialogContentSettings.h" +#include "settings/dialogs/GUIDialogLibExportSettings.h" #include "dialogs/GUIDialogBusy.h" #include "dialogs/GUIDialogKeyboardGeneric.h" #include "dialogs/GUIDialogKeyboardTouch.h" @@ -249,6 +250,8 @@ void CGUIWindowManager::CreateWindows() Add(new CGUIDialogLockSettings); Add(new CGUIDialogContentSettings); + + Add(new CGUIDialogLibExportSettings); Add(new CGUIDialogPlayEject); @@ -351,6 +354,7 @@ bool CGUIWindowManager::DestroyWindows() DestroyWindow(WINDOW_DIALOG_AUDIO_OSD_SETTINGS); DestroyWindow(WINDOW_DIALOG_VIDEO_BOOKMARKS); DestroyWindow(WINDOW_DIALOG_CONTENT_SETTINGS); + DestroyWindow(WINDOW_DIALOG_LIBEXPORT_SETTINGS); DestroyWindow(WINDOW_DIALOG_FAVOURITES); DestroyWindow(WINDOW_DIALOG_SONG_INFO); DestroyWindow(WINDOW_DIALOG_SMART_PLAYLIST_EDITOR); diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index e79f190272..50d9ea30b2 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -82,6 +82,7 @@ #define WINDOW_DIALOG_PROFILE_SETTINGS 10130 #define WINDOW_DIALOG_LOCK_SETTINGS 10131 #define WINDOW_DIALOG_CONTENT_SETTINGS 10132 +#define WINDOW_DIALOG_LIBEXPORT_SETTINGS 10133 #define WINDOW_DIALOG_FAVOURITES 10134 #define WINDOW_DIALOG_SONG_INFO 10135 #define WINDOW_DIALOG_SMART_PLAYLIST_EDITOR 10136 diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp index 2d3c69439c..0ee770ce31 100644 --- a/xbmc/input/WindowTranslator.cpp +++ b/xbmc/input/WindowTranslator.cpp @@ -106,6 +106,7 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName { "profilesettings" , WINDOW_DIALOG_PROFILE_SETTINGS }, { "locksettings" , WINDOW_DIALOG_LOCK_SETTINGS }, { "contentsettings" , WINDOW_DIALOG_CONTENT_SETTINGS }, + { "libexportsettings" , WINDOW_DIALOG_LIBEXPORT_SETTINGS }, { "songinformation" , WINDOW_DIALOG_SONG_INFO }, { "smartplaylisteditor" , WINDOW_DIALOG_SMART_PLAYLIST_EDITOR }, { "smartplaylistrule" , WINDOW_DIALOG_SMART_PLAYLIST_RULE }, diff --git a/xbmc/interfaces/builtins/LibraryBuiltins.cpp b/xbmc/interfaces/builtins/LibraryBuiltins.cpp index a318d161fb..79573d36d5 100644 --- a/xbmc/interfaces/builtins/LibraryBuiltins.cpp +++ b/xbmc/interfaces/builtins/LibraryBuiltins.cpp @@ -29,6 +29,8 @@ #include "MediaSource.h" #include "messaging/helpers/DialogHelper.h" #include "music/MusicDatabase.h" +#include "music/MusicLibraryQueue.h" +#include "settings/LibExportSettings.h" #include "storage/MediaManager.h" #include "utils/log.h" #include "utils/StringUtils.h" @@ -67,8 +69,8 @@ static int CleanLibrary(const std::vector<std::string>& params) /*! \brief Export a library. * \param params The parameters. * \details params[0] = "video" or "music". - * params[1] = "true" to export to a single file (optional). - * params[2] = "true" to export thumbs (optional). + * params[1] = "true" to export to separate files (optional). + * params[2] = "true" to export thumbs (optional) or the file path for export to singlefile. * params[3] = "true" to overwrite existing files (optional). * params[4] = "true" to export actor thumbs (optional). */ @@ -160,18 +162,90 @@ static int ExportLibrary(const std::vector<std::string>& params) } else { - if (URIUtils::HasSlashAtEnd(path)) - path = URIUtils::AddFileToFolder(path, "musicdb.xml"); - CMusicDatabase musicdatabase; - musicdatabase.Open(); - musicdatabase.ExportToXML(path, singleFile, thumbs, overwrite); - musicdatabase.Close(); + CLibExportSettings settings; + // ELIBEXPORT_SINGLEFILE, ELIBEXPORT_ALBUMS + ELIBEXPORT_ALBUMARTISTS by default + settings.m_strPath = path; + if (!singleFile) + settings.SetExportType(ELIBEXPORT_TOLIBRARYFOLDER); + settings.m_artwork = thumbs; + settings.m_overwrite = overwrite; + // Export music library (not showing progress dialog) + CMusicLibraryQueue::GetInstance().ExportLibrary(settings, false); } } return 0; } +/*! \brief Export a library with extended parameters +Avoiding breaking change to original ExportLibrary routine parameters +* \param params The parameters. +* \details params[0] = "video" or "music". +* params[1] = export type "singlefile", "separate", or "library". +* params[2] = path of destination folder. +* params[3,...] = "unscraped" to include unscraped items +* params[3,...] = "overwrite" to overwrite exitsing files. +* params[3,...] = "artwork" to include images such as thumbs and fanart. +* params[3,...] = "skipnfo" to not include nfo files (just art). +* params[3,...] = "ablums" to include albums. +* params[3,...] = "albumartists" to include album artists. +* params[3,...] = "songartists" to include song artists. +* params[3,...] = "otherartists" to include other artists. +*/ +static int ExportLibrary2(const std::vector<std::string>& params) +{ + CLibExportSettings settings; + if (params.size() < 3) + return -1; + settings.m_strPath = params[2]; + settings.SetExportType(ELIBEXPORT_SINGLEFILE); + if (StringUtils::EqualsNoCase(params[1], "separate")) + settings.SetExportType(ELIBEXPORT_SEPARATEFILES); + else if (StringUtils::EqualsNoCase(params[1], "library")) + { + settings.SetExportType(ELIBEXPORT_TOLIBRARYFOLDER); + settings.m_strPath.clear(); + } + settings.ClearItems(); + + for (unsigned int i = 2; i < params.size(); i++) + { + if (StringUtils::EqualsNoCase(params[i], "artwork")) + settings.m_artwork = true; + else if (StringUtils::EqualsNoCase(params[i], "overwrite")) + settings.m_overwrite = true; + else if (StringUtils::EqualsNoCase(params[i], "unscraped")) + settings.m_unscraped = true; + else if (StringUtils::EqualsNoCase(params[i], "skipnfo")) + settings.m_skipnfo = true; + else if (StringUtils::EqualsNoCase(params[i], "albums")) + settings.AddItem(ELIBEXPORT_ALBUMS); + else if (StringUtils::EqualsNoCase(params[i], "albumartists")) + settings.AddItem(ELIBEXPORT_ALBUMARTISTS); + else if (StringUtils::EqualsNoCase(params[i], "songartists")) + settings.AddItem(ELIBEXPORT_SONGARTISTS); + else if (StringUtils::EqualsNoCase(params[i], "otherartists")) + settings.AddItem(ELIBEXPORT_OTHERARTISTS); + else if (StringUtils::EqualsNoCase(params[i], "actorthumbs")) + settings.AddItem(ELIBEXPORT_ACTORTHUMBS); + } + if (StringUtils::EqualsNoCase(params[0], "music")) + { + // Export music library (not showing progress dialog) + CMusicLibraryQueue::GetInstance().ExportLibrary(settings, false); + } + else + { + CVideoDatabase videodatabase; + videodatabase.Open(); + videodatabase.ExportToXML(settings.m_strPath, settings.IsSingleFile(), + settings.m_artwork, settings.IsItemExported(ELIBEXPORT_ACTORTHUMBS), settings.m_overwrite); + videodatabase.Close(); + } + return 0; +} + + /*! \brief Update a library. * \param params The parameters. * \details params[0] = "video" or "music". @@ -233,12 +307,30 @@ static int SearchVideoLibrary(const std::vector<std::string>& params) /// , /// Export the video/music library /// @param[in] type "video" or "music". -/// @param[in] exportSingleFile Add "true" to export to a single file (optional). +/// @param[in] exportSingleFile Add "true" to export to separate files (optional). /// @param[in] exportThumbs Add "true" to export thumbs (optional). /// @param[in] overwrite Add "true" to overwrite existing files (optional). /// @param[in] exportActorThumbs Add "true" to export actor thumbs (optional). /// } /// \table_row2_l{ +/// <b>`exportlibrary2(library\, exportFiletype\, path [\, unscraped][\, overwrite][\, artwork][\, skipnfo] +/// [\, albums][\, albumartists][\, songartists][\, otherartists][\, actorthumbs])`</b> +/// , +/// Export the video/music library with extended parameters +/// @param[in] library "video" or "music". +/// @param[in] exportFiletype "singlefile", "separate" or "library" +/// @param[in] path Path to destination folder +/// @param[in] unscraped Add "unscraped" to include unscraped items +/// @param[in] overwrite Add "overwrite" to overwrite exitsing files. +/// @param[in] artwork Add "artwork" to include images such as thumbs and fanart. +/// @param[in] skipnfo Add "skipnfo" to not include nfo files(just art). +/// @param[in] albums Add "ablums" to include albums. +/// @param[in] albumartists Add "albumartists" to include album artists. +/// @param[in] songartists Add "songartists" to include song artists. +/// @param[in] otherartists Add "otherartists" to include other artists. +/// @param[in] actorthumbs Add "actorthumbs" to include other actor thumbs. +/// } +/// \table_row2_l{ /// <b>`updatelibrary([type\, suppressDialogs])`</b> /// , /// Update the selected library (music or video) @@ -258,6 +350,7 @@ CBuiltins::CommandMap CLibraryBuiltins::GetOperations() const return { {"cleanlibrary", {"Clean the video/music library", 1, CleanLibrary}}, {"exportlibrary", {"Export the video/music library", 1, ExportLibrary}}, + {"exportlibrary2", {"Export the video/music library", 1, ExportLibrary2}}, {"updatelibrary", {"Update the selected library (music or video)", 1, UpdateLibrary}}, {"videolibrary.search", {"Brings up a search dialog which will search the library", 0, SearchVideoLibrary}} }; diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index f3c70c2d8c..82a0c16714 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -686,12 +686,16 @@ JSONRPC_STATUS CAudioLibrary::Export(const std::string &method, ITransportLayer { std::string cmd; if (parameterObject["options"].isMember("path")) - cmd = StringUtils::Format("exportlibrary(music, false, %s)", StringUtils::Paramify(parameterObject["options"]["path"].asString()).c_str()); + cmd = StringUtils::Format("exportlibrary2(music, singlefile, %s, albums, albumartists)", StringUtils::Paramify(parameterObject["options"]["path"].asString()).c_str()); else - cmd = StringUtils::Format("exportlibrary(music, true, %s, %s)", - parameterObject["options"]["images"].asBoolean() ? "true" : "false", - parameterObject["options"]["overwrite"].asBoolean() ? "true" : "false"); - + { + cmd = "exportlibrary2(music, library, dummy, albums, albumartists"; + if (parameterObject["options"].isMember("images")) + cmd += ", artwork"; + if (parameterObject["options"].isMember("overwrite")) + cmd += ", overwrite"; + cmd += ")"; + } CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); return ACK; } diff --git a/xbmc/interfaces/json-rpc/VideoLibrary.cpp b/xbmc/interfaces/json-rpc/VideoLibrary.cpp index 34cf7f3f43..37f126b8c0 100644 --- a/xbmc/interfaces/json-rpc/VideoLibrary.cpp +++ b/xbmc/interfaces/json-rpc/VideoLibrary.cpp @@ -891,12 +891,18 @@ JSONRPC_STATUS CVideoLibrary::Export(const std::string &method, ITransportLayer { std::string cmd; if (parameterObject["options"].isMember("path")) - cmd = StringUtils::Format("exportlibrary(video, false, %s)", StringUtils::Paramify(parameterObject["options"]["path"].asString()).c_str()); + cmd = StringUtils::Format("exportlibrary2(video, singlefile, %s)", StringUtils::Paramify(parameterObject["options"]["path"].asString()).c_str()); else - cmd = StringUtils::Format("exportlibrary(video, true, %s, %s, %s)", - parameterObject["options"]["images"].asBoolean() ? "true" : "false", - parameterObject["options"]["overwrite"].asBoolean() ? "true" : "false", - parameterObject["options"]["actorthumbs"].asBoolean() ? "true" : "false"); + { + cmd = "exportlibrary2(video, separate, dummy"; + if (parameterObject["options"].isMember("images")) + cmd += ", artwork"; + if (parameterObject["options"].isMember("overwrite")) + cmd += ", overwrite"; + if (parameterObject["options"].isMember("actorthumbs")) + cmd += ", actorthumbs"; + cmd += ")"; + } CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); return ACK; diff --git a/xbmc/music/Artist.cpp b/xbmc/music/Artist.cpp index d4a33dcb3f..822a62e582 100644 --- a/xbmc/music/Artist.cpp +++ b/xbmc/music/Artist.cpp @@ -60,8 +60,13 @@ void CArtist::MergeScrapedArtist(const CArtist& source, bool override /* = true strDied = source.strDied; strDisbanded = source.strDisbanded; yearsActive = source.yearsActive; - thumbURL = source.thumbURL; - fanart = source.fanart; + + thumbURL = source.thumbURL; // Available remote thumbs + fanart = source.fanart; // Available remote fanart + // Current artwork - thumb, fanart etc., to be stored in art table + if (!source.art.empty()) + art = source.art; + discography = source.discography; } @@ -90,6 +95,7 @@ bool CArtist::Load(const TiXmlElement *artist, bool append, bool prioritise) size_t iThumbCount = thumbURL.m_url.size(); std::string xmlAdd = thumbURL.m_xml; + // Available artist thumbs const TiXmlElement* thumb = artist->FirstChildElement("thumb"); while (thumb) { @@ -110,6 +116,8 @@ bool CArtist::Load(const TiXmlElement *artist, bool append, bool prioritise) thumbURL.m_url.end()); thumbURL.m_xml = xmlAdd; } + + // Discography const TiXmlElement* node = artist->FirstChildElement("album"); while (node) { @@ -126,7 +134,7 @@ bool CArtist::Load(const TiXmlElement *artist, bool append, bool prioritise) node = node->NextSiblingElement("album"); } - // fanart + // Available artist fanart const TiXmlElement *fanart2 = artist->FirstChildElement("fanart"); if (fanart2) { @@ -142,6 +150,18 @@ bool CArtist::Load(const TiXmlElement *artist, bool append, bool prioritise) fanart.Unpack(); } + // Current artwork - thumb, fanart etc. (the chosen art, not the lists of those available) + node = artist->FirstChildElement("art"); + if (node) + { + const TiXmlNode *artdetailNode = node->FirstChild(); + while (artdetailNode && artdetailNode->FirstChild()) + { + art.insert(make_pair(artdetailNode->ValueStr(), artdetailNode->FirstChild()->ValueStr())); + artdetailNode = artdetailNode->NextSibling(); + } + } + return true; } @@ -168,6 +188,7 @@ bool CArtist::Save(TiXmlNode *node, const std::string &tag, const std::string& s XMLUtils::SetString(artist, "biography", strBiography); XMLUtils::SetString(artist, "died", strDied); XMLUtils::SetString(artist, "disbanded", strDisbanded); + // Available thumbs if (!thumbURL.m_xml.empty()) { CXBMCTinyXML doc; @@ -180,6 +201,7 @@ bool CArtist::Save(TiXmlNode *node, const std::string &tag, const std::string& s } } XMLUtils::SetString(artist, "path", strPath); + // Available fanart if (fanart.m_xml.size()) { CXBMCTinyXML doc; @@ -187,7 +209,7 @@ bool CArtist::Save(TiXmlNode *node, const std::string &tag, const std::string& s artist->InsertEndChild(*doc.RootElement()); } - // albums + // Discography for (std::vector<std::pair<std::string,std::string> >::const_iterator it = discography.begin(); it != discography.end(); ++it) { // add a <album> tag diff --git a/xbmc/music/Artist.h b/xbmc/music/Artist.h index 83d7457631..bc3ead5ac3 100644 --- a/xbmc/music/Artist.h +++ b/xbmc/music/Artist.h @@ -69,6 +69,7 @@ public: strDisbanded.clear(); yearsActive.clear(); thumbURL.Clear(); + art.clear(); discography.clear(); idArtist = -1; strPath.clear(); @@ -103,8 +104,9 @@ public: std::string strDisbanded; std::vector<std::string> yearsActive; std::string strPath; - CScraperUrl thumbURL; - CFanart fanart; + CScraperUrl thumbURL; // Data for available thumbs + CFanart fanart; // Data for available fanart, urls etc. + std::map<std::string, std::string> art; // Current artwork - thumb, fanart etc. std::vector<std::pair<std::string,std::string> > discography; CDateTime dateAdded; bool bScrapedMBID; diff --git a/xbmc/music/CMakeLists.txt b/xbmc/music/CMakeLists.txt index 92d8bad212..97b19a72f0 100644 --- a/xbmc/music/CMakeLists.txt +++ b/xbmc/music/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES Album.cpp MusicDatabase.cpp MusicDbUrl.cpp MusicInfoLoader.cpp + MusicLibraryQueue.cpp MusicThumbLoader.cpp Song.cpp) @@ -16,6 +17,7 @@ set(HEADERS Album.h MusicDatabase.h MusicDbUrl.h MusicInfoLoader.h + MusicLibraryQueue.h MusicThumbLoader.h Song.h) diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index aec9a3d9e1..ea54de35e2 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -50,7 +50,6 @@ #include "playlists/SmartPlayList.h" #include "profiles/ProfilesManager.h" #include "settings/AdvancedSettings.h" -#include "settings/MediaSettings.h" #include "settings/Settings.h" #include "Song.h" #include "storage/MediaManager.h" @@ -58,6 +57,7 @@ #include "TextureCache.h" #include "threads/SystemClock.h" #include "URL.h" +#include "Util.h" #include "utils/FileUtils.h" #include "utils/LegacyPathTranslation.h" #include "utils/log.h" @@ -330,7 +330,7 @@ void CMusicDatabase::CreateViews() " bCompilation, " " bScrapedMBID," " lastScraped," - " (SELECT AVG(song.iTimesPlayed) FROM song WHERE song.idAlbum = album.idAlbum) AS iTimesPlayed, " + " (SELECT ROUND(AVG(song.iTimesPlayed)) FROM song WHERE song.idAlbum = album.idAlbum) AS iTimesPlayed, " " strReleaseType, " " (SELECT MAX(song.dateAdded) FROM song WHERE song.idAlbum = album.idAlbum) AS dateAdded, " " (SELECT MAX(song.lastplayed) FROM song WHERE song.idAlbum = album.idAlbum) AS lastplayed " @@ -1115,6 +1115,10 @@ bool CMusicDatabase::UpdateArtist(const CArtist& artist) AddArtistDiscography(artist.idArtist, disc.first, disc.second); } + // Set current artwork (held in art table) + if (!artist.art.empty()) + SetArtForItem(artist.idArtist, MediaTypeArtist, artist.art); + return true; } @@ -1399,6 +1403,16 @@ bool CMusicDatabase::GetArtistExists(int idArtist) return false; } +int CMusicDatabase::GetLastArtist() +{ + std::string strSQL = "SELECT MAX(idArtist) FROM artist"; + std::string lastArtist = GetSingleValue(strSQL); + if (lastArtist.empty()) + return -1; + + return static_cast<int>(strtol(lastArtist.c_str(), NULL, 10)); +} + bool CMusicDatabase::HasArtistBeenScraped(int idArtist) { std::string strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE idArtist = %i AND lastScraped IS NULL", idArtist); @@ -5319,34 +5333,65 @@ int CMusicDatabase::GetSongsCount(const Filter &filter) return 0; } -bool CMusicDatabase::GetAlbumPath(int idAlbum, std::string& path) +bool CMusicDatabase::GetAlbumPath(int idAlbum, std::string &basePath) { + std::string strSQL; try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; - path.clear(); + basePath.clear(); + + // Get the unique paths of songs on the album, providing there are no songs from + // other albums with the same path. This returns + // a) <album> if is contains all the songs and no others, or + // b) <album>/cd1, <album>/cd2 etc. for disc sets + // but does *not* return any path when albums are mixed together. That could be because of + // deliberate file organisation, or (more likely) because of a tagging error in album name + // or Musicbrainzalbumid. Thus it avoids finding somme generic music path. + strSQL = PrepareSQL("SELECT DISTINCT strPath FROM song " + "JOIN path ON song.idPath = path.idPath " + "WHERE song.idAlbum = %ld " + "AND (SELECT COUNT(DISTINCT(idAlbum)) FROM song AS song2 " + "WHERE idPath = song.idPath) = 1", idAlbum); - std::string strSQL=PrepareSQL("select strPath from song join path on song.idPath = path.idPath where song.idAlbum=%ld", idAlbum); if (!m_pDS2->query(strSQL)) return false; int iRowsFound = m_pDS2->num_rows(); + if (iRowsFound == 0) { + // Album does not have a unique path, files are mixed m_pDS2->close(); return false; } + else if (iRowsFound == 1) + { + // Path contains all the songs and no others + basePath = m_pDS2->fv("strPath").get_asString(); + } + else + { + // e.g. <album>/cd1, <album>/cd2 etc. for disc sets + // Find the common path + while (!m_pDS2->eof()) + { + std::string path = m_pDS2->fv("strPath").get_asString(); + if (basePath.empty()) + basePath = path; + else + URIUtils::GetCommonPath(basePath, path); - // if this returns more than one path, we just grab the first one. It's just for determining where to obtain + place - // a local thumbnail - path = m_pDS2->fv("strPath").get_asString(); - - m_pDS2->close(); // cleanup recordset data + m_pDS2->next(); + } + } + // Cleanup recordset data + m_pDS2->close(); return true; } catch (...) { - CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum); + CLog::Log(LOGERROR, "CMusicDatabase::%s - failed to execute %s", __FUNCTION__, strSQL.c_str()); } return false; @@ -5366,7 +5411,11 @@ bool CMusicDatabase::SaveAlbumThumb(int idAlbum, const std::string& strThumb) return true; } -bool CMusicDatabase::GetArtistPath(int idArtist, std::string &basePath) +// Get old "artist path" - where artist.nfo and art was located v17 and below. +// It is the path common to all albums by an (album) artist, but ensure it is unique +// to that artist and not shared with other artists. Previously this caused incorrect nfo +// and art to be applied to multiple artists. +bool CMusicDatabase::GetOldArtistPath(int idArtist, std::string &basePath) { basePath.clear(); try @@ -5375,56 +5424,185 @@ bool CMusicDatabase::GetArtistPath(int idArtist, std::string &basePath) if (NULL == m_pDS2.get()) return false; // find all albums from this artist, and all the paths to the songs from those albums - std::string strSQL=PrepareSQL("SELECT strPath" - " FROM album_artist" - " JOIN song " - " ON album_artist.idAlbum = song.idAlbum" - " JOIN path" - " ON song.idPath = path.idPath" - " WHERE album_artist.idArtist = %i" - " GROUP BY song.idPath", idArtist); + std::string strSQL = PrepareSQL("SELECT strPath FROM album_artist " + "JOIN song ON album_artist.idAlbum = song.idAlbum " + "JOIN path ON song.idPath = path.idPath " + "WHERE album_artist.idArtist = %ld " + "GROUP BY song.idPath", + idArtist); // run query if (!m_pDS2->query(strSQL)) return false; int iRowsFound = m_pDS2->num_rows(); if (iRowsFound == 0) { + // Artist is not an album artist, no path to find m_pDS2->close(); return false; } - - // special case for single path - assume that we're in an artist/album/songs filesystem - if (iRowsFound == 1) - { + else if (iRowsFound == 1) + { + // Special case for single path - assume that we're in an artist/album/songs filesystem URIUtils::GetParentPath(m_pDS2->fv("strPath").get_asString(), basePath); m_pDS2->close(); - return true; } - - // find the common path (if any) to these albums - while (!m_pDS2->eof()) + else { - std::string path = m_pDS2->fv("strPath").get_asString(); - if (basePath.empty()) - basePath = path; - else - URIUtils::GetCommonPath(basePath,path); + // find the common path (if any) to these albums + while (!m_pDS2->eof()) + { + std::string path = m_pDS2->fv("strPath").get_asString(); + if (basePath.empty()) + basePath = path; + else + URIUtils::GetCommonPath(basePath, path); - m_pDS2->next(); + m_pDS2->next(); + } + m_pDS2->close(); } - // cleanup - m_pDS2->close(); - return true; - + // Check any path found is unique to that album artist, and do *not* return any path + // that is shared with other album artists. That could be because of collaborations + // i.e. albums with more than one album artist, or because there are albums by the + // artist on multiple music sources, or elsewhere in the folder hierarchy. + // Avoid returning some generic music path. + if (!basePath.empty()) + { + strSQL = PrepareSQL("SELECT COUNT(album_artist.idArtist) FROM album_artist " + "JOIN song ON album_artist.idAlbum = song.idAlbum " + "JOIN path ON song.idPath = path.idPath " + "WHERE album_artist.idArtist <> %ld " + "AND strPath LIKE '%s%%'", + idArtist, basePath.c_str()); + std::string strValue = GetSingleValue(strSQL, m_pDS2); + if (!strValue.empty()) + { + int countartists = static_cast<int>(strtol(strValue.c_str(), NULL, 10)); + if (countartists == 0) + return true; + } + } } catch (...) { CLog::Log(LOGERROR, "%s failed", __FUNCTION__); } + basePath.clear(); return false; } +bool CMusicDatabase::GetArtistPath(const CArtist& artist, std::string &path) +{ + // Get path for artist in the artists folder + path = CServiceBroker::GetSettings().GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + if (path.empty()) + return false; // No Artists folder not set; + // Get unique artist folder name + std::string strFolder; + if (GetArtistFolderName(artist, strFolder)) + { + path = URIUtils::AddFileToFolder(path, strFolder); + return true; + } + path.clear(); + return false; +} + +bool CMusicDatabase::GetAlbumFolder(const CAlbum& album, const std::string &strAlbumPath, std::string &strFolder) +{ + strFolder.clear(); + + // First try to get a *unique* album folder name from the music file paths + if (!strAlbumPath.empty()) + { + // Get last folder from full path + std::vector<std::string> folders = URIUtils::SplitPath(strAlbumPath); + if (!folders.empty()) + { + strFolder = folders.back(); + // Check paths to see folder name derived this way is unique for the (first) albumartist. + // Could have different albums on different path with same first album artist and folder name + // or duplicate albums from separate music files on different paths + // At least one will have mbid, so append it to start of mbid to folder. + std::string strSQL = PrepareSQL("SELECT DISTINCT album_artist.idAlbum FROM album_artist " + "JOIN song ON album_artist.idAlbum = song.idAlbum " + "JOIN path on path.idPath = song.idPath " + "WHERE album_artist.iOrder = 0 " + "AND album_artist.idArtist = %ld " + "AND path.strPath LIKE '%%%s%%'", + album.artistCredits[0].GetArtistId(), strFolder.c_str()); + + if (!m_pDS2->query(strSQL)) + return false; + int iRowsFound = m_pDS2->num_rows(); + m_pDS2->close(); + if (iRowsFound > 1 && !album.strMusicBrainzAlbumID.empty()) + { // Only one of the duplicate albums can be without mbid + strFolder += "_" + album.strMusicBrainzAlbumID.substr(0, 4); + } + return true; + } + } + else + { + // Create a valid unique folder name from album title + // @todo: Does UFT8 matter or need normalizing? + // @todo: Simplify punctuation removing unicode appostraphes, "..." etc.? + strFolder = CUtil::MakeLegalFileName(album.strAlbum, LEGAL_WIN32_COMPAT); + StringUtils::Replace(strFolder, " _ ", "_"); + + // Check <first albumartist name>/<albumname> is unique e.g. 2 x Bruckner Symphony No. 3 + // To have duplicate albumartist/album names at least one will have mbid, so append start of mbid to folder. + // This will not handle names that only differ by reserved chars e.g. "a>album" and "a?name" + // will be unique in db, but produce same folder name "a_name", but that kind of album and artist naming is very unlikely + std::string strSQL = PrepareSQL("SELECT COUNT(album_artist.idAlbum) FROM album_artist " + "JOIN album ON album_artist.idAlbum = album.idAlbum " + "WHERE album_artist.iOrder = 0 " + "AND album_artist.idArtist = %ld " + "AND album.strAlbum LIKE '%s' ", + album.artistCredits[0].GetArtistId(), album.strAlbum.c_str()); + std::string strValue = GetSingleValue(strSQL, m_pDS2); + if (strValue.empty()) + return false; + int countalbum = static_cast<int>(strtol(strValue.c_str(), NULL, 10)); + if (countalbum > 1 && !album.strMusicBrainzAlbumID.empty()) + { // Only one of the duplicate albums can be without mbid + strFolder += "_" + album.strMusicBrainzAlbumID.substr(0, 4); + } + return !strFolder.empty(); + } + return false; +} + +bool CMusicDatabase::GetArtistFolderName(const CArtist &artist, std::string &strFolder) +{ + return GetArtistFolderName(artist.strArtist, artist.strMusicBrainzArtistID, strFolder); +} + +bool CMusicDatabase::GetArtistFolderName(const std::string &strArtist, const std::string &strMusicBrainzArtistID, + std::string &strFolder) +{ + // Create a valid unique folder name for artist + // @todo: Does UFT8 matter or need normalizing? + // @todo: Simplify punctuation removing unicode appostraphes, "..." etc.? + strFolder = CUtil::MakeLegalFileName(strArtist, LEGAL_WIN32_COMPAT); + StringUtils::Replace(strFolder, " _ ", "_"); + + // Ensure <artist name> is unique e.g. 2 x John Williams. + // To have duplicate artist names there must both have mbids, so append start of mbid to folder. + // This will not handle names that only differ by reserved chars e.g. "a>name" and "a?name" + // will be unique in db, but produce same folder name "a_name", but that kind of artist naming is very unlikely + std::string strSQL = PrepareSQL("SELECT COUNT(1) FROM artist WHERE strArtist LIKE '%s'", strArtist.c_str()); + std::string strValue = GetSingleValue(strSQL, m_pDS2); + if (strValue.empty()) + return false; + int countartist = static_cast<int>(strtol(strValue.c_str(), NULL, 10)); + if (countartist > 1) + strFolder += "_" + strMusicBrainzArtistID.substr(0, 4); + return !strFolder.empty(); +} + int CMusicDatabase::GetArtistByName(const std::string& strArtist) { try @@ -5453,6 +5631,40 @@ int CMusicDatabase::GetArtistByName(const std::string& strArtist) return -1; } +int CMusicDatabase::GetArtistByMatch(const CArtist& artist) +{ + std::string strSQL; + try + { + if (NULL == m_pDB.get() || NULL == m_pDS.get()) + return false; + // Match on MusicBrainz ID, definitively unique + if (!artist.strMusicBrainzArtistID.empty()) + strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE strMusicBrainzArtistID = '%s'", + artist.strMusicBrainzArtistID.c_str()); + else + // No MusicBrainz ID, artist by name with no mbid + strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE strArtist LIKE '%s' AND strMusicBrainzArtistID IS NULL", + artist.strArtist.c_str()); + if (!m_pDS->query(strSQL)) return false; + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound != 1) + { + m_pDS->close(); + // Match on artist name, relax mbid restriction + return GetArtistByName(artist.strArtist); + } + int lResult = m_pDS->fv("idArtist").get_asInt(); + m_pDS->close(); + return lResult; + } + catch (...) + { + CLog::Log(LOGERROR, "CMusicDatabase::%s - failed to execute %", __FUNCTION__, strSQL.c_str()); + } + return -1; +} + int CMusicDatabase::GetAlbumByName(const std::string& strAlbum, const std::string& strArtist) { try @@ -5464,7 +5676,7 @@ int CMusicDatabase::GetAlbumByName(const std::string& strAlbum, const std::strin if (strArtist.empty()) strSQL=PrepareSQL("SELECT idAlbum FROM album WHERE album.strAlbum LIKE '%s'", strAlbum.c_str()); else - strSQL=PrepareSQL("SELECT album.idAlbum FROM album WHERE album.strAlbum LIKE '%s' AND album.strArtistDisp LIKE '%s'", strAlbum.c_str(),strArtist.c_str()); + strSQL=PrepareSQL("SELECT idAlbum FROM album WHERE album.strAlbum LIKE '%s' AND album.strArtistDisp LIKE '%s'", strAlbum.c_str(),strArtist.c_str()); // run query if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); @@ -5473,7 +5685,7 @@ int CMusicDatabase::GetAlbumByName(const std::string& strAlbum, const std::strin m_pDS->close(); return -1; } - return m_pDS->fv("album.idAlbum").get_asInt(); + return m_pDS->fv("idAlbum").get_asInt(); } catch (...) { @@ -5487,6 +5699,41 @@ int CMusicDatabase::GetAlbumByName(const std::string& strAlbum, const std::vecto return GetAlbumByName(strAlbum, StringUtils::Join(artist, g_advancedSettings.m_musicItemSeparator)); } +int CMusicDatabase::GetAlbumByMatch(const CAlbum &album) +{ + std::string strSQL; + try + { + if (NULL == m_pDB.get() || NULL == m_pDS.get()) + return false; + // Match on MusicBrainz ID, definitively unique + if (!album.strMusicBrainzAlbumID.empty()) + strSQL = PrepareSQL("SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = '%s'", album.strMusicBrainzAlbumID.c_str()); + else + // No mbid, match on album title and album artist descriptive string, ignore those with mbid + strSQL = PrepareSQL("SELECT idAlbum FROM album WHERE strArtistDisp LIKE '%s' AND strAlbum LIKE '%s' AND strMusicBrainzAlbumID IS NULL", + album.GetAlbumArtistString().c_str(), + album.strAlbum.c_str()); + m_pDS->query(strSQL); + if (!m_pDS->query(strSQL)) return false; + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound != 1) + { + m_pDS->close(); + // Match on album title and album artist descriptive string, relax mbid restriction + return GetAlbumByName(album.strAlbum, album.GetAlbumArtistString()); + } + int lResult = m_pDS->fv("idAlbum").get_asInt(); + m_pDS->close(); + return lResult; + } + catch (...) + { + CLog::Log(LOGERROR, "CMusicDatabase::%s - failed to execute %", __FUNCTION__, strSQL.c_str()); + } + return -1; +} + std::string CMusicDatabase::GetGenreById(int id) { return GetSingleValue("genre", "strGenre", PrepareSQL("idGenre=%i", id)); @@ -6232,204 +6479,330 @@ std::string CMusicDatabase::GetItemById(const std::string &itemType, int id) return ""; } -void CMusicDatabase::ExportToXML(const std::string &xmlFile, bool singleFile, bool images, bool overwrite) +void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog /*= NULL*/) { + if (!settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS) && + !settings.IsItemExported(ELIBEXPORT_SONGARTISTS) && + !settings.IsItemExported(ELIBEXPORT_OTHERARTISTS) && + !settings.IsItemExported(ELIBEXPORT_ALBUMS)) + return; + + if (!settings.IsSingleFile() && settings.m_skipnfo && !settings.m_artwork) + return; + + std::string strFolder; + if (!settings.IsToLibFolders()) + { + // Exporting to single file or separate files in a specified location + if (settings.m_strPath.empty()) + return; + + strFolder = settings.m_strPath; + if (!URIUtils::HasSlashAtEnd(strFolder)) + URIUtils::AddSlashAtEnd(strFolder); + strFolder = URIUtils::GetDirectory(strFolder); + if (strFolder.empty()) + return; + } + else + { + // Separate files with artists to library folder and albums to music folders. + // Without an artist information folder can not export artist NFO files or images + strFolder = CServiceBroker::GetSettings().GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + if (!settings.IsItemExported(ELIBEXPORT_ALBUMS) && strFolder.empty()) + return; + } + int iFailCount = 0; - CGUIDialogProgress *progress=NULL; try { if (NULL == m_pDB.get()) return; if (NULL == m_pDS.get()) return; if (NULL == m_pDS2.get()) return; - - // find all albums - std::vector<int> albumIds; - std::string sql = "select idAlbum FROM album WHERE lastScraped IS NOT NULL"; - m_pDS->query(sql); - - int total = m_pDS->num_rows(); - int current = 0; - - albumIds.reserve(total); - while (!m_pDS->eof()) - { - albumIds.push_back(m_pDS->fv("idAlbum").get_asInt()); - m_pDS->next(); - } - m_pDS->close(); - - progress = g_windowManager.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); - if (progress) - { - progress->SetHeading(CVariant{20196}); - progress->SetLine(0, CVariant{650}); - progress->SetLine(1, CVariant{""}); - progress->SetLine(2, CVariant{""}); - progress->SetPercentage(0); - progress->Open(); - progress->ShowProgressBar(true); - } - - // create our xml document + + // Create our xml document CXBMCTinyXML xmlDoc; TiXmlDeclaration decl("1.0", "UTF-8", "yes"); xmlDoc.InsertEndChild(decl); TiXmlNode *pMain = NULL; - if (!singleFile) + if (!settings.IsSingleFile()) pMain = &xmlDoc; else { TiXmlElement xmlMainElement("musicdb"); pMain = xmlDoc.InsertEndChild(xmlMainElement); } - for (const auto &albumId : albumIds) + + if (settings.IsItemExported(ELIBEXPORT_ALBUMS)) { - CAlbum album; - GetAlbum(albumId, album); - std::string strPath; - GetAlbumPath(albumId, strPath); - album.Save(pMain, "album", strPath); - if (!singleFile) + // Find albums to export + std::vector<int> albumIds; + std::string strSQL = PrepareSQL("SELECT idAlbum FROM album WHERE strReleaseType = '%s' ", + CAlbum::ReleaseTypeToString(CAlbum::Album).c_str()); + if (!settings.m_unscraped) + strSQL += "AND lastScraped IS NOT NULL"; + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - %s", __FUNCTION__, strSQL.c_str()); + m_pDS->query(strSQL); + + int total = m_pDS->num_rows(); + int current = 0; + + albumIds.reserve(total); + while (!m_pDS->eof()) { - if (!CDirectory::Exists(strPath)) - CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, strPath.c_str()); - else + albumIds.push_back(m_pDS->fv("idAlbum").get_asInt()); + m_pDS->next(); + } + m_pDS->close(); + + for (const auto &albumId : albumIds) + { + CAlbum album; + GetAlbum(albumId, album); + std::string strAlbumPath; + std::string strPath; + // Get album path, empty unless all album songs are under a unique folder, and + // there are no songs from another album in the same folder. + if (!GetAlbumPath(albumId, strAlbumPath)) + strAlbumPath.clear(); + if (settings.IsSingleFile()) { - std::string nfoFile = URIUtils::AddFileToFolder(strPath, "album.nfo"); - if (overwrite || !CFile::Exists(nfoFile)) - { - if (!xmlDoc.SaveFile(nfoFile)) + // Save album to xml, including album path + album.Save(pMain, "album", strAlbumPath); + } + else + { // Separate files and artwork + bool pathfound = false; + if (settings.IsToLibFolders()) + { // Save album.nfo and artwork with music files. + // Most albums are under a unique folder, but if songs from various albums are mixed then + // avoid overwriting by not allow NFO and art to be exported + if (strAlbumPath.empty()) + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - Not exporting album %s as unique path not found", + __FUNCTION__, album.strAlbum.c_str()); + else if (!CDirectory::Exists(strAlbumPath)) + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - Not exporting album %s as found path %s does not exist", + __FUNCTION__, album.strAlbum.c_str(), strAlbumPath.c_str()); + else { - CLog::Log(LOGERROR, "%s: Album nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str()); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); - iFailCount++; + strPath = strAlbumPath; + pathfound = true; } } - - if (images) + else + { // Save album.nfo and artwork to subfolder on export path + // strPath = strFolder/<albumartist name>/<albumname> + // where <albumname> is either the same name as the album folder containing the music files (if unique) + // or is craeted using the album name + std::string strAlbumArtist; + pathfound = GetArtistFolderName(album.GetAlbumArtist()[0], album.GetMusicBrainzAlbumArtistID()[0], strAlbumArtist); + if (pathfound) + { + strPath = URIUtils::AddFileToFolder(strFolder, strAlbumArtist); + pathfound = CDirectory::Exists(strPath); + if (!pathfound) + pathfound = CDirectory::Create(strPath); + } + if (!pathfound) + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - Not exporting album %s as could not create %s", + __FUNCTION__, album.strAlbum.c_str(), strPath.c_str()); + else + { + std::string strAlbumFolder; + pathfound = GetAlbumFolder(album, strAlbumPath, strAlbumFolder); + if (pathfound) + { + strPath = URIUtils::AddFileToFolder(strPath, strAlbumFolder); + pathfound = CDirectory::Exists(strPath); + if (!pathfound) + pathfound = CDirectory::Create(strPath); + } + if (!pathfound) + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - Not exporting album %s as could not create %s", + __FUNCTION__, album.strAlbum.c_str(), strPath.c_str()); + } + } + if (pathfound) { - std::string thumb = GetArtForItem(album.idAlbum, MediaTypeAlbum, "thumb"); - std::string imagePath = URIUtils::AddFileToFolder(strPath, "folder.jpg"); - if (!thumb.empty() && (overwrite || !CFile::Exists(imagePath))) - CTextureCache::GetInstance().Export(thumb, imagePath); + if (!settings.m_skipnfo) + { + // Save album to NFO, including album path + album.Save(pMain, "album", strAlbumPath); + std::string nfoFile = URIUtils::AddFileToFolder(strPath, "album.nfo"); + if (settings.m_overwrite || !CFile::Exists(nfoFile)) + { + if (!xmlDoc.SaveFile(nfoFile)) + { + CLog::Log(LOGERROR, "CMusicDatabase::%s: Album nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str()); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); + iFailCount++; + } + } + } + if (settings.m_artwork) + { + // Save art in album folder + // Note thumb resoluton may be lower than original when overwriting + std::string thumb = GetArtForItem(album.idAlbum, MediaTypeAlbum, "thumb"); + std::string imagePath = URIUtils::AddFileToFolder(strPath, "folder.jpg"); + if (!thumb.empty() && (settings.m_overwrite || !CFile::Exists(imagePath))) + CTextureCache::GetInstance().Export(thumb, imagePath); + } + xmlDoc.Clear(); + TiXmlDeclaration decl("1.0", "UTF-8", "yes"); + xmlDoc.InsertEndChild(decl); } - xmlDoc.Clear(); - TiXmlDeclaration decl("1.0", "UTF-8", "yes"); - xmlDoc.InsertEndChild(decl); } - } - if ((current % 50) == 0 && progress) - { - progress->SetLine(1, CVariant{album.strAlbum}); - progress->SetPercentage(current * 100 / total); - progress->Progress(); - if (progress->IsCanceled()) + if ((current % 50) == 0 && progressDialog != NULL) { - progress->Close(); - m_pDS->close(); - return; + progressDialog->SetLine(1, CVariant{ album.strAlbum }); + progressDialog->SetPercentage(current * 100 / total); } + current++; } - current++; } + + if ((settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS) || + settings.IsItemExported(ELIBEXPORT_SONGARTISTS) || + settings.IsItemExported(ELIBEXPORT_OTHERARTISTS)) && !strFolder.empty()) + { + // Find artists to export + std::vector<int> artistIds; + std::string sql; + Filter filter; + + if (settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS)) + filter.AppendWhere("EXISTS(SELECT 1 FROM album_artist WHERE album_artist.idArtist = artist.idArtist)", false); + if (settings.IsItemExported(ELIBEXPORT_SONGARTISTS)) + { + if (settings.IsItemExported(ELIBEXPORT_OTHERARTISTS)) + filter.AppendWhere("EXISTS (SELECT 1 FROM song_artist WHERE song_artist.idArtist = artist.idArtist )", false); + else + filter.AppendWhere("EXISTS (SELECT 1 FROM song_artist WHERE song_artist.idArtist = artist.idArtist AND song_artist.idRole = 1)", false); + } + else if (settings.IsItemExported(ELIBEXPORT_OTHERARTISTS)) + filter.AppendWhere("EXISTS (SELECT 1 FROM song_artist WHERE song_artist.idArtist = artist.idArtist AND song_artist.idRole > 1)", false); + + if (!settings.m_unscraped) + filter.AppendWhere("lastScraped IS NOT NULL", true); - // find all artists - std::vector<int> artistIds; - std::string artistSQL = "SELECT idArtist FROM artist where lastScraped IS NOT NULL"; - m_pDS->query(artistSQL); - total = m_pDS->num_rows(); - current = 0; - artistIds.reserve(total); - while (!m_pDS->eof()) - { - artistIds.push_back(m_pDS->fv("idArtist").get_asInt()); - m_pDS->next(); - } - m_pDS->close(); + std::string strSQL = "SELECT idArtist FROM artist"; + BuildSQL(strSQL, filter, strSQL); + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - %s", __FUNCTION__, strSQL.c_str()); - for (const auto &artistId : artistIds) - { - CArtist artist; - GetArtist(artistId, artist); - std::string strPath; - GetArtistPath(artist.idArtist,strPath); - artist.Save(pMain, "artist", strPath); - - std::map<std::string, std::string> artwork; - if (GetArtForItem(artist.idArtist, MediaTypeArtist, artwork) && singleFile) - { // append to the XML - TiXmlElement additionalNode("art"); - for (const auto &i : artwork) - XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second); - pMain->LastChild()->InsertEndChild(additionalNode); + m_pDS->query(strSQL); + int total = m_pDS->num_rows(); + int current = 0; + artistIds.reserve(total); + while (!m_pDS->eof()) + { + artistIds.push_back(m_pDS->fv("idArtist").get_asInt()); + m_pDS->next(); } - if (!singleFile) + m_pDS->close(); + + for (const auto &artistId : artistIds) { - if (!CDirectory::Exists(strPath)) - CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, strPath.c_str()); - else + CArtist artist; + GetArtist(artistId, artist); + std::string strPath; + std::map<std::string, std::string> artwork; + if (settings.IsSingleFile()) { - std::string nfoFile = URIUtils::AddFileToFolder(strPath, "artist.nfo"); - if (overwrite || !CFile::Exists(nfoFile)) + // Save artist to xml, and old path (common to music files) if it has one + GetOldArtistPath(artist.idArtist, strPath); + artist.Save(pMain, "artist", strPath); + + if (GetArtForItem(artist.idArtist, MediaTypeArtist, artwork)) + { // append to the XML + TiXmlElement additionalNode("art"); + for (const auto &i : artwork) + XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second); + pMain->LastChild()->InsertEndChild(additionalNode); + } + } + else + { // Separate files: artist.nfo and artwork in strFolder/<artist name> + // Get unique folder allowing for duplicate names e.g. 2 x John Williams + bool pathfound = GetArtistFolderName(artist, strPath); + if (pathfound) { - if (!xmlDoc.SaveFile(nfoFile)) - { - CLog::Log(LOGERROR, "%s: Artist nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str()); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); - iFailCount++; - } + strPath = URIUtils::AddFileToFolder(strFolder, strPath); + pathfound = CDirectory::Exists(strPath); + if (!pathfound) + pathfound = CDirectory::Create(strPath); } - - if (images && !artwork.empty()) + if (!pathfound) + CLog::Log(LOGDEBUG, "CMusicDatabase::%s - Not exporting artist %s as could not create %s", + __FUNCTION__, artist.strArtist.c_str(), strPath.c_str()); + else { - std::string savedThumb = URIUtils::AddFileToFolder(strPath,"folder.jpg"); - std::string savedFanart = URIUtils::AddFileToFolder(strPath,"fanart.jpg"); - if (artwork.find("thumb") != artwork.end() && (overwrite || !CFile::Exists(savedThumb))) - CTextureCache::GetInstance().Export(artwork["thumb"], savedThumb); - if (artwork.find("fanart") != artwork.end() && (overwrite || !CFile::Exists(savedFanart))) - CTextureCache::GetInstance().Export(artwork["fanart"], savedFanart); + if (!settings.m_skipnfo) + { + artist.Save(pMain, "artist", strPath); + std::string nfoFile = URIUtils::AddFileToFolder(strPath, "artist.nfo"); + if (settings.m_overwrite || !CFile::Exists(nfoFile)) + { + if (!xmlDoc.SaveFile(nfoFile)) + { + CLog::Log(LOGERROR, "CMusicDatabase::%s: Artist nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str()); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); + iFailCount++; + } + } + } + if (settings.m_artwork) + { + if (GetArtForItem(artist.idArtist, MediaTypeArtist, artwork)) + { + std::string savedThumb = URIUtils::AddFileToFolder(strPath, "folder.jpg"); + std::string savedFanart = URIUtils::AddFileToFolder(strPath, "fanart.jpg"); + if (artwork.find("thumb") != artwork.end() && (settings.m_overwrite || !CFile::Exists(savedThumb))) + CTextureCache::GetInstance().Export(artwork["thumb"], savedThumb); + if (artwork.find("fanart") != artwork.end() && (settings.m_overwrite || !CFile::Exists(savedFanart))) + CTextureCache::GetInstance().Export(artwork["fanart"], savedFanart); + } + } + xmlDoc.Clear(); + TiXmlDeclaration decl("1.0", "UTF-8", "yes"); + xmlDoc.InsertEndChild(decl); } - xmlDoc.Clear(); - TiXmlDeclaration decl("1.0", "UTF-8", "yes"); - xmlDoc.InsertEndChild(decl); } - } - - if ((current % 50) == 0 && progress) - { - progress->SetLine(1, CVariant{artist.strArtist}); - progress->SetPercentage(current * 100 / total); - progress->Progress(); - if (progress->IsCanceled()) + if ((current % 50) == 0 && progressDialog != NULL) { - progress->Close(); - m_pDS->close(); - return; + progressDialog->SetLine(1, CVariant{ artist.strArtist }); + progressDialog->SetPercentage(current * 100 / total); } + current++; } - current++; } - xmlDoc.SaveFile(xmlFile); - - CVariant data; - if (singleFile) + if (settings.IsSingleFile()) { - data["file"] = xmlFile; - if (iFailCount > 0) - data["failcount"] = iFailCount; + std::string xmlFile = URIUtils::AddFileToFolder(strFolder, "kodi_musicdb" + CDateTime::GetCurrentDateTime().GetAsDBDate() + ".xml"); + if (!settings.m_overwrite && CFile::Exists(xmlFile)) + xmlFile = URIUtils::AddFileToFolder(strFolder, "kodi_musicdb" + CDateTime::GetCurrentDateTime().GetAsSaveString() + ".xml"); + xmlDoc.SaveFile(xmlFile); + + CVariant data; + if (settings.IsSingleFile()) + { + data["file"] = xmlFile; + if (iFailCount > 0) + data["failcount"] = iFailCount; + } + ANNOUNCEMENT::CAnnouncementManager::GetInstance().Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnExport", data); } - ANNOUNCEMENT::CAnnouncementManager::GetInstance().Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnExport", data); } catch (...) { - CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + CLog::Log(LOGERROR, "CMusicDatabase::%s failed", __FUNCTION__); iFailCount++; } - if (progress) - progress->Close(); + if (progressDialog) + progressDialog->Close(); if (iFailCount > 0) HELPERS::ShowOKDialogLines(CVariant{20196}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011).c_str(), iFailCount)}); @@ -6483,7 +6856,9 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile) CArtist importedArtist; importedArtist.Load(entry); strTitle = importedArtist.strArtist; - int idArtist = GetArtistByName(importedArtist.strArtist); + + // Match by mbid first (that is definatively unique), then name (no mbid), finally by just name + int idArtist = GetArtistByMatch(importedArtist); if (idArtist > -1) { CArtist artist; @@ -6491,7 +6866,8 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile) artist.MergeScrapedArtist(importedArtist, true); UpdateArtist(artist); } - + else + CLog::Log(LOGDEBUG, "%s - Not import additional artist data as %s not found", __FUNCTION__, importedArtist.strArtist.c_str()); current++; } else if (strnicmp(entry->Value(), "album", 5) == 0) @@ -6499,7 +6875,8 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile) CAlbum importedAlbum; importedAlbum.Load(entry); strTitle = importedAlbum.strAlbum; - int idAlbum = GetAlbumByName(importedAlbum.strAlbum, importedAlbum.GetAlbumArtistString()); + // Match by mbid first (that is definatively unique), then title and artist desc (no mbid), finally by just name and artist + int idAlbum = GetAlbumByMatch(importedAlbum); if (idAlbum > -1) { CAlbum album; @@ -6507,6 +6884,8 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile) album.MergeScrapedAlbum(importedAlbum, true); UpdateAlbum(album); //Will replace song artists if present in xml } + else + CLog::Log(LOGDEBUG, "%s - Not import additional album data as %s not found", __FUNCTION__, importedAlbum.strAlbum.c_str()); current++; } diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index c244f5d896..278ea8aaa4 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -29,6 +29,7 @@ #include "Album.h" #include "dbwrappers/Database.h" #include "MusicDbUrl.h" +#include "settings/LibExportSettings.h" #include "utils/SortUtils.h" class CArtist; @@ -245,7 +246,6 @@ public: \return true if the album is retrieved, false otherwise. */ bool GetAlbum(int idAlbum, CAlbum& album, bool getSongs = true); - int UpdateAlbum(int idAlbum, const CAlbum &album); int UpdateAlbum(int idAlbum, const std::string& strAlbum, const std::string& strMusicBrainzAlbumID, const std::string& strReleaseGroupMBID, @@ -278,6 +278,7 @@ public: bool GetAlbumFromSong(int idSong, CAlbum &album); int GetAlbumByName(const std::string& strAlbum, const std::string& strArtist=""); int GetAlbumByName(const std::string& strAlbum, const std::vector<std::string>& artist); + int GetAlbumByMatch(const CAlbum &album); std::string GetAlbumById(int id); bool SetAlbumUserrating(const int idAlbum, int userrating); @@ -290,6 +291,7 @@ public: int AddArtist(const std::string& strArtist, const std::string& strMusicBrainzArtistID, bool bScrapedMBID = false); bool GetArtist(int idArtist, CArtist& artist, bool fetchAll = true); bool GetArtistExists(int idArtist); + int GetLastArtist(); int UpdateArtist(int idArtist, const std::string& strArtist, const std::string& strSortName, const std::string& strMusicBrainzArtistID, bool bScrapedMBID, @@ -309,6 +311,7 @@ public: std::string GetArtistById(int id); int GetArtistByName(const std::string& strArtist); + int GetArtistByMatch(const CArtist& artist); std::string GetRoleById(int id); /*! \brief Propagate artist sort name into the concatenated artist sort name strings @@ -325,8 +328,12 @@ public: bool GetPaths(std::set<std::string> &paths); bool SetPathHash(const std::string &path, const std::string &hash); bool GetPathHash(const std::string &path, std::string &hash); - bool GetAlbumPath(int idAlbum, std::string &path); - bool GetArtistPath(int idArtist, std::string &path); + bool GetAlbumPath(int idAlbum, std::string &basePath); + bool GetOldArtistPath(int idArtist, std::string &path); + bool GetArtistPath(const CArtist& artist, std::string &path); + bool GetAlbumFolder(const CAlbum& album, const std::string &strAlbumPath, std::string &strFolder); + bool GetArtistFolderName(const CArtist& artist, std::string &strFolder); + bool GetArtistFolderName(const std::string &strArtist, const std::string &strMusicBrainzArtistID, std::string &strFolder); ///////////////////////////////////////////////// // Genres @@ -445,7 +452,7 @@ public: ///////////////////////////////////////////////// // XML ///////////////////////////////////////////////// - void ExportToXML(const std::string &xmlFile, bool singleFile = false, bool images=false, bool overwrite=false); + void ExportToXML(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog = NULL); void ImportFromXML(const std::string &xmlFile); ///////////////////////////////////////////////// diff --git a/xbmc/music/MusicLibraryQueue.cpp b/xbmc/music/MusicLibraryQueue.cpp new file mode 100644 index 0000000000..7b5030bcf6 --- /dev/null +++ b/xbmc/music/MusicLibraryQueue.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MusicLibraryQueue.h" + +#include <utility> + +#include "dialogs/GUIDialogProgress.h" +#include "guilib/GUIWindowManager.h" +#include "GUIUserMessages.h" +#include "music/jobs/MusicLibraryExportJob.h" +#include "music/jobs/MusicLibraryJob.h" +#include "threads/SingleLock.h" +#include "Util.h" +#include "utils/Variant.h" + +CMusicLibraryQueue::CMusicLibraryQueue() + : CJobQueue(false, 1, CJob::PRIORITY_LOW), + m_jobs(), + m_modal(false), + m_exporting(false) +{ } + +CMusicLibraryQueue::~CMusicLibraryQueue() +{ + CSingleLock lock(m_critical); + m_jobs.clear(); +} + +CMusicLibraryQueue& CMusicLibraryQueue::GetInstance() +{ + static CMusicLibraryQueue s_instance; + return s_instance; +} + +void CMusicLibraryQueue::ExportLibrary(const CLibExportSettings& settings, bool showDialog /* = false */) +{ + CGUIDialogProgress* progress = NULL; + if (showDialog) + { + progress = g_windowManager.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + if (progress) + { + progress->SetHeading(CVariant{ 20196 }); //"Export music library" + progress->SetText(CVariant{ 650 }); //"Exporting" + progress->SetPercentage(0); + progress->Open(); + progress->ShowProgressBar(true); + } + } + + CMusicLibraryExportJob* exportJob = new CMusicLibraryExportJob(settings, progress); + if (showDialog) + { + AddJob(exportJob); + + if (progress) + { + // Render and wait for export to complete or be cancelled + while (progress->IsActive() && !progress->IsCanceled()) + progress->Progress(); + // Finally close progress dialog + if (progress->IsActive()) + progress->Close(); + } + } + else + { + m_modal = true; + exportJob->DoWork(); + + delete exportJob; + m_modal = false; + Refresh(); + } +} + +void CMusicLibraryQueue::AddJob(CMusicLibraryJob *job) +{ + if (job == NULL) + return; + + CSingleLock lock(m_critical); + if (!CJobQueue::AddJob(job)) + return; + + // add the job to our list of queued/running jobs + std::string jobType = job->GetType(); + MusicLibraryJobMap::iterator jobsIt = m_jobs.find(jobType); + if (jobsIt == m_jobs.end()) + { + MusicLibraryJobs jobs; + jobs.insert(job); + m_jobs.insert(std::make_pair(jobType, jobs)); + } + else + jobsIt->second.insert(job); +} + +void CMusicLibraryQueue::CancelJob(CMusicLibraryJob *job) +{ + if (job == NULL) + return; + + CSingleLock lock(m_critical); + // remember the job type needed later because the job might be deleted + // in the call to CJobQueue::CancelJob() + std::string jobType; + if (job->GetType() != NULL) + jobType = job->GetType(); + + // check if the job supports cancellation and cancel it + if (job->CanBeCancelled()) + job->Cancel(); + + // remove the job from the job queue + CJobQueue::CancelJob(job); + + // remove the job from our list of queued/running jobs + MusicLibraryJobMap::iterator jobsIt = m_jobs.find(jobType); + if (jobsIt != m_jobs.end()) + jobsIt->second.erase(job); +} + +void CMusicLibraryQueue::CancelAllJobs() +{ + CSingleLock lock(m_critical); + CJobQueue::CancelJobs(); + + // remove all scanning jobs + m_jobs.clear(); +} + +bool CMusicLibraryQueue::IsRunning() const +{ + return CJobQueue::IsProcessing() || m_modal; +} + +void CMusicLibraryQueue::Refresh() +{ + CUtil::DeleteMusicDatabaseDirectoryCache(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); + g_windowManager.SendThreadMessage(msg); +} + +void CMusicLibraryQueue::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + if (success) + { + if (QueueEmpty()) + Refresh(); + } + + { + CSingleLock lock(m_critical); + // remove the job from our list of queued/running jobs + MusicLibraryJobMap::iterator jobsIt = m_jobs.find(job->GetType()); + if (jobsIt != m_jobs.end()) + jobsIt->second.erase(static_cast<CMusicLibraryJob*>(job)); + } + + return CJobQueue::OnJobComplete(jobID, success, job); +} diff --git a/xbmc/music/MusicLibraryQueue.h b/xbmc/music/MusicLibraryQueue.h new file mode 100644 index 0000000000..27ecf7f3d2 --- /dev/null +++ b/xbmc/music/MusicLibraryQueue.h @@ -0,0 +1,100 @@ +#pragma once +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <set> + +#include "FileItem.h" +#include "settings/LibExportSettings.h" +#include "threads/CriticalSection.h" +#include "utils/JobManager.h" + +class CGUIDialogProgressBarHandle; +class CMusicLibraryJob; +class CGUIDialogProgress; + +/*! + \brief Queue for music library jobs. + + The queue can only process a single job at any time and every job will be + executed at the lowest priority. + */ +class CMusicLibraryQueue : protected CJobQueue +{ +public: + ~CMusicLibraryQueue() override; + + /*! + \brief Gets the singleton instance of the music library queue. + */ + static CMusicLibraryQueue& GetInstance(); + + /*! + \brief Enqueue a music library export job. + \param[in] settings Library export settings + \param[in] showDialog Show a progress dialog while (asynchronously) exporting, otherwise export in synchronous + */ + void ExportLibrary(const CLibExportSettings& settings, bool showDialog = false); + + /*! + \brief Adds the given job to the queue. + \param[in] job Music library job to be queued. + */ + void AddJob(CMusicLibraryJob *job); + + /*! + \brief Cancels the given job and removes it from the queue. + \param[in] job Music library job to be canceled and removed from the queue. + */ + void CancelJob(CMusicLibraryJob *job); + + /*! + \brief Cancels all running and queued jobs. + */ + void CancelAllJobs(); + + /*! + \brief Whether any jobs are running or not. + */ + bool IsRunning() const; + +protected: + // implementation of IJobCallback + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; + + /*! + \brief Notifies all to refresh the current listings. + */ + void Refresh(); + +private: + CMusicLibraryQueue(); + CMusicLibraryQueue(const CMusicLibraryQueue&); + CMusicLibraryQueue const& operator=(CMusicLibraryQueue const&); + + typedef std::set<CMusicLibraryJob*> MusicLibraryJobs; + typedef std::map<std::string, MusicLibraryJobs> MusicLibraryJobMap; + MusicLibraryJobMap m_jobs; + CCriticalSection m_critical; + + bool m_modal; + bool m_exporting; +}; diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp index a199ff962a..1ec592f4fd 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp @@ -380,17 +380,39 @@ void CGUIDialogMusicInfo::OnGetThumb() // local thumb std::string localThumb; + bool existsThumb = false; if (m_bArtistInfo) { CMusicDatabase database; database.Open(); - std::string strArtistPath; - if (database.GetArtistPath(m_artist.idArtist,strArtistPath)) + // First look for thumb in the artists folder, the primary location + std::string strArtistPath = m_artist.strPath; + // Get path when don't already have it. + bool artistpathfound = !strArtistPath.empty(); + if (!artistpathfound) + artistpathfound = database.GetArtistPath(m_artist, strArtistPath); + if (artistpathfound) + { localThumb = URIUtils::AddFileToFolder(strArtistPath, "folder.jpg"); + existsThumb = CFile::Exists(localThumb); + } + // If not there fall back local to music files (historic location for those album artists with a unique folder) + if (!existsThumb) + { + artistpathfound = database.GetOldArtistPath(m_artist.idArtist, strArtistPath); + if (artistpathfound) + { + localThumb = URIUtils::AddFileToFolder(strArtistPath, "folder.jpg"); + existsThumb = CFile::Exists(localThumb); + } + } } else + { localThumb = m_albumItem->GetUserMusicThumb(); - if (CFile::Exists(localThumb)) + existsThumb = CFile::Exists(localThumb); + } + if (existsThumb) { CFileItemPtr item(new CFileItem("thumb://Local", false)); item->SetArt("thumb", localThumb); @@ -478,13 +500,32 @@ void CGUIDialogMusicInfo::OnGetFanart() items.Add(item); } - // Grab a local thumb + // Grab a local fanart + std::string strLocal; CMusicDatabase database; database.Open(); - std::string strArtistPath; - database.GetArtistPath(m_artist.idArtist,strArtistPath); - CFileItem item(strArtistPath,true); - std::string strLocal = item.GetLocalFanart(); + // First look for fanart in the artists folder, the primary location + std::string strArtistPath = m_artist.strPath; + // Get path when don't already have it. + bool artistpathfound = !strArtistPath.empty(); + if (!artistpathfound) + artistpathfound = database.GetArtistPath(m_artist, strArtistPath); + if (artistpathfound) + { + CFileItem item(strArtistPath, true); + strLocal = item.GetLocalFanart(); + } + // If not there fall back local to music files (historic location for those album artists with a unique folder) + if (strLocal.empty()) + { + artistpathfound = database.GetOldArtistPath(m_artist.idArtist, strArtistPath); + if (artistpathfound) + { + CFileItem item(strArtistPath, true); + strLocal = item.GetLocalFanart(); + } + } + if (!strLocal.empty()) { CFileItemPtr itemLocal(new CFileItem("fanart://Local",false)); diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp index 82d971a83d..7ac2f511b7 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.cpp +++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp @@ -153,9 +153,15 @@ void CMusicInfoScanner::Process() bool scancomplete = DoScan(*it); if (scancomplete) - {// Finally download additional album and artist information for the recently added albums - if ((m_flags & SCAN_ONLINE) && m_albumsAdded.size() > 0) - ScrapeInfoAddedAlbums(); + { + if (m_albumsAdded.size() > 0) + { + if (m_flags & SCAN_ONLINE) + // Download additional album and artist information for the recently added albums. + // This also identifies any local artist thumb and fanart if it exitsts, and gives it priority, + // otherwise it is set to the first available from the remote thumbs and fanart that was scraped. + ScrapeInfoAddedAlbums(); + } } else { @@ -236,7 +242,7 @@ void CMusicInfoScanner::Process() CArtist artist; m_musicDatabase.GetArtist(params.GetArtistId(), artist); - m_musicDatabase.GetArtistPath(params.GetArtistId(), artist.strPath); + m_musicDatabase.GetArtistPath(artist, artist.strPath); if (m_handle) { @@ -287,6 +293,7 @@ void CMusicInfoScanner::Start(const std::string& strDirectory, int flags) m_pathsToScan.clear(); m_seenPaths.clear(); m_albumsAdded.clear(); + m_artistsArt.clear(); m_flags = flags; if (strDirectory.empty()) @@ -809,6 +816,22 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album } } +INFO_RET MUSIC_INFO::CMusicInfoScanner::UpdateAlbumInfo(CAlbum& album, const ADDON::ScraperPtr& scraper, bool bAllowSelection, CGUIDialogProgress* pDialog) +{ + m_musicDatabase.Open(); + INFO_RET result = UpdateDatabaseAlbumInfo(album, scraper, bAllowSelection, pDialog); + m_musicDatabase.Close(); + return result; +} + +INFO_RET MUSIC_INFO::CMusicInfoScanner::UpdateArtistInfo(CArtist& artist, const ADDON::ScraperPtr& scraper, bool bAllowSelection, CGUIDialogProgress* pDialog) +{ + m_musicDatabase.Open(); + INFO_RET result = UpdateDatabaseArtistInfo(artist, scraper, bAllowSelection, pDialog); + m_musicDatabase.Close(); + return result; +} + int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileItemList& items) { MAPSONGS songsMap; @@ -839,7 +862,7 @@ int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileI int numAdded = 0; - // Add all albums to the library + // Add all albums to the library, and hence any new song or album artists or other contributors for (VECALBUMS::iterator album = albums.begin(); album != albums.end(); ++album) { if (m_bStop) @@ -853,22 +876,36 @@ int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileI m_musicDatabase.AddAlbum(*album); m_albumsAdded.emplace_back(album->idAlbum); - // Yuk - this is a kludgy way to do what we want to do, but it will work to sort - // out artist fanart until we can restructure the artist fanart to work more - // like the album fanart. This has to be done after we've added the album so - // we have the artist IDs to update, but before we call UpdateDatabaseArtistInfo. - if (albums.size() == 1 && - !album->artistCredits.empty() && - !StringUtils::EqualsNoCase(album->artistCredits[0].GetArtist(), "various artists") && - !StringUtils::EqualsNoCase(album->artistCredits[0].GetArtist(), "various")) + /* + Make the first attempt (during scanning) to get local album artist art looking for thumbs and + fanart in the folder immediately above the album folder. This is for backwards compatibility. + It can only do this if the folder being processed contains only one album, and can only do so for + the first album artist if the album is a collaboration e.g. composer, conductor, orchestra, or by + several pop artists in their own right. + It avoids repeatedly processing the same artist by maintaining a set. Adding the album may have added + new artists, or provide art for an existing (song) artist, but does not replace any artwork already set. + Hence once art has been found for an album artist, art is not searched for in other folders. + + It will find art for "various artists", if artwork is located above the folder containing compilatons. + */ + if (albums.size() == 1 && !album->artistCredits.empty()) { - CArtist artist; - if (m_musicDatabase.GetArtist(album->artistCredits[0].GetArtistId(), artist)) + if (m_artistsArt.find(album->artistCredits[0].GetArtistId()) == m_artistsArt.end()) { - artist.strPath = URIUtils::GetParentPath(strDirectory); - m_musicDatabase.SetArtForItem(artist.idArtist, MediaTypeArtist, GetArtistArtwork(artist)); + m_artistsArt.insert(album->artistCredits[0].GetArtistId()); // Artist processed + std::map<std::string, std::string> art; + if (!m_musicDatabase.GetArtForItem(album->artistCredits[0].GetArtistId(), MediaTypeArtist, art)) + { + // Artist does not already have art, so try to find some. + // Do not have URL of other available art before scraping, so only ID and path needed + CArtist artist; + artist.idArtist = album->artistCredits[0].GetArtistId(); + artist.strPath = URIUtils::GetParentPath(album->strPath); + m_musicDatabase.SetArtForItem(album->artistCredits[0].GetArtistId(), MediaTypeArtist, GetArtistArtwork(artist, 1)); + } } } + numAdded += album->songs.size(); } @@ -978,6 +1015,78 @@ void MUSIC_INFO::CMusicInfoScanner::ScrapeInfoAddedAlbums() } } +void MUSIC_INFO::CMusicInfoScanner::RetrieveArtistArt() +{ + bool albumartistsonly = !CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS); + std::set<int> artists; + for (auto i = 0u; i < m_albumsAdded.size(); ++i) + { + if (m_bStop) + break; + int albumId = m_albumsAdded[i]; + CAlbum album; + // Fetch album artist(s) ids + m_musicDatabase.GetAlbum(albumId, album, false); + if (m_handle) + { + float percentage = static_cast<float>(i * 100) / static_cast<float>(m_albumsAdded.size()); + m_handle->SetText(album.GetAlbumArtistString() + " - " + album.strAlbum); + m_handle->SetPercentage(percentage); + } + + // Set art for album artists that have not been processed before, avoiding repeating + // unsuccessful attempts for every album by that artist. + for (const auto &artistCredit : album.artistCredits) + { + if (m_bStop) + break; + if (artists.find(artistCredit.GetArtistId()) == artists.end()) + { + artists.insert(artistCredit.GetArtistId()); // Artist processed + std::map<std::string, std::string> art; + if (!m_musicDatabase.GetArtForItem(artistCredit.GetArtistId(), MediaTypeArtist, art)) + { + CArtist artist; + //Get artist details including available art returned by scraping + m_musicDatabase.GetArtist(artistCredit.GetArtistId(), artist); + // Get path for artist in the artists folder (not done in GetArtist) + m_musicDatabase.GetArtistPath(artist, artist.strPath); + m_musicDatabase.SetArtForItem(artist.idArtist, MediaTypeArtist, GetArtistArtwork(artist, 1)); + } + } + } + // Only fetch song artist art if they are being displayed in artists node by default + if (!albumartistsonly) + { + for (auto &song : album.songs) + { + if (m_bStop) + break; + for (const auto &artistCredit : song.artistCredits) + { + if (m_bStop) + break; + + std::map<std::string, std::string> art; + if (artists.find(artistCredit.GetArtistId()) == artists.end()) + { + artists.insert(artistCredit.GetArtistId()); // Artist processed + if (!m_musicDatabase.GetArtForItem(artistCredit.GetArtistId(), MediaTypeArtist, art)) + { + CArtist artist; + //Get artist details including available art returned by scraping + m_musicDatabase.GetArtist(artistCredit.GetArtistId(), artist); + m_musicDatabase.GetArtistPath(artist, artist.strPath); + m_musicDatabase.SetArtForItem(artist.idArtist, MediaTypeArtist, GetArtistArtwork(artist, 1)); + } + } + } + } + } + + } +} + void CMusicInfoScanner::FindArtForAlbums(VECALBUMS &albums, const std::string &path) { /* @@ -1155,8 +1264,12 @@ loop: artist.MergeScrapedArtist(artistInfo.GetArtist(), CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_OVERRIDETAGS)); m_musicDatabase.Open(); m_musicDatabase.UpdateArtist(artist); - m_musicDatabase.GetArtistPath(artist.idArtist, artist.strPath); - m_musicDatabase.SetArtForItem(artist.idArtist, MediaTypeArtist, GetArtistArtwork(artist)); + // If artist art has not been set from <art> tag then look in path or use first available from scraped list + if (artist.art.empty()) + { + m_musicDatabase.GetArtistPath(artist, artist.strPath); + m_musicDatabase.SetArtForItem(artist.idArtist, MediaTypeArtist, GetArtistArtwork(artist, 1)); + } m_musicDatabase.Close(); artistInfo.SetLoaded(); } @@ -1442,17 +1555,40 @@ INFO_RET CMusicInfoScanner::DownloadArtistInfo(const CArtist& artist, const ADDO } } - // handle nfo files - std::string path = artist.strPath; - if (path.empty()) - m_musicDatabase.GetArtistPath(artist.idArtist, path); - - std::string strNfo = URIUtils::AddFileToFolder(path, "artist.nfo"); - CNfoFile::NFOResult result=CNfoFile::NO_NFO; + // Handle nfo files + CNfoFile::NFOResult result = CNfoFile::NO_NFO; CNfoFile nfoReader; - if (XFILE::CFile::Exists(strNfo)) + std::string strNfo; + std::string path; + bool existsNFO = false; + // First look for nfo in the artists folder, the primary location + path = artist.strPath; + // Get path when don't already have it. + bool artistpathfound = !path.empty(); + if (!artistpathfound) + artistpathfound = m_musicDatabase.GetArtistPath(artist, path); + if (artistpathfound) { - CLog::Log(LOGDEBUG,"Found matching nfo file: %s", CURL::GetRedacted(strNfo).c_str()); + strNfo = URIUtils::AddFileToFolder(path, "artist.nfo"); + existsNFO = XFILE::CFile::Exists(strNfo); + } + + // If not there fall back local to music files (historic location for those album artists with a unique folder) + if (!existsNFO) + { + artistpathfound = m_musicDatabase.GetOldArtistPath(artist.idArtist, path); + if (artistpathfound) + { + strNfo = URIUtils::AddFileToFolder(path, "artist.nfo"); + existsNFO = XFILE::CFile::Exists(strNfo); + } + else + CLog::Log(LOGDEBUG, "%s not have path, nfo file not possible", artist.strArtist.c_str()); + } + + if (existsNFO) + { + CLog::Log(LOGDEBUG, "Found matching nfo file: %s", CURL::GetRedacted(strNfo).c_str()); result = nfoReader.Create(strNfo, info); if (result == CNfoFile::FULL_NFO) { @@ -1463,15 +1599,15 @@ INFO_RET CMusicInfoScanner::DownloadArtistInfo(const CArtist& artist, const ADDO else if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO) { CScraperUrl scrUrl(nfoReader.ScraperUrl()); - CMusicArtistInfo artistNfo("nfo",scrUrl); + CMusicArtistInfo artistNfo("nfo", scrUrl); ADDON::ScraperPtr nfoReaderScraper = nfoReader.GetScraperInfo(); - CLog::Log(LOGDEBUG,"-- nfo-scraper: %s",nfoReaderScraper->Name().c_str()); - CLog::Log(LOGDEBUG,"-- nfo url: %s", scrUrl.m_url[0].m_url.c_str()); + CLog::Log(LOGDEBUG, "-- nfo-scraper: %s", nfoReaderScraper->Name().c_str()); + CLog::Log(LOGDEBUG, "-- nfo url: %s", scrUrl.m_url[0].m_url.c_str()); scraper.SetScraperInfo(nfoReaderScraper); scraper.GetArtists().push_back(artistNfo); } else - CLog::Log(LOGERROR,"Unable to find an url in nfo file: %s", strNfo.c_str()); + CLog::Log(LOGERROR, "Unable to find an url in nfo file: %s", strNfo.c_str()); } if (!scraper.GetArtistCount()) @@ -1618,17 +1754,19 @@ bool CMusicInfoScanner::ResolveMusicBrainz(const std::string &strMusicBrainzID, return bMusicBrainz; } -std::map<std::string, std::string> CMusicInfoScanner::GetArtistArtwork(const CArtist& artist) +std::map<std::string, std::string> CMusicInfoScanner::GetArtistArtwork(const CArtist& artist, unsigned int level /* = 3*/) { std::map<std::string, std::string> artwork; + std::string strFolder; + if (level > 3) + level = 3; //Don't go up more than 2 levels of folders // check thumb - std::string strFolder; std::string thumb; if (!artist.strPath.empty()) { strFolder = artist.strPath; - for (int i = 0; i < 3 && thumb.empty(); ++i) + for (unsigned int i = 0; i < level && thumb.empty(); ++i) { CFileItem item(strFolder, true); thumb = item.GetUserMusicThumb(true); @@ -1648,7 +1786,7 @@ std::map<std::string, std::string> CMusicInfoScanner::GetArtistArtwork(const CAr if (!artist.strPath.empty()) { strFolder = artist.strPath; - for (int i = 0; i < 3 && fanart.empty(); ++i) + for (unsigned int i = 0; i < level && fanart.empty(); ++i) { CFileItem item(strFolder, true); fanart = item.GetLocalFanart(); diff --git a/xbmc/music/infoscanner/MusicInfoScanner.h b/xbmc/music/infoscanner/MusicInfoScanner.h index 6948f1bb1d..83ba5c7b61 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.h +++ b/xbmc/music/infoscanner/MusicInfoScanner.h @@ -77,25 +77,29 @@ public: */ static void FileItemsToAlbums(CFileItemList& items, VECALBUMS& albums, MAPSONGS* songsMap = NULL); - /*! \brief Fixup albums and songs - - If albumartist is not available in a song, we determine it from the - common portion of each song's artist list. - - eg the common artist for - Bob Dylan / Tom Petty / Roy Orbison - Bob Dylan / Tom Petty - would be "Bob Dylan / Tom Petty". - - If all songs that share an album - 1. have a non-empty album name - 2. have at least two different primary artists - 3. have no album artist set - 4. and no track numbers overlap - we assume it is a various artists album, and set the albumartist field accordingly. - - */ - static void FixupAlbums(VECALBUMS &albums); + /*! \brief Scrape additional album information and update the music database with it. + Given an album, search for it using the given scraper. + If info is found, update the database and artwork with the new + information. + \param album [in/out] the album to update + \param scraper [in] the album scraper to use + \param bAllowSelection [in] should we allow the user to manually override the info with a GUI if the album is not found? + \param pDialog [in] a progress dialog which this and downstream functions can update with status, if required + */ + INFO_RET UpdateAlbumInfo(CAlbum& album, const ADDON::ScraperPtr& scraper, bool bAllowSelection, CGUIDialogProgress* pDialog = NULL); + + /*! \brief Scrape additional artist information and update the music database with it. + Given an artist, search for it using the given scraper. + If info is found, update the database and artwork with the new + information. + \param artist [in/out] the artist to update + \param scraper [in] the artist scraper to use + \param bAllowSelection [in] should we allow the user to manually override the info with a GUI if the album is not found? + \param pDialog [in] a progress dialog which this and downstream functions can update with status, if required + */ + INFO_RET UpdateArtistInfo(CArtist& artist, const ADDON::ScraperPtr& scraper, bool bAllowSelection, CGUIDialogProgress* pDialog = NULL); + +protected: /*! \brief Find art for albums Based on the albums in the folder, finds whether we have unique album art @@ -114,8 +118,8 @@ public: */ static void FindArtForAlbums(VECALBUMS &albums, const std::string &path); - /*! \brief Update the database information for a MusicDB album - Given an album, search and update its info with the given scraper. + /*! \brief Scrape additional album information and update the database. + Search for the given album using the given scraper. If info is found, update the database and artwork with the new information. \param album [in/out] the album to update @@ -125,8 +129,8 @@ public: */ INFO_RET UpdateDatabaseAlbumInfo(CAlbum& album, const ADDON::ScraperPtr& scraper, bool bAllowSelection, CGUIDialogProgress* pDialog = NULL); - /*! \brief Update the database information for a MusicDB artist - Given an artist, search and update its info with the given scraper. + /*! \brief Scrape additional artist information and update the database. + Search for the given artist using the given scraper. If info is found, update the database and artwork with the new information. \param artist [in/out] the artist to update @@ -162,13 +166,16 @@ public: */ INFO_RET DownloadArtistInfo(const CArtist& artist, const ADDON::ScraperPtr& scraper, MUSIC_GRABBER::CMusicArtistInfo& artistInfo, bool bUseScrapedMBID, CGUIDialogProgress* pDialog = NULL); - /*! \brief Search for art for an artist - Look for art for an artist. Checks the artist structure for thumbs, and checks - the artist path (if non-empty) for artist/folder tbns, etc. + /*! \brief Get art for an artist + Checks for thumb and fanart in given folder, and in parent folders back up the artist path (if non-empty). + If none is found there then it tries to use the first available thumb and fanart from those listed in the + artist structure. Images found are cached. \param artist [in] an artist + \param level [in] how many levels of folders to search in. 1 => just the folder + \return set of art type and file location (URL or path) pairs */ - std::map<std::string, std::string> GetArtistArtwork(const CArtist& artist); -protected: + std::map<std::string, std::string> GetArtistArtwork(const CArtist& artist, unsigned int level = 3); + void Process() override; /*! \brief Scan in the ID3/Ogg/FLAC tags for a bunch of FileItems @@ -182,6 +189,7 @@ protected: int RetrieveMusicInfo(const std::string& strDirectory, CFileItemList& items); void ScrapeInfoAddedAlbums(); + void RetrieveArtistArt(); /*! \brief Scan in the ID3/Ogg/FLAC tags for a bunch of FileItems Given a list of FileItems, scan in the tags for those FileItems @@ -222,6 +230,7 @@ protected: CMusicDatabase m_musicDatabase; std::vector<int> m_albumsAdded; + std::set<int> m_artistsArt; std::set<std::string> m_pathsToScan; std::set<std::string> m_seenPaths; diff --git a/xbmc/music/jobs/CMakeLists.txt b/xbmc/music/jobs/CMakeLists.txt new file mode 100644 index 0000000000..8b317a95bd --- /dev/null +++ b/xbmc/music/jobs/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES MusicLibraryJob.cpp + MusicLibraryProgressJob.cpp + MusicLibraryExportJob.cpp) + +set(HEADERS MusicLibraryJob.h + MusicLibraryProgressJob.h + MusicLibraryExportJob.h) + +core_add_library(music_jobs) diff --git a/xbmc/music/jobs/MusicLibraryExportJob.cpp b/xbmc/music/jobs/MusicLibraryExportJob.cpp new file mode 100644 index 0000000000..f8d05fb46f --- /dev/null +++ b/xbmc/music/jobs/MusicLibraryExportJob.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MusicLibraryExportJob.h" +#include "dialogs/GUIDialogProgress.h" +#include "music/MusicDatabase.h" +#include "settings/LibExportSettings.h" + +CMusicLibraryExportJob::CMusicLibraryExportJob(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog) + : CMusicLibraryProgressJob(NULL), + m_settings(settings) +{ + if (progressDialog) + SetProgressIndicators(NULL, progressDialog); +} + +CMusicLibraryExportJob::~CMusicLibraryExportJob() = default; + +bool CMusicLibraryExportJob::operator==(const CJob* job) const +{ + if (strcmp(job->GetType(), GetType()) != 0) + return false; + + const CMusicLibraryExportJob* exportJob = dynamic_cast<const CMusicLibraryExportJob*>(job); + if (exportJob == NULL) + return false; + + return !(m_settings != exportJob->m_settings); +} + +bool CMusicLibraryExportJob::Work(CMusicDatabase &db) +{ + db.ExportToXML(m_settings, GetProgressDialog()); + + return true; +} diff --git a/xbmc/music/jobs/MusicLibraryExportJob.h b/xbmc/music/jobs/MusicLibraryExportJob.h new file mode 100644 index 0000000000..9b52d24712 --- /dev/null +++ b/xbmc/music/jobs/MusicLibraryExportJob.h @@ -0,0 +1,53 @@ +#pragma once +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MusicLibraryProgressJob.h" +#include "settings/LibExportSettings.h" + +class CGUIDialogProgress; + +/*! + \brief Music library job implementation for exporting the music library. +*/ +class CMusicLibraryExportJob : public CMusicLibraryProgressJob +{ +public: + /*! + \brief Creates a new music library export job for the given paths. + + \param[in] settings Library export settings + \param[in] progressDialog Progress dialog to be used to display the export progress + */ + CMusicLibraryExportJob(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog); + + ~CMusicLibraryExportJob() override; + + // specialization of CJob + const char *GetType() const override { return "MusicLibraryExportJob"; } + bool operator==(const CJob* job) const override; + +protected: + // implementation of CMusicLibraryJob + bool Work(CMusicDatabase &db) override; + +private: + CLibExportSettings m_settings; +}; diff --git a/xbmc/music/jobs/MusicLibraryJob.cpp b/xbmc/music/jobs/MusicLibraryJob.cpp new file mode 100644 index 0000000000..9f940bfb34 --- /dev/null +++ b/xbmc/music/jobs/MusicLibraryJob.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MusicLibraryJob.h" +#include "music/MusicDatabase.h" + +CMusicLibraryJob::CMusicLibraryJob() = default; + +CMusicLibraryJob::~CMusicLibraryJob() = default; + +bool CMusicLibraryJob::DoWork() +{ + CMusicDatabase db; + if (!db.Open()) + return false; + + return Work(db); +} diff --git a/xbmc/music/jobs/MusicLibraryJob.h b/xbmc/music/jobs/MusicLibraryJob.h new file mode 100644 index 0000000000..d8aec1bc01 --- /dev/null +++ b/xbmc/music/jobs/MusicLibraryJob.h @@ -0,0 +1,61 @@ +#pragma once +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/Job.h" + +class CMusicDatabase; + +/*! + \brief Basic implementation/interface of a CJob which interacts with the + music database. + */ +class CMusicLibraryJob : public CJob +{ +public: + ~CMusicLibraryJob() override; + + /*! + \brief Whether the job can be cancelled or not. + */ + virtual bool CanBeCancelled() const { return false; } + + /*! + \brief Tries to cancel the running job. + \return True if the job was cancelled, false otherwise + */ + virtual bool Cancel() { return false; } + + // implementation of CJob + bool DoWork() override; + const char *GetType() const override { return "MusicLibraryJob"; } + bool operator==(const CJob* job) const override { return false; } + +protected: + CMusicLibraryJob(); + + /*! + \brief Worker method to be implemented by an actual implementation. + + \param[in] db Already open music database to be used for interaction + \return True if the process succeeded, false otherwise + */ + virtual bool Work(CMusicDatabase &db) = 0; +}; diff --git a/xbmc/music/jobs/MusicLibraryProgressJob.cpp b/xbmc/music/jobs/MusicLibraryProgressJob.cpp new file mode 100644 index 0000000000..64b0c6d982 --- /dev/null +++ b/xbmc/music/jobs/MusicLibraryProgressJob.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "MusicLibraryProgressJob.h" + +CMusicLibraryProgressJob::CMusicLibraryProgressJob(CGUIDialogProgressBarHandle* progressBar) + : CProgressJob(progressBar) +{ } + +CMusicLibraryProgressJob::~CMusicLibraryProgressJob() = default; + +bool CMusicLibraryProgressJob::DoWork() +{ + bool result = CMusicLibraryJob::DoWork(); + + MarkFinished(); + + return result; +} diff --git a/xbmc/music/jobs/MusicLibraryProgressJob.h b/xbmc/music/jobs/MusicLibraryProgressJob.h new file mode 100644 index 0000000000..a8dd69ce33 --- /dev/null +++ b/xbmc/music/jobs/MusicLibraryProgressJob.h @@ -0,0 +1,40 @@ +#pragma once +/* + * Copyright (C) 2017 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "utils/ProgressJob.h" +#include "music/jobs/MusicLibraryJob.h" + +/*! + \brief Combined base implementation of a music library job with a progress bar. + */ +class CMusicLibraryProgressJob : public CProgressJob, public CMusicLibraryJob +{ +public: + ~CMusicLibraryProgressJob() override; + + // implementation of CJob + bool DoWork() override; + const char *GetType() const override { return "CMusicLibraryProgressJob"; } + bool operator==(const CJob* job) const override { return false; } + +protected: + explicit CMusicLibraryProgressJob(CGUIDialogProgressBarHandle* progressBar); +}; diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index 3a96125106..bc3c0faea0 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -23,6 +23,7 @@ #include "GUIUserMessages.h" #include "GUIWindowMusicBase.h" #include "dialogs/GUIDialogMediaSource.h" +#include "dialogs/GUIDialogFileBrowser.h" #include "music/dialogs/GUIDialogMusicInfo.h" #include "playlists/PlayListFactory.h" #include "Util.h" @@ -49,6 +50,7 @@ #include "dialogs/GUIDialogProgress.h" #include "FileItem.h" #include "filesystem/File.h" +#include "messaging/helpers/DialogHelper.h" #include "messaging/helpers/DialogOKHelper.h" #include "profiles/ProfilesManager.h" #include "storage/MediaManager.h" @@ -79,6 +81,7 @@ using namespace PLAYLIST; using namespace MUSIC_GRABBER; using namespace MUSIC_INFO; using namespace KODI::MESSAGING; +using KODI::MESSAGING::HELPERS::DialogResponse; #define CONTROL_BTNVIEWASICONS 2 #define CONTROL_BTNSORTBY 3 @@ -368,8 +371,20 @@ void CGUIWindowMusicBase::ShowArtistInfo(const CFileItem *pItem, bool bShowInfo CArtist artist; if (!m_musicdatabase.GetArtist(params.GetArtistId(), artist)) - return; - m_musicdatabase.GetArtistPath(params.GetArtistId(), artist.strPath); + return; + // Get the *name* of the folder for this artist within the Artist Info folder (may not exist). + // If there is no Artist Info folder specififed in settings this will be blank + bool artistpathfound = m_musicdatabase.GetArtistPath(artist, artist.strPath); + + // Set up path for *item folder when browsing for art, by default this is in the Artist Info Folder + std::string artistItemPath = artist.strPath; + if (!artistpathfound || !CDirectory::Exists(artist.strPath)) + // Fall back local to music files (historic location for those album artists with a unique folder) + // although there may not be such a unique folder for the arist + if (!m_musicdatabase.GetOldArtistPath(artist.idArtist, artistItemPath)) + // Fall back further to browse the Artist Info Folder itself + artistItemPath = CServiceBroker::GetSettings().GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + bool refresh = false; while (1) { @@ -396,7 +411,7 @@ void CGUIWindowMusicBase::ShowArtistInfo(const CFileItem *pItem, bool bShowInfo } CMusicInfoScanner scanner; - if (scanner.UpdateDatabaseArtistInfo(artist, scraper, bShowInfo, m_dlgProgress) != INFO_ADDED) + if (scanner.UpdateArtistInfo(artist, scraper, bShowInfo, m_dlgProgress) != INFO_ADDED) { HELPERS::ShowOKDialogText(CVariant{21889}, CVariant{20199}); break; @@ -409,7 +424,7 @@ void CGUIWindowMusicBase::ShowArtistInfo(const CFileItem *pItem, bool bShowInfo CGUIDialogMusicInfo *pDlgArtistInfo = g_windowManager.GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO); if (pDlgArtistInfo) { - pDlgArtistInfo->SetArtist(artist, artist.strPath); + pDlgArtistInfo->SetArtist(artist, artistItemPath); pDlgArtistInfo->Open(); if (pDlgArtistInfo->NeedRefresh()) @@ -475,7 +490,7 @@ bool CGUIWindowMusicBase::ShowAlbumInfo(const CFileItem *pItem, bool bShowInfo / } CMusicInfoScanner scanner; - if (scanner.UpdateDatabaseAlbumInfo(album, scraper, bShowInfo, m_dlgProgress) != INFO_ADDED) + if (scanner.UpdateAlbumInfo(album, scraper, bShowInfo, m_dlgProgress) != INFO_ADDED) { HELPERS::ShowOKDialogText(CVariant{185}, CVariant{500}); if (m_dlgProgress) @@ -921,7 +936,7 @@ bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; case CONTEXT_BUTTON_SCAN: - OnScan(itemNumber); + OnScan(itemNumber, true); return true; case CONTEXT_BUTTON_CDDB: @@ -1325,8 +1340,6 @@ void CGUIWindowMusicBase::OnInitWindow() if (CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO)) if (CGUIDialogYesNo::ShowAndGetInput(CVariant{799}, CVariant{38061})) flags |= CMusicInfoScanner::SCAN_ONLINE; - if (CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE)) - flags |= CMusicInfoScanner::SCAN_BACKGROUND; g_application.StartMusicScan("", true, flags); m_musicdatabase.SetMusicTagScanVersion(); // once is enough (user may interrupt, but that's up to them) } @@ -1349,7 +1362,7 @@ std::string CGUIWindowMusicBase::GetStartFolder(const std::string &dir) return CGUIMediaWindow::GetStartFolder(dir); } -void CGUIWindowMusicBase::OnScan(int iItem) +void CGUIWindowMusicBase::OnScan(int iItem, bool bPromptRescan /*= false*/) { std::string strPath; if (iItem < 0 || iItem >= m_vecItems->Size()) @@ -1361,10 +1374,15 @@ void CGUIWindowMusicBase::OnScan(int iItem) //! This will require changes to the info scanner, which assumes we're running on a folder strPath = m_vecItems->GetPath(); } - DoScan(strPath); + // Ask for full rescan of music files when scan item from file view context menu + bool doRescan = false; + if (bPromptRescan) + doRescan = CGUIDialogYesNo::ShowAndGetInput(CVariant{ 799 }, CVariant{ 38062 }); + + DoScan(strPath, doRescan); } -void CGUIWindowMusicBase::DoScan(const std::string &strPath) +void CGUIWindowMusicBase::DoScan(const std::string &strPath, bool bRescan /*= false*/) { if (g_application.IsMusicScanning()) { @@ -1374,7 +1392,12 @@ void CGUIWindowMusicBase::DoScan(const std::string &strPath) // Start background loader int iControl=GetFocusedControlID(); - g_application.StartMusicScan(strPath); + int flags = 0; + if (bRescan) + flags = CMusicInfoScanner::SCAN_RESCAN; + if (CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO)) + flags |= CMusicInfoScanner::SCAN_ONLINE; + g_application.StartMusicScan(strPath, true, flags); SET_CONTROL_FOCUS(iControl, 0); UpdateButtons(); } @@ -1404,10 +1427,37 @@ void CGUIWindowMusicBase::OnPrepareFileItems(CFileItemList &items) void CGUIWindowMusicBase::OnAssignContent(const std::string &path) { - // Add content selection logic here, if music is ready for that some day + // Music scrapers are not source specific, so unlike video there is no content selection logic here. + // Called on having added a music source, this starts scanning items into library when required - // This won't ask you to clean/delete your content, when you change the scraper to none (if music gets this), might ne nice in the future + // "Do you want to add the media from this source to your library?" if (CGUIDialogYesNo::ShowAndGetInput(CVariant{ 20444 }, CVariant{ 20447 })) + { + if (CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO)) + { + // Scraping as well so check artist info folder setting before scanning, may want to scrape artist info from nfo files + std::string folder = CServiceBroker::GetSettings().GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + if (folder.empty()) + { + //"Do you have local artist information (NFO) and art files that you want to fetch? Set the location of these artist folders now" + //Settings (YES) button allows user to enter the artist info folder + if (HELPERS::ShowYesNoDialogText(20223, 38320, 186, 10004) == DialogResponse::YES) + { + // Choose artist info folder + VECSOURCES shares; + g_mediaManager.GetLocalDrives(shares); + g_mediaManager.GetNetworkLocations(shares); + g_mediaManager.GetRemovableDrives(shares); + std::string strDirectory = "default location"; + if (CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(20223), strDirectory, true)) + { + if (!strDirectory.empty()) + CServiceBroker::GetSettings().SetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER, strDirectory); + } + } + } + } g_application.StartMusicScan(path, true); + } } diff --git a/xbmc/music/windows/GUIWindowMusicBase.h b/xbmc/music/windows/GUIWindowMusicBase.h index 2cfa245bdc..3e02f17560 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.h +++ b/xbmc/music/windows/GUIWindowMusicBase.h @@ -50,7 +50,7 @@ public: void OnItemInfo(CFileItem *pItem, bool bShowInfo = false); - void DoScan(const std::string &strPath); + void DoScan(const std::string &strPath, bool bRescan = false); /*! \brief Prompt the user if he wants to start a scan for this folder \param path the path to assign content for @@ -79,7 +79,7 @@ protected: std::string GetStartFolder(const std::string &dir) override; void OnItemLoaded(CFileItem* pItem) override {} - virtual void OnScan(int iItem); + virtual void OnScan(int iItem, bool bPromptRescan = false); bool CheckFilterAdvanced(CFileItemList &items) const override; bool CanContainFilter(const std::string &strDirectory) const override; diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index cb7867c9ed..8359c4c617 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -273,7 +273,6 @@ void CAdvancedSettings::Initialize() m_bMusicLibraryAllItemsOnBottom = false; m_bMusicLibraryCleanOnUpdate = false; - m_bMusicLibraryPromptFullTagScan = false; m_bMusicLibraryArtistSortOnUpdate = false; m_iMusicLibraryRecentlyAddedItems = 25; m_strMusicLibraryAlbumFormat = ""; @@ -725,7 +724,6 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file) XMLUtils::GetBoolean(pElement, "prioritiseapetags", m_prioritiseAPEv2tags); XMLUtils::GetBoolean(pElement, "allitemsonbottom", m_bMusicLibraryAllItemsOnBottom); XMLUtils::GetBoolean(pElement, "cleanonupdate", m_bMusicLibraryCleanOnUpdate); - XMLUtils::GetBoolean(pElement, "promptfulltagscan", m_bMusicLibraryPromptFullTagScan); XMLUtils::GetBoolean(pElement, "artistsortonupdate", m_bMusicLibraryArtistSortOnUpdate); XMLUtils::GetBoolean(pElement, "useartistsortname", m_musicUseArtistSortName); XMLUtils::GetString(pElement, "albumformat", m_strMusicLibraryAlbumFormat); diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h index 672667f0cf..6883023e73 100644 --- a/xbmc/settings/AdvancedSettings.h +++ b/xbmc/settings/AdvancedSettings.h @@ -255,7 +255,6 @@ class CAdvancedSettings : public ISettingCallback, public ISettingsHandler int m_iMusicLibraryDateAdded; bool m_bMusicLibraryAllItemsOnBottom; bool m_bMusicLibraryCleanOnUpdate; - bool m_bMusicLibraryPromptFullTagScan; bool m_bMusicLibraryArtistSortOnUpdate; std::string m_strMusicLibraryAlbumFormat; bool m_prioritiseAPEv2tags; diff --git a/xbmc/settings/CMakeLists.txt b/xbmc/settings/CMakeLists.txt index 155720f3a6..0e9f7f7a5a 100644 --- a/xbmc/settings/CMakeLists.txt +++ b/xbmc/settings/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES AdvancedSettings.cpp DisplaySettings.cpp GameSettings.cpp + LibExportSettings.cpp MediaSettings.cpp MediaSourceSettings.cpp SettingAddon.cpp @@ -18,6 +19,7 @@ set(HEADERS AdvancedSettings.h DiscSettings.h DisplaySettings.h GameSettings.h + LibExportSettings.h MediaSettings.h MediaSourceSettings.h SettingAddon.h diff --git a/xbmc/settings/LibExportSettings.cpp b/xbmc/settings/LibExportSettings.cpp new file mode 100644 index 0000000000..374ce3939f --- /dev/null +++ b/xbmc/settings/LibExportSettings.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 Team KODI + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KODI; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +// LibExportSettings.cpp: implementation of the CLibExportSettings class. +// +////////////////////////////////////////////////////////////////////// + +#include "LibExportSettings.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CLibExportSettings::CLibExportSettings() +{ + m_exporttype = ELIBEXPORT_SINGLEFILE; + m_itemstoexport = ELIBEXPORT_ALBUMS + ELIBEXPORT_ALBUMARTISTS; + m_overwrite = false; + m_artwork = false; + m_unscraped = false; + m_skipnfo = false; +} + +bool CLibExportSettings::operator!=(const CLibExportSettings &right) const +{ + if (m_exporttype != right.m_exporttype) + return true; + if (m_strPath != right.m_strPath) + return true; + if (m_overwrite != right.m_overwrite) + return true; + if (m_itemstoexport != right.m_itemstoexport) + return true; + + if (m_artwork != right.m_artwork) + return true; + if (m_unscraped != right.m_unscraped) + return true; + if (m_skipnfo != right.m_skipnfo) + return true; + + return false; +} + +bool CLibExportSettings::IsItemExported(ELIBEXPORTOPTIONS item) const +{ + return (m_itemstoexport & item); +} + +std::vector<int> CLibExportSettings::GetExportItems() const +{ + std::vector<int> values; + if (IsItemExported(ELIBEXPORT_ALBUMS)) + values.emplace_back(ELIBEXPORT_ALBUMS); + if (IsItemExported(ELIBEXPORT_ALBUMARTISTS)) + values.emplace_back(ELIBEXPORT_ALBUMARTISTS); + if (IsItemExported(ELIBEXPORT_SONGARTISTS)) + values.emplace_back(ELIBEXPORT_SONGARTISTS); + if (IsItemExported(ELIBEXPORT_OTHERARTISTS)) + values.emplace_back(ELIBEXPORT_OTHERARTISTS); + if (IsItemExported(ELIBEXPORT_ACTORTHUMBS)) + values.emplace_back(ELIBEXPORT_ACTORTHUMBS); + + return values; +} + +bool CLibExportSettings::IsSingleFile() const +{ + return (m_exporttype == ELIBEXPORT_SINGLEFILE); +} + +bool CLibExportSettings::IsSeparateFiles() const +{ + return (m_exporttype == ELIBEXPORT_SEPARATEFILES); +} + +bool CLibExportSettings::IsToLibFolders() const +{ + return (m_exporttype == ELIBEXPORT_TOLIBRARYFOLDER); +} diff --git a/xbmc/settings/LibExportSettings.h b/xbmc/settings/LibExportSettings.h new file mode 100644 index 0000000000..2827c55201 --- /dev/null +++ b/xbmc/settings/LibExportSettings.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 Team KODI + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KODI; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ +// LibExportSettings.h: interface for the CLibExportSettings class. +// +////////////////////////////////////////////////////////////////////// + +#pragma once + +#include <string> +#include "settings/lib/Setting.h" + +// Enumeration of library export options (possibly OR'd together) +enum ELIBEXPORTOPTIONS +{ + ELIBEXPORT_SINGLEFILE = 0x0000, + ELIBEXPORT_SEPARATEFILES = 0x0001, + ELIBEXPORT_TOLIBRARYFOLDER = 0x0002, + ELIBEXPORT_OVERWRITE = 0x0004, + ELIBEXPORT_UNSCRAPED = 0x0008, + ELIBEXPORT_ALBUMS = 0x0010, + ELIBEXPORT_ALBUMARTISTS = 0x0020, + ELIBEXPORT_SONGARTISTS = 0x0040, + ELIBEXPORT_OTHERARTISTS = 0x0080, + ELIBEXPORT_ARTWORK = 0x0100, + ELIBEXPORT_NFOFILES = 0x0200, + ELIBEXPORT_ACTORTHUMBS = 0x0400 +}; + +class CLibExportSettings +{ +public: + CLibExportSettings(); + ~CLibExportSettings() = default; + + bool operator!=(const CLibExportSettings &right) const; + bool IsItemExported(ELIBEXPORTOPTIONS item) const; + std::vector<int> GetExportItems() const; + void ClearItems() { m_itemstoexport = 0; } + void AddItem(ELIBEXPORTOPTIONS item) { m_itemstoexport += item; } + unsigned int GetItemsToExport() { return m_itemstoexport; } + void SetItemsToExport(int itemstoexport) { m_itemstoexport = static_cast<unsigned int>(itemstoexport); } + unsigned int GetExportType() { return m_exporttype; } + void SetExportType(int exporttype) { m_exporttype = static_cast<unsigned int>(exporttype); } + bool IsSingleFile() const; + bool IsSeparateFiles() const; + bool IsToLibFolders() const; + + std::string m_strPath; + bool m_overwrite; + bool m_artwork; + bool m_unscraped; + bool m_skipnfo; +private: + unsigned int m_exporttype; //singlefile, separate files, to library folder + unsigned int m_itemstoexport; +}; diff --git a/xbmc/settings/MediaSettings.cpp b/xbmc/settings/MediaSettings.cpp index 2d75aa78e6..e801c52aff 100644 --- a/xbmc/settings/MediaSettings.cpp +++ b/xbmc/settings/MediaSettings.cpp @@ -27,9 +27,11 @@ #include "PlayListPlayer.h" #include "dialogs/GUIDialogContextMenu.h" #include "dialogs/GUIDialogFileBrowser.h" +#include "settings/dialogs/GUIDialogLibExportSettings.h" #include "guilib/LocalizeStrings.h" #include "interfaces/builtins/Builtins.h" #include "music/MusicDatabase.h" +#include "music/MusicLibraryQueue.h" #include "messaging/ApplicationMessenger.h" #include "messaging/helpers/DialogHelper.h" #include "profiles/ProfilesManager.h" @@ -294,7 +296,14 @@ void CMediaSettings::OnSettingAction(std::shared_ptr<const CSetting> setting) g_application.StartMusicCleanup(true); } else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT) - CBuiltins::GetInstance().Execute("exportlibrary(music)"); + { + CLibExportSettings m_musicExportSettings; + if (CGUIDialogLibExportSettings::Show(m_musicExportSettings)) + { + // Export music library showing progress dialog + CMusicLibraryQueue::GetInstance().ExportLibrary(m_musicExportSettings, true); + } + } else if (settingId == CSettings::SETTING_MUSICLIBRARY_IMPORT) { std::string path; diff --git a/xbmc/settings/MediaSettings.h b/xbmc/settings/MediaSettings.h index 0a89536bd4..978b135877 100644 --- a/xbmc/settings/MediaSettings.h +++ b/xbmc/settings/MediaSettings.h @@ -26,7 +26,7 @@ #include "settings/lib/ISettingsHandler.h" #include "settings/lib/ISubSettings.h" #include "settings/GameSettings.h" -#include "cores/VideoSettings.h" +#include "settings/LibExportSettings.h" #include "threads/CriticalSection.h" #define VOLUME_DRC_MINIMUM 0 // 0dB diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index 4d640e990e..587ebcef81 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -260,6 +260,7 @@ const std::string CSettings::SETTING_PVRCLIENT_MENUHOOK = "pvrclient.menuhook"; const std::string CSettings::SETTING_PVRTIMERS_HIDEDISABLEDTIMERS = "pvrtimers.hidedisabledtimers"; const std::string CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS = "musiclibrary.showcompilationartists"; const std::string CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO = "musiclibrary.downloadinfo"; +const std::string CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER = "musiclibrary.artistsfolder"; const std::string CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER = "musiclibrary.albumsscraper"; const std::string CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER = "musiclibrary.artistsscraper"; const std::string CSettings::SETTING_MUSICLIBRARY_OVERRIDETAGS = "musiclibrary.overridetags"; @@ -268,6 +269,13 @@ const std::string CSettings::SETTING_MUSICLIBRARY_UPDATEONSTARTUP = "musiclibrar const std::string CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE = "musiclibrary.backgroundupdate"; const std::string CSettings::SETTING_MUSICLIBRARY_CLEANUP = "musiclibrary.cleanup"; const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT = "musiclibrary.export"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE = "musiclibrary.exportfiletype"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER = "musiclibrary.exportfolder"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS = "musiclibrary.exportitems"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED = "musiclibrary.exportunscraped"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE = "musiclibrary.exportoverwrite"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK = "musiclibrary.exportartwork"; +const std::string CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO = "musiclibrary.exportskipnfo"; const std::string CSettings::SETTING_MUSICLIBRARY_IMPORT = "musiclibrary.import"; const std::string CSettings::SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM = "musicplayer.autoplaynextitem"; const std::string CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT = "musicplayer.queuebydefault"; diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index b76f35fa16..c3decd38ec 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -206,6 +206,7 @@ public: static const std::string SETTING_PVRTIMERS_HIDEDISABLEDTIMERS; static const std::string SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS; static const std::string SETTING_MUSICLIBRARY_DOWNLOADINFO; + static const std::string SETTING_MUSICLIBRARY_ARTISTSFOLDER; static const std::string SETTING_MUSICLIBRARY_ALBUMSSCRAPER; static const std::string SETTING_MUSICLIBRARY_ARTISTSSCRAPER; static const std::string SETTING_MUSICLIBRARY_OVERRIDETAGS; @@ -214,6 +215,13 @@ public: static const std::string SETTING_MUSICLIBRARY_BACKGROUNDUPDATE; static const std::string SETTING_MUSICLIBRARY_CLEANUP; static const std::string SETTING_MUSICLIBRARY_EXPORT; + static const std::string SETTING_MUSICLIBRARY_EXPORT_FILETYPE; + static const std::string SETTING_MUSICLIBRARY_EXPORT_FOLDER; + static const std::string SETTING_MUSICLIBRARY_EXPORT_ITEMS; + static const std::string SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED; + static const std::string SETTING_MUSICLIBRARY_EXPORT_OVERWRITE; + static const std::string SETTING_MUSICLIBRARY_EXPORT_ARTWORK; + static const std::string SETTING_MUSICLIBRARY_EXPORT_SKIPNFO; static const std::string SETTING_MUSICLIBRARY_IMPORT; static const std::string SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM; static const std::string SETTING_MUSICPLAYER_QUEUEBYDEFAULT; diff --git a/xbmc/settings/dialogs/CMakeLists.txt b/xbmc/settings/dialogs/CMakeLists.txt index 1ee3a76306..59ab1c831a 100644 --- a/xbmc/settings/dialogs/CMakeLists.txt +++ b/xbmc/settings/dialogs/CMakeLists.txt @@ -1,11 +1,13 @@ set(SOURCES GUIDialogAudioDSPManager.cpp GUIDialogContentSettings.cpp + GUIDialogLibExportSettings.cpp GUIDialogSettingsBase.cpp GUIDialogSettingsManagerBase.cpp GUIDialogSettingsManualBase.cpp) set(HEADERS GUIDialogAudioDSPManager.h GUIDialogContentSettings.h + GUIDialogLibExportSettings.h GUIDialogSettingsBase.h GUIDialogSettingsManagerBase.h GUIDialogSettingsManualBase.h) diff --git a/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp new file mode 100644 index 0000000000..f9ee2040bc --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp @@ -0,0 +1,368 @@ +/* +* Copyright (C) 2005-2014 Team XBMC +* http://xbmc.org +* +* This Program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* This Program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with XBMC; see the file COPYING. If not, see +* <http://www.gnu.org/licenses/>. +* +*/ + +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include <limits.h> + +#include "GUIDialogLibExportSettings.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/helpers/DialogHelper.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "ServiceBroker.h" +#include "settings/SettingUtils.h" +#include "settings/lib/Setting.h" +#include "settings/Settings.h" +#include "settings/windows/GUIControlSettings.h" +#include "storage/MediaManager.h" +#include "Util.h" +#include "utils/log.h" +#include "utils/URIUtils.h" +#include "filesystem/Directory.h" + +using namespace ADDON; +using namespace KODI::MESSAGING; + +using KODI::MESSAGING::HELPERS::DialogResponse; + +CGUIDialogLibExportSettings::CGUIDialogLibExportSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_LIBEXPORT_SETTINGS, "DialogSettings.xml"), + m_destinationChecked(false) +{ } + +bool CGUIDialogLibExportSettings::Show(CLibExportSettings& settings) +{ + CGUIDialogLibExportSettings *dialog = g_windowManager.GetWindow<CGUIDialogLibExportSettings>(WINDOW_DIALOG_LIBEXPORT_SETTINGS); + if (!dialog) + return false; + + // Get current export settings from service broker + dialog->m_settings.SetExportType(CServiceBroker::GetSettings().GetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE)); + dialog->m_settings.m_strPath = CServiceBroker::GetSettings().GetString(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER); + dialog->m_settings.SetItemsToExport(CServiceBroker::GetSettings().GetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS)); + dialog->m_settings.m_unscraped = CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED); + dialog->m_settings.m_artwork = CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK); + dialog->m_settings.m_skipnfo = CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO); + dialog->m_settings.m_overwrite = CServiceBroker::GetSettings().GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE); + + dialog->m_destinationChecked = false; + dialog->Open(); + + bool confirmed = dialog->IsConfirmed(); + if (confirmed) + { + // Return the new settings (saved by service broker but avoids re-reading) + settings = dialog->m_settings; + } + return confirmed; +} + +void CGUIDialogLibExportSettings::OnInitWindow() +{ + CGUIDialogSettingsManualBase::OnInitWindow(); +} + +void CGUIDialogLibExportSettings::OnSettingChanged(std::shared_ptr<const CSetting> setting) +{ + if (!setting) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE) + { + m_settings.SetExportType(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + SetupView(); + SetFocus(CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER) + { + m_settings.m_strPath = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + UpdateButtons(); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE) + m_settings.m_overwrite = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS) + m_settings.SetItemsToExport(GetExportItemsFromSetting(setting)); + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK) + { + m_settings.m_artwork = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, m_settings.m_artwork); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED) + m_settings.m_unscraped = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO) + m_settings.m_skipnfo = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); +} + +void CGUIDialogLibExportSettings::OnSettingAction(std::shared_ptr<const CSetting> setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingAction(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER) + { + VECSOURCES shares; + g_mediaManager.GetLocalDrives(shares); + g_mediaManager.GetNetworkLocations(shares); + g_mediaManager.GetRemovableDrives(shares); + std::string strDirectory = m_settings.m_strPath; + if (!strDirectory.empty()) + { + URIUtils::AddSlashAtEnd(strDirectory); + bool bIsSource; + if (CUtil::GetMatchingSource(strDirectory, shares, bIsSource) < 0) // path is outside shares - add it as a separate one + { + CMediaSource share; + share.strName = g_localizeStrings.Get(13278); + share.strPath = strDirectory; + shares.push_back(share); + } + } + else + strDirectory = "default location"; + + if (CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(661), strDirectory, true)) + { + if (!strDirectory.empty()) + { + m_destinationChecked = true; + m_settings.m_strPath = strDirectory; + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, strDirectory); + SetFocus(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER); + } + } + UpdateButtons(); + } +} + +bool CGUIDialogLibExportSettings::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_SETTINGS_OKAY_BUTTON) + { + OnOK(); + return true; + } + } + break; + } + return CGUIDialogSettingsManualBase::OnMessage(message); +} + +void CGUIDialogLibExportSettings::OnOK() +{ + // Validate destination folder + if (m_settings.IsToLibFolders()) + { + // Check artist info folder setting + std::string path = CServiceBroker::GetSettings().GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + if (path.empty()) + { + //"Unable to export to library folders as the system artist information folder setting is empty" + //Settings (YES) button takes user to enter the artist info folder setting + if (HELPERS::ShowYesNoDialogText(20223, 38317, 186, 10004) == DialogResponse::YES) + { + m_confirmed = false; + Close(); + g_windowManager.ActivateWindow(WINDOW_SETTINGS_MEDIA, CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + } + return; + } + } + else if (!m_destinationChecked) + { + // ELIBEXPORT_SINGLEFILE or LIBEXPORT_SEPARATEFILES + // Check that destination folder exists + if (!XFILE::CDirectory::Exists(m_settings.m_strPath)) + { + HELPERS::ShowOKDialogText(CVariant{ 38300 }, CVariant{ 38318 }); + return; + } + } + m_confirmed = true; + Save(); + Close(); +} + +void CGUIDialogLibExportSettings::Save() +{ + CLog::Log(LOGINFO, "CGUIDialogMusicExportSettings: Save() called"); + CServiceBroker::GetSettings().SetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE, m_settings.GetExportType()); + CServiceBroker::GetSettings().SetString(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, m_settings.m_strPath); + CServiceBroker::GetSettings().SetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, m_settings.GetItemsToExport()); + CServiceBroker::GetSettings().SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, m_settings.m_unscraped); + CServiceBroker::GetSettings().SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, m_settings.m_overwrite); + CServiceBroker::GetSettings().SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, m_settings.m_artwork); + CServiceBroker::GetSettings().SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, m_settings.m_skipnfo); + CServiceBroker::GetSettings().Save(); +} + +void CGUIDialogLibExportSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + SetHeading(38300); + + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 38319); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222); + + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, m_settings.m_strPath); + + if (m_settings.IsSingleFile()) + { + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, true); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, false); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, false); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, false); + } + else if (m_settings.IsSeparateFiles()) + { + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, true); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, true); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, true); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, m_settings.m_artwork); + } + else // To library folders + { + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, false); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, true); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, true); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, m_settings.m_artwork); + } + UpdateButtons(); +} + +void CGUIDialogLibExportSettings::UpdateButtons() +{ + // Enable Export button when destination folder has a path (but may not exist) + bool enableExport(true); + if (m_settings.IsSingleFile() || + m_settings.IsSeparateFiles()) + enableExport = !m_settings.m_strPath.empty(); + + CONTROL_ENABLE_ON_CONDITION(CONTROL_SETTINGS_OKAY_BUTTON, enableExport); + if (!enableExport) + SetFocus(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER); +} + +void CGUIDialogLibExportSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + std::shared_ptr<CSettingCategory> category = AddCategory("exportsettings", -1); + if (!category) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings: unable to setup settings"); + return; + } + + std::shared_ptr<CSettingGroup> groupDetails = AddGroup(category); + if (!groupDetails) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings: unable to setup settings"); + return; + } + + TranslatableIntegerSettingOptions entries; + + entries.push_back(std::make_pair(38301, ELIBEXPORT_SINGLEFILE)); + entries.push_back(std::make_pair(38302, ELIBEXPORT_SEPARATEFILES)); + entries.push_back(std::make_pair(38303, ELIBEXPORT_TOLIBRARYFOLDER)); + AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE, 38304, SettingLevel::Basic, m_settings.GetExportType(), entries, 38304); // "Choose kind of export output" + AddButton(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, 38305, SettingLevel::Basic); + + entries.clear(); + entries.push_back(std::make_pair(132, ELIBEXPORT_ALBUMS)); //ablums + entries.push_back(std::make_pair(38043, ELIBEXPORT_ALBUMARTISTS)); //album artists + entries.push_back(std::make_pair(38312, ELIBEXPORT_SONGARTISTS)); //song artists + entries.push_back(std::make_pair(38313, ELIBEXPORT_OTHERARTISTS)); //other artists + AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, 38306, SettingLevel::Basic, m_settings.GetExportItems(), entries, 133, 1); + + AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, 38308, SettingLevel::Basic, m_settings.m_unscraped); + AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, 38307, SettingLevel::Basic, m_settings.m_artwork); + AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, 38309, SettingLevel::Basic, m_settings.m_skipnfo); + AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, 38310, SettingLevel::Basic, m_settings.m_overwrite); +} + +void CGUIDialogLibExportSettings::SetLabel2(const std::string &settingid, const std::string &label) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_LABEL2(settingControl->GetID(), label); +} + + +void CGUIDialogLibExportSettings::ToggleState(const std::string & settingid, bool enabled) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + { + if (enabled) + CONTROL_ENABLE(settingControl->GetID()); + else + CONTROL_DISABLE(settingControl->GetID()); + } +} + +void CGUIDialogLibExportSettings::SetFocus(const std::string &settingid) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_FOCUS(settingControl->GetID(), 0); +} + +int CGUIDialogLibExportSettings::GetExportItemsFromSetting(SettingConstPtr setting) +{ + std::shared_ptr<const CSettingList> settingList = std::static_pointer_cast<const CSettingList>(setting); + if (settingList->GetElementType() != SettingType::Integer) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings::%s - wrong items element type", __FUNCTION__); + return 0; + } + int exportitems = 0; + std::vector<CVariant> list = CSettingUtils::GetList(settingList); + for (const auto &value : list) + { + if (!value.isInteger()) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings::%s - wrong items value type", __FUNCTION__); + return 0; + } + exportitems += value.asInteger(); + } + return exportitems; +} diff --git a/xbmc/settings/dialogs/GUIDialogLibExportSettings.h b/xbmc/settings/dialogs/GUIDialogLibExportSettings.h new file mode 100644 index 0000000000..958e8ff209 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogLibExportSettings.h @@ -0,0 +1,66 @@ +#pragma once +/* + * Copyright (C) 2017 Team KODI + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KODI; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> + +#include "settings/dialogs/GUIDialogSettingsManualBase.h" +#include "settings/LibExportSettings.h" + +class CGUIDialogLibExportSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogLibExportSettings(); + + // specialization of CGUIWindow + bool HasListItems() const override { return true; }; + static bool Show(CLibExportSettings& settings); + +protected: + // specializations of CGUIWindow + void OnInitWindow() override; + + // implementations of ISettingCallback + void OnSettingChanged(std::shared_ptr<const CSetting> setting) override; + void OnSettingAction(std::shared_ptr<const CSetting> setting) override; + + // specialization of CGUIDialogSettingsBase + bool OnMessage(CGUIMessage& message) override; + bool AllowResettingSettings() const override { return false; } + void Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + + void OnOK(); + void UpdateButtons(); + +private: + void SetLabel2(const std::string &settingid, const std::string &label); + void ToggleState(const std::string &settingid, bool enabled); + + using CGUIDialogSettingsManualBase::SetFocus; + void SetFocus(const std::string &settingid); + static int GetExportItemsFromSetting(SettingConstPtr setting); + + CLibExportSettings m_settings; + bool m_destinationChecked; +}; diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 2e31462ced..c4b2bfdd48 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -1703,15 +1703,22 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const CFileItemPtr &item, const std::string currentThumb; int idArtist = -1; std::string artistPath; + std::string artistOldPath; std::string artType = "thumb"; if (type == MediaTypeArtist) { CMusicDatabase musicdb; if (musicdb.Open()) { - idArtist = musicdb.GetArtistByName(item->GetLabel()); - if (idArtist >= 0 && musicdb.GetArtistPath(idArtist, artistPath)) + idArtist = musicdb.GetArtistByName(item->GetLabel()); // Fails when name not unique + if (idArtist >= 0 ) { + // Get artist paths - possible locations for thumb - while music db open + musicdb.GetOldArtistPath(idArtist, artistOldPath); // Old artist path, local to music files + CArtist artist; + musicdb.GetArtist(idArtist, artist); // Need name and mbid for artist folder name + musicdb.GetArtistPath(artist, artistPath); // Artist path in artist info folder + currentThumb = musicdb.GetArtForItem(idArtist, MediaTypeArtist, "thumb"); if (currentThumb.empty()) currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); @@ -1808,9 +1815,23 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const CFileItemPtr &item, const noneitem->SetIconImage("DefaultVideo.png"); } else - { - std::string strThumb = URIUtils::AddFileToFolder(artistPath, "folder.jpg"); - if (XFILE::CFile::Exists(strThumb)) + { + std::string strThumb; + bool existsThumb = false; + // First look for artist thumb in the primary location + if (!artistPath.empty()) + { + strThumb = URIUtils::AddFileToFolder(artistPath, "folder.jpg"); + existsThumb = CFile::Exists(strThumb); + } + // If not there fall back local to music files (historic location for those album artists with a unique folder) + if (!existsThumb && !artistOldPath.empty()) + { + strThumb = URIUtils::AddFileToFolder(artistOldPath, "folder.jpg"); + existsThumb = CFile::Exists(strThumb); + } + + if (existsThumb) { CFileItemPtr pItem(new CFileItem(strThumb, false)); pItem->SetLabel(g_localizeStrings.Get(13514)); |