aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaveTBlake <oak99sky@yahoo.co.uk>2017-10-07 17:49:59 +0100
committerDaveTBlake <oak99sky@yahoo.co.uk>2017-10-30 12:43:50 +0000
commitdab69c930db7d1ef8399a204b133c9fb2c482ca8 (patch)
tree99bfc37beda51df631a0f0c40af2fcf60e92fe37
parentfd572b24974fb8437898c93a046fc3b5b7e08a65 (diff)
Fix issues that cause wrong artist and album art and nfo files to be used:
- Fix export to XML writing data for more than one artist into NFO file. - Check that an artist or album path is found before exporting that item to an NFO file. Check GetArtistPath is successful, and clear path and log when fails, before attempting to process NFO file for artist. - Fix import to handle artists with same name by checking for match using mbid first. - Update art table when merging artist after import or scraping NFO files. This processes <art> tag, previously saved by export but never imported or merged, so art had to be re-selected manually. Note artist thumbURL (strImage) and fanart (strFanart) is that available remotely (from Fanart.tv or TADB), not the images selected. Skins will also be able to display this artwork using ListItem.Art() - When getting local album artist art during initial scan to library: only look in the folder immediately above the album folder (when it contains only 1 album), and only for the first album artist if the album is a collaboration. Avoid repeatedly processing the same artist by maintaining a set. - Check when getting album path that it is unique for that album and does not contain songs fro other albums. Implement new artist info folder: - Overcomes the previous restrictions on local artist art and nfo files. Adds local support for album artists that are on collaboration albums (more than one album artist), artists with music on multiple sources, song artists (no albums) and other artists involved in the music in some way e.g. composer, producer etc. - Provides a single location that can be used by addons for other artist artwork, extra fanart etc. - Artist folder names Normalise album and artist folder names to remove reserved characters Add routines to get folder name allowing for complex duplicates Improve music library export facility: - Replace series of yes/no responses with a dialog box and store previous values (using CServiceBroker) for use next time. - Single file output has unique (timestamped) file name. - Separate file (nfo) output can be to a selected location. - Support export of info and art for *all* artists in library, (not just scraped album artists with a unique folder containing all music below it) - Add options for a) what items exported, b) exporting items that have not been scraped, c) to export just artwork. Complimenting the increase in data that could be exported. - Add music lib job queue and export as an asynchronous job, optionally showing progress dialog (UI kept on main thread). Export hanging no longer locks UI. - Add new LibraryBuiltin method for music export with extended parameters, leaving original for backwards compatibility (for now?). Change JSON to use new method for both music and video. Add music source - minor changes to UI: - On adding a new music source to the library, before scanning prompt optionally enter Artist Info Folder setting. Locate folder directly, not go to settings, as in middle of adding source. - When scan is initiated from file view context menu the option to do full rescan of tags (allowing changed system settings and tag processing to be applied regardless of files themselves being unchanged) is offered. Unnecessary when adding a new music source to the library, or from normal "Update Library" facility. This replaces the <promptfulltagscan> advanced setting added in PR11557.
-rw-r--r--addons/resource.language.en_gb/resources/strings.po151
-rw-r--r--cmake/treedata/common/music.txt1
-rwxr-xr-xsystem/settings/settings.xml42
-rw-r--r--xbmc/Application.cpp13
-rw-r--r--xbmc/guilib/GUIWindowManager.cpp4
-rw-r--r--xbmc/guilib/WindowIDs.h1
-rw-r--r--xbmc/input/WindowTranslator.cpp1
-rw-r--r--xbmc/interfaces/builtins/LibraryBuiltins.cpp111
-rw-r--r--xbmc/interfaces/json-rpc/AudioLibrary.cpp14
-rw-r--r--xbmc/interfaces/json-rpc/VideoLibrary.cpp16
-rw-r--r--xbmc/music/Artist.cpp30
-rw-r--r--xbmc/music/Artist.h6
-rw-r--r--xbmc/music/CMakeLists.txt2
-rw-r--r--xbmc/music/MusicDatabase.cpp753
-rw-r--r--xbmc/music/MusicDatabase.h15
-rw-r--r--xbmc/music/MusicLibraryQueue.cpp180
-rw-r--r--xbmc/music/MusicLibraryQueue.h100
-rw-r--r--xbmc/music/dialogs/GUIDialogMusicInfo.cpp57
-rw-r--r--xbmc/music/infoscanner/MusicInfoScanner.cpp210
-rw-r--r--xbmc/music/infoscanner/MusicInfoScanner.h65
-rw-r--r--xbmc/music/jobs/CMakeLists.txt9
-rw-r--r--xbmc/music/jobs/MusicLibraryExportJob.cpp53
-rw-r--r--xbmc/music/jobs/MusicLibraryExportJob.h53
-rw-r--r--xbmc/music/jobs/MusicLibraryJob.cpp35
-rw-r--r--xbmc/music/jobs/MusicLibraryJob.h61
-rw-r--r--xbmc/music/jobs/MusicLibraryProgressJob.cpp36
-rw-r--r--xbmc/music/jobs/MusicLibraryProgressJob.h40
-rw-r--r--xbmc/music/windows/GUIWindowMusicBase.cpp78
-rw-r--r--xbmc/music/windows/GUIWindowMusicBase.h4
-rw-r--r--xbmc/settings/AdvancedSettings.cpp2
-rw-r--r--xbmc/settings/AdvancedSettings.h1
-rw-r--r--xbmc/settings/CMakeLists.txt2
-rw-r--r--xbmc/settings/LibExportSettings.cpp96
-rw-r--r--xbmc/settings/LibExportSettings.h73
-rw-r--r--xbmc/settings/MediaSettings.cpp11
-rw-r--r--xbmc/settings/MediaSettings.h2
-rw-r--r--xbmc/settings/Settings.cpp8
-rw-r--r--xbmc/settings/Settings.h8
-rw-r--r--xbmc/settings/dialogs/CMakeLists.txt2
-rw-r--r--xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp368
-rw-r--r--xbmc/settings/dialogs/GUIDialogLibExportSettings.h66
-rw-r--r--xbmc/video/dialogs/GUIDialogVideoInfo.cpp31
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));