aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/metadata.demo.albums/addon.xml17
-rw-r--r--addons/metadata.demo.albums/demo.py120
-rw-r--r--addons/metadata.demo.artists/addon.xml17
-rw-r--r--addons/metadata.demo.artists/demo.py116
-rw-r--r--addons/metadata.generic.albums/LICENSE.txt282
-rw-r--r--addons/metadata.generic.albums/addon.xml20
-rw-r--r--addons/metadata.generic.albums/changelog.txt31
-rw-r--r--addons/metadata.generic.albums/default.py32
-rw-r--r--addons/metadata.generic.albums/lib/allmusic.py122
-rw-r--r--addons/metadata.generic.albums/lib/discogs.py59
-rw-r--r--addons/metadata.generic.albums/lib/fanarttv.py45
-rw-r--r--addons/metadata.generic.albums/lib/musicbrainz.py154
-rw-r--r--addons/metadata.generic.albums/lib/nfo.py8
-rw-r--r--addons/metadata.generic.albums/lib/scraper.py442
-rw-r--r--addons/metadata.generic.albums/lib/theaudiodb.py124
-rw-r--r--addons/metadata.generic.albums/lib/utils.py24
-rw-r--r--addons/metadata.generic.albums/resources/icon.pngbin0 -> 15700 bytes
-rw-r--r--addons/metadata.generic.albums/resources/language/resource.language.en_gb/strings.po85
-rw-r--r--addons/metadata.generic.albums/resources/settings.xml107
-rw-r--r--addons/metadata.generic.artists/LICENSE.txt282
-rw-r--r--addons/metadata.generic.artists/addon.xml20
-rw-r--r--addons/metadata.generic.artists/changelog.txt22
-rw-r--r--addons/metadata.generic.artists/default.py30
-rw-r--r--addons/metadata.generic.artists/lib/allmusic.py84
-rw-r--r--addons/metadata.generic.artists/lib/discogs.py45
-rw-r--r--addons/metadata.generic.artists/lib/fanarttv.py50
-rw-r--r--addons/metadata.generic.artists/lib/musicbrainz.py53
-rw-r--r--addons/metadata.generic.artists/lib/nfo.py8
-rw-r--r--addons/metadata.generic.artists/lib/scraper.py415
-rw-r--r--addons/metadata.generic.artists/lib/theaudiodb.py124
-rw-r--r--addons/metadata.generic.artists/lib/utils.py24
-rw-r--r--addons/metadata.generic.artists/resources/icon.pngbin0 -> 14308 bytes
-rw-r--r--addons/metadata.generic.artists/resources/language/resource.language.en_gb/strings.po85
-rw-r--r--addons/metadata.generic.artists/resources/settings.xml105
-rw-r--r--addons/resource.language.en_gb/resources/strings.po2
-rw-r--r--addons/skin.estouchy/fonts/NotoSans-Regular.ttfbin313656 -> 565576 bytes
-rw-r--r--addons/skin.estuary/fonts/NotoSans-Bold.ttfbin364100 -> 0 bytes
-rw-r--r--addons/skin.estuary/fonts/NotoSans-Regular.ttfbin359188 -> 565576 bytes
-rw-r--r--addons/skin.estuary/xml/Custom_1109_TopBarOverlay.xml82
-rw-r--r--addons/skin.estuary/xml/DialogFullScreenInfo.xml16
-rw-r--r--addons/skin.estuary/xml/DialogVideoInfo.xml49
-rw-r--r--addons/skin.estuary/xml/Font.xml30
-rw-r--r--addons/skin.estuary/xml/Includes.xml43
-rw-r--r--addons/skin.estuary/xml/Includes_Home.xml64
-rw-r--r--addons/skin.estuary/xml/MusicOSD.xml28
-rw-r--r--addons/skin.estuary/xml/MyWeather.xml2
-rw-r--r--addons/skin.estuary/xml/SlideShow.xml1
-rw-r--r--addons/skin.estuary/xml/Variables.xml7
-rw-r--r--addons/skin.estuary/xml/VideoOSD.xml27
-rw-r--r--addons/xbmc.gui/addon.xml2
-rw-r--r--cmake/installdata/common/addons.txt2
-rw-r--r--cmake/modules/FindFFMPEG.cmake3
-rw-r--r--docs/CODE_GUIDELINES.md2
-rw-r--r--docs/MANIFESTO.md36
-rw-r--r--docs/README.RaspberryPi.md98
-rw-r--r--project/BuildDependencies/scripts/0_package.target-win10-arm.list4
-rw-r--r--project/BuildDependencies/scripts/0_package.target-win10-win32.list4
-rw-r--r--project/BuildDependencies/scripts/0_package.target-win10-x64.list4
-rw-r--r--project/BuildDependencies/scripts/0_package.target-win32.list4
-rw-r--r--project/BuildDependencies/scripts/0_package.target-x64.list4
-rw-r--r--system/addon-manifest.xml2
-rw-r--r--system/keymaps/keyboard.xml1
-rwxr-xr-xsystem/settings/settings.xml4
-rwxr-xr-xtools/EventClients/Clients/PS3BDRemote/ps3_remote.py2
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG4
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c2
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c4
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c10
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c2
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c2
-rw-r--r--tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h2
-rw-r--r--tools/depends/target/ffmpeg/CMakeLists.txt4
-rw-r--r--tools/depends/target/libudfread/UDFREAD-VERSION2
-rw-r--r--tools/depends/target/mariadb/01-android.patch23
-rw-r--r--tools/depends/target/mariadb/Makefile11
-rw-r--r--xbmc/Application.cpp115
-rw-r--r--xbmc/Application.h3
-rw-r--r--xbmc/CMakeLists.txt1
-rw-r--r--xbmc/FileItem.cpp7
-rw-r--r--xbmc/HDRStatus.h17
-rw-r--r--xbmc/Util.cpp2
-rw-r--r--xbmc/addons/AddonDatabase.cpp47
-rw-r--r--xbmc/addons/AddonDatabase.h29
-rw-r--r--xbmc/addons/AddonInstaller.cpp2
-rw-r--r--xbmc/addons/AddonManager.cpp83
-rw-r--r--xbmc/addons/AddonManager.h82
-rw-r--r--xbmc/addons/ContextMenus.cpp3
-rw-r--r--xbmc/addons/GUIDialogAddonInfo.cpp2
-rw-r--r--xbmc/addons/Scraper.cpp7
-rw-r--r--xbmc/addons/addoninfo/AddonInfo.h11
-rw-r--r--xbmc/addons/addoninfo/AddonInfoBuilder.cpp4
-rw-r--r--xbmc/addons/addoninfo/AddonType.cpp14
-rw-r--r--xbmc/addons/addoninfo/AddonType.h9
-rw-r--r--xbmc/addons/interfaces/AddonBase.cpp46
-rw-r--r--xbmc/addons/interfaces/AddonBase.h1
-rw-r--r--xbmc/addons/interfaces/Filesystem.cpp7
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/AddonBase.h18
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/Inputstream.h2
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/PVR.h8
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/VideoCodec.h2
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h20
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Channels.h26
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EDL.h8
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EPG.h36
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/General.h23
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/MenuHook.h9
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Recordings.h10
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Stream.h26
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Timers.h29
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/c-api/addon_base.h3
-rw-r--r--xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h2
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp2
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp16
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h1
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp2
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp2
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h2
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h2
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp50
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h2
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp100
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h3
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp3
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp5
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp151
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h26
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp9
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp9
-rw-r--r--xbmc/cores/paplayer/PAPlayer.cpp4
-rw-r--r--xbmc/cores/paplayer/VideoPlayerCodec.cpp18
-rw-r--r--xbmc/dbwrappers/mysqldataset.cpp52
-rw-r--r--xbmc/dbwrappers/sqlitedataset.cpp9
-rw-r--r--xbmc/filesystem/AddonsDirectory.cpp19
-rw-r--r--xbmc/filesystem/CurlFile.cpp5
-rw-r--r--xbmc/guilib/GUIColorManager.cpp2
-rw-r--r--xbmc/guilib/GUIFontManager.cpp6
-rw-r--r--xbmc/guilib/VisibleEffect.cpp2
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoLabel.cpp2
-rw-r--r--xbmc/input/actions/ActionIDs.h2
-rw-r--r--xbmc/input/actions/ActionTranslator.cpp3
-rw-r--r--xbmc/input/joysticks/generic/FeatureHandling.h6
-rw-r--r--xbmc/input/touch/ITouchInputHandler.h4
-rw-r--r--xbmc/input/touch/generic/IGenericTouchGestureDetector.h2
-rw-r--r--xbmc/interfaces/builtins/PlayerBuiltins.cpp8
-rw-r--r--xbmc/interfaces/json-rpc/AddonsOperations.cpp14
-rw-r--r--xbmc/interfaces/json-rpc/schema/methods.json6
-rw-r--r--xbmc/interfaces/json-rpc/schema/types.json11
-rw-r--r--xbmc/interfaces/json-rpc/schema/version.txt2
-rw-r--r--xbmc/interfaces/legacy/Player.cpp6
-rw-r--r--xbmc/interfaces/python/swig.cpp2
-rw-r--r--xbmc/music/Artist.cpp31
-rw-r--r--xbmc/music/Artist.h10
-rw-r--r--xbmc/music/MusicDatabase.cpp188
-rw-r--r--xbmc/music/MusicDatabase.h5
-rw-r--r--xbmc/network/WakeOnAccess.cpp2
-rw-r--r--xbmc/network/WebServer.cpp459
-rw-r--r--xbmc/network/WebServer.h30
-rw-r--r--xbmc/network/cddb.cpp2
-rw-r--r--xbmc/network/httprequesthandler/HTTPFileHandler.cpp2
-rw-r--r--xbmc/network/httprequesthandler/HTTPFileHandler.h2
-rw-r--r--xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp2
-rw-r--r--xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h2
-rw-r--r--xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp2
-rw-r--r--xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h2
-rw-r--r--xbmc/network/httprequesthandler/HTTPPythonHandler.cpp2
-rw-r--r--xbmc/network/httprequesthandler/HTTPPythonHandler.h2
-rw-r--r--xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp4
-rw-r--r--xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h4
-rw-r--r--xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp2
-rw-r--r--xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h2
-rw-r--r--xbmc/network/httprequesthandler/IHTTPRequestHandler.h8
-rw-r--r--xbmc/platform/win32/WIN32Util.cpp283
-rw-r--r--xbmc/platform/win32/WIN32Util.h7
-rw-r--r--xbmc/powermanagement/IPowerSyscall.h2
-rw-r--r--xbmc/pvr/PVRManager.cpp2
-rw-r--r--xbmc/pvr/addons/PVRClients.cpp3
-rw-r--r--xbmc/pvr/epg/Epg.h2
-rw-r--r--xbmc/pvr/epg/EpgTagsContainer.h2
-rw-r--r--xbmc/pvr/guilib/PVRGUIActions.cpp2
-rw-r--r--xbmc/rendering/dx/DeviceResources.cpp181
-rw-r--r--xbmc/rendering/dx/DeviceResources.h26
-rw-r--r--xbmc/settings/AdvancedSettings.cpp5
-rw-r--r--xbmc/settings/AdvancedSettings.h1
-rw-r--r--xbmc/settings/MediaSettings.cpp2
-rw-r--r--xbmc/settings/MediaSettings.h6
-rw-r--r--xbmc/utils/ColorUtils.cpp2
-rw-r--r--xbmc/utils/EGLUtils.cpp6
-rw-r--r--xbmc/video/VideoDatabase.cpp12
-rw-r--r--xbmc/windowing/WinSystem.h3
-rw-r--r--xbmc/windowing/X11/WinSystemX11GLESContext.cpp2
-rw-r--r--xbmc/windowing/wayland/SeatInputProcessing.h2
-rw-r--r--xbmc/windowing/win10/WinSystemWin10.h1
-rw-r--r--xbmc/windowing/win10/WinSystemWin10DX.cpp32
-rw-r--r--xbmc/windowing/win10/WinSystemWin10DX.h11
-rw-r--r--xbmc/windowing/windows/WinSystemWin32.h1
-rw-r--r--xbmc/windowing/windows/WinSystemWin32DX.cpp31
-rw-r--r--xbmc/windowing/windows/WinSystemWin32DX.h11
-rw-r--r--xbmc/windows/GUIWindowFileManager.cpp2
198 files changed, 5321 insertions, 1107 deletions
diff --git a/addons/metadata.demo.albums/addon.xml b/addons/metadata.demo.albums/addon.xml
deleted file mode 100644
index 023c22fc23..0000000000
--- a/addons/metadata.demo.albums/addon.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="metadata.demo.albums"
- name="Demo albums python scraper"
- version="1.0.0"
- provider-name="spiff">
- <requires>
- <import addon="xbmc.metadata" version="2.1.0"/>
- </requires>
- <extension point="xbmc.metadata.scraper.albums"
- library="demo.py"/>
- <extension point="xbmc.addon.metadata">
- <summary lang="en">Demo albums python scraper</summary>
- <description lang="en">Demo albums python scraper.</description>
- <platform>all</platform>
- <license>GPL v2.0</license>
- </extension>
-</addon>
diff --git a/addons/metadata.demo.albums/demo.py b/addons/metadata.demo.albums/demo.py
deleted file mode 100644
index 8ff9542f39..0000000000
--- a/addons/metadata.demo.albums/demo.py
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: UTF-8 -*-
-
-import xbmcplugin,xbmcgui,xbmc,xbmcaddon
-import os,sys,urllib
-
-def get_params():
- param=[]
- paramstring=sys.argv[2]
- if len(paramstring)>=2:
- params=sys.argv[2]
- cleanedparams=params.replace('?','')
- if (params[len(params)-1]=='/'):
- params=params[0:len(params)-2]
- pairsofparams=cleanedparams.split('&')
- param={}
- for i in range(len(pairsofparams)):
- splitparams={}
- splitparams=pairsofparams[i].split('=')
- if (len(splitparams))==2:
- param[splitparams[0]]=splitparams[1]
-
- return param
-
-
-params=get_params()
-print(params)
-
-try:
- action=urllib.unquote_plus(params["action"])
-except:
- pass
-
-print ("Action: "+action)
-
-if action == 'find':
- try:
- artist=urllib.unquote_plus(params["artist"])
- album=urllib.unquote_plus(params["title"])
- except:
- pass
-
- print('Find album with title %s from artist %s' %(album, artist))
- liz=xbmcgui.ListItem('Demo album 1', thumbnailImage='DefaultAlbum.png', offscreen=True)
- liz.setProperty('relevance', '0.5')
- liz.setProperty('album.artist', artist)
- liz.setProperty('album.year', '2005')
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url="/path/to/album", listitem=liz, isFolder=True)
-
- liz=xbmcgui.ListItem('Demo album 2', thumbnailImage='DefaultVideo.png', offscreen=True)
- liz.setProperty('relevance', '0.3')
- liz.setProperty('album.artist', 'spiff')
- liz.setProperty('album.year', '2016')
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url="/path/to/album2", listitem=liz, isFolder=True)
-elif action == 'getdetails':
- try:
- url=urllib.unquote_plus(params["url"])
- except:
- pass
-
- if url == '/path/to/album':
- liz=xbmcgui.ListItem('Demo album 1', offscreen=True)
- liz.setProperty('album.musicbrainzid', '123')
- liz.setProperty('album.artists', '2')
- liz.setProperty('album.artist1.name', 'Jan')
- liz.setProperty('album.artist1.musicbrainzid', '456')
- liz.setProperty('album.artist2.name', 'Banan')
- liz.setProperty('album.artist2.musicbrainzid', '789')
- liz.setProperty('album.artist_description', 'I hate this album.')
- liz.setProperty('album.genre', 'rock / pop')
- liz.setProperty('album.styles', 'light / heavy')
- liz.setProperty('album.moods', 'angry / happy')
- liz.setProperty('album.themes', 'Morbid sexual things.. And urmumz.')
- liz.setProperty('album.compiliation', 'true')
- liz.setProperty('album.review', 'Somebody should die for making this')
- liz.setProperty('album.release_date', '2005-01-02')
- liz.setProperty('album.label', 'ArtistExploitation inc')
- liz.setProperty('album.type', 'what is this?')
- liz.setProperty('album.release_type', 'single')
- liz.setProperty('album.year', '2005')
- liz.setProperty('album.rating', '2.5')
- liz.setProperty('album.userrating', '4.5')
- liz.setProperty('album.votes', '100')
- liz.setProperty('album.thumbs', '2')
- liz.setProperty('album.thumb1.url', 'DefaultBackFanart.png')
- liz.setProperty('album.thumb1.aspect', '1.78')
- liz.setProperty('album.thumb2.url', '/home/akva/Pictures/hawaii-shirt.png')
- liz.setProperty('album.thumb2.aspect', '2.35')
- xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=liz)
- elif url == '/path/to/album2':
- liz=xbmcgui.ListItem('Demo album 2', offscreen=True)
- liz.setProperty('album.musicbrainzid', '123')
- liz.setProperty('album.artists', '2')
- liz.setProperty('album.artist1.name', 'Heise')
- liz.setProperty('album.artist1.musicbrainzid', '456')
- liz.setProperty('album.artist2.name', 'Kran')
- liz.setProperty('album.artist2.musicbrainzid', '789')
- liz.setProperty('album.artist_description', 'I love this album.')
- liz.setProperty('album.genre', 'classical / jazz')
- liz.setProperty('album.styles', 'yay / hurrah')
- liz.setProperty('album.moods', 'sad / excited')
- liz.setProperty('album.themes', 'Nice things.. And unicorns.')
- liz.setProperty('album.compiliation', 'false')
- liz.setProperty('album.review', 'Somebody should be rewarded for making this')
- liz.setProperty('album.release_date', '2015-01-02')
- liz.setProperty('album.label', 'Artists inc')
- liz.setProperty('album.type', 'what is that?')
- liz.setProperty('album.release_type', 'album')
- liz.setProperty('album.year', '2015')
- liz.setProperty('album.rating', '4.5')
- liz.setProperty('album.userrating', '3.5')
- liz.setProperty('album.votes', '200')
- liz.setProperty('album.thumbs', '2')
- liz.setProperty('album.thumb1.url', 'DefaultBackFanart.png')
- liz.setProperty('album.thumb1.aspect', '1.78')
- liz.setProperty('album.thumb2.url', '/home/akva/Pictures/hawaii-shirt.png')
- liz.setProperty('album.thumb2.aspect', '2.35')
- xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=liz)
-
-xbmcplugin.endOfDirectory(int(sys.argv[1]))
diff --git a/addons/metadata.demo.artists/addon.xml b/addons/metadata.demo.artists/addon.xml
deleted file mode 100644
index 59b6f7d0bb..0000000000
--- a/addons/metadata.demo.artists/addon.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="metadata.demo.artists"
- name="Demo artists python scraper"
- version="1.0.0"
- provider-name="spiff">
- <requires>
- <import addon="xbmc.metadata" version="2.1.0"/>
- </requires>
- <extension point="xbmc.metadata.scraper.artists"
- library="demo.py"/>
- <extension point="xbmc.addon.metadata">
- <summary lang="en">Demo artists python scraper</summary>
- <description lang="en">Demo artists python scraper</description>
- <platform>all</platform>
- <license>GPL v2.0</license>
- </extension>
-</addon>
diff --git a/addons/metadata.demo.artists/demo.py b/addons/metadata.demo.artists/demo.py
deleted file mode 100644
index 37a31bef65..0000000000
--- a/addons/metadata.demo.artists/demo.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: UTF-8 -*-
-
-import xbmcplugin,xbmcgui,xbmc,xbmcaddon
-import os,sys,urllib
-
-def get_params():
- param=[]
- paramstring=sys.argv[2]
- if len(paramstring)>=2:
- params=sys.argv[2]
- cleanedparams=params.replace('?','')
- if (params[len(params)-1]=='/'):
- params=params[0:len(params)-2]
- pairsofparams=cleanedparams.split('&')
- param={}
- for i in range(len(pairsofparams)):
- splitparams={}
- splitparams=pairsofparams[i].split('=')
- if (len(splitparams))==2:
- param[splitparams[0]]=splitparams[1]
-
- return param
-
-
-params=get_params()
-
-try:
- action=urllib.unquote_plus(params["action"])
-except:
- pass
-
-if action == 'find':
- try:
- artist=urllib.unquote_plus(params["artist"])
- except:
- pass
-
- print('Find artist with name %s' %(artist))
- liz=xbmcgui.ListItem('Demo artist 1', thumbnailImage='DefaultAlbum.png', offscreen=True)
- liz.setProperty('artist.genre', 'rock / pop')
- liz.setProperty('artist.born', '2002')
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url="/path/to/artist", listitem=liz, isFolder=True)
-
- liz=xbmcgui.ListItem('Demo artist 2', thumbnailImage='DefaultAlbum.png', offscreen=True)
- liz.setProperty('artist.genre', 'classical / jazz')
- liz.setProperty('artist.born', '2012')
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url="/path/to/artist2", listitem=liz, isFolder=True)
-elif action == 'resolveid':
- liz=xbmcgui.ListItem(path='/path/to/artist2', offscreen=True)
- xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=liz)
-elif action == 'getdetails':
- url=urllib.unquote_plus(params["url"])
- print('Artist with url %s' %(url))
- if url == '/path/to/artist':
- liz=xbmcgui.ListItem('Demo artist 1', offscreen=True)
- liz.setProperty('artist.musicbrainzid', '123')
- liz.setProperty('artist.genre', 'rock / pop')
- liz.setProperty('artist.styles', 'heavy / light')
- liz.setProperty('artist.moods', 'angry / happy')
- liz.setProperty('artist.years_active', '1980 / 2012')
- liz.setProperty('artist.instruments', 'guitar / drums')
- liz.setProperty('artist.born', '1/1/2001')
- liz.setProperty('artist.formed', '1980')
- liz.setProperty('artist.biography', 'Wrote lots of crap. Likes to torture cats.')
- liz.setProperty('artist.died', 'Tomorrow.')
- liz.setProperty('artist.disbanded', 'Dec 21 2012')
- liz.setProperty('artist.fanarts', '2')
- liz.setProperty('artist.fanart1.url', 'DefaultBackFanart.png')
- liz.setProperty('artist.fanart1.preview', 'DefaultBackFanart.png')
- liz.setProperty('artist.fanart1.dim', '720')
- liz.setProperty('artist.fanart2.url', '/home/akva/Pictures/hawaii-shirt.png')
- liz.setProperty('artist.fanart2.preview', '/home/akva/Pictures/hawaii-shirt.png')
- liz.setProperty('artist.fanart2.dim', '1080')
- liz.setProperty('artist.albums', '2')
- liz.setProperty('artist.album1.title', 'Demo album 1')
- liz.setProperty('artist.album1.year', '2002')
- liz.setProperty('artist.album2.title', 'Demo album 2')
- liz.setProperty('artist.album2.year', '2007')
- liz.setProperty('artist.thumbs', '2')
- liz.setProperty('artist.thumb1.url', 'DefaultBackFanart.png')
- liz.setProperty('artist.thumb1.aspect', '1.78')
- liz.setProperty('artist.thumb2.url', '/home/akva/Pictures/hawaii-shirt.png')
- liz.setProperty('artist.thumb2.aspect', '2.35')
- xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=liz)
- if url == '/path/to/artist2':
- liz=xbmcgui.ListItem('Demo artist 2', thumbnailImage='DefaultAlbum.png', offscreen=True)
- liz.setProperty('artist.musicbrainzid', '456')
- liz.setProperty('artist.genre', 'classical / jazz')
- liz.setProperty('artist.styles', 'morbid / funny')
- liz.setProperty('artist.moods', 'fast / dance')
- liz.setProperty('artist.years_active', '1990 / 2016')
- liz.setProperty('artist.instruments', 'bass / flute')
- liz.setProperty('artist.born', '2/2/1971')
- liz.setProperty('artist.formed', '1990')
- liz.setProperty('artist.biography', 'Tortured lots of cats. Likes crap.')
- liz.setProperty('artist.died', 'Yesterday.')
- liz.setProperty('artist.disbanded', 'Nov 20 1980')
- liz.setProperty('artist.fanarts', '2')
- liz.setProperty('artist.fanart1.thumb', 'DefaultBackFanart.png')
- liz.setProperty('artist.fanart1.dim', '720')
- liz.setProperty('artist.fanart2.thumb', '/home/akva/Pictures/gnome-tshirt.png')
- liz.setProperty('artist.fanart2.dim', '1080')
- liz.setProperty('artist.albums', '2')
- liz.setProperty('artist.album1.title', 'Demo album 1')
- liz.setProperty('artist.album1.year', '2002')
- liz.setProperty('artist.album2.title', 'Demo album 2')
- liz.setProperty('artist.album2.year', '2005')
- liz.setProperty('artist.thumbs', '2')
- liz.setProperty('artist.thumb1.url', 'DefaultBackFanart.png')
- liz.setProperty('artist.thumb1.aspect', '1.78')
- liz.setProperty('artist.thumb2.url', '/home/akva/Pictures/hawaii-shirt.png')
- liz.setProperty('artist.thumb2.aspect', '2.35')
- xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=liz)
-
-xbmcplugin.endOfDirectory(int(sys.argv[1]))
diff --git a/addons/metadata.generic.albums/LICENSE.txt b/addons/metadata.generic.albums/LICENSE.txt
new file mode 100644
index 0000000000..4f8e8eb30c
--- /dev/null
+++ b/addons/metadata.generic.albums/LICENSE.txt
@@ -0,0 +1,282 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+-------------------------------------------------------------------------
diff --git a/addons/metadata.generic.albums/addon.xml b/addons/metadata.generic.albums/addon.xml
new file mode 100644
index 0000000000..acdc7b4f16
--- /dev/null
+++ b/addons/metadata.generic.albums/addon.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<addon id="metadata.generic.albums" name="Generic Album Scraper" version="1.0.7" provider-name="Team Kodi">
+ <requires>
+ <import addon="xbmc.python" version="3.0.0"/>
+ <import addon="xbmc.metadata" version="2.1.0"/>
+ </requires>
+ <extension point="xbmc.metadata.scraper.albums" library="default.py"/>
+ <extension point="xbmc.addon.metadata">
+ <summary lang="en_GB">Generic music scraper for albums</summary>
+ <description lang="en_GB">Searches for album information and artwork across multiple websites.</description>
+ <platform>all</platform>
+ <license>GPL-2.0-only</license>
+ <forum>https://forum.kodi.tv/showthread.php?tid=351570</forum>
+ <source>https://gitlab.com/ronie/metadata.generic.albums/</source>
+ <assets>
+ <icon>resources/icon.png</icon>
+ </assets>
+ <news>- first release</news>
+ </extension>
+</addon>
diff --git a/addons/metadata.generic.albums/changelog.txt b/addons/metadata.generic.albums/changelog.txt
new file mode 100644
index 0000000000..9d67dccb9f
--- /dev/null
+++ b/addons/metadata.generic.albums/changelog.txt
@@ -0,0 +1,31 @@
+v1.0.7
+- fix crash when album type is absent or empty in the api response
+- filter inaccurate search results from discogs
+- filter inaccurate albumdetails from allmusic
+- filter blank allmusic album thumb
+- consider both score and releasedate when selecting the top release from releasegroup
+
+v1.0.6
+- improve custom scoring
+- add support for original release date
+- fix release date from musicbrainz
+- use releasegroup id to fetch coverartarchive artwork
+- only use one release from each releasegroup
+- provide detailed search results
+
+v1.0.5
+- don't set releasetype
+- fix types from musicbrainz
+- add release status
+
+v1.0.4
+- catch time-outs
+
+v1.0.3
+- replace beautifulsoup with regex
+
+v1.0.2
+- replace requests with urllib
+
+v1.0.1
+- release
diff --git a/addons/metadata.generic.albums/default.py b/addons/metadata.generic.albums/default.py
new file mode 100644
index 0000000000..85aeb90dff
--- /dev/null
+++ b/addons/metadata.generic.albums/default.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+import sys
+from urllib.parse import parse_qsl
+from lib.scraper import Scraper
+
+
+class Main:
+ def __init__(self):
+ action, key, artist, album, url, nfo, settings = self._parse_argv()
+ Scraper(action, key, artist, album, url, nfo, settings)
+
+ def _parse_argv(self):
+ params = dict(parse_qsl(sys.argv[2].lstrip('?')))
+ # actions: resolveid, find, getdetails, NfoUrl
+ action = params['action']
+ # key: musicbrainz id
+ key = params.get('key', '')
+ # artist: artistname
+ artist = params.get('artist', '')
+ # album: albumtitle
+ album = params.get('title', '')
+ # url: provided by the scraper on previous run
+ url = params.get('url', '')
+ # nfo: musicbrainz url from .nfo file
+ nfo = params.get('nfo', '')
+ # path specific settings
+ settings = params.get('pathSettings', {})
+ return action, key, artist, album, url, nfo, settings
+
+
+if (__name__ == '__main__'):
+ Main()
diff --git a/addons/metadata.generic.albums/lib/allmusic.py b/addons/metadata.generic.albums/lib/allmusic.py
new file mode 100644
index 0000000000..ede10368c0
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/allmusic.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import difflib
+import time
+import re
+
+# not used for 'find', but needed for 'getdetails'
+def allmusic_albumfind(data, artist, album):
+ data = data.decode('utf-8')
+ albums = []
+ albumlist = re.findall('class="album">\s*(.*?)\s*</li', data, re.S)
+ for item in albumlist:
+ albumdata = {}
+ albumartist = re.search('class="artist">.*?>(.*?)</a', item, re.S)
+ if albumartist:
+ albumdata['artist'] = albumartist.group(1)
+ else: # classical album
+ continue
+ albumname = re.search('class="title">.*?>(.*?)</a', item, re.S)
+ if albumname:
+ albumdata['album'] = albumname.group(1)
+ else: # not likely to happen, but just in case
+ continue
+ # filter inaccurate results
+ artistmatch = difflib.SequenceMatcher(None, artist.decode('utf-8').lower(), albumdata['artist'].lower()).ratio()
+ albummatch = difflib.SequenceMatcher(None, album.decode('utf-8').lower(), albumdata['album'].lower()).ratio()
+ if artistmatch > 0.90 and albummatch > 0.90:
+ albumurl = re.search('class="title">\s*<a href="(.*?)"', item)
+ if albumurl:
+ albumdata['url'] = albumurl.group(1)
+ else: # not likely to happen, but just in case
+ continue
+ albumyear = re.search('class="year">\s*(.*?)\s*<', item, re.S)
+ if albumyear:
+ albumdata['year'] = albumyear.group(1)
+ else:
+ albumdata['year'] = ''
+ albumthumb = re.search('img src="(.*?)"', item)
+ if albumthumb:
+ albumdata['thumb'] = albumthumb.group(1)
+ else:
+ albumdata['thumb'] = ''
+ albums.append(albumdata)
+ return albums
+
+def allmusic_albumdetails(data):
+ data = data.decode('utf-8')
+ albumdata = {}
+ releasedata = re.search('class="release-date">.*?<span>(.*?)<', data, re.S)
+ if releasedata:
+ dateformat = releasedata.group(1)
+ if len(dateformat) > 4:
+ try:
+ # month day, year
+ albumdata['releasedate'] = datetime.datetime(*(time.strptime(dateformat, '%B %d, %Y')[0:3])).strftime('%Y-%m-%d')
+ except:
+ # month, year
+ albumdata['releasedate'] = datetime.datetime(*(time.strptime(dateformat, '%B, %Y')[0:3])).strftime('%Y-%m')
+ else:
+ # year
+ albumdata['releasedate'] = dateformat
+ yeardata = re.search('class="year".*?>\s*(.*?)\s*<', data)
+ if yeardata:
+ albumdata['year'] = yeardata.group(1)
+ genredata = re.search('class="genre">.*?">(.*?)<', data, re.S)
+ if genredata:
+ albumdata['genre'] = genredata.group(1)
+ styledata = re.search('class="styles">.*?div>\s*(.*?)\s*</div', data, re.S)
+ if styledata:
+ stylelist = re.findall('">(.*?)<', styledata.group(1))
+ if stylelist:
+ albumdata['styles'] = ' / '.join(stylelist)
+ mooddata = re.search('class="moods">.*?div>\s*(.*?)\s*</div', data, re.S)
+ if mooddata:
+ moodlist = re.findall('">(.*?)<', mooddata.group(1))
+ if moodlist:
+ albumdata['moods'] = ' / '.join(moodlist)
+ themedata = re.search('class="themes">.*?div>\s*(.*?)\s*</div', data, re.S)
+ if themedata:
+ themelist = re.findall('">(.*?)<', themedata.group(1))
+ if themelist:
+ albumdata['themes'] = ' / '.join(themelist)
+ ratingdata = re.search('itemprop="ratingValue">\s*(.*?)\s*</div', data)
+ if ratingdata:
+ albumdata['rating'] = ratingdata.group(1)
+ albumdata['votes'] = ''
+ titledata = re.search('class="album-title".*?>\s*(.*?)\s*<', data, re.S)
+ if titledata:
+ albumdata['album'] = titledata.group(1)
+ labeldata = re.search('class="label-catalog".*?<.*?>(.*?)<', data, re.S)
+ if labeldata:
+ albumdata['label'] = labeldata.group(1)
+ artistdata = re.search('class="album-artist".*?<span.*?>\s*(.*?)\s*</span', data, re.S)
+ if artistdata:
+ artistlist = re.findall('">(.*?)<', artistdata.group(1))
+ artists = []
+ for item in artistlist:
+ artistinfo = {}
+ artistinfo['artist'] = item
+ artists.append(artistinfo)
+ if artists:
+ albumdata['artist'] = artists
+ albumdata['artist_description'] = ' / '.join(artistlist)
+ thumbsdata = re.search('class="album-contain".*?src="(.*?)"', data, re.S)
+ if thumbsdata:
+ thumbs = []
+ thumbdata = {}
+ thumb = thumbsdata.group(1).rstrip('?partner=allrovi.com')
+ # ignore internal blank thumb
+ if thumb.startswith('http'):
+ # 0=largest / 1=75 / 2=150 / 3=250 / 4=400 / 5=500 / 6=1080
+ if thumb.endswith('f=5'):
+ thumbdata['image'] = thumb.replace('f=5', 'f=0')
+ thumbdata['preview'] = thumb.replace('f=5', 'f=2')
+ else:
+ thumbdata['image'] = thumb
+ thumbdata['preview'] = thumb
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ albumdata['thumb'] = thumbs
+ return albumdata
diff --git a/addons/metadata.generic.albums/lib/discogs.py b/addons/metadata.generic.albums/lib/discogs.py
new file mode 100644
index 0000000000..09730ef0fc
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/discogs.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+import difflib
+
+def discogs_albumfind(data, artist, album):
+ albums = []
+ masters = []
+ # sort results by lowest release id (first version of a release)
+ releases = sorted(data.get('results',[]), key=lambda k: k['id'])
+ for item in releases:
+ masterid = item['master_id']
+ # we are not interested in multiple versions that belong to the same master release
+ if masterid not in masters:
+ masters.append(masterid)
+ albumdata = {}
+ albumdata['artist'] = item['title'].split(' - ',1)[0]
+ albumdata['album'] = item['title'].split(' - ',1)[1]
+ albumdata['artist_description'] = item['title'].split(' - ',1)[0]
+ albumdata['year'] = str(item.get('year', ''))
+ albumdata['label'] = item['label'][0]
+ albumdata['thumb'] = item['thumb']
+ albumdata['dcalbumid'] = item['id']
+ # discogs does not provide relevance, use our own
+ artistmatch = difflib.SequenceMatcher(None, artist.lower(), albumdata['artist'].lower()).ratio()
+ albummatch = difflib.SequenceMatcher(None, album.lower(), albumdata['album'].lower()).ratio()
+ artistscore = round(artistmatch, 2)
+ albumscore = round(albummatch, 2)
+ score = round(((artistscore + albumscore) / 2), 2)
+ albumdata['relevance'] = str(score)
+ albums.append(albumdata)
+ return albums
+
+def discogs_albumdetails(data):
+ albumdata = {}
+ albumdata['album'] = data['title']
+ if 'styles' in data:
+ albumdata['styles'] = ' / '.join(data['styles'])
+ albumdata['genres'] = ' / '.join(data['genres'])
+ albumdata['year'] = str(data['year'])
+ albumdata['label'] = data['labels'][0]['name']
+ artists = []
+ for artist in data['artists']:
+ artistdata = {}
+ artistdata['artist'] = artist['name']
+ artists.append(artistdata)
+ albumdata['artist'] = artists
+ albumdata['artist_description'] = data['artists_sort']
+ albumdata['rating'] = str(int((float(data['community']['rating']['average']) * 2) + 0.5))
+ albumdata['votes'] = str(data['community']['rating']['count'])
+ if 'images' in data:
+ thumbs = []
+ for thumb in data['images']:
+ thumbdata = {}
+ thumbdata['image'] = thumb['uri']
+ thumbdata['preview'] = thumb['uri150']
+ # not accurate: discogs can provide any art type, there is no indication if it is an album front cover (thumb)
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ albumdata['thumb'] = thumbs
+ return albumdata
diff --git a/addons/metadata.generic.albums/lib/fanarttv.py b/addons/metadata.generic.albums/lib/fanarttv.py
new file mode 100644
index 0000000000..1724945078
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/fanarttv.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+def fanarttv_albumart(data):
+ if 'albums' in data:
+ albumdata = {}
+ thumbs = []
+ extras = []
+ discs = {}
+ for mbid, art in data['albums'].items():
+ if 'albumcover' in art:
+ for thumb in art['albumcover']:
+ thumbdata = {}
+ thumbdata['image'] = thumb['url']
+ thumbdata['preview'] = thumb['url'].replace('/fanart/', '/preview/')
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ if 'cdart' in art:
+ albumdata['discart'] = art['cdart'][0]['url']
+ for cdart in art['cdart']:
+ extradata = {}
+ extradata['image'] = cdart['url']
+ extradata['preview'] = cdart['url'].replace('/fanart/', '/preview/')
+ extradata['aspect'] = 'discart'
+ extras.append(extradata)
+ # support for multi-disc albums
+ multidata = {}
+ num = cdart['disc']
+ multidata['image'] = cdart['url']
+ multidata['preview'] = cdart['url'].replace('/fanart/', '/preview/')
+ multidata['aspect'] = 'discart%s' % num
+ if not num in discs:
+ discs[num] = [multidata]
+ else:
+ discs[num].append(multidata)
+ if thumbs:
+ albumdata['thumb'] = thumbs
+ # only return for multi-discs, not single discs
+ if len(discs) > 1:
+ albumdata['multidiscart'] = discs
+ for k, v in discs.items():
+ for item in v:
+ extras.append(item)
+ if extras:
+ albumdata['extras'] = extras
+ return albumdata
diff --git a/addons/metadata.generic.albums/lib/musicbrainz.py b/addons/metadata.generic.albums/lib/musicbrainz.py
new file mode 100644
index 0000000000..0b7c9a36f6
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/musicbrainz.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+def musicbrainz_albumfind(data, artist, album):
+ albums = []
+ # count how often each releasegroup occurs in the release results
+ # keep track of the release with the highest score and earliest releasedate in each releasegroup
+ releasegroups = {}
+ for item in data.get('releases'):
+ mbid = item['id']
+ score = item.get('score', 0)
+ releasegroup = item['release-group']['id']
+ if 'date' in item and item['date']:
+ date = item['date'].replace('-','')
+ if len(date) == 4:
+ date = date + '9999'
+ else:
+ date = '99999999'
+ if releasegroup in releasegroups:
+ count = releasegroups[releasegroup][0] + 1
+ topmbid = releasegroups[releasegroup][1]
+ topdate = releasegroups[releasegroup][2]
+ topscore = releasegroups[releasegroup][3]
+ if date < topdate and score >= topscore:
+ topdate = date
+ topmbid = mbid
+ releasegroups[releasegroup] = [count, topmbid, topdate, topscore]
+ else:
+ releasegroups[releasegroup] = [1, mbid, date, score]
+ if releasegroups:
+ # get the highest releasegroup count
+ maxcount = max(releasegroups.values())[0]
+ # get the releasegroup(s) that match this highest value
+ topgroups = [k for k, v in releasegroups.items() if v[0] == maxcount]
+ for item in data.get('releases'):
+ # only use the 'top' release from each releasegroup
+ if item['id'] != releasegroups[item['release-group']['id']][1]:
+ continue
+ albumdata = {}
+ if item.get('artist-credit'):
+ artists = []
+ artistdisp = ""
+ for artist in item['artist-credit']:
+ artistdata = {}
+ artistdata['artist'] = artist['artist']['name']
+ artistdata['mbartistid'] = artist['artist']['id']
+ artistdata['artistsort'] = artist['artist']['sort-name']
+ artistdisp = artistdisp + artist['artist']['name']
+ artistdisp = artistdisp + artist.get('joinphrase', '')
+ artists.append(artistdata)
+ albumdata['artist'] = artists
+ albumdata['artist_description'] = artistdisp
+ if item.get('label-info','') and item['label-info'][0].get('label','') and item['label-info'][0]['label'].get('name',''):
+ albumdata['label'] = item['label-info'][0]['label']['name']
+ albumdata['album'] = item['title']
+ if item.get('date',''):
+ albumdata['year'] = item['date'][:4]
+ albumdata['thumb'] = 'https://coverartarchive.org/release-group/%s/front-250' % item['release-group']['id']
+ if item.get('label-info','') and item['label-info'][0].get('label','') and item['label-info'][0]['label'].get('name',''):
+ albumdata['label'] = item['label-info'][0]['label']['name']
+ if item.get('status',''):
+ albumdata['releasestatus'] = item['status']
+ albumdata['type'] = item['release-group'].get('primary-type')
+ albumdata['mbalbumid'] = item['id']
+ albumdata['mbreleasegroupid'] = item['release-group']['id']
+ if item.get('score'):
+ releasescore = item['score'] / 100.0
+ # if the release is in the releasegroup with most releases, it is considered the most accurate one
+ # (this also helps with prefering official releases over bootlegs, assuming there are more variations of an official release than of a bootleg)
+ if item['release-group']['id'] not in topgroups:
+ releasescore -= 0.001
+ # if the release is an album, prefer it over singles/ep's
+ # (this needs to be the double of the above, as me might have just given the album a lesser score if the single happened to be in the topgroup)
+ if item['release-group'].get('primary-type') != 'Album':
+ releasescore -= 0.002
+ albumdata['relevance'] = str(releasescore)
+ albums.append(albumdata)
+ return albums
+
+def musicbrainz_albumdetails(data):
+ albumdata = {}
+ albumdata['album'] = data['title']
+ albumdata['mbalbumid'] = data['id']
+ if data.get('release-group',''):
+ albumdata['mbreleasegroupid'] = data['release-group']['id']
+ if data['release-group']['rating'] and data['release-group']['rating']['value']:
+ albumdata['rating'] = str(int((float(data['release-group']['rating']['value']) * 2) + 0.5))
+ albumdata['votes'] = str(data['release-group']['rating']['votes-count'])
+ if data['release-group'].get('primary-type'):
+ albumtypes = [data['release-group']['primary-type']] + data['release-group']['secondary-types']
+ albumdata['type'] = ' / '.join(albumtypes)
+ if 'Compilation' in albumtypes:
+ albumdata['compilation'] = 'true'
+ if data['release-group'].get('first-release-date',''):
+ albumdata['originaldate'] = data['release-group']['first-release-date']
+ if data.get('release-events',''):
+ albumdata['year'] = data['release-events'][0]['date'][:4]
+ albumdata['releasedate'] = data['release-events'][0]['date']
+ if data.get('label-info','') and data['label-info'][0].get('label','') and data['label-info'][0]['label'].get('name',''):
+ albumdata['label'] = data['label-info'][0]['label']['name']
+ if data.get('status',''):
+ albumdata['releasestatus'] = data['status']
+ if data.get('artist-credit'):
+ artists = []
+ artistdisp = ''
+ for artist in data['artist-credit']:
+ artistdata = {}
+ artistdata['artist'] = artist['name']
+ artistdata['mbartistid'] = artist['artist']['id']
+ artistdata['artistsort'] = artist['artist']['sort-name']
+ artistdisp = artistdisp + artist['name']
+ artistdisp = artistdisp + artist.get('joinphrase', '')
+ artists.append(artistdata)
+ albumdata['artist'] = artists
+ albumdata['artist_description'] = artistdisp
+ return albumdata
+
+def musicbrainz_albumart(data):
+ albumdata = {}
+ thumbs = []
+ extras = []
+ for item in data['images']:
+ if 'Front' in item['types']:
+ thumbdata = {}
+ thumbdata['image'] = item['image']
+ thumbdata['preview'] = item['thumbnails']['small']
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ if 'Back' in item['types']:
+ albumdata['back'] = item['image']
+ backdata = {}
+ backdata['image'] = item['image']
+ backdata['preview'] = item['thumbnails']['small']
+ backdata['aspect'] = 'back'
+ extras.append(backdata)
+ if 'Medium' in item['types']:
+ albumdata['discart'] = item['image']
+ discartdata = {}
+ discartdata['image'] = item['image']
+ discartdata['preview'] = item['thumbnails']['small']
+ discartdata['aspect'] = 'discart'
+ extras.append(discartdata)
+ # exculde spine+back images
+ if 'Spine' in item['types'] and len(item['types']) == 1:
+ albumdata['spine'] = item['image']
+ spinedata = {}
+ spinedata['image'] = item['image']
+ spinedata['preview'] = item['thumbnails']['small']
+ spinedata['aspect'] = 'spine'
+ extras.append(spinedata)
+ if thumbs:
+ albumdata['thumb'] = thumbs
+ if extras:
+ albumdata['extras'] = extras
+ return albumdata
diff --git a/addons/metadata.generic.albums/lib/nfo.py b/addons/metadata.generic.albums/lib/nfo.py
new file mode 100644
index 0000000000..ef996ce52b
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/nfo.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+def nfo_geturl(data):
+ result = re.search('https://musicbrainz.org/(ws/2/)?release/([0-9a-z\-]*)', data)
+ if result:
+ return result.group(2)
diff --git a/addons/metadata.generic.albums/lib/scraper.py b/addons/metadata.generic.albums/lib/scraper.py
new file mode 100644
index 0000000000..7a05a11c58
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/scraper.py
@@ -0,0 +1,442 @@
+# -*- coding: utf-8 -*-
+
+import json
+import socket
+import sys
+import time
+import urllib.parse
+import urllib.request
+import _strptime # https://bugs.python.org/issue7980
+from threading import Thread
+from urllib.error import HTTPError, URLError
+from socket import timeout
+import xbmc
+import xbmcgui
+import xbmcplugin
+import xbmcaddon
+from .theaudiodb import theaudiodb_albumdetails
+from .musicbrainz import musicbrainz_albumfind
+from .musicbrainz import musicbrainz_albumdetails
+from .musicbrainz import musicbrainz_albumart
+from .discogs import discogs_albumfind
+from .discogs import discogs_albumdetails
+from .allmusic import allmusic_albumfind
+from .allmusic import allmusic_albumdetails
+from .nfo import nfo_geturl
+from .fanarttv import fanarttv_albumart
+from .utils import *
+
+ADDONID = xbmcaddon.Addon().getAddonInfo('id')
+ADDONNAME = xbmcaddon.Addon().getAddonInfo('name')
+ADDONVERSION = xbmcaddon.Addon().getAddonInfo('version')
+
+
+def log(txt):
+ message = '%s: %s' % (ADDONID, txt)
+ xbmc.log(msg=message, level=xbmc.LOGDEBUG)
+
+def get_data(url, jsonformat):
+ try:
+ headers = {}
+ headers['User-Agent'] = '%s/%s ( http://kodi.tv )' % (ADDONNAME, ADDONVERSION)
+ req = urllib.request.Request(url, headers=headers)
+ resp = urllib.request.urlopen(req, timeout=5)
+ respdata = resp.read()
+ except URLError as e:
+ log('URLError: %s - %s' % (e.reason, url))
+ return
+ except HTTPError as e:
+ log('HTTPError: %s - %s' % (e.reason, url))
+ return
+ except socket.timeout as e:
+ log('socket: %s - %s' % (e, url))
+ return
+ if resp.getcode() == 503:
+ log('exceeding musicbrainz api limit')
+ return
+ elif resp.getcode() == 429:
+ log('exceeding discogs api limit')
+ return
+ if jsonformat:
+ respdata = json.loads(respdata)
+ return respdata
+
+
+class Scraper():
+ def __init__(self, action, key, artist, album, url, nfo, settings):
+ # get start time in milliseconds
+ self.start = int(round(time.time() * 1000))
+ # parse path settings
+ self.parse_settings(settings)
+ # return a dummy result, this is just for backward compitability with xml based scrapers https://github.com/xbmc/xbmc/pull/11632
+ if action == 'resolveid':
+ result = self.resolve_mbid(key)
+ if result:
+ self.return_resolved(result)
+ # search for artist name / album title matches
+ elif action == 'find':
+ # both musicbrainz and discogs allow 1 api per second. this query requires 1 musicbrainz api call and optionally 1 discogs api call
+ RATELIMIT = 1000
+ # try musicbrainz first
+ result = self.find_album(artist, album, 'musicbrainz')
+ if result:
+ self.return_search(result)
+ # fallback to discogs
+ else:
+ result = self.find_album(artist, album, 'discogs')
+ if result:
+ self.return_search(result)
+ # return info using artistname / albumtitle / id's
+ elif action == 'getdetails':
+ details = {}
+ url = json.loads(url)
+ artist = url['artist'].encode('utf-8')
+ album = url['album'].encode('utf-8')
+ mbid = url.get('mbalbumid', '')
+ dcid = url.get('dcalbumid', '')
+ mbreleasegroupid = url.get('mbreleasegroupid', '')
+ threads = []
+ # we have a musicbrainz album id, but no musicbrainz releasegroupid
+ if mbid and not mbreleasegroupid:
+ # musicbrainz allows 1 api per second.
+ RATELIMIT = 1000
+ for item in [[mbid, 'musicbrainz']]:
+ thread = Thread(target = self.get_details, args = (item[0], item[1], details))
+ threads.append(thread)
+ thread.start()
+ # wait for musicbrainz to finish
+ threads[0].join()
+ # check if we have a result:
+ if 'musicbrainz' in details:
+ artist = details['musicbrainz']['artist_description'].encode('utf-8')
+ album = details['musicbrainz']['album'].encode('utf-8')
+ mbreleasegroupid = details['musicbrainz']['mbreleasegroupid']
+ scrapers = [[mbreleasegroupid, 'theaudiodb'], [mbreleasegroupid, 'fanarttv'], [mbreleasegroupid, 'coverarchive'], [[artist, album], 'allmusic']]
+ if self.usediscogs == 1:
+ scrapers.append([[artist, album, dcid], 'discogs'])
+ # discogs allows 1 api per second. this query requires 2 discogs api calls
+ RATELIMIT = 2000
+ for item in scrapers:
+ thread = Thread(target = self.get_details, args = (item[0], item[1], details))
+ threads.append(thread)
+ thread.start()
+ # we have a discogs id and artistname and albumtitle
+ elif dcid:
+ # discogs allows 1 api per second. this query requires 1 discogs api call
+ RATELIMIT = 1000
+ for item in [[[artist, album, dcid], 'discogs'], [[artist, album], 'allmusic']]:
+ thread = Thread(target = self.get_details, args = (item[0], item[1], details))
+ threads.append(thread)
+ thread.start()
+ # we have musicbrainz album id, musicbrainz releasegroupid, artistname and albumtitle
+ else:
+ # musicbrainz allows 1 api per second.
+ RATELIMIT = 1000
+ scrapers = [[mbid, 'musicbrainz'], [mbreleasegroupid, 'theaudiodb'], [mbreleasegroupid, 'fanarttv'], [mbreleasegroupid, 'coverarchive'], [[artist, album], 'allmusic']]
+ if self.usediscogs == 1:
+ scrapers.append([[artist, album, dcid], 'discogs'])
+ # discogs allows 1 api per second. this query requires 2 discogs api calls
+ RATELIMIT = 2000
+ for item in scrapers:
+ thread = Thread(target = self.get_details, args = (item[0], item[1], details))
+ threads.append(thread)
+ thread.start()
+ for thread in threads:
+ thread.join()
+ result = self.compile_results(details)
+ if result:
+ self.return_details(result)
+ # extract the mbid from the provided musicbrainz url
+ elif action == 'NfoUrl':
+ mbid = nfo_geturl(nfo)
+ if mbid:
+ # create a dummy item
+ result = self.resolve_mbid(mbid)
+ if result:
+ self.return_nfourl(result)
+ # get end time in milliseconds
+ self.end = int(round(time.time() * 1000))
+ # handle musicbrainz and discogs ratelimit
+ if action == 'find' or action == 'getdetails':
+ if self.end - self.start < RATELIMIT:
+ # wait max 2 seconds
+ diff = RATELIMIT - (self.end - self.start)
+ xbmc.sleep(diff)
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
+ def parse_settings(self, data):
+ settings = json.loads(data)
+ # note: path settings are taken from the db, they may not reflect the current settings.xml file
+ self.genre = settings['genre']
+ self.lang = settings['lang']
+ self.mood = settings['mood']
+ self.rating = settings['rating']
+ self.style = settings['style']
+ self.theme = settings['theme']
+ self.usediscogs = settings['usediscogs']
+
+ def resolve_mbid(self, mbid):
+ # create dummy result
+ item = {}
+ item['artist_description'] = ''
+ item['album'] = ''
+ item['mbalbumid'] = mbid
+ item['mbreleasegroupid'] = ''
+ return item
+
+ def find_album(self, artist, album, site):
+ json = True
+ # musicbrainz
+ if site == 'musicbrainz':
+ url = MUSICBRAINZURL % (MUSICBRAINZSEARCH % (urllib.parse.quote_plus(album), urllib.parse.quote_plus(artist), urllib.parse.quote_plus(artist)))
+ scraper = musicbrainz_albumfind
+ # discogs
+ elif site == 'discogs':
+ url = DISCOGSURL % (DISCOGSSEARCH % (urllib.parse.quote_plus(album), urllib.parse.quote_plus(artist), DISCOGSKEY , DISCOGSSECRET))
+ scraper = discogs_albumfind
+ # allmusic
+ elif site == 'allmusic':
+ url = ALLMUSICURL % (ALLMUSICSEARCH % (urllib.parse.quote_plus(artist), urllib.parse.quote_plus(album)))
+ scraper = allmusic_albumfind
+ json = False
+ result = get_data(url, json)
+ if not result:
+ return
+ albumresults = scraper(result, artist, album)
+ return albumresults
+
+ def get_details(self, param, site, details):
+ json = True
+ # theaudiodb
+ if site == 'theaudiodb':
+ url = AUDIODBURL % (AUDIODBKEY, AUDIODBDETAILS % param)
+ albumscraper = theaudiodb_albumdetails
+ # musicbrainz
+ elif site == 'musicbrainz':
+ url = MUSICBRAINZURL % (MUSICBRAINZDETAILS % param)
+ albumscraper = musicbrainz_albumdetails
+ # fanarttv
+ elif site == 'fanarttv':
+ url = FANARTVURL % (param, FANARTVKEY)
+ albumscraper = fanarttv_albumart
+ # coverarchive
+ elif site == 'coverarchive':
+ url = MUSICBRAINZART % (param)
+ albumscraper = musicbrainz_albumart
+ # discogs
+ elif site == 'discogs':
+ dcalbumid = param[2]
+ if not dcalbumid:
+ # search
+ found = self.find_album(param[0], param[1], 'discogs')
+ if found:
+ # get details
+ dcalbumid = found[0]['dcalbumid']
+ else:
+ return
+ url = DISCOGSURL % (DISCOGSDETAILS % (dcalbumid, DISCOGSKEY, DISCOGSSECRET))
+ albumscraper = discogs_albumdetails
+ # allmusic
+ elif site == 'allmusic':
+ # search
+ found = self.find_album(param[0], param[1], 'allmusic')
+ if found:
+ # get details
+ url = ALLMUSICDETAILS % found[0]['url']
+ albumscraper = allmusic_albumdetails
+ json = False
+ else:
+ return
+ result = get_data(url, json)
+ if not result:
+ return
+ albumresults = albumscraper(result)
+ if not albumresults:
+ return
+ details[site] = albumresults
+ return details
+
+ def compile_results(self, details):
+ result = {}
+ thumbs = []
+ extras = []
+ # merge metadata results, start with the least accurate sources
+ if 'discogs' in details:
+ for k, v in details['discogs'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if 'allmusic' in details:
+ for k, v in details['allmusic'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if 'theaudiodb' in details:
+ for k, v in details['theaudiodb'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if k == 'extras':
+ extras.append(v)
+ if 'musicbrainz' in details:
+ for k, v in details['musicbrainz'].items():
+ result[k] = v
+ if 'coverarchive' in details:
+ for k, v in details['coverarchive'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if k == 'extras':
+ extras.append(v)
+ # prefer artwork from fanarttv
+ if 'fanarttv' in details:
+ for k, v in details['fanarttv'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if k == 'extras':
+ extras.append(v)
+ # use musicbrainz artist list as they provide mbid's, these can be passed to the artist scraper
+ if 'musicbrainz' in details:
+ result['artist'] = details['musicbrainz']['artist']
+ # provide artwork from all scrapers for getthumb option
+ if result:
+ # thumb list from most accurate sources first
+ thumbs.reverse()
+ thumbnails = []
+ for thumblist in thumbs:
+ for item in thumblist:
+ thumbnails.append(item)
+ # the order for extra art does not matter
+ extraart = []
+ for extralist in extras:
+ for item in extralist:
+ extraart.append(item)
+ # add the extra art to the end of the thumb list
+ thumbnails.extend(extraart)
+ result['thumb'] = thumbnails
+ data = self.user_prefs(details, result)
+ return data
+
+ def user_prefs(self, details, result):
+ # user preferences
+ lang = 'description' + self.lang
+ if 'theaudiodb' in details:
+ if lang in details['theaudiodb']:
+ result['description'] = details['theaudiodb'][lang]
+ elif 'descriptionEN' in details['theaudiodb']:
+ result['description'] = details['theaudiodb']['descriptionEN']
+ if (self.genre in details) and ('genre' in details[self.genre]):
+ result['genre'] = details[self.genre]['genre']
+ if (self.style in details) and ('styles' in details[self.style]):
+ result['styles'] = details[self.style]['styles']
+ if (self.mood in details) and ('moods' in details[self.mood]):
+ result['moods'] = details[self.mood]['moods']
+ if (self.theme in details) and ('themes' in details[self.theme]):
+ result['themes'] = details[self.theme]['themes']
+ if (self.rating in details) and ('rating' in details[self.rating]):
+ result['rating'] = details[self.rating]['rating']
+ result['votes'] = details[self.rating]['votes']
+ return result
+
+ def return_search(self, data):
+ for count, item in enumerate(data):
+ listitem = xbmcgui.ListItem(item['album'], offscreen=True)
+ listitem.setArt({'thumb': item['thumb']})
+ listitem.setProperty('album.artist', item['artist_description'])
+ listitem.setProperty('album.year', item.get('year',''))
+ listitem.setProperty('album.type', item.get('type',''))
+ listitem.setProperty('album.releasestatus', item.get('releasestatus',''))
+ listitem.setProperty('album.label', item.get('label',''))
+ listitem.setProperty('relevance', item['relevance'])
+ url = {'artist':item['artist_description'], 'album':item['album']}
+ if 'mbalbumid' in item:
+ url['mbalbumid'] = item['mbalbumid']
+ if 'mbreleasegroupid' in item:
+ url['mbreleasegroupid'] = item['mbreleasegroupid']
+ if 'dcalbumid' in item:
+ url['dcalbumid'] = item['dcalbumid']
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=json.dumps(url), listitem=listitem, isFolder=True)
+
+ def return_nfourl(self, item):
+ url = {'artist':item['artist_description'], 'album':item['album'], 'mbalbumid':item['mbalbumid'], 'mbreleasegroupid':item['mbreleasegroupid']}
+ listitem = xbmcgui.ListItem(offscreen=True)
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=json.dumps(url), listitem=listitem, isFolder=True)
+
+ def return_resolved(self, item):
+ url = {'artist':item['artist_description'], 'album':item['album'], 'mbalbumid':item['mbalbumid'], 'mbreleasegroupid':item['mbreleasegroupid']}
+ listitem = xbmcgui.ListItem(path=json.dumps(url), offscreen=True)
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)
+
+ def return_details(self, item):
+ if not 'album' in item:
+ return
+ listitem = xbmcgui.ListItem(item['album'], offscreen=True)
+ if 'mbalbumid' in item:
+ listitem.setProperty('album.musicbrainzid', item['mbalbumid'])
+ listitem.setProperty('album.releaseid', item['mbalbumid'])
+ if 'mbreleasegroupid' in item:
+ listitem.setProperty('album.releasegroupid', item['mbreleasegroupid'])
+ if 'scrapedmbid' in item:
+ listitem.setProperty('album.scrapedmbid', item['scrapedmbid'])
+ if 'artist' in item:
+ listitem.setProperty('album.artists', str(len(item['artist'])))
+ for count, artist in enumerate(item['artist']):
+ listitem.setProperty('album.artist%i.name' % (count + 1), artist['artist'])
+ listitem.setProperty('album.artist%i.musicbrainzid' % (count + 1), artist.get('mbartistid', ''))
+ listitem.setProperty('album.artist%i.sortname' % (count + 1), artist.get('artistsort', ''))
+ if 'genre' in item:
+ listitem.setProperty('album.genre', item['genre'])
+ if 'styles' in item:
+ listitem.setProperty('album.styles', item['styles'])
+ if 'moods' in item:
+ listitem.setProperty('album.moods', item['moods'])
+ if 'themes' in item:
+ listitem.setProperty('album.themes', item['themes'])
+ if 'description' in item:
+ listitem.setProperty('album.review', item['description'])
+ if 'releasedate' in item:
+ listitem.setProperty('album.releasedate', item['releasedate'])
+ if 'originaldate' in item:
+ listitem.setProperty('album.originaldate', item['originaldate'])
+ if 'releasestatus' in item:
+ listitem.setProperty('album.releasestatus', item['releasestatus'])
+ if 'artist_description' in item:
+ listitem.setProperty('album.artist_description', item['artist_description'])
+ if 'label' in item:
+ listitem.setProperty('album.label', item['label'])
+ if 'type' in item:
+ listitem.setProperty('album.type', item['type'])
+ if 'compilation' in item:
+ listitem.setProperty('album.compilation', item['compilation'])
+ if 'year' in item:
+ listitem.setProperty('album.year', item['year'])
+ if 'rating' in item:
+ listitem.setProperty('album.rating', item['rating'])
+ if 'votes' in item:
+ listitem.setProperty('album.votes', item['votes'])
+ art = {}
+ if 'discart' in item:
+ art['discart'] = item['discart']
+ if 'multidiscart' in item:
+ for k, v in item['multidiscart'].items():
+ discart = 'discart%s' % k
+ art[discart] = v[0]['image']
+ if 'back' in item:
+ art['back'] = item['back']
+ if 'spine' in item:
+ art['spine'] = item['spine']
+ if '3dcase' in item:
+ art['3dcase'] = item['3dcase']
+ if '3dflat' in item:
+ art['3dflat'] = item['3dflat']
+ if '3dface' in item:
+ art['3dface'] = item['3dface']
+ listitem.setArt(art)
+ if 'thumb' in item:
+ listitem.setProperty('album.thumbs', str(len(item['thumb'])))
+ for count, thumb in enumerate(item['thumb']):
+ listitem.setProperty('album.thumb%i.url' % (count + 1), thumb['image'])
+ listitem.setProperty('album.thumb%i.aspect' % (count + 1), thumb['aspect'])
+ listitem.setProperty('album.thumb%i.preview' % (count + 1), thumb['preview'])
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)
diff --git a/addons/metadata.generic.albums/lib/theaudiodb.py b/addons/metadata.generic.albums/lib/theaudiodb.py
new file mode 100644
index 0000000000..196e195c9a
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/theaudiodb.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+def theaudiodb_albumdetails(data):
+ if data.get('album'):
+ item = data['album'][0]
+ albumdata = {}
+ albumdata['album'] = item['strAlbum']
+ if item.get('intYearReleased',''):
+ albumdata['year'] = item['intYearReleased']
+ if item.get('strStyle',''):
+ albumdata['styles'] = item['strStyle']
+ if item.get('strGenre',''):
+ albumdata['genre'] = item['strGenre']
+ if item.get('strLabel',''):
+ albumdata['label'] = item['strLabel']
+ if item.get('strReleaseFormat',''):
+ albumdata['type'] = item['strReleaseFormat']
+ if item.get('intScore',''):
+ albumdata['rating'] = str(int(float(item['intScore']) + 0.5))
+ if item.get('intScoreVotes',''):
+ albumdata['votes'] = item['intScoreVotes']
+ if item.get('strMood',''):
+ albumdata['moods'] = item['strMood']
+ if item.get('strTheme',''):
+ albumdata['themes'] = item['strTheme']
+ if item.get('strMusicBrainzID',''):
+ albumdata['mbreleasegroupid'] = item['strMusicBrainzID']
+ # api inconsistent
+ if item.get('strDescription',''):
+ albumdata['descriptionEN'] = item['strDescription']
+ elif item.get('strDescriptionEN',''):
+ albumdata['descriptionEN'] = item['strDescriptionEN']
+ if item.get('strDescriptionDE',''):
+ albumdata['descriptionDE'] = item['strDescriptionDE']
+ if item.get('strDescriptionFR',''):
+ albumdata['descriptionFR'] = item['strDescriptionFR']
+ if item.get('strDescriptionCN',''):
+ albumdata['descriptionCN'] = item['strDescriptionCN']
+ if item.get('strDescriptionIT',''):
+ albumdata['descriptionIT'] = item['strDescriptionIT']
+ if item.get('strDescriptionJP',''):
+ albumdata['descriptionJP'] = item['strDescriptionJP']
+ if item.get('strDescriptionRU',''):
+ albumdata['descriptionRU'] = item['strDescriptionRU']
+ if item.get('strDescriptionES',''):
+ albumdata['descriptionES'] = item['strDescriptionES']
+ if item.get('strDescriptionPT',''):
+ albumdata['descriptionPT'] = item['strDescriptionPT']
+ if item.get('strDescriptionSE',''):
+ albumdata['descriptionSE'] = item['strDescriptionSE']
+ if item.get('strDescriptionNL',''):
+ albumdata['descriptionNL'] = item['strDescriptionNL']
+ if item.get('strDescriptionHU',''):
+ albumdata['descriptionHU'] = item['strDescriptionHU']
+ if item.get('strDescriptionNO',''):
+ albumdata['descriptionNO'] = item['strDescriptionNO']
+ if item.get('strDescriptionIL',''):
+ albumdata['descriptionIL'] = item['strDescriptionIL']
+ if item.get('strDescriptionPL',''):
+ albumdata['descriptionPL'] = item['strDescriptionPL']
+ if item.get('strArtist',''):
+ albumdata['artist_description'] = item['strArtist']
+ artists = []
+ artistdata = {}
+ artistdata['artist'] = item['strArtist']
+ if item.get('strMusicBrainzArtistID',''):
+ artistdata['mbartistid'] = item['strMusicBrainzArtistID']
+ artists.append(artistdata)
+ albumdata['artist'] = artists
+ thumbs = []
+ extras = []
+ if item.get('strAlbumThumb',''):
+ thumbdata = {}
+ thumbdata['image'] = item['strAlbumThumb']
+ thumbdata['preview'] = item['strAlbumThumb'] + '/preview'
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ if item.get('strAlbumThumbBack',''):
+ albumdata['back'] = item['strAlbumThumbBack']
+ extradata = {}
+ extradata['image'] = item['strAlbumThumbBack']
+ extradata['preview'] = item['strAlbumThumbBack'] + '/preview'
+ extradata['aspect'] = 'back'
+ extras.append(extradata)
+ if item.get('strAlbumSpine',''):
+ albumdata['spine'] = item['strAlbumSpine']
+ extradata = {}
+ extradata['image'] = item['strAlbumSpine']
+ extradata['preview'] = item['strAlbumSpine'] + '/preview'
+ extradata['aspect'] = 'spine'
+ extras.append(extradata)
+ if item.get('strAlbumCDart',''):
+ albumdata['discart'] = item['strAlbumCDart']
+ extradata = {}
+ extradata['image'] = item['strAlbumCDart']
+ extradata['preview'] = item['strAlbumCDart'] + '/preview'
+ extradata['aspect'] = 'discart'
+ extras.append(extradata)
+ if item.get('strAlbum3DCase',''):
+ albumdata['3dcase'] = item['strAlbum3DCase']
+ extradata = {}
+ extradata['image'] = item['strAlbum3DCase']
+ extradata['preview'] = item['strAlbum3DCase'] + '/preview'
+ extradata['aspect'] = '3dcase'
+ extras.append(extradata)
+ if item.get('strAlbum3DFlat',''):
+ albumdata['3dflat'] = item['strAlbum3DFlat']
+ extradata = {}
+ extradata['image'] = item['strAlbum3DFlat']
+ extradata['preview'] = item['strAlbum3DFlat'] + '/preview'
+ extradata['aspect'] = '3dflat'
+ extras.append(extradata)
+ if item.get('strAlbum3DFace',''):
+ albumdata['3dface'] = item['strAlbum3DFace']
+ extradata = {}
+ extradata['image'] = item['strAlbum3DFace']
+ extradata['preview'] = item['strAlbum3DFace'] + '/preview'
+ extradata['aspect'] = '3dface'
+ extras.append(extradata)
+ if thumbs:
+ albumdata['thumb'] = thumbs
+ if extras:
+ albumdata['extras'] = extras
+ return albumdata
diff --git a/addons/metadata.generic.albums/lib/utils.py b/addons/metadata.generic.albums/lib/utils.py
new file mode 100644
index 0000000000..e0ef3083c6
--- /dev/null
+++ b/addons/metadata.generic.albums/lib/utils.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+AUDIODBKEY = '58424d43204d6564696120'
+AUDIODBURL = 'https://www.theaudiodb.com/api/v1/json/%s/%s'
+AUDIODBSEARCH = 'searchalbum.php?s=%s&a=%s'
+AUDIODBDETAILS = 'album-mb.php?i=%s'
+
+MUSICBRAINZURL = 'https://musicbrainz.org/ws/2/release/%s'
+MUSICBRAINZSEARCH = '?query=release:"%s"%%20AND%%20(artistname:"%s"%%20OR%%20artist:"%s")&fmt=json'
+MUSICBRAINZDETAILS = '%s?inc=recordings+release-groups+artists+labels+ratings&fmt=json'
+MUSICBRAINZART = 'https://coverartarchive.org/release-group/%s'
+
+DISCOGSKEY = 'zACPgktOmNegwbwKWMaC'
+DISCOGSSECRET = 'wGuSOeMtfdkQxtERKQKPquyBwExSHdQq'
+DISCOGSURL = 'https://api.discogs.com/%s'
+DISCOGSSEARCH = 'database/search?release_title=%s&type=release&artist=%s&page=1&per_page=100&key=%s&secret=%s'
+DISCOGSDETAILS = 'releases/%i?key=%s&secret=%s'
+
+ALLMUSICURL = 'https://www.allmusic.com/%s'
+ALLMUSICSEARCH = 'search/albums/%s+%s'
+ALLMUSICDETAILS = '%s/releases'
+
+FANARTVKEY = 'ed4b784f97227358b31ca4dd966a04f1'
+FANARTVURL = 'https://webservice.fanart.tv/v3/music/albums/%s?api_key=%s'
diff --git a/addons/metadata.generic.albums/resources/icon.png b/addons/metadata.generic.albums/resources/icon.png
new file mode 100644
index 0000000000..b6748b3f54
--- /dev/null
+++ b/addons/metadata.generic.albums/resources/icon.png
Binary files differ
diff --git a/addons/metadata.generic.albums/resources/language/resource.language.en_gb/strings.po b/addons/metadata.generic.albums/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..f0e777ee6b
--- /dev/null
+++ b/addons/metadata.generic.albums/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,85 @@
+# Kodi Media Center language file
+# Addon Name: Generic Album Scraper
+# Addon id: metadata.generic.albums
+# Addon Provider: Team Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: KODI Main\n"
+"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/kodi-main/language/en_GB/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en_GB\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#30000"
+msgid "Preferences"
+msgstr ""
+
+msgctxt "#30001"
+msgid "Prefered language for album review"
+msgstr ""
+
+msgctxt "#30002"
+msgid "Prefer genres from"
+msgstr ""
+
+msgctxt "#30003"
+msgid "Prefer styles from"
+msgstr ""
+
+msgctxt "#30004"
+msgid "Prefer moods from"
+msgstr ""
+
+msgctxt "#30005"
+msgid "Prefer themes from"
+msgstr ""
+
+msgctxt "#30006"
+msgid "Prefer rating from"
+msgstr ""
+
+msgctxt "#30101"
+msgid "Use Discogs.com"
+msgstr ""
+
+msgctxt "#30102"
+msgid "As fallback only"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Always"
+msgstr ""
+
+msgctxt "#30201"
+msgid "If available, the album review will be downloaded in the selected language. It will fallback to english."
+msgstr ""
+
+msgctxt "#30202"
+msgid "Try to get genre info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30203"
+msgid "Try to get style info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30204"
+msgid "Try to get mood info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30205"
+msgid "Try to get theme info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30206"
+msgid "Try to get rating info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30301"
+msgid "Fallback: only use the discogs scraper if the album can't be found on MusicBrainz (faster, but could result in less complete results). Always: retrieve info from discogs.com for each album (slower, but results could be more complete)"
+msgstr ""
diff --git a/addons/metadata.generic.albums/resources/settings.xml b/addons/metadata.generic.albums/resources/settings.xml
new file mode 100644
index 0000000000..51150c2c52
--- /dev/null
+++ b/addons/metadata.generic.albums/resources/settings.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" ?>
+<settings version="1">
+ <section id="metadata.generic.albums">
+ <category id="preferences" label="30000">
+ <group id="1">
+ <setting help="30201" id="lang" label="30001" type="string">
+ <level>0</level>
+ <default>EN</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="CN">CN</option>
+ <option label="DE">DE</option>
+ <option label="EN">EN</option>
+ <option label="ES">ES</option>
+ <option label="FR">FR</option>
+ <option label="HU">HU</option>
+ <option label="IL">IL</option>
+ <option label="IT">IT</option>
+ <option label="JP">JP</option>
+ <option label="NL">NL</option>
+ <option label="NO">NO</option>
+ <option label="PL">PL</option>
+ <option label="PT">PT</option>
+ <option label="RU">RU</option>
+ <option label="SE">SE</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30202" id="genre" label="30002" type="string">
+ <level>0</level>
+ <default>theaudiodb</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="discogs">discogs</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30203" id="style" label="30003" type="string">
+ <level>0</level>
+ <default>theaudiodb</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="discogs">discogs</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30204" id="mood" label="30004" type="string">
+ <level>0</level>
+ <default>theaudiodb</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30205" id="theme" label="30005" type="string">
+ <level>0</level>
+ <default>theaudiodb</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30206" id="rating" label="30006" type="string">
+ <level>0</level>
+ <default>musicbrainz</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="discogs">discogs</option>
+ <option label="musicbrainz">musicbrainz</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ </group>
+ </category>
+ <category id="options" label="33063">
+ <group id="1">
+ <setting help="30301" id="usediscogs" label="30101" type="integer">
+ <level>0</level>
+ <default>0</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="30102">0</option>
+ <option label="30103">1</option>
+ </options>
+ </constraints>
+ </setting>
+ </group>
+ </category>
+ </section>
+</settings>
diff --git a/addons/metadata.generic.artists/LICENSE.txt b/addons/metadata.generic.artists/LICENSE.txt
new file mode 100644
index 0000000000..4f8e8eb30c
--- /dev/null
+++ b/addons/metadata.generic.artists/LICENSE.txt
@@ -0,0 +1,282 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+-------------------------------------------------------------------------
diff --git a/addons/metadata.generic.artists/addon.xml b/addons/metadata.generic.artists/addon.xml
new file mode 100644
index 0000000000..723cda0dc2
--- /dev/null
+++ b/addons/metadata.generic.artists/addon.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<addon id="metadata.generic.artists" name="Generic Artist Scraper" version="1.0.7" provider-name="Team Kodi">
+ <requires>
+ <import addon="xbmc.python" version="3.0.0"/>
+ <import addon="xbmc.metadata" version="2.1.0"/>
+ </requires>
+ <extension point="xbmc.metadata.scraper.artists" library="default.py"/>
+ <extension point="xbmc.addon.metadata">
+ <summary lang="en_GB">Generic music scraper for artists</summary>
+ <description lang="en_GB">Searches for artist information and artwork across multiple websites.</description>
+ <platform>all</platform>
+ <license>GPL-2.0-only</license>
+ <forum>https://forum.kodi.tv/showthread.php?tid=351571</forum>
+ <source>https://gitlab.com/ronie/metadata.generic.artists/</source>
+ <assets>
+ <icon>resources/icon.png</icon>
+ </assets>
+ <news>- first release</news>
+ </extension>
+</addon>
diff --git a/addons/metadata.generic.artists/changelog.txt b/addons/metadata.generic.artists/changelog.txt
new file mode 100644
index 0000000000..a41e2803f2
--- /dev/null
+++ b/addons/metadata.generic.artists/changelog.txt
@@ -0,0 +1,22 @@
+v1.0.7
+- include alias and sortname in artist search
+- filter inaccurate search results from discogs
+
+v1.0.6
+- provide musicbrainzreleasegroupid for artist albums
+
+v1.0.5
+- add artist gender
+- disallow 0 value born/formed/died/disbanded dates from theaudiodb
+
+v1.0.4
+- catch time-outs
+
+v1.0.3
+- replace beautifulsoup with regex
+
+v1.0.2
+- replace requests with urllib
+
+v1.0.1
+- release
diff --git a/addons/metadata.generic.artists/default.py b/addons/metadata.generic.artists/default.py
new file mode 100644
index 0000000000..c9a5a4b5d3
--- /dev/null
+++ b/addons/metadata.generic.artists/default.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+import sys
+from urllib.parse import parse_qsl
+from lib.scraper import Scraper
+
+
+class Main:
+ def __init__(self):
+ action, key, artist, url, nfo, settings = self._parse_argv()
+ Scraper(action, key, artist, url, nfo, settings)
+
+ def _parse_argv(self):
+ params = dict(parse_qsl(sys.argv[2].lstrip('?')))
+ # actions: resolveid, find, getdetails, NfoUrl
+ action = params['action']
+ # key: musicbrainz id
+ key = params.get('key', '')
+ # artist: artistname
+ artist = params.get('artist', '')
+ # url: provided by the scraper on previous run
+ url = params.get('url', '')
+ # nfo: musicbrainz url from .nfo file
+ nfo = params.get('nfo', '')
+ # path specific settings
+ settings = params.get('pathSettings', {})
+ return action, key, artist, url, nfo, settings
+
+
+if (__name__ == '__main__'):
+ Main()
diff --git a/addons/metadata.generic.artists/lib/allmusic.py b/addons/metadata.generic.artists/lib/allmusic.py
new file mode 100644
index 0000000000..5bd1597c16
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/allmusic.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+def allmusic_artistdetails(data):
+ data = data.decode('utf-8')
+ artistdata = {}
+ artist = re.search(r'artist-name" itemprop="name">\s*(.*?)\s*<', data)
+ if artist:
+ artistdata['artist'] = artist.group(1)
+ else:
+ # no discography page available for this artist
+ return
+ active = re.search(r'class="active-dates">.*?<div>(.*?)<', data, re.S)
+ if active:
+ artistdata['active'] = active.group(1)
+ begin = re.search(r'class="birth">.*?<h4>\s*(.*?)\s*<', data, re.S)
+ if begin and begin.group(1) == 'Born':
+ born = re.search(r'class="birth">.*?<a.*?>(.*?)<', data, re.S)
+ if born:
+ artistdata['born'] = born.group(1)
+ elif begin and begin.group(1) == 'Formed':
+ formed = re.search(r'class="birth">.*?<a.*?>(.*?)<', data, re.S)
+ if formed:
+ artistdata['formed'] = formed.group(1)
+ end = re.search(r'class="died">.*?<h4>\s*(.*?)\s*<', data, re.S)
+ if end and end.group(1) == 'Died':
+ died = re.search(r'class="died">.*?<a.*?>(.*?)<', data, re.S)
+ if died:
+ artistdata['died'] = died.group(1)
+ elif end and end.group(1) == 'Disbanded':
+ disbanded = re.search(r'class="died">.*?<a.*?>(.*?)<', data, re.S)
+ if disbanded:
+ artistdata['disbanded'] = disbanded.group(1)
+ genre = re.search(r'class="genre">.*?<a.*?>(.*?)<', data, re.S)
+ if genre:
+ artistdata['genre'] = genre.group(1)
+ styledata = re.search(r'class="styles">.*?<div>\s*(.*?)\s*</div', data, re.S)
+ if styledata:
+ styles = re.findall(r'">(.*?)<', styledata.group(1))
+ if styles:
+ artistdata['styles'] = ' / '.join(styles)
+ mooddata = re.search(r'class="moods">.*?<li>\s*(.*?)\s*</ul', data, re.S)
+ if mooddata:
+ moods = re.findall(r'">(.*?)<', mooddata.group(1))
+ if moods:
+ artistdata['moods'] = ' / '.join(moods)
+ thumbsdata = re.search(r'class="artist-image">.*?<img src="(.*?)"', data, re.S)
+ if thumbsdata:
+ thumbs = []
+ thumbdata = {}
+ thumb = thumbsdata.group(1).rstrip('?partner=allrovi.com')
+ # 0=largest / 1=75 / 2=150 / 3=250 / 4=400 / 5=500 / 6=1080
+ if thumb.endswith('f=4'):
+ thumbdata['image'] = thumb.replace('f=4', 'f=0')
+ thumbdata['preview'] = thumb.replace('f=4', 'f=2')
+ else:
+ thumbdata['image'] = thumb
+ thumbdata['preview'] = thumb
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ artistdata['thumb'] = thumbs
+ return artistdata
+
+def allmusic_artistalbums(data):
+ data = data.decode('utf-8')
+ albums = []
+ albumdata = re.search(r'tbody>\s*(.*?)\s*</tbody', data, re.S)
+ if albumdata:
+ albumlist = re.findall(r'tr.*?>\s*(.*?)\s*</tr', albumdata.group(1), re.S)
+ if albumlist:
+ for album in albumlist:
+ albumdata = {}
+ title = re.search(r'<a.*?>(.*?)<', album)
+ if title:
+ albumdata['title'] = title.group(1)
+ year = re.search(r'class="year".*?>\s*(.*?)\s*<', album)
+ if year:
+ albumdata['year'] = year.group(1)
+ else:
+ albumdata['year'] = ''
+ if albumdata:
+ albums.append(albumdata)
+ return albums
diff --git a/addons/metadata.generic.artists/lib/discogs.py b/addons/metadata.generic.artists/lib/discogs.py
new file mode 100644
index 0000000000..42fa1301d2
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/discogs.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+import difflib
+
+def discogs_artistfind(data, artist):
+ artists = []
+ for item in data.get('results',[]):
+ artistdata = {}
+ artistdata['artist'] = item['title']
+ # filter inaccurate results
+ match = difflib.SequenceMatcher(None, artist.lower(), item['title'].lower()).ratio()
+ score = round(match, 2)
+ if score > 0.90:
+ artistdata['thumb'] = item['thumb']
+ artistdata['genre'] = ''
+ artistdata['born'] = ''
+ artistdata['dcid'] = item['id']
+ # discogs does not provide relevance, use our own
+ artistdata['relevance'] = str(score)
+ artists.append(artistdata)
+ return artists
+
+def discogs_artistdetails(data):
+ artistdata = {}
+ artistdata['artist'] = data['name']
+ artistdata['biography'] = data['profile']
+ if 'images' in data:
+ thumbs = []
+ for item in data['images']:
+ thumbdata = {}
+ thumbdata['image'] = item['uri']
+ thumbdata['preview'] = item['uri150']
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ artistdata['thumb'] = thumbs
+ return artistdata
+
+def discogs_artistalbums(data):
+ albums = []
+ for item in data['releases']:
+ if item['role'] == 'Main':
+ albumdata = {}
+ albumdata['title'] = item['title']
+ albumdata['year'] = str(item.get('year', ''))
+ albums.append(albumdata)
+ return albums
diff --git a/addons/metadata.generic.artists/lib/fanarttv.py b/addons/metadata.generic.artists/lib/fanarttv.py
new file mode 100644
index 0000000000..22ae1f2e5c
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/fanarttv.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+def fanarttv_artistart(data):
+ artistdata = {}
+ extras = []
+ if 'artistbackground' in data:
+ fanart = []
+ for item in data['artistbackground']:
+ fanartdata = {}
+ fanartdata['image'] = item['url']
+ fanartdata['preview'] = item['url'].replace('/fanart/', '/preview/')
+ fanart.append(fanartdata)
+ artistdata['fanart'] = fanart
+ if 'artistthumb' in data:
+ thumbs = []
+ for item in data['artistthumb']:
+ thumbdata = {}
+ thumbdata['image'] = item['url']
+ thumbdata['preview'] = item['url'].replace('/fanart/', '/preview/')
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ if thumbs:
+ artistdata['thumb'] = thumbs
+ if 'musicbanner' in data:
+ artistdata['banner'] = data['musicbanner'][0]['url']
+ for item in data['musicbanner']:
+ extradata = {}
+ extradata['image'] = item['url']
+ extradata['preview'] = item['url'].replace('/fanart/', '/preview/')
+ extradata['aspect'] = 'banner'
+ extras.append(extradata)
+ if 'hdmusiclogo' in data:
+ artistdata['clearlogo'] = data['hdmusiclogo'][0]['url']
+ for item in data['hdmusiclogo']:
+ extradata = {}
+ extradata['image'] = item['url']
+ extradata['preview'] = item['url'].replace('/fanart/', '/preview/')
+ extradata['aspect'] = 'clearlogo'
+ extras.append(extradata)
+ elif 'musiclogo' in data:
+ artistdata['clearlogo'] = data['musiclogo'][0]['url']
+ for item in data['musiclogo']:
+ extradata = {}
+ extradata['image'] = item['url']
+ extradata['preview'] = item['url'].replace('/fanart/', '/preview/')
+ extradata['aspect'] = 'clearlogo'
+ extras.append(extradata)
+ if extras:
+ artistdata['extras'] = extras
+ return artistdata
diff --git a/addons/metadata.generic.artists/lib/musicbrainz.py b/addons/metadata.generic.artists/lib/musicbrainz.py
new file mode 100644
index 0000000000..4a9cbced0a
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/musicbrainz.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+def musicbrainz_artistfind(data, artist):
+ artists = []
+ for item in data.get('artists',[]):
+ artistdata = {}
+ artistdata['artist'] = item['name']
+ artistdata['thumb'] = ''
+ artistdata['genre'] = ''
+ artistdata['born'] = item['life-span'].get('begin', '')
+ if 'type' in item:
+ artistdata['type'] = item['type']
+ if 'gender' in item:
+ artistdata['gender'] = item['gender']
+ if 'disambiguation' in item:
+ artistdata['disambiguation'] = item['disambiguation']
+ artistdata['mbid'] = item['id']
+ if item.get('score',1):
+ artistdata['relevance'] = str(item['score'] / 100.00)
+ artists.append(artistdata)
+ return artists
+
+def musicbrainz_artistdetails(data):
+ artistdata = {}
+ artistdata['artist'] = data['name']
+ artistdata['mbartistid'] = data['id']
+ artistdata['type'] = data['type']
+ artistdata['gender'] = data['gender']
+ artistdata['disambiguation'] = data['disambiguation']
+ if data.get('life-span','') and data.get('type',''):
+ begin = data['life-span'].get('begin', '')
+ end = data['life-span'].get('end', '')
+ if data['type'] in ['Group', 'Orchestra', 'Choir']:
+ artistdata['formed'] = begin
+ artistdata['disbanded'] = end
+ elif data['type'] in ['Person', 'Character']:
+ artistdata['born'] = begin
+ artistdata['died'] = end
+ albums = []
+ for item in data.get('release-groups',[]):
+ albumdata = {}
+ albumdata['title'] = item.get('title','')
+ albumdata['year'] = item.get('first-release-date','')
+ albumdata['musicbrainzreleasegroupid'] = item.get('id','')
+ albums.append(albumdata)
+ if albums:
+ artistdata['albums'] = albums
+ for item in data['relations']:
+ if item['type'] == 'allmusic':
+ artistdata['allmusic-url'] = item['url']['resource']
+ elif item['type'] == 'discogs':
+ artistdata['discogs-url'] = item['url']['resource']
+ return artistdata
diff --git a/addons/metadata.generic.artists/lib/nfo.py b/addons/metadata.generic.artists/lib/nfo.py
new file mode 100644
index 0000000000..7aeb7ff06c
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/nfo.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+def nfo_geturl(data):
+ result = re.search('https://musicbrainz.org/(ws/2/)?artist/([0-9a-z\-]*)', data)
+ if result:
+ return result.group(2)
diff --git a/addons/metadata.generic.artists/lib/scraper.py b/addons/metadata.generic.artists/lib/scraper.py
new file mode 100644
index 0000000000..8828a435c5
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/scraper.py
@@ -0,0 +1,415 @@
+# -*- coding: utf-8 -*-
+
+import json
+import socket
+import sys
+import time
+import urllib.parse
+import urllib.request
+import _strptime # https://bugs.python.org/issue7980
+from threading import Thread
+from urllib.error import HTTPError, URLError
+from socket import timeout
+import xbmc
+import xbmcgui
+import xbmcplugin
+import xbmcaddon
+from .theaudiodb import theaudiodb_artistdetails
+from .theaudiodb import theaudiodb_artistalbums
+from .musicbrainz import musicbrainz_artistfind
+from .musicbrainz import musicbrainz_artistdetails
+from .discogs import discogs_artistfind
+from .discogs import discogs_artistdetails
+from .discogs import discogs_artistalbums
+from .allmusic import allmusic_artistdetails
+from .allmusic import allmusic_artistalbums
+from .nfo import nfo_geturl
+from .fanarttv import fanarttv_artistart
+from .utils import *
+
+ADDONID = xbmcaddon.Addon().getAddonInfo('id')
+ADDONNAME = xbmcaddon.Addon().getAddonInfo('name')
+ADDONVERSION = xbmcaddon.Addon().getAddonInfo('version')
+
+
+def log(txt):
+ message = '%s: %s' % (ADDONID, txt)
+ xbmc.log(msg=message, level=xbmc.LOGDEBUG)
+
+def get_data(url, jsonformat):
+ try:
+ headers = {}
+ headers['User-Agent'] = '%s/%s ( http://kodi.tv )' % (ADDONNAME, ADDONVERSION)
+ req = urllib.request.Request(url, headers=headers)
+ resp = urllib.request.urlopen(req, timeout=5)
+ respdata = resp.read()
+ except URLError as e:
+ log('URLError: %s - %s' % (e.reason, url))
+ return
+ except HTTPError as e:
+ log('HTTPError: %s - %s' % (e.reason, url))
+ return
+ except socket.timeout as e:
+ log('socket: %s - %s' % (e, url))
+ return
+ if resp.getcode() == 503:
+ log('exceeding musicbrainz api limit')
+ return
+ elif resp.getcode() == 429:
+ log('exceeding discogs api limit')
+ return
+ if jsonformat:
+ respdata = json.loads(respdata)
+ return respdata
+
+
+class Scraper():
+ def __init__(self, action, key, artist, url, nfo, settings):
+ # get start time in milliseconds
+ self.start = int(round(time.time() * 1000))
+ # parse path settings
+ self.parse_settings(settings)
+ # return a dummy result, this is just for backward compitability with xml based scrapers https://github.com/xbmc/xbmc/pull/11632
+ if action == 'resolveid':
+ result = self.resolve_mbid(key)
+ if result:
+ self.return_resolved(result)
+ # search for artist name matches
+ elif action == 'find':
+ # both musicbrainz and discogs allow 1 api per second. this query requires 1 musicbrainz api call and optionally 1 discogs api call
+ RATELIMIT = 1000
+ # try musicbrainz first
+ result = self.find_artist(artist, 'musicbrainz')
+ if result:
+ self.return_search(result)
+ # fallback to discogs
+ else:
+ result = self.find_artist(artist, 'discogs')
+ if result:
+ self.return_search(result)
+ # return info using artistname / id's
+ elif action == 'getdetails':
+ details = {}
+ url = json.loads(url)
+ artist = url['artist'].encode('utf-8')
+ mbid = url.get('mbid', '')
+ dcid = url.get('dcid', '')
+ threads = []
+ extrathreads = []
+ # we have a musicbrainz id
+ if mbid:
+ # musicbrainz allows 1 api per second.
+ RATELIMIT = 1000
+ scrapers = [[mbid, 'musicbrainz'], [mbid, 'theaudiodb'], [mbid, 'fanarttv']]
+
+ for item in scrapers:
+ thread = Thread(target = self.get_details, args = (item[0], item[1], details))
+ threads.append(thread)
+ thread.start()
+ # wait for musicbrainz to finish
+ threads[0].join()
+ # check if we have a result:
+ if 'musicbrainz' in details:
+ extrascrapers = []
+ # only scrape allmusic if we have an url provided by musicbrainz
+ if 'allmusic-url' in details['musicbrainz']:
+ extrascrapers.append([details['musicbrainz']['allmusic-url'], 'allmusic'])
+ # only scrape discogs if we have an url provided by musicbrainz and discogs scraping is explicitly enabled (as it is slower)
+ if 'discogs-url' in details['musicbrainz'] and self.usediscogs == 1:
+ dcid = int(details['musicbrainz']['discogs-url'].rsplit('/', 1)[1])
+ extrascrapers.append([dcid, 'discogs'])
+ # discogs allows 1 api per second. this query requires 2 discogs api calls
+ RATELIMIT = 2000
+ for item in extrascrapers:
+ thread = Thread(target = self.get_details, args = (item[0], item[1], details))
+ extrathreads.append(thread)
+ thread.start()
+ # we have a discogs id
+ else:
+ result = self.get_details(dcid, 'discogs', details)
+ # discogs allow 1 api per second. this query requires 2 discogs api call
+ RATELIMIT = 2000
+ if threads:
+ for thread in threads:
+ thread.join()
+ if extrathreads:
+ for thread in extrathreads:
+ thread.join()
+ result = self.compile_results(details)
+ if result:
+ self.return_details(result)
+ elif action == 'NfoUrl':
+ mbid = nfo_geturl(nfo)
+ if mbid:
+ result = self.resolve_mbid(mbid)
+ if result:
+ self.return_nfourl(result)
+ # get end time in milliseconds
+ self.end = int(round(time.time() * 1000))
+ # handle musicbrainz and discogs ratelimit
+ if action == 'find' or action == 'getdetails':
+ if self.end - self.start < RATELIMIT:
+ # wait max 2 seconds
+ diff = RATELIMIT - (self.end - self.start)
+ xbmc.sleep(diff)
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
+ def parse_settings(self, data):
+ settings = json.loads(data)
+ # note: path settings are taken from the db, they may not reflect the current settings.xml file
+ self.bio = settings['bio']
+ self.discog = settings['discog']
+ self.genre = settings['genre']
+ self.lang = settings['lang']
+ self.mood = settings['mood']
+ self.style = settings['style']
+ self.usediscogs = settings['usediscogs']
+
+ def resolve_mbid(self, mbid):
+ # create dummy result
+ item = {}
+ item['artist'] = ''
+ item['mbartistid'] = mbid
+ return item
+
+ def find_artist(self, artist, site):
+ json = True
+ # musicbrainz
+ if site == 'musicbrainz':
+ url = MUSICBRAINZURL % (MUSICBRAINZSEARCH % urllib.parse.quote_plus(artist))
+ scraper = musicbrainz_artistfind
+ # musicbrainz
+ if site == 'discogs':
+ url = DISCOGSURL % (DISCOGSSEARCH % (urllib.parse.quote_plus(artist), DISCOGSKEY , DISCOGSSECRET))
+ scraper = discogs_artistfind
+ result = get_data(url, json)
+ if not result:
+ return
+ artistresults = scraper(result, artist)
+ return artistresults
+
+ def get_details(self, param, site, details):
+ json = True
+ # theaudiodb
+ if site == 'theaudiodb':
+ url = AUDIODBURL % (AUDIODBKEY, AUDIODBDETAILS % param)
+ artistscraper = theaudiodb_artistdetails
+ # musicbrainz
+ elif site == 'musicbrainz':
+ url = MUSICBRAINZURL % (MUSICBRAINZDETAILS % param)
+ artistscraper = musicbrainz_artistdetails
+ # fanarttv
+ elif site == 'fanarttv':
+ url = FANARTVURL % (param, FANARTVKEY)
+ artistscraper = fanarttv_artistart
+ # discogs
+ elif site == 'discogs':
+ url = DISCOGSURL % (DISCOGSDETAILS % (param, DISCOGSKEY, DISCOGSSECRET))
+ artistscraper = discogs_artistdetails
+ # allmusic
+ elif site == 'allmusic':
+ url = param + '/discography'
+ artistscraper = allmusic_artistdetails
+ json = False
+ result = get_data(url, json)
+ if not result:
+ return
+ artistresults = artistscraper(result)
+ if not artistresults:
+ return
+ if site == 'theaudiodb' or site == 'discogs' or site == 'allmusic':
+ if site == 'theaudiodb':
+ # theaudiodb - discography
+ albumsurl = AUDIODBURL % (AUDIODBKEY, AUDIODBDISCOGRAPHY % artistresults['mbartistid'])
+ scraper = theaudiodb_artistalbums
+ elif site == 'discogs':
+ # discogs - discography
+ albumsurl = DISCOGSURL % (DISCOGSDISCOGRAPHY % (param, DISCOGSKEY, DISCOGSSECRET))
+ scraper = discogs_artistalbums
+ elif site == 'allmusic':
+ # allmusic - discography
+ albumsurl = param + '/discography'
+ scraper = allmusic_artistalbums
+ albumdata = get_data(albumsurl, json)
+ if albumdata:
+ albumresults = scraper(albumdata)
+ if albumresults:
+ artistresults['albums'] = albumresults
+ details[site] = artistresults
+ return details
+
+ def compile_results(self, details):
+ result = {}
+ thumbs = []
+ fanart = []
+ extras = []
+ # merge metadata results, start with the least accurate sources
+ if 'discogs' in details:
+ for k, v in details['discogs'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if 'allmusic' in details:
+ for k, v in details['allmusic'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ if 'theaudiodb' in details:
+ for k, v in details['theaudiodb'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ elif k == 'fanart':
+ fanart.append(v)
+ if k == 'extras':
+ extras.append(v)
+ if 'musicbrainz' in details:
+ for k, v in details['musicbrainz'].items():
+ result[k] = v
+ if 'fanarttv' in details:
+ for k, v in details['fanarttv'].items():
+ result[k] = v
+ if k == 'thumb':
+ thumbs.append(v)
+ elif k == 'fanart':
+ fanart.append(v)
+ if k == 'extras':
+ extras.append(v)
+ # provide artwork from all scrapers for getthumb / getfanart option
+ if result:
+ # artworks from most accurate sources first
+ thumbs.reverse()
+ thumbnails = []
+ fanart.reverse()
+ fanarts = []
+ # the order for extra art does not matter
+ extraart = []
+ for thumblist in thumbs:
+ for item in thumblist:
+ thumbnails.append(item)
+ for extralist in extras:
+ for item in extralist:
+ extraart.append(item)
+ # add the extra art to the end of the thumb list
+ thumbnails.extend(extraart)
+ result['thumb'] = thumbnails
+ for fanartlist in fanart:
+ for item in fanartlist:
+ fanarts.append(item)
+ result['fanart'] = fanarts
+ data = self.user_prefs(details, result)
+ return data
+
+ def user_prefs(self, details, result):
+ # user preferences
+ lang = 'biography' + self.lang
+ if self.bio == 'theaudiodb' and 'theaudiodb' in details:
+ if lang in details['theaudiodb']:
+ result['biography'] = details['theaudiodb'][lang]
+ elif 'biographyEN' in details['theaudiodb']:
+ result['biography'] = details['theaudiodb']['biographyEN']
+ elif self.bio == 'discogs' and 'discogs' in details:
+ result['biography'] = details['discogs']['biography']
+ if (self.discog in details) and ('albums' in details[self.discog]):
+ result['albums'] = details[self.discog]['albums']
+ if (self.genre in details) and ('genre' in details[self.genre]):
+ result['genre'] = details[self.genre]['genre']
+ if (self.style in details) and ('styles' in details[self.style]):
+ result['styles'] = details[self.style]['styles']
+ if (self.mood in details) and ('moods' in details[self.mood]):
+ result['moods'] = details[self.mood]['moods']
+ return result
+
+ def return_search(self, data):
+ for item in data:
+ listitem = xbmcgui.ListItem(item['artist'], offscreen=True)
+ listitem.setArt({'thumb': item['thumb']})
+ listitem.setProperty('artist.genre', item['genre'])
+ listitem.setProperty('artist.born', item['born'])
+ listitem.setProperty('relevance', item['relevance'])
+ if 'type' in item:
+ listitem.setProperty('artist.type', item['type'])
+ if 'gender' in item:
+ listitem.setProperty('artist.gender', item['gender'])
+ if 'disambiguation' in item:
+ listitem.setProperty('artist.disambiguation', item['disambiguation'])
+ url = {'artist':item['artist']}
+ if 'mbid' in item:
+ url['mbid'] = item['mbid']
+ if 'dcid' in item:
+ url['dcid'] = item['dcid']
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=json.dumps(url), listitem=listitem, isFolder=True)
+
+ def return_nfourl(self, item):
+ url = {'artist':item['artist'], 'mbid':item['mbartistid']}
+ listitem = xbmcgui.ListItem(offscreen=True)
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=json.dumps(url), listitem=listitem, isFolder=True)
+
+ def return_resolved(self, item):
+ url = {'artist':item['artist'], 'mbid':item['mbartistid']}
+ listitem = xbmcgui.ListItem(path=json.dumps(url), offscreen=True)
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)
+
+ def return_details(self, item):
+ if not 'artist' in item:
+ return
+ listitem = xbmcgui.ListItem(item['artist'], offscreen=True)
+ if 'mbartistid' in item:
+ listitem.setProperty('artist.musicbrainzid', item['mbartistid'])
+ if 'genre' in item:
+ listitem.setProperty('artist.genre', item['genre'])
+ if 'biography' in item:
+ listitem.setProperty('artist.biography', item['biography'])
+ if 'gender' in item:
+ listitem.setProperty('artist.gender', item['gender'])
+ if 'styles' in item:
+ listitem.setProperty('artist.styles', item['styles'])
+ if 'moods' in item:
+ listitem.setProperty('artist.moods', item['moods'])
+ if 'instruments' in item:
+ listitem.setProperty('artist.instruments', item['instruments'])
+ if 'disambiguation' in item:
+ listitem.setProperty('artist.disambiguation', item['disambiguation'])
+ if 'type' in item:
+ listitem.setProperty('artist.type', item['type'])
+ if 'sortname' in item:
+ listitem.setProperty('artist.sortname', item['sortname'])
+ if 'active' in item:
+ listitem.setProperty('artist.years_active', item['active'])
+ if 'born' in item:
+ listitem.setProperty('artist.born', item['born'])
+ if 'formed' in item:
+ listitem.setProperty('artist.formed', item['formed'])
+ if 'died' in item:
+ listitem.setProperty('artist.died', item['died'])
+ if 'disbanded' in item:
+ listitem.setProperty('artist.disbanded', item['disbanded'])
+ art = {}
+ if 'clearlogo' in item:
+ art['clearlogo'] = item['clearlogo']
+ if 'banner' in item:
+ art['banner'] = item['banner']
+ if 'clearart' in item:
+ art['clearart'] = item['clearart']
+ if 'landscape' in item:
+ art['landscape'] = item['landscape']
+ listitem.setArt(art)
+ if 'fanart' in item:
+ listitem.setProperty('artist.fanarts', str(len(item['fanart'])))
+ for count, fanart in enumerate(item['fanart']):
+ listitem.setProperty('artist.fanart%i.url' % (count + 1), fanart['image'])
+ listitem.setProperty('artist.fanart%i.preview' % (count + 1), fanart['preview'])
+ if 'thumb' in item:
+ listitem.setProperty('artist.thumbs', str(len(item['thumb'])))
+ for count, thumb in enumerate(item['thumb']):
+ listitem.setProperty('artist.thumb%i.url' % (count + 1), thumb['image'])
+ listitem.setProperty('artist.thumb%i.preview' % (count + 1), thumb['preview'])
+ listitem.setProperty('artist.thumb%i.aspect' % (count + 1), thumb['aspect'])
+ if 'albums' in item:
+ listitem.setProperty('artist.albums', str(len(item['albums'])))
+ for count, album in enumerate(item['albums']):
+ listitem.setProperty('artist.album%i.title' % (count + 1), album['title'])
+ listitem.setProperty('artist.album%i.year' % (count + 1), album['year'])
+ if 'musicbrainzreleasegroupid' in album:
+ listitem.setProperty('artist.album%i.musicbrainzreleasegroupid' % (count + 1), album['musicbrainzreleasegroupid'])
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)
diff --git a/addons/metadata.generic.artists/lib/theaudiodb.py b/addons/metadata.generic.artists/lib/theaudiodb.py
new file mode 100644
index 0000000000..c2a80a5614
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/theaudiodb.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+def theaudiodb_artistdetails(data):
+ if data.get('artists',[]):
+ item = data['artists'][0]
+ artistdata = {}
+ extras = []
+ artistdata['artist'] = item['strArtist']
+ # api inconsistent
+ if item.get('intFormedYear','') and item['intFormedYear'] != '0':
+ artistdata['formed'] = item['intFormedYear']
+ if item.get('intBornYear','') and item['intBornYear'] != '0':
+ artistdata['born'] = item['intBornYear']
+ if item.get('intDiedYear','') and item['intDiedYear'] != '0':
+ artistdata['died'] = item['intDiedYear']
+ if item.get('strDisbanded','') and item['strDisbanded'] != '0':
+ artistdata['disbanded'] = item['strDisbanded']
+ if item.get('strStyle',''):
+ artistdata['styles'] = item['strStyle']
+ if item.get('strGenre',''):
+ artistdata['genre'] = item['strGenre']
+ if item.get('strMood',''):
+ artistdata['moods'] = item['strMood']
+ if item.get('strGender',''):
+ artistdata['gender'] = item['strGender']
+ if item.get('strBiographyEN',''):
+ artistdata['biographyEN'] = item['strBiographyEN']
+ if item.get('strBiographyDE',''):
+ artistdata['biographyDE'] = item['strBiographyDE']
+ if item.get('strBiographyFR',''):
+ artistdata['biographyFR'] = item['strBiographyFR']
+ if item.get('strBiographyCN',''):
+ artistdata['biographyCN'] = item['strBiographyCN']
+ if item.get('strBiographyIT',''):
+ artistdata['biographyIT'] = item['strBiographyIT']
+ if item.get('strBiographyJP',''):
+ artistdata['biographyJP'] = item['strBiographyJP']
+ if item.get('strBiographyRU',''):
+ artistdata['biographyRU'] = item['strBiographyRU']
+ if item.get('strBiographyES',''):
+ artistdata['biographyES'] = item['strBiographyES']
+ if item.get('strBiographyPT',''):
+ artistdata['biographyPT'] = item['strBiographyPT']
+ if item.get('strBiographySE',''):
+ artistdata['biographySE'] = item['strBiographySE']
+ if item.get('strBiographyNL',''):
+ artistdata['biographyNL'] = item['strBiographyNL']
+ if item.get('strBiographyHU',''):
+ artistdata['biographyHU'] = item['strBiographyHU']
+ if item.get('strBiographyNO',''):
+ artistdata['biographyNO'] = item['strBiographyNO']
+ if item.get('strBiographyIL',''):
+ artistdata['biographyIL'] = item['strBiographyIL']
+ if item.get('strBiographyPL',''):
+ artistdata['biographyPL'] = item['strBiographyPL']
+ if item.get('strMusicBrainzID',''):
+ artistdata['mbartistid'] = item['strMusicBrainzID']
+ if item.get('strArtistFanart',''):
+ fanart = []
+ fanartdata = {}
+ fanartdata['image'] = item['strArtistFanart']
+ fanartdata['preview'] = item['strArtistFanart'] + '/preview'
+ fanart.append(fanartdata)
+ if item['strArtistFanart2']:
+ fanartdata = {}
+ fanartdata['image'] = item['strArtistFanart2']
+ fanartdata['preview'] = item['strArtistFanart2'] + '/preview'
+ fanart.append(fanartdata)
+ if item['strArtistFanart3']:
+ fanartdata = {}
+ fanartdata['image'] = item['strArtistFanart3']
+ fanartdata['preview'] = item['strArtistFanart3'] + '/preview'
+ fanart.append(fanartdata)
+ artistdata['fanart'] = fanart
+ if item.get('strArtistThumb',''):
+ thumbs = []
+ thumbdata = {}
+ thumbdata['image'] = item['strArtistThumb']
+ thumbdata['preview'] = item['strArtistThumb'] + '/preview'
+ thumbdata['aspect'] = 'thumb'
+ thumbs.append(thumbdata)
+ artistdata['thumb'] = thumbs
+ if item.get('strArtistLogo',''):
+ artistdata['clearlogo'] = item['strArtistLogo']
+ extradata = {}
+ extradata['image'] = item['strArtistLogo']
+ extradata['preview'] = item['strArtistLogo'] + '/preview'
+ extradata['aspect'] = 'clearlogo'
+ extras.append(extradata)
+ if item.get('strArtistClearart',''):
+ artistdata['clearart'] = item['strArtistClearart']
+ extradata = {}
+ extradata['image'] = item['strArtistClearart']
+ extradata['preview'] = item['strArtistClearart'] + '/preview'
+ extradata['aspect'] = 'clearart'
+ extras.append(extradata)
+ if item.get('strArtistWideThumb',''):
+ artistdata['landscape'] = item['strArtistWideThumb']
+ extradata = {}
+ extradata['image'] = item['strArtistWideThumb']
+ extradata['preview'] = item['strArtistWideThumb'] + '/preview'
+ extradata['aspect'] = 'landscape'
+ extras.append(extradata)
+ if item.get('strArtistBanner',''):
+ artistdata['banner'] = item['strArtistBanner']
+ extradata = {}
+ extradata['image'] = item['strArtistBanner']
+ extradata['preview'] = item['strArtistBanner'] + '/preview'
+ extradata['aspect'] = 'banner'
+ extras.append(extradata)
+ if extras:
+ artistdata['extras'] = extras
+ return artistdata
+
+def theaudiodb_artistalbums(data):
+ albums = []
+ albumlist = data.get('album',[])
+ if albumlist:
+ for item in data.get('album',[]):
+ albumdata = {}
+ albumdata['title'] = item['strAlbum']
+ albumdata['year'] = item.get('intYearReleased', '')
+ albums.append(albumdata)
+ return albums
diff --git a/addons/metadata.generic.artists/lib/utils.py b/addons/metadata.generic.artists/lib/utils.py
new file mode 100644
index 0000000000..5d432f01f5
--- /dev/null
+++ b/addons/metadata.generic.artists/lib/utils.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+AUDIODBKEY = '58424d43204d6564696120'
+AUDIODBURL = 'https://www.theaudiodb.com/api/v1/json/%s/%s'
+AUDIODBSEARCH = 'search.php?s=%s'
+AUDIODBDETAILS = 'artist-mb.php?i=%s'
+AUDIODBDISCOGRAPHY = 'discography-mb.php?s=%s'
+
+MUSICBRAINZURL = 'https://musicbrainz.org/ws/2/artist/%s'
+MUSICBRAINZSEARCH = '?query="%s"&fmt=json'
+MUSICBRAINZDETAILS = '%s?inc=url-rels+release-groups&type=album&fmt=json'
+
+DISCOGSKEY = 'zACPgktOmNegwbwKWMaC'
+DISCOGSSECRET = 'wGuSOeMtfdkQxtERKQKPquyBwExSHdQq'
+DISCOGSURL = 'https://api.discogs.com/%s'
+DISCOGSSEARCH = 'database/search?q=%s&type=artist&key=%s&secret=%s'
+DISCOGSDETAILS = 'artists/%i?key=%s&secret=%s'
+DISCOGSDISCOGRAPHY = 'artists/%i/releases?sort=format&page=1&per_page=100&key=%s&secret=%s'
+
+ALLMUSICURL = 'https://www.allmusic.com/%s'
+ALLMUSICSEARCH = 'search/artists/%s'
+
+FANARTVKEY = 'ed4b784f97227358b31ca4dd966a04f1'
+FANARTVURL = 'https://webservice.fanart.tv/v3/music/%s?api_key=%s'
diff --git a/addons/metadata.generic.artists/resources/icon.png b/addons/metadata.generic.artists/resources/icon.png
new file mode 100644
index 0000000000..25fa698f67
--- /dev/null
+++ b/addons/metadata.generic.artists/resources/icon.png
Binary files differ
diff --git a/addons/metadata.generic.artists/resources/language/resource.language.en_gb/strings.po b/addons/metadata.generic.artists/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 0000000000..5c4998f6ff
--- /dev/null
+++ b/addons/metadata.generic.artists/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,85 @@
+# Kodi Media Center language file
+# Addon Name: Generic Artist Scraper
+# Addon id: metadata.generic.artists
+# Addon Provider: Team Kodi
+msgid ""
+msgstr ""
+"Project-Id-Version: KODI Main\n"
+"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Kodi Translation Team\n"
+"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/kodi-main/language/en_GB/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en_GB\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#30000"
+msgid "Preferences"
+msgstr ""
+
+msgctxt "#30001"
+msgid "Prefered language for artist biography"
+msgstr ""
+
+msgctxt "#30002"
+msgid "Prefer biography from"
+msgstr ""
+
+msgctxt "#30003"
+msgid "Prefer discography from"
+msgstr ""
+
+msgctxt "#30004"
+msgid "Prefer genres from"
+msgstr ""
+
+msgctxt "#30005"
+msgid "Prefer styles from"
+msgstr ""
+
+msgctxt "#30006"
+msgid "Prefer moods from"
+msgstr ""
+
+msgctxt "#30101"
+msgid "Use Discogs.com"
+msgstr ""
+
+msgctxt "#30102"
+msgid "As fallback only"
+msgstr ""
+
+msgctxt "#30103"
+msgid "Always"
+msgstr ""
+
+msgctxt "#30201"
+msgid "If available, the artist biography will be downloaded in the selected language. It will fallback to english."
+msgstr ""
+
+msgctxt "#30202"
+msgid "Try to get the artist biography using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30203"
+msgid "Try to get the artist discography using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30204"
+msgid "Try to get genre info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30205"
+msgid "Try to get style info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30206"
+msgid "Try to get mood info using the selected scraper. Other scrapers will be used if the prefered scraper returns no results."
+msgstr ""
+
+msgctxt "#30301"
+msgid "Fallback: only use the discogs scraper if the artist can't be found on MusicBrainz (faster, but could result in less complete results). Always: retrieve info from discogs.com for each artist (slower, but results could be more complete)"
+msgstr ""
diff --git a/addons/metadata.generic.artists/resources/settings.xml b/addons/metadata.generic.artists/resources/settings.xml
new file mode 100644
index 0000000000..53508b1be9
--- /dev/null
+++ b/addons/metadata.generic.artists/resources/settings.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" ?>
+<settings version="1">
+ <section id="metadata.generic.artists">
+ <category id="general" label="30000">
+ <group id="1">
+ <setting help="30201" id="lang" label="30001" type="string">
+ <level>0</level>
+ <default>EN</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="CN">CN</option>
+ <option label="DE">DE</option>
+ <option label="EN">EN</option>
+ <option label="ES">ES</option>
+ <option label="FR">FR</option>
+ <option label="HU">HU</option>
+ <option label="IL">IL</option>
+ <option label="IT">IT</option>
+ <option label="JP">JP</option>
+ <option label="NL">NL</option>
+ <option label="NO">NO</option>
+ <option label="PL">PL</option>
+ <option label="PT">PT</option>
+ <option label="RU">RU</option>
+ <option label="SE">SE</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30202" id="bio" label="30002" type="string">
+ <level>0</level>
+ <default>theaudiodb</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="discogs">discogs</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30203" id="discog" label="30003" type="string">
+ <level>0</level>
+ <default>allmusic</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="discogs">discogs</option>
+ <option label="musicbrainz">musicbrainz</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30204" id="genre" label="30004" type="string">
+ <level>0</level>
+ <default>allmusic</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30205" id="style" label="30005" type="string">
+ <level>0</level>
+ <default>allmusic</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ <setting help="30206" id="mood" label="30006" type="string">
+ <level>0</level>
+ <default>allmusic</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="allmusic">allmusic</option>
+ <option label="theaudiodb">theaudiodb</option>
+ </options>
+ </constraints>
+ </setting>
+ </group>
+ </category>
+ <category id="options" label="33063">
+ <group id="1">
+ <setting help="30301" id="usediscogs" label="30101" type="integer">
+ <level>0</level>
+ <default>0</default>
+ <control format="string" type="spinner"/>
+ <constraints>
+ <options>
+ <option label="30102">0</option>
+ <option label="30103">1</option>
+ </options>
+ </constraints>
+ </setting>
+ </group>
+ </category>
+ </section>
+</settings>
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po
index c654211c68..64432e7700 100644
--- a/addons/resource.language.en_gb/resources/strings.po
+++ b/addons/resource.language.en_gb/resources/strings.po
@@ -9825,7 +9825,7 @@ msgctxt "#19110"
msgid "Could not delete the timer. Check the log for more information about this message."
msgstr ""
-#. message box text stating that a PVR backend error occured
+#. message box text stating that a PVR backend error occurred
#: xbmc/pvr/PVRGUIActions.cpp
msgctxt "#19111"
msgid "PVR backend error. Check the log for more information about this message."
diff --git a/addons/skin.estouchy/fonts/NotoSans-Regular.ttf b/addons/skin.estouchy/fonts/NotoSans-Regular.ttf
index bc49be7008..148fe820c4 100644
--- a/addons/skin.estouchy/fonts/NotoSans-Regular.ttf
+++ b/addons/skin.estouchy/fonts/NotoSans-Regular.ttf
Binary files differ
diff --git a/addons/skin.estuary/fonts/NotoSans-Bold.ttf b/addons/skin.estuary/fonts/NotoSans-Bold.ttf
deleted file mode 100644
index 032031f746..0000000000
--- a/addons/skin.estuary/fonts/NotoSans-Bold.ttf
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/fonts/NotoSans-Regular.ttf b/addons/skin.estuary/fonts/NotoSans-Regular.ttf
index 0d7289b914..148fe820c4 100644
--- a/addons/skin.estuary/fonts/NotoSans-Regular.ttf
+++ b/addons/skin.estuary/fonts/NotoSans-Regular.ttf
Binary files differ
diff --git a/addons/skin.estuary/xml/Custom_1109_TopBarOverlay.xml b/addons/skin.estuary/xml/Custom_1109_TopBarOverlay.xml
index c2aede95c9..7e3ecc0395 100644
--- a/addons/skin.estuary/xml/Custom_1109_TopBarOverlay.xml
+++ b/addons/skin.estuary/xml/Custom_1109_TopBarOverlay.xml
@@ -16,48 +16,50 @@
<height>170</height>
<texture>frame/osdfade.png</texture>
</control>
- <control type="image">
- <left>15</left>
- <top>10</top>
- <width>300</width>
- <height>140</height>
- <texture>$VAR[PlayerClearLogoVar]</texture>
- <aspectratio aligny="center" align="center">keep</aspectratio>
- </control>
- <control type="textbox">
- <label>$VAR[NowPlayingBreadcrumbsVar]</label>
- <font>font45</font>
- <shadowcolor>text_shadow</shadowcolor>
- <top>0</top>
- <height>150</height>
- <left>330</left>
- <right>400</right>
- <aligny>center</aligny>
- <visible>!String.IsEmpty(Player.Art(tvshow.clearlogo))</visible>
- </control>
<control type="group">
- <visible>!Window.IsActive(pvrosdchannels) + !Window.IsActive(pvrchannelguide)</visible>
- <visible>String.IsEmpty(Player.Art(clearlogo))</visible>
- <visible>String.IsEmpty(Player.Art(tvshow.clearlogo))</visible>
- <animation effect="fade" time="150">VisibleChange</animation>
- <left>20</left>
- <right>400</right>
- <control type="label">
- <label>$VAR[NowPlayingBreadcrumbsVar]</label>
- <font>font45</font>
- <shadowcolor>text_shadow</shadowcolor>
- <top>7</top>
- <height>50</height>
- <left>0</left>
- <right>0</right>
+ <animation effect="slide" end="90,0" time="0" condition="Skin.HasSetting(touchmode)">conditional</animation>
+ <control type="grouplist">
+ <visible>!String.IsEmpty(Player.Art(tvshow.clearlogo)) | !String.IsEmpty(Player.Art(clearlogo))</visible>
+ <top>10</top>
+ <left>20</left>
+ <right>400</right>
+ <height>100</height>
+ <itemgap>10</itemgap>
+ <orientation>horizontal</orientation>
+ <control type="image">
+ <width>300</width>
+ <texture>$VAR[PlayerClearLogoVar]</texture>
+ <aspectratio aligny="center" align="center">keep</aspectratio>
+ </control>
+ <control type="label">
+ <align>left</align>
+ <aligny>center</aligny>
+ <font>font13</font>
+ <label>$VAR[OSDSubLabelVar]</label>
+ <shadowcolor>text_shadow</shadowcolor>
+ <scroll>true</scroll>
+ </control>
</control>
- <control type="label">
- <top>60</top>
- <label>$VAR[OSDSubLabelVar]</label>
- <shadowcolor>text_shadow</shadowcolor>
- <height>60</height>
- <left>0</left>
- <right>0</right>
+ <control type="group">
+ <visible>!Window.IsActive(pvrosdchannels) + !Window.IsActive(pvrchannelguide)</visible>
+ <visible>String.IsEmpty(Player.Art(clearlogo))</visible>
+ <visible>String.IsEmpty(Player.Art(tvshow.clearlogo))</visible>
+ <animation effect="fade" time="150">VisibleChange</animation>
+ <left>20</left>
+ <right>400</right>
+ <control type="label">
+ <label>$VAR[NowPlayingBreadcrumbsVar]</label>
+ <font>font45</font>
+ <shadowcolor>text_shadow</shadowcolor>
+ <top>7</top>
+ <height>50</height>
+ </control>
+ <control type="label">
+ <top>60</top>
+ <label>$VAR[OSDSubLabelVar]</label>
+ <shadowcolor>text_shadow</shadowcolor>
+ <height>60</height>
+ </control>
</control>
</control>
<control type="group">
diff --git a/addons/skin.estuary/xml/DialogFullScreenInfo.xml b/addons/skin.estuary/xml/DialogFullScreenInfo.xml
index bfe6d61c65..7a0bed7d15 100644
--- a/addons/skin.estuary/xml/DialogFullScreenInfo.xml
+++ b/addons/skin.estuary/xml/DialogFullScreenInfo.xml
@@ -1,3 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<window>
+ <defaultcontrol always="true">999</defaultcontrol>
+ <controls>
+ <control type="button" id="999">
+ <left>0</left>
+ <top>0</top>
+ <width>100%</width>
+ <height>100%</height>
+ <texturefocus />
+ <texturenofocus />
+ <onright>StepForward</onright>
+ <onleft>StepBack</onleft>
+ <onup>ChapterOrBigStepForward</onup>
+ <ondown>ChapterOrBigStepBack</ondown>
+ <onclick>OSD</onclick>
+ </control>
+ </controls>
</window>
diff --git a/addons/skin.estuary/xml/DialogVideoInfo.xml b/addons/skin.estuary/xml/DialogVideoInfo.xml
index 7000a062bf..df2389923f 100644
--- a/addons/skin.estuary/xml/DialogVideoInfo.xml
+++ b/addons/skin.estuary/xml/DialogVideoInfo.xml
@@ -411,14 +411,15 @@
</control>
</control>
</focusedlayout>
- <itemlayout height="317" width="245" condition="Container.Content(Sets)">
+ <itemlayout height="317" width="210" condition="Container.Content(Sets)">
<control type="group">
+ <left>-10</left>
<top>10</top>
<control type="image">
<top>0</top>
- <width>264</width>
+ <width>224</width>
<height>317</height>
- <texture>DefaultActorSolid.png</texture>
+ <texture>DefaultMovies.png</texture>
<aspectratio aligny="center">scale</aspectratio>
<bordertexture border="21">overlays/shadow.png</bordertexture>
<bordersize>20</bordersize>
@@ -426,40 +427,41 @@
<control type="image">
<top>20</top>
<left>20</left>
- <width>224</width>
+ <width>204</width>
<height>277</height>
- <texture background="true">$INFO[ListItem.Thumb]</texture>
- <aspectratio aligny="center">scale</aspectratio>
+ <texture background="true">$INFO[ListItem.Art(poster)]</texture>
+ <aspectratio aligny="center">keep</aspectratio>
</control>
<control type="image">
- <left>20</left>
- <width>224</width>
+ <left>30</left>
+ <width>184</width>
<height>180</height>
<bottom>10</bottom>
<texture>overlays/overlayfade.png</texture>
<animation effect="fade" start="100" end="80" time="0" condition="true">Conditional</animation>
</control>
<control type="textbox">
- <left>25</left>
- <width>214</width>
+ <left>30</left>
+ <width>182</width>
<height>84</height>
<top>210</top>
<align>center</align>
<aligny>center</aligny>
<font>font23_narrow</font>
<label>$INFO[ListItem.Label]</label>
+ <visible>String.IsEmpty(ListItem.Art(poster))</visible>
</control>
</control>
</itemlayout>
- <focusedlayout height="317" width="245" condition="Container.Content(Sets)">
+ <focusedlayout height="317" width="210" condition="Container.Content(Sets)">
<control type="group">
- <left>0</left>
+ <left>-10</left>
<top>10</top>
<control type="image">
<top>0</top>
- <width>264</width>
+ <width>224</width>
<height>317</height>
- <texture>DefaultActorSolid.png</texture>
+ <texture>DefaultMovies.png</texture>
<aspectratio aligny="center">scale</aspectratio>
<bordertexture border="21">overlays/shadow.png</bordertexture>
<bordersize>20</bordersize>
@@ -467,33 +469,34 @@
<control type="image">
<top>20</top>
<left>20</left>
- <width>224</width>
+ <width>204</width>
<height>277</height>
- <texture background="true">$INFO[ListItem.Thumb]</texture>
- <aspectratio aligny="center">scale</aspectratio>
+ <texture background="true">$INFO[ListItem.Art(poster)]</texture>
+ <aspectratio aligny="center">keep</aspectratio>
</control>
<control type="image">
- <left>20</left>
- <width>224</width>
+ <left>30</left>
+ <width>184</width>
<height>180</height>
<bottom>10</bottom>
<texture>overlays/overlayfade.png</texture>
<animation effect="fade" start="100" end="80" time="0" condition="true">Conditional</animation>
</control>
<control type="textbox">
- <left>25</left>
- <width>214</width>
+ <left>30</left>
+ <width>182</width>
<height>84</height>
<top>210</top>
<align>center</align>
<aligny>center</aligny>
<font>font23_narrow</font>
<label>$INFO[ListItem.Label]</label>
+ <visible>String.IsEmpty(ListItem.Art(poster)</visible>
</control>
<control type="image">
- <left>16</left>
+ <left>27</left>
<top>16</top>
- <width>232</width>
+ <width>190</width>
<height>285</height>
<texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture>
</control>
diff --git a/addons/skin.estuary/xml/Font.xml b/addons/skin.estuary/xml/Font.xml
index 742699e9d4..347807d5d8 100644
--- a/addons/skin.estuary/xml/Font.xml
+++ b/addons/skin.estuary/xml/Font.xml
@@ -79,54 +79,64 @@
<!-- Title Fonts -->
<font>
<name>font_flag</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>18</size>
+ <style>bold</style>
</font>
<font>
<name>font20_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>20</size>
+ <style>bold</style>
</font>
<font>
<name>font25_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>25</size>
+ <style>bold</style>
</font>
<font>
<name>font30_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>30</size>
+ <style>bold</style>
</font>
<font>
<name>font32_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>32</size>
+ <style>bold</style>
</font>
<font>
<name>font36_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>36</size>
+ <style>bold</style>
</font>
<font>
<name>font45_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>45</size>
+ <style>bold</style>
</font>
<font>
<name>font52_title</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>52</size>
+ <style>bold</style>
</font>
<font>
<name>font_MainMenu</name>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>60</size>
+ <style>bold</style>
</font>
<font>
<name>WeatherTemp</name>
<aspect>0.85</aspect>
- <filename>NotoSans-Bold.ttf</filename>
+ <filename>NotoSans-Regular.ttf</filename>
<size>120</size>
+ <style>bold</style>
</font>
<font>
<name>Mono26</name>
diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml
index 50d18775db..9b58087a5e 100644
--- a/addons/skin.estuary/xml/Includes.xml
+++ b/addons/skin.estuary/xml/Includes.xml
@@ -1356,6 +1356,49 @@
<textureradiooffnofocus colordiffuse="grey">icons/back.png</textureradiooffnofocus>
</control>
</include>
+ <include name="TouchBackOSDButton">
+ <control type="radiobutton" id="799">
+ <left>-10</left>
+ <top>-10</top>
+ <width>120</width>
+ <height>120</height>
+ <align>center</align>
+ <aligny>center</aligny>
+ <onclick>Dialog.Close(all,true)</onclick>
+ <onclick>Action(FullScreen)</onclick>
+ <texturefocus colordiffuse="button_focus">buttons/roundbutton-fo.png</texturefocus>
+ <texturenofocus />
+ <radioposx>30</radioposx>
+ <radiowidth>60</radiowidth>
+ <radioheight>60</radioheight>
+ <textureradioonfocus>icons/back.png</textureradioonfocus>
+ <textureradioonnofocus colordiffuse="grey">icons/back.png</textureradioonnofocus>
+ <textureradioofffocus>icons/back.png</textureradioofffocus>
+ <textureradiooffnofocus colordiffuse="grey">icons/back.png</textureradiooffnofocus>
+ <include>Animation_TopSlide</include>
+ </control>
+ </include>
+ <include name="TouchBackSlideshowButton">
+ <control type="radiobutton" id="13">
+ <left>-10</left>
+ <top>-10</top>
+ <width>120</width>
+ <height>120</height>
+ <align>center</align>
+ <aligny>center</aligny>
+ <onfocus>Action(Back)</onfocus>
+ <texturefocus colordiffuse="button_focus">buttons/roundbutton-fo.png</texturefocus>
+ <texturenofocus />
+ <radioposx>30</radioposx>
+ <radiowidth>60</radiowidth>
+ <radioheight>60</radioheight>
+ <textureradioonfocus>icons/back.png</textureradioonfocus>
+ <textureradioonnofocus colordiffuse="grey">icons/back.png</textureradioonnofocus>
+ <textureradioofffocus>icons/back.png</textureradioofffocus>
+ <textureradiooffnofocus colordiffuse="grey">icons/back.png</textureradiooffnofocus>
+ <visible>!Window.IsVisible(PictureInfo)</visible>
+ </control>
+ </include>
<include name="SettingsPanel">
<itemlayout height="260" width="400">
<control type="image">
diff --git a/addons/skin.estuary/xml/Includes_Home.xml b/addons/skin.estuary/xml/Includes_Home.xml
index d3c24b2b81..e64ce81767 100644
--- a/addons/skin.estuary/xml/Includes_Home.xml
+++ b/addons/skin.estuary/xml/Includes_Home.xml
@@ -751,8 +751,29 @@
<item>
<icon>resource://resource.images.weathericons.default/na.png</icon>
<onclick>noop</onclick>
- <visible>String.IsEmpty(Window(weather).Property(Daily.IsFetched))</visible>
+ <visible>!Weather.IsFetched</visible>
</item>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="0" />
+ </include>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="1" />
+ </include>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="2" />
+ </include>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="3" />
+ </include>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="4" />
+ </include>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="5" />
+ </include>
+ <include content="WeatherDayItem">
+ <param name="item_index" value="6" />
+ </include>
<include content="WeatherDailyItem">
<param name="item_index" value="1" />
</include>
@@ -823,7 +844,22 @@
<property name="FanartCode">$INFO[Window(weather).Property(Daily.$PARAM[item_index].FanartCode)]</property>
<thumb>resource://resource.images.weathericons.default/$INFO[Window(weather).Property(Daily.$PARAM[item_index].OutlookIcon)]</thumb>
<onclick>noop</onclick>
- <visible>!String.IsEmpty(Window(weather).Property(Daily.$PARAM[item_index].Outlook))</visible>
+ <visible>!String.IsEmpty(Window(weather).Property(Daily.IsFetched)) + !String.IsEmpty(Window(weather).Property(Daily.$PARAM[item_index].Outlook))</visible>
+ </item>
+ </include>
+ <include name="WeatherDayItem">
+ <item>
+ <label>$INFO[Window(weather).Property(Day$PARAM[item_index].Title)]</label>
+ <label2>[COLOR blue]$INFO[Window(weather).Property(Day$PARAM[item_index].LowTemp)]$INFO[System.TemperatureUnits][/COLOR] ∙ [COLOR red]$INFO[Window(weather).Property(Day$PARAM[item_index].HighTemp)]$INFO[System.TemperatureUnits][/COLOR]</label2>
+ <property name="LongDay"></property>
+ <property name="TempDay"></property>
+ <property name="Cloudiness"></property>
+ <property name="Outlook"></property>
+ <property name="ShortDate"></property>
+ <property name="FanartCode">$INFO[Window(weather).Property(Day$PARAM[item_index].FanartCode)]</property>
+ <thumb>$INFO[Window(weather).Property(Day$PARAM[item_index].OutlookIcon)]</thumb>
+ <onclick>noop</onclick>
+ <visible>String.IsEmpty(Window(weather).Property(Daily.IsFetched))!String.IsEmpty(Window(weather).Property(Day$PARAM[item_index].Outlook))</visible>
</item>
</include>
<include name="WeatherMapItem">
@@ -832,6 +868,14 @@
<visible>Weather.IsFetched + !String.IsEmpty(Window(weather).Property(Map.$PARAM[item_id].Area)) + !String.IsEmpty(Window(weather).Property(Map.IsFetched))</visible>
<centerleft>50%</centerleft>
<width>1920</width>
+ <control type="button" id="700$PARAM[item_id]0">
+ <left>50</left>
+ <top>100</top>
+ <width>1820</width>
+ <height>920</height>
+ <onup>$PARAM[onup_id]</onup>
+ <ondown>$PARAM[ondown_id]</ondown>
+ </control>
<control type="image" id="700$PARAM[item_id]1">
<left>50</left>
<top>100</top>
@@ -859,6 +903,13 @@
<colordiffuse>B0FFFFFF</colordiffuse>
</control>
<control type="image" id="700$PARAM[item_id]4">
+ <left>1000</left>
+ <top>858</top>
+ <width>340</width>
+ <height>100</height>
+ <texture border="21">buttons/button-nofo.png</texture>
+ </control>
+ <control type="image" id="700$PARAM[item_id]5">
<left>1340</left>
<top>880</top>
<width>350</width>
@@ -866,20 +917,15 @@
<texture>$INFO[Window(weather).Property(Map.$PARAM[item_id].Legend)]</texture>
</control>
</control>
- <control type="button" id="700$PARAM[item_id]0">
+ <control type="label" id="700$PARAM[item_id]9">
<left>1000</left>
<top>0</top>
<width>340</width>
<height>100</height>
<align>center</align>
<aligny>center</aligny>
- <textoffsetx>40</textoffsetx>
- <textoffsety>0</textoffsety>
- <texturenofocus border="21">buttons/button-nofo.png</texturenofocus>
- <animation effect="slide" end="0,-90" time="0" condition="true">Conditional</animation>
- <texturefocus border="21" colordiffuse="button_focus">buttons/button-fo.png</texturefocus>
+ <animation effect="slide" end="0,-92" time="0" condition="true">Conditional</animation>
<font>font30_title</font>
- <onclick>noop</onclick>
<label>$INFO[Window(weather).Property(Map.$PARAM[item_id].Heading)]</label>
<visible>Weather.IsFetched + !String.IsEmpty(Window(weather).Property(Map.$PARAM[item_id].Area)) + !String.IsEmpty(Window(weather).Property(Map.IsFetched))</visible>
</control>
diff --git a/addons/skin.estuary/xml/MusicOSD.xml b/addons/skin.estuary/xml/MusicOSD.xml
index 8f32b08625..6e025a2f68 100644
--- a/addons/skin.estuary/xml/MusicOSD.xml
+++ b/addons/skin.estuary/xml/MusicOSD.xml
@@ -2,7 +2,6 @@
<window>
<onload condition="!Player.PauseEnabled">SetFocus(603)</onload>
<defaultcontrol always="true">602</defaultcontrol>
- <include>Animation_BottomSlide</include>
<depth>DepthOSD</depth>
<controls>
<control type="button">
@@ -15,9 +14,12 @@
<texturenofocus />
<onclick>Action(close)</onclick>
</control>
+ <include condition="Skin.HasSetting(touchmode)">TouchBackOSDButton</include>
<control type="group" id="200">
+ <include>Animation_BottomSlide</include>
<bottom>0</bottom>
<height>120</height>
+ <include>Animation_BottomSlide</include>
<visible>!Window.IsActive(osdaudiosettings) + !Window.IsActive(osdvideosettings) + !Window.IsActive(playerprocessinfo)</visible>
<animation type="Visible" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
@@ -230,8 +232,20 @@
<control type="group">
<bottom>0</bottom>
<height>120</height>
- <animation effect="fade" start="0" end="100" time="250">WindowOpen</animation>
- <animation effect="fade" start="100" end="0" time="250">WindowClose</animation>
+ <animation type="WindowOpen" condition="!Player.ShowInfo" reversible="False">
+ <effect type="fade" start="0" end="100" time="300"/>
+ <effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" />
+ </animation>
+ <animation type="WindowClose" condition="!Player.ShowInfo" reversible="False">
+ <effect type="fade" start="100" end="0" time="300"/>
+ <effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" />
+ </animation>
+ <animation type="WindowOpen" condition="Player.ShowInfo" reversible="False">
+ <effect type="fade" start="0" end="100" time="300"/>
+ </animation>
+ <animation type="WindowClose" condition="Player.ShowInfo" reversible="False">
+ <effect type="fade" start="100" end="0" time="300"/>
+ </animation>
<control type="button" id="87">
<include>HiddenObject</include>
<onup condition="Player.Forwarding | Player.Rewinding">PlayerControl(Play)</onup>
@@ -254,6 +268,8 @@
<textureslidernib>osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>Player.Progress</info>
+ <onup>201</onup>
+ <ondown>200</ondown>
<visible>Player.SeekEnabled + !Control.HasFocus(87) + !MusicPlayer.Content(LiveTV)</visible>
<action>seek</action>
</control>
@@ -268,6 +284,8 @@
<textureslidernib colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>Player.Progress</info>
+ <onup>201</onup>
+ <ondown>200</ondown>
<visible>Player.SeekEnabled + Control.HasFocus(87) + !MusicPlayer.Content(LiveTV)</visible>
<action>seek</action>
</control>
@@ -282,6 +300,8 @@
<textureslidernib>osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>PVR.TimeshiftProgressPlayPos</info>
+ <onup>201</onup>
+ <ondown>200</ondown>
<action>pvr.seek</action>
<visible>Player.SeekEnabled + !Control.HasFocus(87) + MusicPlayer.Content(LiveTV)</visible>
</control>
@@ -296,6 +316,8 @@
<textureslidernib colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>PVR.TimeshiftProgressPlayPos</info>
+ <onup>201</onup>
+ <ondown>200</ondown>
<action>pvr.seek</action>
<visible>Player.SeekEnabled + Control.HasFocus(87) + MusicPlayer.Content(LiveTV)</visible>
</control>
diff --git a/addons/skin.estuary/xml/MyWeather.xml b/addons/skin.estuary/xml/MyWeather.xml
index 404e922211..5bde1c22dc 100644
--- a/addons/skin.estuary/xml/MyWeather.xml
+++ b/addons/skin.estuary/xml/MyWeather.xml
@@ -174,7 +174,7 @@
</include>
<include content="WeatherMapItem">
<param name="item_id" value="1" />
- <param name="onup_id" value="15100" />
+ <param name="onup_id" value="15200" />
<param name="ondown_id" value="70020" />
</include>
<include content="WeatherMapItem">
diff --git a/addons/skin.estuary/xml/SlideShow.xml b/addons/skin.estuary/xml/SlideShow.xml
index 6cc32f41cd..d002709d5c 100644
--- a/addons/skin.estuary/xml/SlideShow.xml
+++ b/addons/skin.estuary/xml/SlideShow.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<window>
<controls>
+ <include condition="Skin.HasSetting(touchmode)">TouchBackSlideshowButton</include>
<control type="image">
<centerleft>50%</centerleft>
<centertop>50%</centertop>
diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml
index 748282ecd0..47ceb87b2b 100644
--- a/addons/skin.estuary/xml/Variables.xml
+++ b/addons/skin.estuary/xml/Variables.xml
@@ -208,7 +208,7 @@
</variable>
<variable name="SeekLabel">
<value condition="!String.IsEmpty(Player.SeekStepSize)">[COLOR button_focus]$LOCALIZE[773][/COLOR] $INFO[Player.SeekStepSize]</value>
- <value condition="!String.IsEmpty(Player.SeekOffset)">[COLOR button_focus]$LOCALIZE[773][/COLOR] $INFO[Player.SeekOffset]</value>
+ <value condition="!String.IsEmpty(Player.SeekOffset) + Player.DisplayAfterSeek">[COLOR button_focus]$LOCALIZE[773][/COLOR] $INFO[Player.SeekOffset]</value>
<value condition="Player.Paused">$LOCALIZE[112]</value>
<value condition="Player.Forwarding">$LOCALIZE[31039] $VAR[VideoPlayerForwardRewindVar]</value>
<value condition="Player.Rewinding">$LOCALIZE[31038] $VAR[VideoPlayerForwardRewindVar]</value>
@@ -307,7 +307,6 @@
<variable name="NowPlayingBreadcrumbsVar">
<value condition="MusicPlayer.Content(livetv) + Player.HasAudio">$INFO[MusicPlayer.Title]</value>
<value condition="VideoPlayer.Content(livetv)">$INFO[VideoPlayer.Title]</value>
- <value condition="VideoPlayer.Content(episodes) + !String.IsEmpty(Player.Art(tvshow.clearlogo))">$INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title]</value>
<value condition="VideoPlayer.Content(episodes) + Window.IsActive(fullscreenvideo)">$INFO[VideoPlayer.TVShowTitle]$INFO[VideoPlayer.Year, (,)]</value>
<value condition="!VideoPlayer.Content(episodes) + Window.IsActive(fullscreenvideo)">$INFO[VideoPlayer.Title]$INFO[VideoPlayer.Year, (,)]</value>
<value condition="MusicPartyMode.Enabled">$LOCALIZE[589]</value>
@@ -316,10 +315,10 @@
<variable name="OSDSubLabelVar">
<value condition="Window.IsActive(visualisation) + Integer.IsGreater(Playlist.Length(music),1) + Integer.IsGreater(Playlist.Position(music),0)">$LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length]</value>
<value condition="VideoPlayer.Content(musicvideos)">$INFO[VideoPlayer.Artist]$INFO[VideoPlayer.Album, - ]</value>
- <value condition="VideoPlayer.Content(episodes) + !player.chaptercount">$INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E,: ]$INFO[VideoPlayer.Title]</value>
+ <value condition="VideoPlayer.Content(episodes) + !player.chaptercount">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E,: ]$INFO[VideoPlayer.Title]</value>
<value condition="VideoPlayer.Content(episodes) + player.chaptercount">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.Title,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
- <value condition="player.chaptercount + [!VideoPlayer.Content(episodes) + !VideoPlayer.Content(LiveTV)]">$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
<value condition="VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag">$INFO[VideoPlayer.ChannelNumberLabel,([COLOR button_focus],[/COLOR]) ]$INFO[VideoPlayer.ChannelName] $INFO[VideoPlayer.EpisodeName, - ]</value>
+ <value condition="player.chaptercount + [!VideoPlayer.Content(episodes) + !VideoPlayer.Content(LiveTV)]">$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
<value>$INFO[VideoPlayer.Genre]</value>
</variable>
<variable name="PlayerClearLogoVar">
diff --git a/addons/skin.estuary/xml/VideoOSD.xml b/addons/skin.estuary/xml/VideoOSD.xml
index c024a5c1aa..3013ae654d 100644
--- a/addons/skin.estuary/xml/VideoOSD.xml
+++ b/addons/skin.estuary/xml/VideoOSD.xml
@@ -2,7 +2,6 @@
<window>
<onload condition="!Player.PauseEnabled">SetFocus(603)</onload>
<defaultcontrol always="true">602</defaultcontrol>
- <include>Animation_BottomSlide</include>
<depth>DepthOSD</depth>
<controls>
<control type="button">
@@ -15,7 +14,9 @@
<texturenofocus />
<onclick>Action(close)</onclick>
</control>
+ <include condition="Skin.HasSetting(touchmode)">TouchBackOSDButton</include>
<control type="group">
+ <include>Animation_BottomSlide</include>
<bottom>0</bottom>
<height>180</height>
<visible>![Window.IsVisible(SliderDialog) | Window.IsVisible(osdaudiosettings) | Window.IsVisible(osdvideosettings) | Window.IsVisible(VideoBookmarks) | Window.IsVisible(playerprocessinfo) | Window.IsVisible(osdcmssettings) | Window.IsVisible(PVROSDChannels) | Window.IsVisible(pvrchannelguide)]</visible>
@@ -30,8 +31,10 @@
<height>50</height>
<label>$VAR[VideoOSDHelpTextVar]</label>
<visible>!Player.ShowInfo</visible>
+ <include>Animation_BottomSlide</include>
</control>
<control type="group" id="200">
+ <include>Animation_BottomSlide</include>
<control type="grouplist" id="201">
<left>20</left>
<top>90</top>
@@ -191,6 +194,20 @@
</control>
<control type="group" id="6000">
<top>60</top>
+ <animation type="WindowOpen" condition="!Window.IsVisible(fullscreeninfo)" reversible="False">
+ <effect type="fade" start="0" end="100" time="300"/>
+ <effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" />
+ </animation>
+ <animation type="WindowClose" condition="!Window.IsVisible(fullscreeninfo)" reversible="False">
+ <effect type="fade" start="100" end="0" time="300"/>
+ <effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" />
+ </animation>
+ <animation type="WindowOpen" condition="Window.IsVisible(fullscreeninfo)" reversible="False">
+ <effect type="fade" start="0" end="100" time="300"/>
+ </animation>
+ <animation type="WindowClose" condition="Window.IsVisible(fullscreeninfo)" reversible="False">
+ <effect type="fade" start="100" end="0" time="300"/>
+ </animation>
<visible>Player.SeekEnabled</visible>
<control type="button" id="87">
<include>HiddenObject</include>
@@ -210,6 +227,8 @@
<textureslidernib>osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>Player.Progress</info>
+ <onup>200</onup>
+ <ondown>200</ondown>
<action>seek</action>
<visible>!Control.HasFocus(87) + !VideoPlayer.Content(LiveTV)</visible>
</control>
@@ -222,6 +241,8 @@
<textureslidernib colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>Player.Progress</info>
+ <onup>200</onup>
+ <ondown>200</ondown>
<action>seek</action>
<visible>Control.HasFocus(87) + !VideoPlayer.Content(LiveTV)</visible>
</control>
@@ -234,6 +255,8 @@
<textureslidernib>osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>PVR.TimeshiftProgressPlayPos</info>
+ <onup>200</onup>
+ <ondown>200</ondown>
<action>pvr.seek</action>
<visible>!Control.HasFocus(87) + VideoPlayer.Content(LiveTV)</visible>
</control>
@@ -246,6 +269,8 @@
<textureslidernib colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernib>
<textureslidernibfocus colordiffuse="button_focus">osd/progress/nub_leftright.png</textureslidernibfocus>
<info>PVR.TimeshiftProgressPlayPos</info>
+ <onup>200</onup>
+ <ondown>200</ondown>
<action>pvr.seek</action>
<visible>Control.HasFocus(87) + VideoPlayer.Content(LiveTV)</visible>
</control>
diff --git a/addons/xbmc.gui/addon.xml b/addons/xbmc.gui/addon.xml
index 3169190806..261166970a 100644
--- a/addons/xbmc.gui/addon.xml
+++ b/addons/xbmc.gui/addon.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon id="xbmc.gui" version="5.15.0" provider-name="Team Kodi">
- <backwards-compatibility abi="5.14.0"/>
+ <backwards-compatibility abi="5.15.0"/>
<requires>
<import addon="xbmc.core" version="0.1.0"/>
</requires>
diff --git a/cmake/installdata/common/addons.txt b/cmake/installdata/common/addons.txt
index 13dbab39ec..897420faa7 100644
--- a/cmake/installdata/common/addons.txt
+++ b/cmake/installdata/common/addons.txt
@@ -39,6 +39,8 @@ addons/metadata.common.imdb.com/*
addons/metadata.common.musicbrainz.org/*
addons/metadata.common.theaudiodb.com/*
addons/metadata.common.themoviedb.org/*
+addons/metadata.generic.albums/*
+addons/metadata.generic.artists/*
addons/metadata.themoviedb.org/*
addons/metadata.tvshows.themoviedb.org/*
addons/kodi.vfs/*
diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake
index 7597ba76e2..81d26979e3 100644
--- a/cmake/modules/FindFFMPEG.cmake
+++ b/cmake/modules/FindFFMPEG.cmake
@@ -237,7 +237,8 @@ if(NOT FFMPEG_FOUND)
-DCCACHE_PROGRAM=${CCACHE_PROGRAM}
-DENABLE_VAAPI=${ENABLE_VAAPI}
-DENABLE_VDPAU=${ENABLE_VDPAU}
- -DENABLE_DAV1D=${DAV1D_FOUND})
+ -DENABLE_DAV1D=${DAV1D_FOUND}
+ -DEXTRA_FLAGS=${FFMPEG_EXTRA_FLAGS})
if(KODI_DEPENDSBUILD)
set(CROSS_ARGS -DDEPENDS_PATH=${DEPENDS_PATH}
diff --git a/docs/CODE_GUIDELINES.md b/docs/CODE_GUIDELINES.md
index 85e6495e2d..6ea0b0cb2a 100644
--- a/docs/CODE_GUIDELINES.md
+++ b/docs/CODE_GUIDELINES.md
@@ -244,7 +244,7 @@ void Test();
void Test(void);
```
-### 3.7. Exceptions to the Formating Rules For Beter Readability
+### 3.7. Exceptions to the Formatting Rules For Better Readability
There are some special situations where vertical alignment and longer lines does greatly aid readability, for example the initialization of some table-like multiple row structures. In these **rare** cases exceptions can be made to the formatting rules on vertical alignment, and the defined line length can be exceeded.
The layout can be protected from being reformatted when `clang-format` is applied by adding `// clang-format off` and `// clang-format on` statements either side of the lines of code.
diff --git a/docs/MANIFESTO.md b/docs/MANIFESTO.md
new file mode 100644
index 0000000000..1b7dae9ba3
--- /dev/null
+++ b/docs/MANIFESTO.md
@@ -0,0 +1,36 @@
+Team Kodi Manifesto
+===================
+
+The Kodi manifesto is Team-Kodi's public declaration of the Kodi project members principles, philosophy and intentions. This manifesto tries to outline the goals we aim and hope to achieve with Kodi and sum up the Kodi project's strategic direction vision for the present and the future.
+
+In one sentence, the vision of Team-Kodi is to create the best cross-platform media center software there is.
+
+User-friendliness is next to godlyness
+--------------------------------------
+
+One major ongoing goal of Team-Kodi has always been to make Kodi and its user interface feel even more intuitive and user-friendly for its end-users, based on the **KISS** (**K**eep **I**t **S**imple, **S**tupid) principle of simplicity. It is our belief that usability is the most important aspect of a media center like Kodi. Many other media center projects make user interface decisions by developers, who often have little experience in user interface design. In contrast, Team-Kodi does its best to listen to Kodi's end-users to learn how Kodi is actually being used and how we can improve the user experience. We also aim to do regular overhauls, improving existing features/functions, and scrapping outdated code and features/functions (as "too much stuff" adds unnecessary complexity and can thus also be a bad thing). Everything should be made as simple as possible, but no simpler.
+
+Kodi as a whole must...
+
+* First and foremost be aimed at a large-screen (28" or more) [10-foot user interface](https://kodi.wiki/view/10-foot_user_interface) for the living-room experience.
+ * Large menus, text/fonts and buttons that is designed to be navigated by a hand-held remote-control.
+* Be focused around the main features of playing music, watching movies, recorded television broadcasts, and viewing pictures.
+ * Kodi may be capable of converging other things but those things should never take over the main focus in the interface.
+* Be easy to install, set up and maintain (so that our valuable end-users do not get fed up with it and quit).
+* Have an user interface that is simple and intuitive enough so that less tech-savvy people are not intimidated by it.
+ * Make common usage easy, simple 'Human–Computer Interaction (HCI)', from the viewpoint of an ordinary user.
+* Be able to play audio and video files that have been encoded using DivX, XviD, etc. directly out-of-the-box.
+* Be able to organize audio and video files in an easy and user-friendly way.
+* Use standards and be consistent, (the Music section can for example not use completely different controls from the Video section).
+* Perform actions in the GUI with as few 'clicks' as possible.
+* Be aimed at an international audience, internationalization and localization by supporting different languages, timezones and other regional differences
+* Require little to no non-GUI configuration (and all such non-GUI configuration should be done in just one file: [advancedsettings.xml](https://kodi.wiki/view/Advancedsettings.xml)).
+* Be beautiful to look at, after all we hope you will be using it a lot!
+
+1.2 Team-Kodi members should always strive to
+
+* **Promote open source** - Kodi is based on the ideas of FOSS (free open source software), licensed under the GPL and builds partly on other open source projects which we do our best to support. The GPL should be respected at all times. All code should be committed to the Kodi project’s [git repo](https://github.com/xbmc/xbmc) before any public binaries are released.
+* **Promote the sharing of knowledge and collaboration** - Through the use of information sharing tools and practices Kodi is a collaborative environment.
+* **Understand that development is a team effort** - Treating our users as co-developers has proven to be the most effective option for rapid development. Always strive to work as a team at all times. Actively promote discussion on new features and bug fixes, and respect others comments and criticisms with replies in a timely fashion.
+* **Apply the Law of Diminishing Return** - The majority of the effort should be invested in implementing features which have the most benefit and widest general usage by the community.
+* **Try to make all code, feature, and functions to be platform agnostic** - Kodi is a multi-platform software, thus any single platform specific features should be discussed with other team members before implemented. Major features should be developed in a separate branch or committed in small increments so that other members have the opportunity to review the code and comment on it during development.
diff --git a/docs/README.RaspberryPi.md b/docs/README.RaspberryPi.md
index 3c13cdb62e..4d8ce4f0e6 100644
--- a/docs/README.RaspberryPi.md
+++ b/docs/README.RaspberryPi.md
@@ -12,6 +12,10 @@ If you're looking to build Kodi natively using **[Raspbian](https://www.raspberr
3.1. **[Get Raspberry Pi tools and firmware](#31-get-raspberry-pi-tools-and-firmware)**
4. **[Build tools and dependencies](#4-build-tools-and-dependencies)**
5. **[Build Kodi](#5-build-kodi)**
+6. **[Docker](#6-docker)**
+7. **[Troubleshooting](#7-troubleshooting)**
+ 7.1. **[ImportError: No module named \_sysconfigdata\_nd](#71-importerror-no-module-named-_sysconfigdata_nd)**
+ 7.2. **[Errors connecting to any internet (TLS) service](#72-errors-connecting-to-any-internet-tls-service)**
## 1. Document conventions
This guide assumes you are using `terminal`, also known as `console`, `command-line` or simply `cli`. Commands need to be run at the terminal, one at a time and in the provided order.
@@ -142,3 +146,97 @@ After the build process is finished, you can find the files ready to be installe
**[back to top](#table-of-contents)**
+## 6. Docker
+
+If you encounter issues with the previous instructions, or if you don't have a proper system for cross-compiling Kodi, it's also possible to use a [Docker](https://www.docker.com/) image to perform the build. This method, although it should work just like the build instructions mentioned above, is **not** supported. Therefore, issues related specifically to Docker should **not** be opened.
+
+Here is an example Dockerfile, summarizing basically all the instructions described above (/!\ may not be up to date with the actual instructions!). **Please read the comments as they describe things you NEED to change and/or consider before building.**
+
+```Dockerfile
+# Change 'latest' to the officially supported version of Ubuntu for cross-compilation
+FROM ubuntu:latest
+
+RUN apt-get update && apt-get upgrade -y
+RUN apt-get -y install autoconf bison build-essential curl default-jdk gawk git gperf libcurl4-openssl-dev zlib1g-dev file
+
+# The 'HOME' variable doesn't really matter - it is only the location of the files within the image
+ARG HOME=/home/pi
+# This is the location kodi will be built for, that means you will have to put the built files in
+# this directory afterwards. It is important because many paths end up hardcoded during the build.
+ARG PREFIX=/opt/kodi
+
+RUN mkdir $PREFIX
+WORKDIR $HOME
+
+# Replace 'master' with whichever branch/tag you wish to build - be careful with nightly builds!
+RUN git clone -b master https://github.com/xbmc/xbmc kodi --depth 1
+RUN git clone https://github.com/raspberrypi/tools --depth=1
+RUN git clone https://github.com/raspberrypi/firmware --depth=1
+
+WORKDIR $HOME/kodi/tools/depends
+RUN ./bootstrap
+
+# Change this if you're building on a RPi1, as described above
+RUN ./configure --host=arm-linux-gnueabihf --prefix=$PREFIX --with-toolchain=$HOME/tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf --with-firmware=$HOME/firmware --with-platform=raspberry-pi2 --disable-debug
+
+RUN make -j$(getconf _NPROCESSORS_ONLN)
+
+WORKDIR $HOME/kodi
+# This step builds all the binary addons.
+# Kodi - at its core - works fine without them, however they are used by many other addons.
+# Therefore, it is recommended to simply compile all of them.
+RUN make -j$(getconf _NPROCESSORS_ONLN) -C tools/depends/target/binary-addons
+RUN make -C tools/depends/target/cmakebuildsys
+
+WORKDIR $HOME/kodi/build
+RUN make -j$(getconf _NPROCESSORS_ONLN)
+
+RUN make install
+RUN tar zfc /kodi.tar.gz $PREFIX
+```
+
+You can then build the image, and afterwards retrieve the build files from a dummy container:
+
+```bash
+docker build -t kodi_build .
+docker run --name some-temp-container-name kodi_build /bin/bash
+docker cp some-temp-container-name:/kodi.tar.gz ./
+docker rm some-temp-container-name
+```
+
+You should now have a file `kodi.tar.gz` in your current directory. Now you need to uncompress this file in the `$PREFIX` directory (as mentioned in the Dockerfile) of your Raspberry. Note that the archive contains multiple directories in its root, but only the `raspberry-pi2-release` (or `raspberry-pi-release`) is needed, so you can delete the others safely. If you encounter problems, please take a look at the [Troubleshooting](#7-troubleshooting) section below before filing an issue.
+
+**[back to top](#table-of-contents)**
+
+## 7. Troubleshooting
+
+### 7.1 ImportError: No module named \_sysconfigdata\_nd
+
+This is caused by an issue with a python package. The solution is to simply add a missing symlink so the library can be found, i.e.:
+
+```bash
+ln -s /usr/lib/python2.7/plat-arm-linux-gnueabihf/_sysconfigdata_nd.py /usr/lib/python2.7/
+```
+
+### 7.2 Errors connecting to any internet (TLS) service
+
+First, you should enable debug logging (instructions [here](https://kodi.wiki/view/Log_file)). Then you need to check the logs and find what the source of your problem is. If, when trying to access TLS services (e.g. when installing an addon), the connection fails and your log contains entries such as:
+
+```log
+# note that those logs appear when enabling component-specific logs -> libcurl
+2019-05-19 17:18:39.570 T:1854288832 DEBUG: Curl::Debug - TEXT: SSL certificate problem: unable to get local issuer certificate
+2019-05-19 17:18:39.570 T:1854288832 DEBUG: Curl::Debug - TEXT: Closing connection 0
+
+# this is part of the regular Kodi logs
+2019-05-19 17:18:39.570 T:1854288832 ERROR: CCurlFile::FillBuffer - Failed: Peer certificate cannot be authenticated with given CA certificates(60)
+```
+
+Then, you need to define the environment variable `SSL_CERT_FILE` so it points to your system's certificate file. Depending on how you start Kodi, putting this line in your in your `.profile` file should fix this issue:
+
+```bash
+export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
+```
+
+Note that you need to define this variable *before* starting Kodi. For example, if you start Kodi on startup through a crontab, your `.profile` will *not* be sourced.
+
+**[back to top](#table-of-contents)**
diff --git a/project/BuildDependencies/scripts/0_package.target-win10-arm.list b/project/BuildDependencies/scripts/0_package.target-win10-arm.list
index 77e974461f..ef0c736ae7 100644
--- a/project/BuildDependencies/scripts/0_package.target-win10-arm.list
+++ b/project/BuildDependencies/scripts/0_package.target-win10-arm.list
@@ -18,13 +18,13 @@ lcms2-2.9-win10-arm-v141-20200105.7z
libaacs-0.9.0-win10-arm-v141-20200203.7z
libass-0.14.0-win10-arm-v141-20200105.7z
libbdplus-0.1.2-win10-arm-v141-20200105.7z
-libbluray-1.1.2-win10-arm-v141-20200105.7z
+libbluray-1.1.2-win10-arm-v142-20200706.7z
libcdio-2.1.0-win10-arm-v141-20200112.7z
libfribidi-1.0.8-win10-arm-v141-20200105.7z
libiconv-1.16-win10-arm-v141-20200105.7z
libmicrohttpd-0.9.69-win10-arm-v141-20200105.7z
libnfs-4.0.0-win10-arm-v142-20200605.7z
-libudfread-1.0.0-win10-arm-v141-20200411.7z
+libudfread-1.1.0-win10-arm-v142-20200706.7z
libxml2-2.9.9-win10-arm-v141-20200105.7z
libxslt-1.1.34-win10-arm-v141-20200105.7z
lzo2-2.10-win10-arm-v141-20200105.7z
diff --git a/project/BuildDependencies/scripts/0_package.target-win10-win32.list b/project/BuildDependencies/scripts/0_package.target-win10-win32.list
index 60458e0890..96998a095a 100644
--- a/project/BuildDependencies/scripts/0_package.target-win10-win32.list
+++ b/project/BuildDependencies/scripts/0_package.target-win10-win32.list
@@ -18,13 +18,13 @@ lcms2-2.9-win10-win32-v141-20200105.7z
libaacs-0.9.0-win10-win32-v141-20200203.7z
libass-0.14.0-win10-win32-v141-20200105.7z
libbdplus-0.1.2-win10-win32-v141-20200105.7z
-libbluray-1.1.2-win10-win32-v141-20200105.7z
+libbluray-1.1.2-win10-win32-v142-20200706.7z
libcdio-2.1.0-win10-win32-v141-20200112.7z
libfribidi-1.0.8-win10-win32-v141-20200105.7z
libiconv-1.16-win10-win32-v141-20200105.7z
libmicrohttpd-0.9.69-win10-win32-v141-20200105.7z
libnfs-4.0.0-win10-win32-v142-20200605.7z
-libudfread-1.0.0-win10-win32-v141-20200411.7z
+libudfread-1.1.0-win10-win32-v142-20200706.7z
libxml2-2.9.9-win10-win32-v141-20200105.7z
libxslt-1.1.34-win10-win32-v141-20200105.7z
lzo2-2.10-win10-win32-v141-20200105.7z
diff --git a/project/BuildDependencies/scripts/0_package.target-win10-x64.list b/project/BuildDependencies/scripts/0_package.target-win10-x64.list
index 531baa1163..90040af5e1 100644
--- a/project/BuildDependencies/scripts/0_package.target-win10-x64.list
+++ b/project/BuildDependencies/scripts/0_package.target-win10-x64.list
@@ -18,13 +18,13 @@ lcms2-2.9-win10-x64-v141-20200105.7z
libaacs-0.9.0-win10-x64-v141-20200203.7z
libass-0.14.0-win10-x64-v141-20200105.7z
libbdplus-0.1.2-win10-x64-v141-20200105.7z
-libbluray-1.1.2-win10-x64-v141-20200105.7z
+libbluray-1.1.2-win10-x64-v142-20200706.7z
libcdio-2.1.0-win10-x64-v141-20200112.7z
libfribidi-1.0.8-win10-x64-v141-20200105.7z
libiconv-1.16-win10-x64-v141-20200105.7z
libmicrohttpd-0.9.69-win10-x64-v141-20200105.7z
libnfs-4.0.0-win10-x64-v142-20200605.7z
-libudfread-1.0.0-win10-x64-v141-20200411.7z
+libudfread-1.1.0-win10-x64-v142-20200706.7z
libxml2-2.9.9-win10-x64-v141-20200105.7z
libxslt-1.1.34-win10-x64-v141-20200105.7z
lzo2-2.10-win10-x64-v141-20200105.7z
diff --git a/project/BuildDependencies/scripts/0_package.target-win32.list b/project/BuildDependencies/scripts/0_package.target-win32.list
index 55e4cd455f..541a4acfcd 100644
--- a/project/BuildDependencies/scripts/0_package.target-win32.list
+++ b/project/BuildDependencies/scripts/0_package.target-win32.list
@@ -21,7 +21,7 @@ lcms2-2.9-win32-v141-20200105.7z
libaacs-0.9.0-win32-v141-20200203.7z
libass-0.14.0-win32-v141-20200105.7z
libbdplus-0.1.2-win32-v141-20200105.7z
-libbluray-1.1.2-win32-v141-20200105.7z
+libbluray-1.1.2-win32-v142-20200706.7z
libcdio-2.1.0-win32-v141-20200112.7z
libcec-4.0.4-win32-v141-20200105.7z
libfribidi-1.0.8-win32-v141-20200105.7z
@@ -31,7 +31,7 @@ libmicrohttpd-0.9.69-win32-v141-20200105.7z
libnfs-4.0.0-win32-v142-20200605.7z
libplist-2.1.0-win32-v141-20200105.7z
libpng-1.6.37-win32-v141-20200105.7z
-libudfread-1.0.0-win32-v141-20200411.7z
+libudfread-1.1.0-win32-v142-20200706.7z
libxml2-2.9.9-win32-v141-20200105.7z
libxslt-1.1.34-win32-v141-20200105.7z
lzo2-2.10-win32-v141-20200105.7z
diff --git a/project/BuildDependencies/scripts/0_package.target-x64.list b/project/BuildDependencies/scripts/0_package.target-x64.list
index 8f0e2c0bc7..65a7a52b57 100644
--- a/project/BuildDependencies/scripts/0_package.target-x64.list
+++ b/project/BuildDependencies/scripts/0_package.target-x64.list
@@ -20,7 +20,7 @@ lcms2-2.9-x64-v141-20200105.7z
libaacs-0.9.0-x64-v141-20200203.7z
libass-0.14.0-x64-v141-20200105.7z
libbdplus-0.1.2-x64-v141-20200105.7z
-libbluray-1.1.2-x64-v141-20200105.7z
+libbluray-1.1.2-x64-v142-20200706.7z
libcdio-2.1.0-x64-v141-20200112.7z
libcec-4.0.4-x64-v141-20200105.7z
libfribidi-1.0.8-x64-v141-20200105.7z
@@ -28,7 +28,7 @@ libiconv-1.16-x64-v141-20200105.7z
libmicrohttpd-0.9.69-x64-v141-20200105.7z
libnfs-4.0.0-x64-v142-20200605.7z
libplist-2.1.0-x64-v141-20200105.7z
-libudfread-1.0.0-x64-v141-20200411.7z
+libudfread-1.1.0-x64-v142-20200706.7z
libxml2-2.9.9-x64-v141-20200105.7z
libxslt-1.1.34-x64-v141-20200105.7z
lzo2-2.10-x64-v141-20200105.7z
diff --git a/system/addon-manifest.xml b/system/addon-manifest.xml
index abeb5ec60c..40732c8d18 100644
--- a/system/addon-manifest.xml
+++ b/system/addon-manifest.xml
@@ -30,6 +30,8 @@
<addon>metadata.common.musicbrainz.org</addon>
<addon>metadata.common.theaudiodb.com</addon>
<addon>metadata.common.themoviedb.org</addon>
+ <addon>metadata.generic.albums</addon>
+ <addon>metadata.generic.artists</addon>
<addon>metadata.local</addon>
<addon>metadata.themoviedb.org</addon>
<addon>metadata.tvshows.themoviedb.org</addon>
diff --git a/system/keymaps/keyboard.xml b/system/keymaps/keyboard.xml
index b1638067de..5cb986f1b6 100644
--- a/system/keymaps/keyboard.xml
+++ b/system/keymaps/keyboard.xml
@@ -167,6 +167,7 @@
<k mod="ctrl,shift">ReloadKeymaps</k>
<d mod="ctrl,shift">ToggleDebug</d>
<r mod="ctrl,shift">ToggleDirtyRegionVisualization</r>
+ <f11>HDRToggle</f11>
</keyboard>
</global>
<LoginScreen>
diff --git a/system/settings/settings.xml b/system/settings/settings.xml
index 671fe325c9..def3740936 100755
--- a/system/settings/settings.xml
+++ b/system/settings/settings.xml
@@ -1101,7 +1101,7 @@
</setting>
<setting id="musiclibrary.albumsscraper" type="addon" label="20193" help="36257">
<level>1</level>
- <default>metadata.album.universal</default>
+ <default>metadata.generic.albums</default>
<constraints>
<addontype>xbmc.metadata.scraper.albums</addontype>
</constraints>
@@ -1111,7 +1111,7 @@
</setting>
<setting id="musiclibrary.artistsscraper" type="addon" label="20194" help="36258">
<level>1</level>
- <default>metadata.artists.universal</default>
+ <default>metadata.generic.artists</default>
<constraints>
<addontype>xbmc.metadata.scraper.artists</addontype>
</constraints>
diff --git a/tools/EventClients/Clients/PS3BDRemote/ps3_remote.py b/tools/EventClients/Clients/PS3BDRemote/ps3_remote.py
index e902bb8519..5b9d5b3a6b 100755
--- a/tools/EventClients/Clients/PS3BDRemote/ps3_remote.py
+++ b/tools/EventClients/Clients/PS3BDRemote/ps3_remote.py
@@ -155,7 +155,7 @@ def process_keys(remote, xbmc):
return 2
time.sleep(2)
- # some other read exception occured, so raise it
+ # some other read exception occurred, so raise it
raise e
if datalen == 13:
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG
index 5e2d5a2b6e..8ca34392aa 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/CHANGELOG
@@ -70,7 +70,7 @@ v0.10 -- 11 Feb 2008
- Renamed INFO/WARNING/DEBUG macros to WIIUSE_* (by noisehole)
- Updated Makefiles (by noisehole)
- Fixed incorrect roll/pitch when smoothing was enabled
- - Fixed nunchuk and classic controller flooding events when significant changes occured
+ - Fixed nunchuk and classic controller flooding events when significant changes occurred
- Fixed bug where IR was not correct on roll if IR was enabled before handshake
Removed:
@@ -115,7 +115,7 @@ v0.7 -- 19 Oct 2007
Fixed:
- [Windows] Problem where a connection is made to a wiimote that does not exist.
- - [Windows] Issue that occured while using multiple wiimotes.
+ - [Windows] Issue that occurred while using multiple wiimotes.
---------------------------
v0.6 -- 16 Oct 2007
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c
index 378611c7c8..c4699681c8 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example-sdl/sdl.c
@@ -424,7 +424,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
for (; i < MAX_WIIMOTES; ++i) {
switch (wiimotes[i]->event) {
case WIIUSE_EVENT:
- /* a generic event occured */
+ /* a generic event occurred */
handle_event(wiimotes[i]);
break;
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c
index 8536cf683c..d401d80b7a 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/example/example.c
@@ -368,12 +368,12 @@ int main(int argc, char** argv) {
for (; i < MAX_WIIMOTES; ++i) {
switch (wiimotes[i]->event) {
case WIIUSE_EVENT:
- /* a generic event occured */
+ /* a generic event occurred */
handle_event(wiimotes[i]);
break;
case WIIUSE_STATUS:
- /* a status event occured */
+ /* a status event occurred */
handle_ctrl_status(wiimotes[i]);
break;
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c
index 578ee3f08b..04c2ea50bb 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/events.c
@@ -74,7 +74,7 @@ static int state_changed(struct wiimote_t* wm);
* @param wm An array of pointers to wiimote_t structures.
* @param wiimotes The number of wiimote_t structures in the \a wm array.
*
- * @return Returns number of wiimotes that an event has occured on.
+ * @return Returns number of wiimotes that an event has occurred on.
*
* It is necessary to poll the wiimote devices for events
* that occur. If an event occurs on a particular wiimote,
@@ -241,10 +241,10 @@ static void clear_dirty_reads(struct wiimote_t* wm) {
/**
- * @brief Analyze the event that occured on a wiimote.
+ * @brief Analyze the event that occurred on a wiimote.
*
* @param wm An array of pointers to wiimote_t structures.
- * @param event The event that occured.
+ * @param event The event that occurred.
* @param msg The message specified in the event packet.
*
* Pass the event to the registered event callback.
@@ -530,7 +530,7 @@ static void event_status(struct wiimote_t* wm, byte* msg) {
int exp_changed = 0;
/*
- * An event occured.
+ * An event occurred.
* This event can be overwritten by a more specific
* event type during a handshake or expansion removal.
*/
@@ -780,7 +780,7 @@ static void save_state(struct wiimote_t* wm) {
/**
* @brief Determine if the current state differs significantly from the previous.
* @param wm A pointer to a wiimote_t structure.
- * @return 1 if a significant change occured, 0 if not.
+ * @return 1 if a significant change occurred, 0 if not.
*/
static int state_changed(struct wiimote_t* wm) {
#define STATE_CHANGED(a, b) if (a != b) return 1
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c
index 85ddd109cd..6aeeb81dd2 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/io_win.c
@@ -195,7 +195,7 @@ int wiiuse_io_read(struct wiimote_t* wm) {
ResetEvent(wm->hid_overlap.hEvent);
return 0;
} else if (r == WAIT_FAILED) {
- WIIUSE_WARNING("A wait error occured on reading from wiimote %i.", wm->unid);
+ WIIUSE_WARNING("A wait error occurred on reading from wiimote %i.", wm->unid);
return 0;
}
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c
index cdd31a68fb..1bb617d907 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.c
@@ -706,7 +706,7 @@ void wiiuse_set_bluetooth_stack(struct wiimote_t** wm, int wiimotes, enum win_bt
* @param threshold The decimal place that should be considered a significant change.
*
* If threshold is 0.01, and any angle changes by 0.01 then a significant change
- * has occured and the event callback will be invoked. If threshold is 1 then
+ * has occurred and the event callback will be invoked. If threshold is 1 then
* the angle has to change by a full degree to generate an event.
*/
void wiiuse_set_orient_threshold(struct wiimote_t* wm, float threshold) {
diff --git a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h
index dc886743e7..fce263c38f 100644
--- a/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h
+++ b/tools/EventClients/Clients/WiiRemote/wiiuse_v0.12/src/wiiuse.h
@@ -572,7 +572,7 @@ typedef struct wiimote_t {
WCONST struct wiimote_state_t lstate; /**< last saved state */
- WCONST WIIUSE_EVENT_TYPE event; /**< type of event that occured */
+ WCONST WIIUSE_EVENT_TYPE event; /**< type of event that occurred */
WCONST byte event_buf[MAX_PAYLOAD]; /**< event buffer */
} wiimote;
diff --git a/tools/depends/target/ffmpeg/CMakeLists.txt b/tools/depends/target/ffmpeg/CMakeLists.txt
index cc3086e15a..3ebd65cf13 100644
--- a/tools/depends/target/ffmpeg/CMakeLists.txt
+++ b/tools/depends/target/ffmpeg/CMakeLists.txt
@@ -93,6 +93,10 @@ else()
list(APPEND ffmpeg_conf --disable-libdav1d)
endif()
+if(EXTRA_FLAGS)
+ list(APPEND ffmpeg_conf ${EXTRA_FLAGS})
+endif()
+
message(STATUS "FFMPEG_CONF: ${ffmpeg_conf}")
include(ExternalProject)
diff --git a/tools/depends/target/libudfread/UDFREAD-VERSION b/tools/depends/target/libudfread/UDFREAD-VERSION
index 9a13b55e1e..f0cb3bab5e 100644
--- a/tools/depends/target/libudfread/UDFREAD-VERSION
+++ b/tools/depends/target/libudfread/UDFREAD-VERSION
@@ -1,3 +1,3 @@
LIBNAME=libudfread
-VERSION=1.0.0
+VERSION=1.1.0
ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
diff --git a/tools/depends/target/mariadb/01-android.patch b/tools/depends/target/mariadb/01-android.patch
index 1087cd5dd8..4e397d5079 100644
--- a/tools/depends/target/mariadb/01-android.patch
+++ b/tools/depends/target/mariadb/01-android.patch
@@ -1,10 +1,9 @@
--- a/cmake/CheckIncludeFiles.cmake
+++ b/cmake/CheckIncludeFiles.cmake
-@@ -74,3 +74,12 @@
- CHECK_INCLUDE_FILES (unistd.h HAVE_UNISTD_H)
- CHECK_INCLUDE_FILES (utime.h HAVE_UTIME_H)
- CHECK_INCLUDE_FILES (ucontext.h HAVE_UCONTEXT_H)
-+IF(HAVE_UCONTEXT_H)
+@@ -50,4 +50,11 @@
+ CHECK_INCLUDE_FILES (ucontext.h HAVE_FILE_UCONTEXT_H)
+ IF(NOT HAVE_FILE_UCONTEXT_H)
+ CHECK_INCLUDE_FILES (sys/ucontext.h HAVE_FILE_UCONTEXT_H)
+ CHECK_FUNCTION_EXISTS (getcontext HAVE_GETCONTEXT)
+ CHECK_FUNCTION_EXISTS (makecontext HAVE_MAKECONTEXT)
+ CHECK_FUNCTION_EXISTS (setcontext HAVE_SETCONTEXT)
@@ -12,11 +11,11 @@
+ IF(NOT HAVE_GETCONTEXT OR NOT HAVE_MAKECONTEXT OR NOT HAVE_SETCONTEXT OR NOT HAVE_SWAPCONTEXT)
+ SET(HAVE_UCONTEXT_H 0 CACHE INTERNAL "")
+ ENDIF()
-+ENDIF(HAVE_UCONTEXT_H)
+ ENDIF()
--- a/cmake/CheckTypes.cmake
+++ b/cmake/CheckTypes.cmake
-@@ -25,6 +25,7 @@
- CHECK_TYPE_SIZE(off_t SIZEOF_OFF_T)
+@@ -22,6 +22,7 @@
+ SET(CMAKE_EXTRA_INCLUDE_FILES sys/types.h)
CHECK_TYPE_SIZE(uchar SIZEOF_UCHAR)
CHECK_TYPE_SIZE(uint SIZEOF_UINT)
+CHECK_TYPE_SIZE(ushort SIZEOF_USHORT)
@@ -25,7 +24,7 @@
CHECK_TYPE_SIZE(uint8 SIZEOF_UINT8)
--- a/include/ma_config.h.in
+++ b/include/ma_config.h.in
-@@ -217,6 +217,11 @@
+@@ -85,6 +85,11 @@
# define HAVE_UINT 1
#endif
@@ -35,11 +34,11 @@
+#endif
+
#cmakedefine SIZEOF_ULONG @SIZEOF_ULONG@
- #if SIZEOF_ULONG
+ #if defined(SIZEOF_ULONG)
# define HAVE_ULONG 1
--- a/include/ma_global.h
+++ b/include/ma_global.h
-@@ -272,6 +272,8 @@
+@@ -268,6 +268,8 @@
#if defined(__EMX__) || !defined(HAVE_UINT)
typedef unsigned int uint;
@@ -50,7 +49,7 @@
--- a/libmariadb/CMakeLists.txt
+++ b/libmariadb/CMakeLists.txt
-@@ -291,7 +291,6 @@
+@@ -311,7 +311,6 @@
IF(ICONV_INCLUDE_DIR)
INCLUDE_DIRECTORIES(BEFORE ${ICONV_INCLUDE_DIR})
ENDIF()
diff --git a/tools/depends/target/mariadb/Makefile b/tools/depends/target/mariadb/Makefile
index ff810583c1..a161249323 100644
--- a/tools/depends/target/mariadb/Makefile
+++ b/tools/depends/target/mariadb/Makefile
@@ -2,11 +2,17 @@ include ../../Makefile.include
DEPS= ../../Makefile.include Makefile 01-android.patch
LIBNAME=mariadb
-VERSION=3.0.3
+VERSION=3.1.9
ARCHIVE=$(LIBNAME)-connector-c-$(VERSION)-src.tar.gz
LIBDYLIB=$(PLATFORM)/build/lib$(LIBNAME)/lib$(LIBNAME)client.a
+# build all plugins as static
+PLUGIN_BUILD_FLAGS=-DCLIENT_PLUGIN_DIALOG=STATIC -DAUTH_GSSAPI_PLUGIN_TYPE=OFF
+PLUGIN_BUILD_FLAGS+=-DCLIENT_PLUGIN_SHA256_PASSWORD=STATIC -DCLIENT_PLUGIN_CACHING_SHA2_PASSWORD=STATIC
+PLUGIN_BUILD_FLAGS+=-DCLIENT_PLUGIN_MYSQL_CLEAR_PASSWORD=STATIC -DCLIENT_PLUGIN_MYSQL_OLD_PASSWORD=STATIC
+PLUGIN_BUILD_FLAGS+=-DCLIENT_PLUGIN_CLIENT_ED25519=OFF
+
all: .installed-$(PLATFORM)
$(TARBALLS_LOCATION)/$(ARCHIVE):
@@ -16,8 +22,7 @@ $(PLATFORM): $(TARBALLS_LOCATION)/$(ARCHIVE) $(DEPS)
rm -rf $(PLATFORM); mkdir -p $(PLATFORM)/build
cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE)
cd $(PLATFORM); patch -p1 -i ../01-android.patch
- sed -ie 's| "DYNAMIC" | "STATIC" |' $(PLATFORM)/cmake/plugins.cmake
- cd $(PLATFORM)/build; $(CMAKE) -DAUTH_GSSAPI=OFF -DWITH_UNITTEST:BOOL=OFF -DWITH_EXTERNAL_ZLIB:BOOL=ON -DWITH_CURL:BOOL=OFF ..
+ cd $(PLATFORM)/build; $(CMAKE) $(PLUGIN_BUILD_FLAGS) -DWITH_UNIT_TESTS:BOOL=OFF -DWITH_EXTERNAL_ZLIB:BOOL=ON -DWITH_CURL:BOOL=OFF ..
$(LIBDYLIB): $(PLATFORM)
$(MAKE) -C $(PLATFORM)/build
diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp
index b0c31db9ea..ed44da24ed 100644
--- a/xbmc/Application.cpp
+++ b/xbmc/Application.cpp
@@ -6,42 +6,45 @@
* See LICENSES/README.md for more information.
*/
-#include "network/EventServer.h"
-#include "network/Network.h"
-#include "threads/SystemClock.h"
#include "Application.h"
-#include "AppParamParser.h"
+
#include "AppInboundProtocol.h"
-#include "dialogs/GUIDialogBusy.h"
-#include "events/EventLog.h"
-#include "events/NotificationEvent.h"
-#include "interfaces/builtins/Builtins.h"
-#include "utils/JobManager.h"
-#include "utils/Variant.h"
+#include "AppParamParser.h"
+#include "Autorun.h"
+#include "GUIInfoManager.h"
+#include "HDRStatus.h"
#include "LangInfo.h"
-#include "utils/Screenshot.h"
-#include "Util.h"
+#include "PlayListPlayer.h"
#include "URL.h"
-#include "guilib/GUIComponent.h"
-#include "guilib/TextureManager.h"
-#include "cores/IPlayer.h"
+#include "Util.h"
+#include "addons/Skin.h"
+#include "addons/VFSEntry.h"
#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
+#include "cores/IPlayer.h"
#include "cores/playercorefactory/PlayerCoreFactory.h"
-#include "PlayListPlayer.h"
-#include "Autorun.h"
-#include "video/Bookmark.h"
-#include "video/VideoLibraryQueue.h"
-#include "music/MusicLibraryQueue.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "events/EventLog.h"
+#include "events/NotificationEvent.h"
+#include "guilib/GUIColorManager.h"
+#include "guilib/GUIComponent.h"
#include "guilib/GUIControlProfiler.h"
-#include "utils/LangCodeExpander.h"
-#include "GUIInfoManager.h"
-#include "playlists/PlayListFactory.h"
#include "guilib/GUIFontManager.h"
-#include "guilib/GUIColorManager.h"
#include "guilib/StereoscopicsManager.h"
-#include "addons/Skin.h"
-#include "addons/VFSEntry.h"
+#include "guilib/TextureManager.h"
+#include "interfaces/builtins/Builtins.h"
#include "interfaces/generic/ScriptInvocationManager.h"
+#include "music/MusicLibraryQueue.h"
+#include "network/EventServer.h"
+#include "network/Network.h"
+#include "playlists/PlayListFactory.h"
+#include "threads/SystemClock.h"
+#include "utils/JobManager.h"
+#include "utils/LangCodeExpander.h"
+#include "utils/Screenshot.h"
+#include "utils/Variant.h"
+#include "video/Bookmark.h"
+#include "video/VideoLibraryQueue.h"
#ifdef HAS_PYTHON
#include "interfaces/python/XBPython.h"
#endif
@@ -497,6 +500,12 @@ bool CApplication::Create(const CAppParamParser &params)
(CWIN32Util::IsCurrentUserLocalAdministrator() == TRUE) ? "administrator"
: "restricted");
CLog::Log(LOGINFO, "Aero is %s", (g_sysinfo.IsAeroDisabled() == true) ? "disabled" : "enabled");
+ HDR_STATUS hdrStatus = CWIN32Util::GetWindowsHDRStatus();
+ if (hdrStatus == HDR_STATUS::HDR_UNSUPPORTED)
+ CLog::Log(LOGINFO, "Display is not HDR capable or cannot be detected");
+ else
+ CLog::Log(LOGINFO, "Display HDR capable is detected and Windows HDR switch is %s",
+ (hdrStatus == HDR_STATUS::HDR_ON) ? "ON" : "OFF");
#endif
#if defined(TARGET_ANDROID)
CLog::Log(
@@ -780,12 +789,11 @@ bool CApplication::Initialize()
m_confirmSkinChange = false;
- std::vector<std::string> incompatibleAddons;
+ std::vector<AddonInfoPtr> incompatibleAddons;
event.Reset();
// Addon migration
- std::vector<AddonInfoPtr> incompatible;
- if (CServiceBroker::GetAddonMgr().GetIncompatibleAddons(incompatible))
+ if (CServiceBroker::GetAddonMgr().GetIncompatibleEnabledAddonInfos(incompatibleAddons))
{
if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON)
{
@@ -815,7 +823,7 @@ bool CApplication::Initialize()
{
// If no update is active disable all incompatible addons during start
m_incompatibleAddons =
- CServiceBroker::GetAddonMgr().DisableIncompatibleAddons(incompatible);
+ CServiceBroker::GetAddonMgr().DisableIncompatibleAddons(incompatibleAddons);
}
}
@@ -1656,6 +1664,25 @@ bool CApplication::OnAction(const CAction &action)
CScreenShot::TakeScreenshot();
return true;
}
+ // Display HDR : toggle HDR on/off
+ if (action.GetID() == ACTION_HDR_TOGGLE)
+ {
+ HDR_STATUS hdrStatus = CServiceBroker::GetWinSystem()->ToggleHDR();
+
+ if (hdrStatus == HDR_STATUS::HDR_OFF)
+ {
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::eMessageType::Info, "HDR is OFF",
+ "Display HDR is Off", TOAST_DISPLAY_TIME, true,
+ TOAST_DISPLAY_TIME);
+ }
+ else if (hdrStatus == HDR_STATUS::HDR_ON)
+ {
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::eMessageType::Info, "HDR is ON",
+ "Display HDR is On", TOAST_DISPLAY_TIME, true,
+ TOAST_DISPLAY_TIME);
+ }
+ return true;
+ }
// built in functions : execute the built-in
if (action.GetID() == ACTION_BUILT_IN_FUNCTION)
{
@@ -2303,7 +2330,7 @@ void CApplication::OnApplicationMessage(ThreadMessage* pMsg)
}
}
break;
-
+
default:
CLog::Log(LOGERROR, "%s: Unhandled threadmessage sent, %u", __FUNCTION__, msg);
break;
@@ -2948,31 +2975,36 @@ bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRes
// don't switch to fullscreen if we are not playing the first item...
options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() &&
CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
- CSettings::SETTING_MUSICFILES_SELECTACTION);
+ CSettings::SETTING_MUSICFILES_SELECTACTION) &&
+ !CMediaSettings::GetInstance().DoesMediaStartWindowed();
}
else if (item.IsVideo() && playlist == PLAYLIST_VIDEO &&
CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlist).size() > 1)
{ // playing from a playlist by the looks
// don't switch to fullscreen if we are not playing the first item...
- options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesVideoStartWindowed();
+ options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart &&
+ !CMediaSettings::GetInstance().DoesMediaStartWindowed();
}
else if(m_stackHelper.IsPlayingRegularStack())
{
//! @todo - this will fail if user seeks back to first file in stack
if(m_stackHelper.GetCurrentPartNumber() == 0 || m_stackHelper.GetRegisteredStack(item)->m_lStartOffset != 0)
- options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesVideoStartWindowed();
+ options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->
+ m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed();
else
options.fullscreen = false;
}
else
- options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesVideoStartWindowed();
+ options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->
+ m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed();
// stereo streams may have lower quality, i.e. 32bit vs 16 bit
options.preferStereo = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoPreferStereoStream &&
CServiceBroker::GetActiveAE()->HasStereoAudioChannelCount();
// reset VideoStartWindowed as it's a temp setting
- CMediaSettings::GetInstance().SetVideoStartWindowed(false);
+ CMediaSettings::GetInstance().SetMediaStartWindowed(false);
{
// for playing a new item, previous playing item's callback may already
@@ -3794,7 +3826,16 @@ bool CApplication::OnMessage(CGUIMessage& message)
if (!m_incompatibleAddons.empty())
{
- auto addonList = StringUtils::Join(m_incompatibleAddons, ", ");
+ // filter addons that are not dependencies
+ std::vector<std::string> disabledAddonNames;
+ for (const auto& addoninfo : m_incompatibleAddons)
+ {
+ if (!CAddonType::IsDependencyType(addoninfo->MainType()))
+ disabledAddonNames.emplace_back(addoninfo->Name());
+ }
+
+ // migration (incompatible addons) dialog
+ auto addonList = StringUtils::Join(disabledAddonNames, ", ");
auto msg = StringUtils::Format(g_localizeStrings.Get(24149).c_str(), addonList.c_str());
HELPERS::ShowOKDialogText(CVariant{24148}, CVariant{std::move(msg)});
m_incompatibleAddons.clear();
diff --git a/xbmc/Application.h b/xbmc/Application.h
index eb98a37f78..5f94dd3e07 100644
--- a/xbmc/Application.h
+++ b/xbmc/Application.h
@@ -473,7 +473,8 @@ protected:
ReplayGainSettings m_replayGainSettings;
std::vector<IActionListener *> m_actionListeners;
- std::vector<std::string> m_incompatibleAddons; /*!< Result of addon migration */
+ std::vector<ADDON::AddonInfoPtr>
+ m_incompatibleAddons; /*!< Result of addon migration (incompatible addon infos) */
private:
mutable CCriticalSection m_critSection; /*!< critical section for all changes to this class, except for changes to triggers */
diff --git a/xbmc/CMakeLists.txt b/xbmc/CMakeLists.txt
index 863fb854bd..a68d1a56a9 100644
--- a/xbmc/CMakeLists.txt
+++ b/xbmc/CMakeLists.txt
@@ -61,6 +61,7 @@ set(HEADERS AppParamParser.h
GUILargeTextureManager.h
GUIPassword.h
GUIUserMessages.h
+ HDRStatus.h
IFileItemListModifier.h
IProgressCallback.h
InfoScanner.h
diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp
index d0a86211a8..ab14fa3355 100644
--- a/xbmc/FileItem.cpp
+++ b/xbmc/FileItem.cpp
@@ -668,6 +668,13 @@ void CFileItem::Serialize(CVariant& value) const
if (m_gameInfoTag)
(*m_gameInfoTag).Serialize(value["gameInfoTag"]);
+
+ if (!m_mapProperties.empty())
+ {
+ auto& customProperties = value["customproperties"];
+ for (const auto& prop : m_mapProperties)
+ customProperties[prop.first] = prop.second;
+ }
}
void CFileItem::ToSortable(SortItem &sortable, Field field) const
diff --git a/xbmc/HDRStatus.h b/xbmc/HDRStatus.h
new file mode 100644
index 0000000000..b500351e9b
--- /dev/null
+++ b/xbmc/HDRStatus.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+enum class HDR_STATUS : int
+{
+ HDR_TOGGLE_FAILED = -1,
+ HDR_UNSUPPORTED = 0,
+ HDR_OFF = 1,
+ HDR_ON = 2
+};
diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp
index 11d0e49754..5fde1eccf6 100644
--- a/xbmc/Util.cpp
+++ b/xbmc/Util.cpp
@@ -1219,7 +1219,7 @@ int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES
return i;
}
- // doesnt match a name, so try the source path
+ // doesn't match a name, so try the source path
std::vector<std::string> vecPaths;
// add any concatenated paths if they exist
diff --git a/xbmc/addons/AddonDatabase.cpp b/xbmc/addons/AddonDatabase.cpp
index fcce03a77b..edc63e9b59 100644
--- a/xbmc/addons/AddonDatabase.cpp
+++ b/xbmc/addons/AddonDatabase.cpp
@@ -131,7 +131,7 @@ int CAddonDatabase::GetMinSchemaVersion() const
int CAddonDatabase::GetSchemaVersion() const
{
- return 27;
+ return 28;
}
void CAddonDatabase::CreateTables()
@@ -165,8 +165,8 @@ void CAddonDatabase::CreateTables()
CLog::Log(LOGINFO, "create installed table");
m_pDS->exec("CREATE TABLE installed (id INTEGER PRIMARY KEY, addonID TEXT UNIQUE, "
- "enabled BOOLEAN, installDate TEXT, lastUpdated TEXT, lastUsed TEXT, "
- "origin TEXT NOT NULL DEFAULT '') \n");
+ "enabled BOOLEAN, installDate TEXT, lastUpdated TEXT, lastUsed TEXT, "
+ "disabledReason INTEGER NOT NULL DEFAULT 0, origin TEXT NOT NULL DEFAULT '') \n");
}
void CAddonDatabase::CreateAnalytics()
@@ -218,6 +218,12 @@ void CAddonDatabase::UpdateTables(int version)
{
m_pDS->exec("ALTER TABLE addons ADD news TEXT NOT NULL DEFAULT ''");
}
+ if (version < 28)
+ {
+ m_pDS->exec("ALTER TABLE installed ADD disabledReason INTEGER NOT NULL DEFAULT 0");
+ // On adding this field we will use user disabled as the default reason for any disabled addons
+ m_pDS->exec("UPDATE installed SET disabledReason=1 WHERE enabled=0");
+ }
}
void CAddonDatabase::SyncInstalled(const std::set<std::string>& ids,
@@ -862,7 +868,7 @@ bool CAddonDatabase::Search(const std::string& search, VECADDONS& addons)
return false;
}
-bool CAddonDatabase::DisableAddon(const std::string &addonID, bool disable /* = true */)
+bool CAddonDatabase::DisableAddon(const std::string& addonID, AddonDisabledReason disabledReason)
{
try
{
@@ -871,7 +877,30 @@ bool CAddonDatabase::DisableAddon(const std::string &addonID, bool disable /* =
if (!m_pDS)
return false;
- std::string sql = PrepareSQL("UPDATE installed SET enabled=%d WHERE addonID='%s'", disable ? 0 : 1, addonID.c_str());
+ const std::string sql =
+ PrepareSQL("UPDATE installed SET enabled=0, disabledReason=%d WHERE addonID='%s'",
+ static_cast<int>(disabledReason), addonID.c_str());
+ m_pDS->exec(sql);
+ return true;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addonID.c_str());
+ }
+ return false;
+}
+
+bool CAddonDatabase::EnableAddon(const std::string& addonID)
+{
+ try
+ {
+ if (!m_pDB)
+ return false;
+ if (!m_pDS)
+ return false;
+
+ const std::string sql = PrepareSQL(
+ "UPDATE installed SET enabled=1, disabledReason=0 WHERE addonID='%s'", addonID.c_str());
m_pDS->exec(sql);
return true;
}
@@ -891,7 +920,7 @@ bool CAddonDatabase::BreakAddon(const std::string &addonID, const std::string& r
addonID.c_str(), reason.c_str()));
}
-bool CAddonDatabase::GetDisabled(std::set<std::string>& addons)
+bool CAddonDatabase::GetDisabled(std::map<std::string, AddonDisabledReason>& addons)
{
try
{
@@ -900,11 +929,13 @@ bool CAddonDatabase::GetDisabled(std::set<std::string>& addons)
if (!m_pDS)
return false;
- std::string sql = PrepareSQL("SELECT addonID FROM installed WHERE enabled=0");
+ const std::string sql =
+ PrepareSQL("SELECT addonID, disabledReason FROM installed WHERE enabled=0");
m_pDS->query(sql);
while (!m_pDS->eof())
{
- addons.insert(m_pDS->fv(0).get_asString());
+ addons.insert({m_pDS->fv(0).get_asString(),
+ static_cast<AddonDisabledReason>(m_pDS->fv(1).get_asInt())});
m_pDS->next();
}
m_pDS->close();
diff --git a/xbmc/addons/AddonDatabase.h b/xbmc/addons/AddonDatabase.h
index fd65b55cac..6c320d6865 100644
--- a/xbmc/addons/AddonDatabase.h
+++ b/xbmc/addons/AddonDatabase.h
@@ -29,8 +29,8 @@ public:
/*! \brief Get an addon with a specific version and repository. */
bool GetAddon(const std::string& addonID, const ADDON::AddonVersion& version, const std::string& repoId, ADDON::AddonPtr& addon);
- /*! Get the addon IDs that has been set to disabled */
- bool GetDisabled(std::set<std::string>& addons);
+ /*! Get the addon IDs that have been set to disabled */
+ bool GetDisabled(std::map<std::string, ADDON::AddonDisabledReason>& addons);
/*! @deprecated: use FindByAddonId */
bool GetAvailableVersions(const std::string& addonId,
@@ -73,13 +73,24 @@ public:
bool Search(const std::string& search, ADDON::VECADDONS& items);
- /*! \brief Disable an addon.
- Sets a flag that this addon has been disabled. If disabled, it is usually still available on disk.
- \param addonID id of the addon to disable
- \param disable whether to enable or disable. Defaults to true (disable)
- \return true on success, false on failure
- \sa IsAddonDisabled, HasDisabledAddons */
- bool DisableAddon(const std::string &addonID, bool disable = true);
+ /*!
+ * \brief Disable an addon.
+ * Sets a flag that this addon has been disabled. If disabled, it is usually still available on
+ * disk.
+ * \param addonID id of the addon to disable
+ * \param disabledReason the reason why the addon is being disabled
+ * \return true on success, false on failure
+ * \sa IsAddonDisabled, HasDisabledAddons, EnableAddon
+ */
+ bool DisableAddon(const std::string& addonID, ADDON::AddonDisabledReason disabledReason);
+
+ /*! \brief Enable an addon.
+ * Enables an addon that has previously been disabled
+ * \param addonID id of the addon to enable
+ * \return true on success, false on failure
+ * \sa DisableAddon, IsAddonDisabled, HasDisabledAddons
+ */
+ bool EnableAddon(const std::string& addonID);
/*! \brief Mark an addon as broken
Sets a flag that this addon has been marked as broken in the repository.
diff --git a/xbmc/addons/AddonInstaller.cpp b/xbmc/addons/AddonInstaller.cpp
index 289cc1e1b7..f98978fa62 100644
--- a/xbmc/addons/AddonInstaller.cpp
+++ b/xbmc/addons/AddonInstaller.cpp
@@ -656,7 +656,7 @@ bool CAddonInstallJob::DoWork()
if (m_isAutoUpdate && m_addon->IsBroken())
{
CLog::Log(LOGDEBUG, "CAddonInstallJob[%s]: auto-disabling due to being marked as broken", m_addon->ID().c_str());
- CServiceBroker::GetAddonMgr().DisableAddon(m_addon->ID());
+ CServiceBroker::GetAddonMgr().DisableAddon(m_addon->ID(), AddonDisabledReason::USER);
CServiceBroker::GetEventLog().Add(EventPtr(new CAddonManagementEvent(m_addon, 24094)), true, false);
}
diff --git a/xbmc/addons/AddonManager.cpp b/xbmc/addons/AddonManager.cpp
index ad3a89075b..8efbf5a1ae 100644
--- a/xbmc/addons/AddonManager.cpp
+++ b/xbmc/addons/AddonManager.cpp
@@ -223,7 +223,7 @@ VECADDONS CAddonMgr::GetAvailableUpdates() const
last_versions[addon->ID()] = std::move(addon);
}
- GetAddons(installed);
+ GetAddonsForUpdate(installed);
for (const auto& addon : installed)
{
const auto& remote = last_versions.find(addon->ID());
@@ -239,6 +239,11 @@ bool CAddonMgr::HasAvailableUpdates()
return !GetAvailableUpdates().empty();
}
+bool CAddonMgr::GetAddonsForUpdate(VECADDONS& addons) const
+{
+ return GetAddonsInternal(ADDON_UNKNOWN, addons, true, true);
+}
+
bool CAddonMgr::GetAddons(VECADDONS& addons) const
{
return GetAddonsInternal(ADDON_UNKNOWN, addons, true);
@@ -323,7 +328,10 @@ bool CAddonMgr::FindInstallableById(const std::string& addonId, AddonPtr& result
return true;
}
-bool CAddonMgr::GetAddonsInternal(const TYPE& type, VECADDONS& addons, bool enabledOnly) const
+bool CAddonMgr::GetAddonsInternal(const TYPE& type,
+ VECADDONS& addons,
+ bool enabledOnly,
+ bool checkIncompatible) const
{
CSingleLock lock(m_critSection);
@@ -332,7 +340,10 @@ bool CAddonMgr::GetAddonsInternal(const TYPE& type, VECADDONS& addons, bool enab
if (type != ADDON_UNKNOWN && !addonInfo.second->HasType(type))
continue;
- if (enabledOnly && IsAddonDisabled(addonInfo.second->ID()))
+ if (enabledOnly &&
+ ((!checkIncompatible && IsAddonDisabled(addonInfo.second->ID())) ||
+ (checkIncompatible &&
+ IsAddonDisabledExcept(addonInfo.second->ID(), AddonDisabledReason::INCOMPATIBLE))))
continue;
//FIXME: hack for skipping special dependency addons (xbmc.python etc.).
@@ -353,16 +364,24 @@ bool CAddonMgr::GetAddonsInternal(const TYPE& type, VECADDONS& addons, bool enab
return addons.size() > 0;
}
-bool CAddonMgr::GetIncompatibleAddons(std::vector<AddonInfoPtr>& incompatible) const
+bool CAddonMgr::GetIncompatibleEnabledAddonInfos(std::vector<AddonInfoPtr>& incompatible) const
+{
+ return GetIncompatibleAddonInfos(incompatible, false);
+}
+
+bool CAddonMgr::GetIncompatibleAddonInfos(std::vector<AddonInfoPtr>& incompatible,
+ bool includeDisabled) const
{
GetAddonInfos(incompatible, true, ADDON_UNKNOWN);
+ if (includeDisabled)
+ GetDisabledAddonInfos(incompatible, ADDON_UNKNOWN, AddonDisabledReason::INCOMPATIBLE);
incompatible.erase(std::remove_if(incompatible.begin(), incompatible.end(),
[this](const AddonInfoPtr& a) { return IsCompatible(a); }),
incompatible.end());
return !incompatible.empty();
}
-std::vector<std::string> CAddonMgr::MigrateAddons()
+std::vector<AddonInfoPtr> CAddonMgr::MigrateAddons()
{
// install all addon updates
std::lock_guard<std::mutex> lock(m_installAddonsMutex);
@@ -373,15 +392,15 @@ std::vector<std::string> CAddonMgr::MigrateAddons()
// get addons that became incompatible and disable them
std::vector<AddonInfoPtr> incompatible;
- GetIncompatibleAddons(incompatible);
+ GetIncompatibleAddonInfos(incompatible, true);
return DisableIncompatibleAddons(incompatible);
}
-std::vector<std::string> CAddonMgr::DisableIncompatibleAddons(
+std::vector<AddonInfoPtr> CAddonMgr::DisableIncompatibleAddons(
const std::vector<AddonInfoPtr>& incompatible)
{
- std::vector<std::string> changed;
+ std::vector<AddonInfoPtr> changed;
for (const auto& addon : incompatible)
{
CLog::Log(LOGINFO, "ADDON: {} version {} is incompatible", addon->ID(),
@@ -392,11 +411,12 @@ std::vector<std::string> CAddonMgr::DisableIncompatibleAddons(
CLog::Log(LOGWARNING, "ADDON: failed to unset {}", addon->ID());
continue;
}
- if (!DisableAddon(addon->ID()))
+ if (!DisableAddon(addon->ID(), AddonDisabledReason::INCOMPATIBLE))
{
CLog::Log(LOGWARNING, "ADDON: failed to disable {}", addon->ID());
}
- changed.push_back(addon->Name());
+
+ changed.emplace_back(addon);
}
return changed;
@@ -532,13 +552,13 @@ bool CAddonMgr::FindAddons()
m_installedAddons = std::move(installedAddons);
// Reload caches
- std::set<std::string> tmp;
- m_database.GetDisabled(tmp);
- m_disabled = std::move(tmp);
+ std::map<std::string, AddonDisabledReason> tmpDisabled;
+ m_database.GetDisabled(tmpDisabled);
+ m_disabled = std::move(tmpDisabled);
- tmp.clear();
- m_database.GetBlacklisted(tmp);
- m_updateBlacklist = std::move(tmp);
+ std::set<std::string> tmpBlacklist;
+ m_database.GetBlacklisted(tmpBlacklist);
+ m_updateBlacklist = std::move(tmpBlacklist);
return true;
}
@@ -659,16 +679,16 @@ static void ResolveDependencies(const std::string& addonId, std::vector<std::str
}
}
-bool CAddonMgr::DisableAddon(const std::string& id)
+bool CAddonMgr::DisableAddon(const std::string& id, AddonDisabledReason disabledReason)
{
CSingleLock lock(m_critSection);
if (!CanAddonBeDisabled(id))
return false;
if (m_disabled.find(id) != m_disabled.end())
return true; //already disabled
- if (!m_database.DisableAddon(id))
+ if (!m_database.DisableAddon(id, disabledReason))
return false;
- if (!m_disabled.insert(id).second)
+ if (!m_disabled.emplace(id, disabledReason).second)
return false;
//success
@@ -701,7 +721,7 @@ bool CAddonMgr::EnableSingle(const std::string& id)
return false;
}
- if (!m_database.DisableAddon(id, false))
+ if (!m_database.EnableAddon(id))
return false;
m_disabled.erase(id);
@@ -734,6 +754,14 @@ bool CAddonMgr::IsAddonDisabled(const std::string& ID) const
return m_disabled.find(ID) != m_disabled.end();
}
+bool CAddonMgr::IsAddonDisabledExcept(const std::string& ID,
+ AddonDisabledReason disabledReason) const
+{
+ CSingleLock lock(m_critSection);
+ const auto disabledAddon = m_disabled.find(ID);
+ return disabledAddon != m_disabled.end() && disabledAddon->second != disabledReason;
+}
+
bool CAddonMgr::CanAddonBeDisabled(const std::string& ID)
{
if (ID.empty())
@@ -925,17 +953,26 @@ bool CAddonMgr::GetAddonInfos(AddonInfos& addonInfos, bool enabledOnly, TYPE typ
return !addonInfos.empty();
}
-bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos, TYPE type)
+bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos, TYPE type) const
+{
+ return GetDisabledAddonInfos(addonInfos, type, AddonDisabledReason::NONE);
+}
+
+bool CAddonMgr::GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos,
+ TYPE type,
+ AddonDisabledReason disabledReason) const
{
CSingleLock lock(m_critSection);
bool forUnknown = type == ADDON_UNKNOWN;
for (const auto& info : m_installedAddons)
{
- if (m_disabled.find(info.first) == m_disabled.end())
+ const auto disabledAddon = m_disabled.find(info.first);
+ if (disabledAddon == m_disabled.end())
continue;
- if (info.second->MainType() != ADDON_UNKNOWN && (forUnknown || info.second->HasType(type)))
+ if (info.second->MainType() != ADDON_UNKNOWN && (forUnknown || info.second->HasType(type)) &&
+ (disabledReason == AddonDisabledReason::NONE || disabledReason == disabledAddon->second))
addonInfos.emplace_back(info.second);
}
diff --git a/xbmc/addons/AddonManager.h b/xbmc/addons/AddonManager.h
index bfb6e3c790..a230bb2e5b 100644
--- a/xbmc/addons/AddonManager.h
+++ b/xbmc/addons/AddonManager.h
@@ -14,6 +14,7 @@
#include "threads/CriticalSection.h"
#include "utils/EventStream.h"
+#include <map>
#include <mutex>
namespace ADDON
@@ -79,6 +80,9 @@ namespace ADDON
bool HasInstalledAddons(const TYPE &type);
+ /*! Returns all installed, enabled and incompatible (and disabled) add-ons. */
+ bool GetAddonsForUpdate(VECADDONS& addons) const;
+
/*! Returns all installed, enabled add-ons. */
bool GetAddons(VECADDONS& addons) const;
@@ -113,8 +117,6 @@ namespace ADDON
/*! Returns true if there is any addon with available updates, otherwise false */
bool HasAvailableUpdates();
- static AddonPtr AddonFromProps(const AddonInfoPtr& addonInfo);
-
/*! \brief Checks for new / updated add-ons
\return True if everything went ok, false otherwise
*/
@@ -122,29 +124,29 @@ namespace ADDON
/*!
* @brief Fills the the provided vector with the list of incompatible
- * addons and returns if there's any.
+ * enabled addons and returns if there's any.
*
* @param[out] incompatible List of incompatible addons
* @return true if there are incompatible addons
*/
- bool GetIncompatibleAddons(std::vector<AddonInfoPtr>& incompatible) const;
+ bool GetIncompatibleEnabledAddonInfos(std::vector<AddonInfoPtr>& incompatible) const;
/*!
- * @brief Disable addons in given list.
+ * Migrate all the addons (updates all addons that have an update pending and disables those
+ * that got incompatible)
*
- * @param[in] incompatible List of incompatible addons
- * @return list of all addon **names** that were disabled
+ * @return list of all addons (infos) that were modified.
*/
- std::vector<std::string> DisableIncompatibleAddons(
- const std::vector<AddonInfoPtr>& incompatible);
+ std::vector<AddonInfoPtr> MigrateAddons();
/*!
- * Migrate all the addons (updates all addons that have an update pending and disables those
- * that got incompatible)
+ * @brief Try to disable addons in the given list.
*
- * @return list of all addons that were modified.
+ * @param[in] incompatible List of incompatible addon infos
+ * @return list of all addon Infos that were disabled
*/
- std::vector<std::string> MigrateAddons();
+ std::vector<AddonInfoPtr> DisableIncompatibleAddons(
+ const std::vector<AddonInfoPtr>& incompatible);
/*!
* Install available addon updates, if any.
@@ -173,7 +175,7 @@ namespace ADDON
void OnPostUnInstall(const std::string& id);
/*! \brief Disable an addon. Returns true on success, false on failure. */
- bool DisableAddon(const std::string& ID);
+ bool DisableAddon(const std::string& ID, AddonDisabledReason disabledReason);
/*! \brief Enable an addon. Returns true on success, false on failure. */
bool EnableAddon(const std::string& ID);
@@ -185,6 +187,17 @@ namespace ADDON
*/
bool IsAddonDisabled(const std::string& ID) const;
+ /*!
+ * @brief Check whether an addon has been disabled via DisableAddon except for a particular
+ * reason In case the disabled cache does not know about the current state the database routine
+ * will be used.
+ * @param[in] ID id of the addon
+ * @param[in] disabledReason the reason that will be an exception to being disabled
+ * @return true if the addon was disabled except for the specified reason
+ * @sa DisableAddon
+ */
+ bool IsAddonDisabledExcept(const std::string& ID, AddonDisabledReason disabledReason) const;
+
/* \brief Checks whether an addon can be disabled via DisableAddon.
\param ID id of the addon
\sa DisableAddon
@@ -266,8 +279,21 @@ namespace ADDON
bool GetAddonInfos(AddonInfos& addonInfos, bool enabledOnly, TYPE type) const;
/*!
+ * @brief Get a list of disabled add-on's with info's
+ *
+ * @param[out] addonInfos list where finded addon information becomes stored
+ * @param[in] type The requested type, with "ADDON_UNKNOWN"
+ * are all add-on types given back who match the case
+ * with value before.
+ * If a type id becomes added are only add-ons
+ * returned who match them. Default is for all types.
+ * @return true if the list contains entries
+ */
+ bool GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos, TYPE type) const;
+
+ /*!
* @brief Get a list of disabled add-on's with info's for the on system
- * available ones.
+ * available ones with a specific disabled reason.
*
* @param[out] addonInfos list where finded addon information becomes stored
* @param[in] type The requested type, with "ADDON_UNKNOWN"
@@ -275,9 +301,15 @@ namespace ADDON
* with value before.
* If a type id becomes added are only add-ons
* returned who match them. Default is for all types.
+ * @param[in] disabledReason To get all disabled addons use the value
+ * "AddonDiasbledReason::NONE". If any other value
+ * is supplied only addons with that reason will be
+ * returned.
* @return true if the list contains entries
*/
- bool GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos, TYPE type);
+ bool GetDisabledAddonInfos(std::vector<AddonInfoPtr>& addonInfos,
+ TYPE type,
+ AddonDisabledReason disabledReason) const;
const AddonInfoPtr GetAddonInfo(const std::string& id, TYPE type = ADDON_UNKNOWN) const;
@@ -295,12 +327,26 @@ namespace ADDON
VECADDONS m_updateableAddons;
- bool GetAddonsInternal(const TYPE& type, VECADDONS& addons, bool enabledOnly) const;
+ bool GetAddonsInternal(const TYPE& type,
+ VECADDONS& addons,
+ bool enabledOnly,
+ bool checkIncompatible = false) const;
bool EnableSingle(const std::string& id);
void FindAddons(ADDON_INFO_LIST& addonmap, const std::string& path);
/*!
+ * @brief Fills the the provided vector with the list of incompatible
+ * addons and returns if there's any.
+ *
+ * @param[out] incompatible List of incompatible addons
+ * @param[in] whether or not to include incompatible addons that are disabled
+ * @return true if there are incompatible addons
+ */
+ bool GetIncompatibleAddonInfos(std::vector<AddonInfoPtr>& incompatible,
+ bool includeDisabled) const;
+
+ /*!
* Get the list of of available updates
* \param[in,out] updates the vector of addons to be filled with addons that need to be updated (not blacklisted)
* \return if there are any addons needing updates
@@ -327,7 +373,7 @@ namespace ADDON
// (migration will install any available update anyway)
mutable std::mutex m_installAddonsMutex;
- std::set<std::string> m_disabled;
+ std::map<std::string, AddonDisabledReason> m_disabled;
std::set<std::string> m_updateBlacklist;
static std::map<TYPE, IAddonMgrCallback*> m_managers;
mutable CCriticalSection m_critSection;
diff --git a/xbmc/addons/ContextMenus.cpp b/xbmc/addons/ContextMenus.cpp
index 2e4ea07ed1..e5335e181a 100644
--- a/xbmc/addons/ContextMenus.cpp
+++ b/xbmc/addons/ContextMenus.cpp
@@ -74,6 +74,7 @@ bool CDisableAddon::IsVisible(const CFileItem& item) const
bool CDisableAddon::Execute(const CFileItemPtr& item) const
{
- return CServiceBroker::GetAddonMgr().DisableAddon(item->GetAddonInfo()->ID());
+ return CServiceBroker::GetAddonMgr().DisableAddon(item->GetAddonInfo()->ID(),
+ AddonDisabledReason::USER);
}
}
diff --git a/xbmc/addons/GUIDialogAddonInfo.cpp b/xbmc/addons/GUIDialogAddonInfo.cpp
index 8b5643e68a..a11490df3b 100644
--- a/xbmc/addons/GUIDialogAddonInfo.cpp
+++ b/xbmc/addons/GUIDialogAddonInfo.cpp
@@ -451,7 +451,7 @@ void CGUIDialogAddonInfo::OnEnableDisable()
if (PromptIfDependency(24075, 24091))
return; //required. can't disable
- CServiceBroker::GetAddonMgr().DisableAddon(m_localAddon->ID());
+ CServiceBroker::GetAddonMgr().DisableAddon(m_localAddon->ID(), AddonDisabledReason::USER);
}
else
CServiceBroker::GetAddonMgr().EnableAddon(m_localAddon->ID());
diff --git a/xbmc/addons/Scraper.cpp b/xbmc/addons/Scraper.cpp
index 1d72b0235a..76a036bbec 100644
--- a/xbmc/addons/Scraper.cpp
+++ b/xbmc/addons/Scraper.cpp
@@ -802,8 +802,11 @@ void DetailsFromFileItem<CArtist>(const CFileItem &item, CArtist &artist)
{
std::stringstream prefix;
prefix << "artist.album" << i + 1;
- artist.discography.emplace_back(FromString(item, prefix.str() + ".title"),
- FromString(item, prefix.str() + ".year"));
+ CDiscoAlbum discoAlbum;
+ discoAlbum.strAlbum = FromString(item, prefix.str() + ".title");
+ discoAlbum.strYear = FromString(item, prefix.str() + ".year");
+ discoAlbum.strReleaseGroupMBID = FromString(item, prefix.str() + ".musicbrainzreleasegroupid");
+ artist.discography.emplace_back(discoAlbum);
}
int nThumbs = item.GetProperty("artist.thumbs").asInteger32();
diff --git a/xbmc/addons/addoninfo/AddonInfo.h b/xbmc/addons/addoninfo/AddonInfo.h
index 355703ce34..59f2b30c5b 100644
--- a/xbmc/addons/addoninfo/AddonInfo.h
+++ b/xbmc/addons/addoninfo/AddonInfo.h
@@ -26,6 +26,17 @@ class CAddonInfo;
typedef std::shared_ptr<CAddonInfo> AddonInfoPtr;
typedef std::vector<AddonInfoPtr> AddonInfos;
+enum class AddonDisabledReason
+{
+ /// @brief Special reason for returning all disabled addons.
+ ///
+ /// Only used as an actual value when an addon is enabled.
+ NONE = 0,
+ USER = 1,
+ INCOMPATIBLE = 2,
+ PERMANENT_FAILURE = 3
+};
+
struct DependencyInfo
{
std::string id;
diff --git a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp
index 19388e2278..f62446521b 100644
--- a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp
+++ b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp
@@ -114,7 +114,7 @@ bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, const TiXmlElement*
if (!StringUtils::EqualsNoCase(element->Value(), "addon"))
{
- CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file from '{}' doesnt contain <addon>", __FUNCTION__, addonPath);
+ CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file from '{}' doesn't contain <addon>", __FUNCTION__, addonPath);
return false;
}
@@ -135,7 +135,7 @@ bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, const TiXmlElement*
addon->m_author = cstring ? cstring : "";
if (addon->m_id.empty() || addon->m_version.empty())
{
- CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesnt contain required values on <addon ... > id='{}', version='{}'",
+ CLog::Log(LOGERROR, "CAddonInfoBuilder::{}: file '{}' doesn't contain required values on <addon ... > id='{}', version='{}'",
__FUNCTION__,
addonPath,
addon->m_id.empty() ? "missing" : addon->m_id,
diff --git a/xbmc/addons/addoninfo/AddonType.cpp b/xbmc/addons/addoninfo/AddonType.cpp
index 19e96bd73f..e17d82ed88 100644
--- a/xbmc/addons/addoninfo/AddonType.cpp
+++ b/xbmc/addons/addoninfo/AddonType.cpp
@@ -11,6 +11,15 @@
#include "addons/addoninfo/AddonInfo.h"
#include "utils/URIUtils.h"
+namespace ADDON
+{
+static const std::set<TYPE> dependencyTypes = {
+ ADDON_SCRAPER_LIBRARY,
+ ADDON_SCRIPT_LIBRARY,
+ ADDON_SCRIPT_MODULE,
+};
+} /* namespace ADDON */
+
using namespace ADDON;
std::string CAddonType::LibPath() const
@@ -42,3 +51,8 @@ void CAddonType::SetProvides(const std::string& content)
}
}
}
+
+bool CAddonType::IsDependencyType(TYPE type)
+{
+ return dependencyTypes.find(type) != dependencyTypes.end();
+}
diff --git a/xbmc/addons/addoninfo/AddonType.h b/xbmc/addons/addoninfo/AddonType.h
index 3d420ccf81..34babd9e06 100644
--- a/xbmc/addons/addoninfo/AddonType.h
+++ b/xbmc/addons/addoninfo/AddonType.h
@@ -97,6 +97,15 @@ public:
return m_providedSubContent.size();
}
+ /*!
+ * @brief Indicates whether a given type is a dependency type (e.g. addons which the main type is
+ * a script.module)
+ *
+ * @param[in] type the provided type
+ * @return true if type is one of the dependency types
+ */
+ static bool IsDependencyType(TYPE type);
+
private:
friend class CAddonInfoBuilder;
diff --git a/xbmc/addons/interfaces/AddonBase.cpp b/xbmc/addons/interfaces/AddonBase.cpp
index 7ceba7e279..6c3633c194 100644
--- a/xbmc/addons/interfaces/AddonBase.cpp
+++ b/xbmc/addons/interfaces/AddonBase.cpp
@@ -50,6 +50,7 @@ bool Interface_Base::InitInterface(CAddonDll* addon,
addonInterface.toKodi->get_addon_path = get_addon_path;
addonInterface.toKodi->get_base_user_path = get_base_user_path;
addonInterface.toKodi->addon_log_msg = addon_log_msg;
+ addonInterface.toKodi->is_setting_using_default = is_setting_using_default;
addonInterface.toKodi->get_setting_bool = get_setting_bool;
addonInterface.toKodi->get_setting_int = get_setting_int;
addonInterface.toKodi->get_setting_float = get_setting_float;
@@ -198,6 +199,35 @@ void Interface_Base::addon_log_msg(void* kodiBase, const int addonLogLevel, cons
CLog::Log(logLevel, "AddOnLog: {}: {}", addon->ID(), strMessage);
}
+bool Interface_Base::is_setting_using_default(void* kodiBase, const char* id)
+{
+ CAddonDll* addon = static_cast<CAddonDll*>(kodiBase);
+ if (addon == nullptr || id == nullptr)
+ {
+ CLog::Log(LOGERROR, "Interface_Base::{} - invalid data (addon='{}', id='{}')", __func__,
+ kodiBase, static_cast<const void*>(id));
+
+ return false;
+ }
+
+ if (!addon->HasSettings())
+ {
+ CLog::Log(LOGERROR, "Interface_Base::{} - couldn't get settings for add-on '{}'", __func__,
+ addon->Name());
+ return false;
+ }
+
+ auto setting = addon->GetSettings()->GetSetting(id);
+ if (setting == nullptr)
+ {
+ CLog::Log(LOGERROR, "Interface_Base::{} - can't find setting '{}' in '{}'", __func__, id,
+ addon->Name());
+ return false;
+ }
+
+ return setting->IsDefault();
+}
+
bool Interface_Base::get_setting_bool(void* kodiBase, const char* id, bool* value)
{
CAddonDll* addon = static_cast<CAddonDll*>(kodiBase);
@@ -209,9 +239,9 @@ bool Interface_Base::get_setting_bool(void* kodiBase, const char* id, bool* valu
return false;
}
- if (!addon->ReloadSettings())
+ if (!addon->HasSettings())
{
- CLog::Log(LOGERROR, "Interface_Base::{} - could't get settings for add-on '{}'", __func__,
+ CLog::Log(LOGERROR, "Interface_Base::{} - couldn't get settings for add-on '{}'", __func__,
addon->Name());
return false;
}
@@ -246,9 +276,9 @@ bool Interface_Base::get_setting_int(void* kodiBase, const char* id, int* value)
return false;
}
- if (!addon->ReloadSettings())
+ if (!addon->HasSettings())
{
- CLog::Log(LOGERROR, "Interface_Base::{} - could't get settings for add-on '{}'", __func__,
+ CLog::Log(LOGERROR, "Interface_Base::{} - couldn't get settings for add-on '{}'", __func__,
addon->Name());
return false;
}
@@ -286,9 +316,9 @@ bool Interface_Base::get_setting_float(void* kodiBase, const char* id, float* va
return false;
}
- if (!addon->ReloadSettings())
+ if (!addon->HasSettings())
{
- CLog::Log(LOGERROR, "Interface_Base::{} - could't get settings for add-on '{}'", __func__,
+ CLog::Log(LOGERROR, "Interface_Base::{} - couldn't get settings for add-on '{}'", __func__,
addon->Name());
return false;
}
@@ -323,9 +353,9 @@ bool Interface_Base::get_setting_string(void* kodiBase, const char* id, char** v
return false;
}
- if (!addon->ReloadSettings())
+ if (!addon->HasSettings())
{
- CLog::Log(LOGERROR, "Interface_Base::{} - could't get settings for add-on '{}'", __func__,
+ CLog::Log(LOGERROR, "Interface_Base::{} - couldn't get settings for add-on '{}'", __func__,
addon->Name());
return false;
}
diff --git a/xbmc/addons/interfaces/AddonBase.h b/xbmc/addons/interfaces/AddonBase.h
index 67ec035274..58270a6ba6 100644
--- a/xbmc/addons/interfaces/AddonBase.h
+++ b/xbmc/addons/interfaces/AddonBase.h
@@ -55,6 +55,7 @@ struct Interface_Base
static char* get_addon_path(void* kodiBase);
static char* get_base_user_path(void* kodiBase);
static void addon_log_msg(void* kodiBase, const int addonLogLevel, const char* strMessage);
+ static bool is_setting_using_default(void* kodiBase, const char* id);
static bool get_setting_bool(void* kodiBase, const char* id, bool* value);
static bool get_setting_int(void* kodiBase, const char* id, int* value);
static bool get_setting_float(void* kodiBase, const char* id, float* value);
diff --git a/xbmc/addons/interfaces/Filesystem.cpp b/xbmc/addons/interfaces/Filesystem.cpp
index 73bd368c3a..534e617680 100644
--- a/xbmc/addons/interfaces/Filesystem.cpp
+++ b/xbmc/addons/interfaces/Filesystem.cpp
@@ -378,10 +378,9 @@ char* Interface_Filesystem::get_cache_thumb_name(void* kodiBase, const char* fil
return nullptr;
}
- Crc32 crc;
- crc.ComputeFromLowerCase(filename);
- std::string string = StringUtils::Format("%08x.tbn", static_cast<unsigned int>(crc));
- char* buffer = strdup(string.c_str());
+ const auto crc = Crc32::ComputeFromLowerCase(filename);
+ const auto hex = StringUtils::Format("%08x.tbn", crc);
+ char* buffer = strdup(hex.c_str());
return buffer;
}
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/AddonBase.h
index f1fcc91683..b0fddda87c 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/AddonBase.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/AddonBase.h
@@ -689,6 +689,22 @@ inline void ATTRIBUTE_HIDDEN Log(const AddonLog loglevel, const char* format, ..
/*!@{*/
//==============================================================================
+/// @brief Check the given setting name is set to default value.
+///
+/// The setting name relate to names used in his <b>settings.xml</b> file.
+///
+/// @param[in] settingName The name of asked setting
+/// @return true if setting is the default
+///
+inline bool ATTRIBUTE_HIDDEN IsSettingUsingDefault(const std::string& settingName)
+{
+ using namespace kodi::addon;
+ return CAddonBase::m_interface->toKodi->is_setting_using_default(
+ CAddonBase::m_interface->toKodi->kodiBase, settingName.c_str());
+}
+//------------------------------------------------------------------------------
+
+//==============================================================================
/// @brief Check and get a string setting value.
///
/// The setting name relate to names used in his <b>settings.xml</b> file.
@@ -1241,7 +1257,7 @@ inline void* GetInterface(const std::string& name, const std::string& version)
*/
#define ADDONCREATOR(AddonClass) \
extern "C" __declspec(dllexport) ADDON_STATUS ADDON_Create( \
- KODI_HANDLE addonInterface, const char* globalApiVersion, void* unused) \
+ KODI_HANDLE addonInterface, const char* /*globalApiVersion*/, void* /*unused*/) \
{ \
kodi::addon::CAddonBase::m_interface = static_cast<AddonGlobalInterface*>(addonInterface); \
kodi::addon::CAddonBase::m_interface->addonBase = new AddonClass; \
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/Inputstream.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/Inputstream.h
index 0ffdcf9362..354806e501 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/Inputstream.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/Inputstream.h
@@ -480,7 +480,7 @@ public:
* then, the add-on should call AllocateDemuxPacket(0) on the
* callback, and set the streamid to DMX_SPECIALID_STREAMCHANGE and
* return the value.
- * The add-on should return NULL if an error occured.
+ * The add-on should return NULL if an error occurred.
* @remarks Return NULL if this add-on won't provide this function.
*/
virtual DemuxPacket* DemuxRead() { return nullptr; }
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/PVR.h
index 7bb972da43..0bca8e2d75 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/PVR.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/PVR.h
@@ -842,7 +842,7 @@ public:
///
/// @param[in] radio True to get the radio channels, false to get the TV channels.
/// @param[out] results The channels defined with @ref cpp_kodi_addon_pvr_Defs_Channel_PVRChannel
- /// and available at the addon, them transfered with
+ /// and available at the addon, them transferred with
/// @ref cpp_kodi_addon_pvr_Defs_Channel_PVRChannelsResultSet.
/// @return @ref PVR_ERROR_NO_ERROR if the list has been fetched successfully.
///
@@ -1057,7 +1057,7 @@ public:
/// TV channel groups.
/// @param[out] results List of available groups on addon defined with
/// @ref cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroup,
- /// them transfered with
+ /// them transferred with
/// @ref cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroupsResultSet.
/// @return @ref PVR_ERROR_NO_ERROR if the list has been fetched successfully.
///
@@ -1107,7 +1107,7 @@ public:
/// @param[in] group The group to get the members for.
/// @param[out] results List of available group member channels defined with
/// @ref cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroupMember,
- /// them transfered with
+ /// them transferred with
/// @ref PVRChannelGroupMembersResultSet.
/// @return @ref PVR_ERROR_NO_ERROR if the list has been fetched successfully.
///
@@ -2319,7 +2319,7 @@ public:
/// If the stream changed and Kodi's player needs to be reinitialised, then,
/// the add-on should call @ref AllocateDemuxPacket(0) on the callback, and set
/// the streamid to @ref DMX_SPECIALID_STREAMCHANGE and return the value.
- /// The add-on should return `nullptr` if an error occured.
+ /// The add-on should return `nullptr` if an error occurred.
///
/// @remarks Required, and only used if addon has its own demuxer.
/// Return `nullptr` if this add-on won't provide this function.
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/VideoCodec.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/VideoCodec.h
index 6151cf6854..54246f0c9c 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/VideoCodec.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/VideoCodec.h
@@ -86,7 +86,7 @@ extern "C"
enum VIDEOCODEC_RETVAL
{
VC_NONE = 0, //< noop
- VC_ERROR, //< an error occured, no other messages will be returned
+ VC_ERROR, //< an error occurred, no other messages will be returned
VC_BUFFER, //< the decoder needs more data
VC_PICTURE, //< the decoder got a picture
VC_EOF, //< the decoder signals EOF
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h
index 3464909f69..17995bb2e3 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h
@@ -36,12 +36,12 @@ namespace addon
///@{
class PVRChannelGroup : public CStructHdl<PVRChannelGroup, PVR_CHANNEL_GROUP>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRChannelGroup() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP)); }
PVRChannelGroup(const PVRChannelGroup& channel) : CStructHdl(channel) {}
- PVRChannelGroup(const PVR_CHANNEL_GROUP* channel) : CStructHdl(channel) {}
- PVRChannelGroup(PVR_CHANNEL_GROUP* channel) : CStructHdl(channel) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroup_Help Value Help
@@ -84,6 +84,10 @@ public:
unsigned int GetPosition() const { return m_cStructure->iPosition; }
///@}
+
+private:
+ PVRChannelGroup(const PVR_CHANNEL_GROUP* channel) : CStructHdl(channel) {}
+ PVRChannelGroup(PVR_CHANNEL_GROUP* channel) : CStructHdl(channel) {}
};
///@}
//------------------------------------------------------------------------------
@@ -112,7 +116,7 @@ public:
/// @brief To add and give content from addon to Kodi on related call.
///
- /// @param[in] tag The to transfered data.
+ /// @param[in] tag The to transferred data.
void Add(const kodi::addon::PVRChannelGroup& tag)
{
m_instance->toKodi->TransferChannelGroup(m_instance->toKodi->kodiInstance, m_handle, tag);
@@ -143,12 +147,12 @@ private:
///@{
class PVRChannelGroupMember : public CStructHdl<PVRChannelGroupMember, PVR_CHANNEL_GROUP_MEMBER>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRChannelGroupMember() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); }
PVRChannelGroupMember(const PVRChannelGroupMember& channel) : CStructHdl(channel) {}
- PVRChannelGroupMember(const PVR_CHANNEL_GROUP_MEMBER* channel) : CStructHdl(channel) {}
- PVRChannelGroupMember(PVR_CHANNEL_GROUP_MEMBER* channel) : CStructHdl(channel) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroupMember_Help Value Help
@@ -215,6 +219,10 @@ public:
bool GetOrder() const { return m_cStructure->iOrder; }
///@}
+
+private:
+ PVRChannelGroupMember(const PVR_CHANNEL_GROUP_MEMBER* channel) : CStructHdl(channel) {}
+ PVRChannelGroupMember(PVR_CHANNEL_GROUP_MEMBER* channel) : CStructHdl(channel) {}
};
///@}
//------------------------------------------------------------------------------
@@ -242,7 +250,7 @@ public:
/// @brief To add and give content from addon to Kodi on related call.
///
- /// @param[in] tag The to transfered data.
+ /// @param[in] tag The to transferred data.
void Add(const kodi::addon::PVRChannelGroupMember& tag)
{
m_instance->toKodi->TransferChannelGroupMember(m_instance->toKodi->kodiInstance, m_handle, tag);
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Channels.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Channels.h
index 454cd28875..9c2f5d26e9 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Channels.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Channels.h
@@ -37,12 +37,12 @@ namespace addon
///@{
class PVRChannel : public CStructHdl<PVRChannel, PVR_CHANNEL>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRChannel() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL)); }
PVRChannel(const PVRChannel& channel) : CStructHdl(channel) {}
- PVRChannel(const PVR_CHANNEL* channel) : CStructHdl(channel) {}
- PVRChannel(PVR_CHANNEL* channel) : CStructHdl(channel) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Channel_PVRChannel_Help Value Help
@@ -172,6 +172,10 @@ public:
/// @brief To get with @ref SetOrder changed values.
bool GetOrder() const { return m_cStructure->iOrder; }
///@}
+
+private:
+ PVRChannel(const PVR_CHANNEL* channel) : CStructHdl(channel) {}
+ PVRChannel(PVR_CHANNEL* channel) : CStructHdl(channel) {}
};
///@}
//------------------------------------------------------------------------------
@@ -199,7 +203,7 @@ public:
/// @brief To add and give content from addon to Kodi on related call.
///
- /// @param[in] tag The to transfered data.
+ /// @param[in] tag The to transferred data.
void Add(const kodi::addon::PVRChannel& tag)
{
m_instance->toKodi->TransferChannelEntry(m_instance->toKodi->kodiInstance, m_handle, tag);
@@ -230,12 +234,12 @@ private:
///@{
class PVRSignalStatus : public CStructHdl<PVRSignalStatus, PVR_SIGNAL_STATUS>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRSignalStatus() = default;
PVRSignalStatus(const PVRSignalStatus& type) : CStructHdl(type) {}
- PVRSignalStatus(const PVR_SIGNAL_STATUS* type) : CStructHdl(type) {}
- PVRSignalStatus(PVR_SIGNAL_STATUS* type) : CStructHdl(type) {}
/*! \endcond */
@@ -345,6 +349,10 @@ public:
/// @brief To get with @ref SetBER changed values.
long GetUNC() const { return m_cStructure->iUNC; }
///@}
+
+private:
+ PVRSignalStatus(const PVR_SIGNAL_STATUS* type) : CStructHdl(type) {}
+ PVRSignalStatus(PVR_SIGNAL_STATUS* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
@@ -366,6 +374,8 @@ public:
///@{
class PVRDescrambleInfo : public CStructHdl<PVRDescrambleInfo, PVR_DESCRAMBLE_INFO>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRDescrambleInfo()
@@ -377,8 +387,6 @@ public:
m_cStructure->iHops = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE;
}
PVRDescrambleInfo(const PVRDescrambleInfo& type) : CStructHdl(type) {}
- PVRDescrambleInfo(const PVR_DESCRAMBLE_INFO* type) : CStructHdl(type) {}
- PVRDescrambleInfo(PVR_DESCRAMBLE_INFO* type) : CStructHdl(type) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Channel_PVRDescrambleInfo_Help Value Help
@@ -496,6 +504,10 @@ public:
/// @brief To get with @ref SetProtocol changed values.
std::string GetProtocol() const { return m_cStructure->strProtocol; }
///@}
+
+private:
+ PVRDescrambleInfo(const PVR_DESCRAMBLE_INFO* type) : CStructHdl(type) {}
+ PVRDescrambleInfo(PVR_DESCRAMBLE_INFO* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EDL.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EDL.h
index 0fb3152723..34c7c413bf 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EDL.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EDL.h
@@ -36,12 +36,12 @@ namespace addon
///@{
class PVREDLEntry : public CStructHdl<PVREDLEntry, PVR_EDL_ENTRY>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVREDLEntry() { memset(m_cStructure, 0, sizeof(PVR_EDL_ENTRY)); }
PVREDLEntry(const PVREDLEntry& type) : CStructHdl(type) {}
- PVREDLEntry(const PVR_EDL_ENTRY* type) : CStructHdl(type) {}
- PVREDLEntry(PVR_EDL_ENTRY* type) : CStructHdl(type) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_EDLEntry_PVREDLEntry_Help Value Help
@@ -76,6 +76,10 @@ public:
/// @brief To get with @ref SetType() changed values.
PVR_EDL_TYPE GetType() const { return m_cStructure->type; }
///@}
+
+private:
+ PVREDLEntry(const PVR_EDL_ENTRY* type) : CStructHdl(type) {}
+ PVREDLEntry(PVR_EDL_ENTRY* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EPG.h
index cdfb304a8e..e1fc04f867 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EPG.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/EPG.h
@@ -38,6 +38,8 @@ namespace addon
///@{
class PVREPGTag : public CStructHdl<PVREPGTag, EPG_TAG>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVREPGTag()
@@ -63,8 +65,6 @@ public:
m_seriesLink = epg.m_seriesLink;
m_firstAired = epg.m_firstAired;
}
- PVREPGTag(const EPG_TAG* epg) : CStructHdl(epg) { SetData(epg); }
- PVREPGTag(EPG_TAG* epg) : CStructHdl(epg) { SetData(epg); }
/*! \endcond */
@@ -413,7 +413,9 @@ public:
}
private:
- // prevent the use of them
+ PVREPGTag(const EPG_TAG* epg) : CStructHdl(epg) { SetData(epg); }
+ PVREPGTag(EPG_TAG* epg) : CStructHdl(epg) { SetData(epg); }
+
const PVREPGTag& operator=(const PVREPGTag& right);
const PVREPGTag& operator=(const EPG_TAG& right);
operator EPG_TAG*();
@@ -434,19 +436,19 @@ private:
void SetData(const EPG_TAG* tag)
{
- m_title = tag->strTitle;
- m_plotOutline = tag->strPlotOutline;
- m_plot = tag->strPlot;
- m_originalTitle = tag->strOriginalTitle;
- m_cast = tag->strCast;
- m_director = tag->strDirector;
- m_writer = tag->strWriter;
- m_IMDBNumber = tag->strIMDBNumber;
- m_iconPath = tag->strIconPath;
- m_genreDescription = tag->strGenreDescription;
- m_episodeName = tag->strEpisodeName;
- m_seriesLink = tag->strSeriesLink;
- m_firstAired = tag->strFirstAired;
+ m_title = tag->strTitle == nullptr ? "" : tag->strTitle;
+ m_plotOutline = tag->strPlotOutline == nullptr ? "" : tag->strPlotOutline;
+ m_plot = tag->strPlot == nullptr ? "" : tag->strPlot;
+ m_originalTitle = tag->strOriginalTitle == nullptr ? "" : tag->strOriginalTitle;
+ m_cast = tag->strCast == nullptr ? "" : tag->strCast;
+ m_director = tag->strDirector == nullptr ? "" : tag->strDirector;
+ m_writer = tag->strWriter == nullptr ? "" : tag->strWriter;
+ m_IMDBNumber = tag->strIMDBNumber == nullptr ? "" : tag->strIMDBNumber;
+ m_iconPath = tag->strIconPath == nullptr ? "" : tag->strIconPath;
+ m_genreDescription = tag->strGenreDescription == nullptr ? "" : tag->strGenreDescription;
+ m_episodeName = tag->strEpisodeName == nullptr ? "" : tag->strEpisodeName;
+ m_seriesLink = tag->strSeriesLink == nullptr ? "" : tag->strSeriesLink;
+ m_firstAired = tag->strFirstAired == nullptr ? "" : tag->strFirstAired;
}
};
///@}
@@ -477,7 +479,7 @@ public:
/// @brief To add and give content from addon to Kodi on related call.
///
- /// @param[in] tag The to transfered data.
+ /// @param[in] tag The to transferred data.
void Add(const kodi::addon::PVREPGTag& tag)
{
m_instance->toKodi->TransferEpgEntry(m_instance->toKodi->kodiInstance, m_handle, tag.GetTag());
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/General.h
index 3b0eec2bad..c7977c2786 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/General.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/General.h
@@ -33,11 +33,11 @@ namespace addon
///@{
class PVRTypeIntValue : public CStructHdl<PVRTypeIntValue, PVR_ATTRIBUTE_INT_VALUE>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRTypeIntValue(const PVRTypeIntValue& data) : CStructHdl(data) {}
- PVRTypeIntValue(const PVR_ATTRIBUTE_INT_VALUE* data) : CStructHdl(data) {}
- PVRTypeIntValue(PVR_ATTRIBUTE_INT_VALUE* data) : CStructHdl(data) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help Value Help
@@ -84,8 +84,11 @@ public:
/// @brief To get with the description text of the value.
std::string GetDescription() const { return m_cStructure->strDescription; }
-
///@}
+
+private:
+ PVRTypeIntValue(const PVR_ATTRIBUTE_INT_VALUE* data) : CStructHdl(data) {}
+ PVRTypeIntValue(PVR_ATTRIBUTE_INT_VALUE* data) : CStructHdl(data) {}
};
///@}
//------------------------------------------------------------------------------
@@ -111,10 +114,11 @@ public:
///@{
class PVRCapabilities
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
explicit PVRCapabilities() = delete;
- PVRCapabilities(PVR_ADDON_CAPABILITIES* capabilities) : m_capabilities(capabilities) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_PVRCapabilities_Help Value Help
@@ -383,6 +387,8 @@ public:
///@}
private:
+ PVRCapabilities(PVR_ADDON_CAPABILITIES* capabilities) : m_capabilities(capabilities) {}
+
PVR_ADDON_CAPABILITIES* m_capabilities;
};
///@}
@@ -437,11 +443,11 @@ private:
///@{
class PVRStreamProperty : public CStructHdl<PVRStreamProperty, PVR_NAMED_VALUE>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRStreamProperty(const PVRStreamProperty& data) : CStructHdl(data) {}
- PVRStreamProperty(const PVR_NAMED_VALUE* data) : CStructHdl(data) {}
- PVRStreamProperty(PVR_NAMED_VALUE* data) : CStructHdl(data) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_General_Inputstream_PVRStreamProperty_Help Value Help
@@ -490,8 +496,11 @@ public:
/// @brief To get with the used property value.
std::string GetValue() const { return m_cStructure->strValue; }
-
///@}
+
+private:
+ PVRStreamProperty(const PVR_NAMED_VALUE* data) : CStructHdl(data) {}
+ PVRStreamProperty(PVR_NAMED_VALUE* data) : CStructHdl(data) {}
};
///@}
//------------------------------------------------------------------------------
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/MenuHook.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/MenuHook.h
index b3f65f1199..053a4d5cf1 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/MenuHook.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/MenuHook.h
@@ -44,6 +44,8 @@ namespace addon
///@{
class PVRMenuhook : public CStructHdl<PVRMenuhook, PVR_MENUHOOK>
{
+ friend class CInstancePVRClient;
+
public:
/// @addtogroup cpp_kodi_addon_pvr_Defs_Menuhook_PVRMenuhook
/// @brief Optional class constructor with value set.
@@ -75,8 +77,6 @@ public:
m_cStructure->category = PVR_MENUHOOK_UNKNOWN;
}
PVRMenuhook(const PVRMenuhook& data) : CStructHdl(data) {}
- PVRMenuhook(const PVR_MENUHOOK* data) : CStructHdl(data) {}
- PVRMenuhook(PVR_MENUHOOK* data) : CStructHdl(data) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Menuhook_PVRMenuhook_Help Value Help
@@ -115,8 +115,11 @@ public:
/// @brief To get with @ref SetCategory() changed values.
PVR_MENUHOOK_CAT GetCategory() const { return m_cStructure->category; }
-
///@}
+
+private:
+ PVRMenuhook(const PVR_MENUHOOK* data) : CStructHdl(data) {}
+ PVRMenuhook(PVR_MENUHOOK* data) : CStructHdl(data) {}
};
///@}
//------------------------------------------------------------------------------
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Recordings.h
index 56feb899d7..24ecf11c8d 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Recordings.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Recordings.h
@@ -37,6 +37,8 @@ namespace addon
///@{
class PVRRecording : public CStructHdl<PVRRecording, PVR_RECORDING>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRRecording()
@@ -59,8 +61,6 @@ public:
m_cStructure->sizeInBytes = PVR_RECORDING_VALUE_NOT_AVAILABLE;
}
PVRRecording(const PVRRecording& recording) : CStructHdl(recording) {}
- PVRRecording(const PVR_RECORDING* recording) : CStructHdl(recording) {}
- PVRRecording(PVR_RECORDING* recording) : CStructHdl(recording) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Recording_PVRRecording_Help Value Help
@@ -466,6 +466,10 @@ public:
/// @brief To get with @ref SetSizeInBytes changed values.
int64_t GetSizeInBytes() const { return m_cStructure->sizeInBytes; }
///@}
+
+private:
+ PVRRecording(const PVR_RECORDING* recording) : CStructHdl(recording) {}
+ PVRRecording(PVR_RECORDING* recording) : CStructHdl(recording) {}
};
///@}
//------------------------------------------------------------------------------
@@ -495,7 +499,7 @@ public:
/// @brief To add and give content from addon to Kodi on related call.
///
- /// @param[in] tag The to transfered data.
+ /// @param[in] tag The to transferred data.
void Add(const kodi::addon::PVRRecording& tag)
{
m_instance->toKodi->TransferRecordingEntry(m_instance->toKodi->kodiInstance, m_handle, tag);
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Stream.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Stream.h
index e0de1e810b..5613947059 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Stream.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Stream.h
@@ -37,6 +37,8 @@ namespace addon
///@{
class PVRCodec : public CStructHdl<PVRCodec, PVR_CODEC>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRCodec()
@@ -45,9 +47,6 @@ public:
m_cStructure->codec_id = PVR_INVALID_CODEC_ID;
}
PVRCodec(const PVRCodec& type) : CStructHdl(type) {}
- PVRCodec(const PVR_CODEC& type) : CStructHdl(&type) {}
- PVRCodec(const PVR_CODEC* type) : CStructHdl(type) {}
- PVRCodec(PVR_CODEC* type) : CStructHdl(type) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Stream_PVRCodec_Help Value Help
@@ -77,6 +76,11 @@ public:
/// @brief To get with @ref SetCodecId() changed values.
unsigned int GetCodecId() const { return m_cStructure->codec_id; }
///@}
+
+private:
+ PVRCodec(const PVR_CODEC& type) : CStructHdl(&type) {}
+ PVRCodec(const PVR_CODEC* type) : CStructHdl(type) {}
+ PVRCodec(PVR_CODEC* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
@@ -96,12 +100,12 @@ public:
class PVRStreamProperties
: public CStructHdl<PVRStreamProperties, PVR_STREAM_PROPERTIES::PVR_STREAM>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRStreamProperties() { memset(m_cStructure, 0, sizeof(PVR_STREAM_PROPERTIES::PVR_STREAM)); }
PVRStreamProperties(const PVRStreamProperties& type) : CStructHdl(type) {}
- PVRStreamProperties(const PVR_STREAM_PROPERTIES::PVR_STREAM* type) : CStructHdl(type) {}
- PVRStreamProperties(PVR_STREAM_PROPERTIES::PVR_STREAM* type) : CStructHdl(type) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Stream_PVRStreamProperties_Help Value Help
@@ -234,6 +238,10 @@ public:
/// @brief To get with @ref SetBitsPerSample() changed values.
int GetBitsPerSample() const { return m_cStructure->iBitsPerSample; }
///@}
+
+private:
+ PVRStreamProperties(const PVR_STREAM_PROPERTIES::PVR_STREAM* type) : CStructHdl(type) {}
+ PVRStreamProperties(PVR_STREAM_PROPERTIES::PVR_STREAM* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
@@ -252,12 +260,12 @@ public:
///@{
class PVRStreamTimes : public CStructHdl<PVRStreamTimes, PVR_STREAM_TIMES>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRStreamTimes() { memset(m_cStructure, 0, sizeof(PVR_STREAM_TIMES)); }
PVRStreamTimes(const PVRStreamTimes& type) : CStructHdl(type) {}
- PVRStreamTimes(const PVR_STREAM_TIMES* type) : CStructHdl(type) {}
- PVRStreamTimes(PVR_STREAM_TIMES* type) : CStructHdl(type) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Stream_PVRStreamTimes_Help Value Help
@@ -308,6 +316,10 @@ public:
/// @brief To get with @ref SetPTSEnd() changed values.
int64_t GetPTSEnd() const { return m_cStructure->ptsEnd; }
///@}
+
+private:
+ PVRStreamTimes(const PVR_STREAM_TIMES* type) : CStructHdl(type) {}
+ PVRStreamTimes(PVR_STREAM_TIMES* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Timers.h
index b838f1c08d..6e05e55b6f 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Timers.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/addon-instance/pvr/Timers.h
@@ -37,6 +37,8 @@ namespace addon
///@{
class PVRTimer : public CStructHdl<PVRTimer, PVR_TIMER>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRTimer()
@@ -65,8 +67,6 @@ public:
m_cStructure->iGenreSubType = PVR_TIMER_VALUE_NOT_AVAILABLE;
}
PVRTimer(const PVRTimer& data) : CStructHdl(data) {}
- PVRTimer(const PVR_TIMER* data) : CStructHdl(data) {}
- PVRTimer(PVR_TIMER* data) : CStructHdl(data) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimer_Help Value Help
@@ -465,6 +465,10 @@ public:
/// @brief To get with @ref SetSeriesLink changed values.
std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; }
///@}
+
+private:
+ PVRTimer(const PVR_TIMER* data) : CStructHdl(data) {}
+ PVRTimer(PVR_TIMER* data) : CStructHdl(data) {}
};
///@}
@@ -495,7 +499,7 @@ public:
/// @brief To add and give content from addon to Kodi on related call.
///
- /// @param[in] tag The to transfered data.
+ /// @param[in] tag The to transferred data.
void Add(const kodi::addon::PVRTimer& tag)
{
m_instance->toKodi->TransferTimerEntry(m_instance->toKodi->kodiInstance, m_handle, tag);
@@ -524,6 +528,8 @@ private:
///@{
class PVRTimerType : public CStructHdl<PVRTimerType, PVR_TIMER_TYPE>
{
+ friend class CInstancePVRClient;
+
public:
/*! \cond PRIVATE */
PVRTimerType()
@@ -536,8 +542,6 @@ public:
m_cStructure->iMaxRecordingsDefault = -1;
}
PVRTimerType(const PVRTimerType& type) : CStructHdl(type) {}
- PVRTimerType(const PVR_TIMER_TYPE* type) : CStructHdl(type) {}
- PVRTimerType(PVR_TIMER_TYPE* type) : CStructHdl(type) {}
/*! \endcond */
/// @defgroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimerType_Help Value Help
@@ -627,7 +631,7 @@ public:
/// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help
void SetPriorities(const std::vector<PVRTypeIntValue>& priorities, int prioritiesDefault = -1)
{
- m_cStructure->iPrioritiesSize = priorities.size();
+ m_cStructure->iPrioritiesSize = static_cast<unsigned int>(priorities.size());
for (unsigned int i = 0;
i < m_cStructure->iPrioritiesSize && i < sizeof(m_cStructure->priorities); ++i)
{
@@ -679,7 +683,7 @@ public:
/// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help
void SetLifetimes(const std::vector<PVRTypeIntValue>& lifetimes, int lifetimesDefault = -1)
{
- m_cStructure->iLifetimesSize = lifetimes.size();
+ m_cStructure->iLifetimesSize = static_cast<unsigned int>(lifetimes.size());
for (unsigned int i = 0;
i < m_cStructure->iLifetimesSize && i < sizeof(m_cStructure->lifetimes); ++i)
{
@@ -735,7 +739,8 @@ public:
const std::vector<PVRTypeIntValue>& preventDuplicateEpisodes,
int preventDuplicateEpisodesDefault = -1)
{
- m_cStructure->iPreventDuplicateEpisodesSize = preventDuplicateEpisodes.size();
+ m_cStructure->iPreventDuplicateEpisodesSize =
+ static_cast<unsigned int>(preventDuplicateEpisodes.size());
for (unsigned int i = 0; i < m_cStructure->iPreventDuplicateEpisodesSize &&
i < sizeof(m_cStructure->preventDuplicateEpisodes);
++i)
@@ -791,7 +796,7 @@ public:
void SetRecordingGroups(const std::vector<PVRTypeIntValue>& recordingGroup,
int recordingGroupDefault = -1)
{
- m_cStructure->iRecordingGroupSize = recordingGroup.size();
+ m_cStructure->iRecordingGroupSize = static_cast<unsigned int>(recordingGroup.size());
for (unsigned int i = 0;
i < m_cStructure->iRecordingGroupSize && i < sizeof(m_cStructure->recordingGroup); ++i)
{
@@ -842,7 +847,7 @@ public:
void SetMaxRecordings(const std::vector<PVRTypeIntValue>& maxRecordings,
int maxRecordingsDefault = -1)
{
- m_cStructure->iMaxRecordingsSize = maxRecordings.size();
+ m_cStructure->iMaxRecordingsSize = static_cast<unsigned int>(maxRecordings.size());
for (unsigned int i = 0;
i < m_cStructure->iMaxRecordingsSize && i < sizeof(m_cStructure->maxRecordings); ++i)
{
@@ -877,6 +882,10 @@ public:
/// @brief To get with @ref SetMaxRecordingsDefault changed values
int GetMaxRecordingsDefault() const { return m_cStructure->iMaxRecordingsDefault; }
///@}
+
+private:
+ PVRTimerType(const PVR_TIMER_TYPE* type) : CStructHdl(type) {}
+ PVRTimerType(PVR_TIMER_TYPE* type) : CStructHdl(type) {}
};
///@}
//------------------------------------------------------------------------------
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/c-api/addon_base.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/c-api/addon_base.h
index bc392c4740..1924d7711b 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/c-api/addon_base.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/c-api/addon_base.h
@@ -190,6 +190,9 @@ extern "C"
struct AddonToKodiFuncTable_kodi_filesystem* kodi_filesystem;
struct AddonToKodiFuncTable_kodi_gui* kodi_gui;
struct AddonToKodiFuncTable_kodi_network* kodi_network;
+
+ // Move up by min version change about
+ bool (*is_setting_using_default)(void* kodiBase, const char* id);
} AddonToKodiFuncTable_Addon;
/*!
diff --git a/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
index 86045c7fae..041e22b1fa 100644
--- a/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
+++ b/xbmc/addons/kodi-addon-dev-kit/include/kodi/versions.h
@@ -34,7 +34,7 @@
// because cmake uses this area in this form to perform its addon dependency
// check.
// clang-format off
-#define ADDON_GLOBAL_VERSION_MAIN "1.2.3"
+#define ADDON_GLOBAL_VERSION_MAIN "1.2.4"
#define ADDON_GLOBAL_VERSION_MAIN_MIN "1.2.0"
#define ADDON_GLOBAL_VERSION_MAIN_XML_ID "kodi.binary.global.main"
#define ADDON_GLOBAL_VERSION_MAIN_DEPENDS "AddonBase.h" \
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
index 41befc8859..9bf0733cdf 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
@@ -942,7 +942,7 @@ void CAESinkALSA::HandleError(const char* name, int err)
while((err = snd_pcm_resume(m_pcm)) == -EAGAIN)
KODI::TIME::Sleep(1);
- /* if the hardware doesnt support resume, prepare the stream */
+ /* if the hardware doesn't support resume, prepare the stream */
if (err == -ENOSYS)
if ((err = snd_pcm_prepare(m_pcm)) < 0)
CLog::Log(LOGERROR, "CAESinkALSA::HandleError(%s) - snd_pcm_prepare returned %d (%s)", name, err, snd_strerror(err));
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
index 9034e27a6a..10efd5a43a 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
@@ -234,6 +234,7 @@ CAEDeviceInfo CAESinkAUDIOTRACK::m_info_raw;
bool CAESinkAUDIOTRACK::m_hasIEC = false;
std::set<unsigned int> CAESinkAUDIOTRACK::m_sink_sampleRates;
bool CAESinkAUDIOTRACK::m_sinkSupportsFloat = false;
+bool CAESinkAUDIOTRACK::m_sinkSupportsMultiChannelFloat = false;
////////////////////////////////////////////////////////////////////////////////////////////
CAESinkAUDIOTRACK::CAESinkAUDIOTRACK()
@@ -384,7 +385,12 @@ bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device)
{
m_passthrough = false;
m_format.m_sampleRate = m_sink_sampleRate;
- if (m_sinkSupportsFloat && m_format.m_channelLayout.Count() == 2)
+ if (m_sinkSupportsMultiChannelFloat)
+ {
+ m_encoding = CJNIAudioFormat::ENCODING_PCM_FLOAT;
+ m_format.m_dataFormat = AE_FMT_FLOAT;
+ }
+ else if (m_sinkSupportsFloat && m_format.m_channelLayout.Count() == 2)
{
m_encoding = CJNIAudioFormat::ENCODING_PCM_FLOAT;
m_format.m_dataFormat = AE_FMT_FLOAT;
@@ -1070,6 +1076,10 @@ void CAESinkAUDIOTRACK::UpdateAvailablePCMCapabilities()
int encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;
m_sinkSupportsFloat = VerifySinkConfiguration(native_sampleRate, CJNIAudioFormat::CHANNEL_OUT_STEREO, CJNIAudioFormat::ENCODING_PCM_FLOAT);
+ // Only try for Android 7 or later - there are a lot of old devices that open successfully
+ // but won't work correctly under the hood (famouse example: old FireTV)
+ if (CJNIAudioManager::GetSDKVersion() > 23)
+ m_sinkSupportsMultiChannelFloat = VerifySinkConfiguration(native_sampleRate, CJNIAudioFormat::CHANNEL_OUT_7POINT1_SURROUND, CJNIAudioFormat::ENCODING_PCM_FLOAT);
if (m_sinkSupportsFloat)
{
@@ -1077,6 +1087,10 @@ void CAESinkAUDIOTRACK::UpdateAvailablePCMCapabilities()
m_info.m_dataFormats.push_back(AE_FMT_FLOAT);
CLog::Log(LOGINFO, "Float is supported");
}
+ if (m_sinkSupportsMultiChannelFloat)
+ {
+ CLog::Log(LOGNOTICE, "Multi channel Float is supported");
+ }
int test_sample[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
int test_sample_sz = sizeof(test_sample) / sizeof(int);
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h
index 88ae3be1b4..6409c42c23 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h
@@ -78,6 +78,7 @@ private:
static bool m_hasIEC;
static std::set<unsigned int> m_sink_sampleRates;
static bool m_sinkSupportsFloat;
+ static bool m_sinkSupportsMultiChannelFloat;
AEAudioFormat m_format;
int16_t *m_alignedS16;
diff --git a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
index f6704ad139..8ef9a2aa48 100644
--- a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
+++ b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
@@ -140,7 +140,7 @@ void CAEBitstreamPacker::PackTrueHD(CAEStreamInfo &info, uint8_t* data, int size
static const uint8_t mat_middle_code[12] = { 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0 };
static const uint8_t mat_end_code [16] = { 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11 };
- /* create the buffer if it doesnt already exist */
+ /* create the buffer if it doesn't already exist */
if (!m_trueHD)
{
m_trueHD = new uint8_t[MAT_FRAME_SIZE];
diff --git a/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp
index 6af528377d..60b1d6e40e 100644
--- a/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp
+++ b/xbmc/cores/AudioEngine/Utils/AEChannelInfo.cpp
@@ -180,7 +180,7 @@ CAEChannelInfo& CAEChannelInfo::operator=(const enum AEStdChLayout rhs)
bool CAEChannelInfo::operator==(const CAEChannelInfo& rhs) const
{
- /* if the channel count doesnt match, no need to check further */
+ /* if the channel count doesn't match, no need to check further */
if (m_channelCount != rhs.m_channelCount)
return false;
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h
index 2d26b2adb8..e0b1d0955a 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodec.h
@@ -15,7 +15,7 @@
#include "PlatformDefs.h"
// VC_ messages, messages can be combined
-#define OC_ERROR 0x00000001 // an error occured, no other messages will be returned
+#define OC_ERROR 0x00000001 // an error occurred, no other messages will be returned
#define OC_BUFFER 0x00000002 // the decoder needs more data
#define OC_OVERLAY 0x00000004 // the decoder decoded an overlay, call Decode(NULL, 0) again to parse the rest of the data
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
index 3b38482ff8..e1591c2234 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
@@ -109,7 +109,7 @@ public:
enum VCReturn
{
VC_NONE = 0,
- VC_ERROR, //< an error occured, no other messages will be returned
+ VC_ERROR, //< an error occurred, no other messages will be returned
VC_FATAL, //< non recoverable error
VC_BUFFER, //< the decoder needs more data
VC_PICTURE, //< the decoder got a picture, call Decode(NULL, 0) again to parse the rest of the data
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp
index eba164c89b..1c49fdfca9 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp
@@ -281,32 +281,38 @@ bool CContext::CreateContext()
ComPtr<ID3D11DeviceContext> pD3DDeviceContext;
m_sharingAllowed = DX::DeviceResources::Get()->DoesTextureSharingWork();
+ // Workaround for Nvidia stuttering on 4K HDR playback
+ // Some tests/feedback on Windows 10 2004 / NV driver 446.14
+ // Not needed: GTX 1650, GTX 1060, ...
+ // Needed: RTX 2080 Ti, ...
+ if (m_sharingAllowed &&
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_disableDXVAdiscreteDecoding)
+ {
+ m_sharingAllowed = false;
+ CLog::LogF(LOGWARNING, "disabled discrete d3d11va device for decoding due advancedsettings "
+ "option 'disableDXVAdiscretedecoder'.");
+ }
+
if (m_sharingAllowed)
{
CLog::LogF(LOGWARNING, "creating discrete d3d11va device for decoding.");
- D3D_FEATURE_LEVEL featureLevels[] =
- {
- D3D_FEATURE_LEVEL_11_1,
- D3D_FEATURE_LEVEL_11_0,
- D3D_FEATURE_LEVEL_10_1,
- D3D_FEATURE_LEVEL_10_0,
- D3D_FEATURE_LEVEL_9_3,
- D3D_FEATURE_LEVEL_9_2,
- D3D_FEATURE_LEVEL_9_1
- };
-
- hr = D3D11CreateDevice(
- DX::DeviceResources::Get()->GetAdapter(),
- D3D_DRIVER_TYPE_UNKNOWN,
- nullptr,
- D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
- featureLevels,
- ARRAYSIZE(featureLevels),
- D3D11_SDK_VERSION,
- &pD3DDevice,
- nullptr,
- &pD3DDeviceContext);
+ std::vector<D3D_FEATURE_LEVEL> featureLevels;
+ if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10))
+ featureLevels.push_back(D3D_FEATURE_LEVEL_12_0);
+ if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8))
+ featureLevels.push_back(D3D_FEATURE_LEVEL_11_1);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_11_0);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_10_1);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_10_0);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_9_3);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_9_2);
+ featureLevels.push_back(D3D_FEATURE_LEVEL_9_1);
+
+ hr = D3D11CreateDevice(DX::DeviceResources::Get()->GetAdapter(), D3D_DRIVER_TYPE_UNKNOWN,
+ nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, featureLevels.data(),
+ featureLevels.size(), D3D11_SDK_VERSION, &pD3DDevice, nullptr,
+ &pD3DDeviceContext);
if (SUCCEEDED(hr))
{
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h
index 407e18cf6e..20c1942d4d 100644
--- a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/dvdnav_events.h
@@ -149,7 +149,7 @@ typedef struct {
* they carry the start and end PTS values for the current VOBU.
* (pci.vobu_s_ptm and pci.vobu_e_ptm) Whenever the start PTS of the
* current NAV does not match the end PTS of the previous NAV, a PTS
- * discontinuity has occured.
+ * discontinuity has occurred.
* NAV packets can also be used for time display, because they are
* timestamped relatively to the current Cell.
*/
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp
index dab2be6475..5c18398157 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp
@@ -12,17 +12,16 @@
#define DEFAULT_STREAM_INDEX (0)
#include "DXVAHD.h"
-#include "platform/win32/WIN32Util.h"
-#include "rendering/dx/RenderContext.h"
-#include "rendering/dx/DeviceResources.h"
-#include "VideoRenderers/RenderManager.h"
+
#include "VideoRenderers/RenderFlags.h"
+#include "VideoRenderers/RenderManager.h"
#include "VideoRenderers/windows/RendererBase.h"
+#include "rendering/dx/RenderContext.h"
#include "utils/log.h"
#include <Windows.h>
-#include <dxgi1_5.h>
#include <d3d11_4.h>
+#include <dxgi1_5.h>
using namespace DXVA;
using namespace Microsoft::WRL;
@@ -36,12 +35,6 @@ do { \
} \
} while(0);
-template<typename T>
-T from_rational(uint64_t default_factor, AVRational rat)
-{
- return static_cast<T>(default_factor * rat.num / rat.den);
-}
-
CProcessorHD::CProcessorHD()
{
DX::Windowing()->Register(this);
@@ -146,7 +139,7 @@ bool CProcessorHD::InitProcessor()
CLog::LogF(LOGDEBUG, "video processor has %#x input format caps.", m_vcaps.InputFormatCaps);
CLog::LogF(LOGDEBUG, "video processor has %d max input streams.", m_vcaps.MaxInputStreams);
CLog::LogF(LOGDEBUG, "video processor has %d max stream states.", m_vcaps.MaxStreamStates);
- if ((m_bSupportHDR10 = m_vcaps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_METADATA_HDR10))
+ if (m_vcaps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_METADATA_HDR10)
CLog::LogF(LOGDEBUG, "video processor supports HDR10.");
if (0 != (m_vcaps.FeatureCaps & D3D11_VIDEO_PROCESSOR_FEATURE_CAPS_LEGACY))
@@ -331,7 +324,7 @@ ID3D11VideoProcessorInputView* CProcessorHD::GetInputView(CRenderBuffer* view) c
return inputView.Detach();
}
-DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpace(CRenderBuffer* view, bool supportHDR)
+DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpaceSource(CRenderBuffer* view, bool supportHDR)
{
// RGB
if (view->color_space == AVCOL_SPC_RGB)
@@ -364,11 +357,14 @@ DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpace(CRenderBuffer* view, bool
// UHDTV
if (view->primaries == AVCOL_PRI_BT2020)
{
- if (view->color_transfer == AVCOL_TRC_SMPTEST2084 && supportHDR) // HDR
+ if (view->color_transfer == AVCOL_TRC_SMPTEST2084 && supportHDR) // HDR10
// Could also be:
// DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020
return DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020;
+ if (view->color_transfer == AVCOL_TRC_ARIB_STD_B67) // HLG
+ return DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020;
+
if (view->full_range)
return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020;
@@ -397,6 +393,38 @@ DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpace(CRenderBuffer* view, bool
return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709;
}
+DXGI_COLOR_SPACE_TYPE CProcessorHD::GetDXGIColorSpaceTarget(CRenderBuffer* view)
+{
+ DXGI_COLOR_SPACE_TYPE color;
+
+ // HDR10
+ if (view->color_transfer == AVCOL_TRC_SMPTE2084 && DX::Windowing()->IsHDROutput())
+ {
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020
+ : DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+ }
+ // HLG
+ else if (view->color_transfer == AVCOL_TRC_ARIB_STD_B67 && view->primaries == AVCOL_PRI_BT2020)
+ {
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020
+ : DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020;
+ }
+ // Rec.2020
+ else if (view->color_transfer != AVCOL_TRC_SMPTE2084 && view->primaries == AVCOL_PRI_BT2020)
+ {
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020
+ : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020;
+ }
+ // SRD - Default
+ else
+ {
+ color = DX::Windowing()->UseLimitedColor() ? DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709
+ : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ }
+
+ return color;
+}
+
bool CProcessorHD::Render(CRect src, CRect dst, ID3D11Resource* target, CRenderBuffer** views, DWORD flags, UINT frameIdx, UINT rotation, float contrast, float brightness)
{
CSingleLock lock(m_section);
@@ -504,47 +532,15 @@ bool CProcessorHD::Render(CRect src, CRect dst, ID3D11Resource* target, CRenderB
ComPtr<ID3D11VideoContext1> videoCtx1;
if (SUCCEEDED(m_pVideoContext.As(&videoCtx1)))
{
- const DXGI_COLOR_SPACE_TYPE source_color = GetDXGIColorSpace(views[2], m_bSupportHDR10);
- const DXGI_COLOR_SPACE_TYPE target_color = DX::Windowing()->UseLimitedColor()
- ? DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709
- : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ const DXGI_COLOR_SPACE_TYPE sourceColor =
+ GetDXGIColorSpaceSource(views[2], DX::Windowing()->IsHDROutput());
+ const DXGI_COLOR_SPACE_TYPE targetColor = GetDXGIColorSpaceTarget(views[2]);
- videoCtx1->VideoProcessorSetStreamColorSpace1(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX, source_color);
- videoCtx1->VideoProcessorSetOutputColorSpace1(m_pVideoProcessor.Get(), target_color);
+ videoCtx1->VideoProcessorSetStreamColorSpace1(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX,
+ sourceColor);
+ videoCtx1->VideoProcessorSetOutputColorSpace1(m_pVideoProcessor.Get(), targetColor);
// makes target available for processing in shaders
videoCtx1->VideoProcessorSetOutputShaderUsage(m_pVideoProcessor.Get(), 1);
-
- if (m_bSupportHDR10)
- {
- ComPtr<ID3D11VideoContext2> videoCtx2;
- if (SUCCEEDED(m_pVideoContext.As(&videoCtx2)) && views[2]->hasDisplayMetadata)
- {
- DXGI_HDR_METADATA_HDR10 hdr10 = {};
- hdr10.WhitePoint[0] = from_rational<uint16_t>(50000, views[2]->displayMetadata.white_point[0]);
- hdr10.WhitePoint[1] = from_rational<uint16_t>(50000, views[2]->displayMetadata.white_point[1]);
- if (views[2]->displayMetadata.has_primaries)
- {
- hdr10.RedPrimary[0] = from_rational<uint16_t>(50000, views[2]->displayMetadata.display_primaries[0][0]);
- hdr10.RedPrimary[1] = from_rational<uint16_t>(50000, views[2]->displayMetadata.display_primaries[0][1]);
- hdr10.GreenPrimary[0] = from_rational<uint16_t>(50000, views[2]->displayMetadata.display_primaries[1][0]);
- hdr10.GreenPrimary[1] = from_rational<uint16_t>(50000, views[2]->displayMetadata.display_primaries[1][1]);
- hdr10.BluePrimary[0] = from_rational<uint16_t>(50000, views[2]->displayMetadata.display_primaries[2][0]);
- hdr10.BluePrimary[1] = from_rational<uint16_t>(50000, views[2]->displayMetadata.display_primaries[2][1]);
- }
- if (views[2]->displayMetadata.has_luminance)
- {
- hdr10.MinMasteringLuminance = from_rational<uint32_t>(10000, views[2]->displayMetadata.min_luminance);
- hdr10.MaxMasteringLuminance = from_rational<uint32_t>(10000, views[2]->displayMetadata.max_luminance);
- }
- if (views[2]->hasLightMetadata)
- {
- hdr10.MaxContentLightLevel = static_cast<uint16_t>(views[2]->lightMetadata.MaxCLL);
- hdr10.MaxFrameAverageLightLevel = static_cast<uint16_t>(views[2]->lightMetadata.MaxFALL);
- }
- videoCtx2->VideoProcessorSetStreamHDRMetaData(m_pVideoProcessor.Get(), DEFAULT_STREAM_INDEX,
- DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10), &hdr10);
- }
- }
}
else
{
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h
index aaecda1bb5..c54fec074e 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.h
@@ -49,7 +49,8 @@ public:
void OnCreateDevice() override {}
void OnDestroyDevice(bool) override { CSingleLock lock(m_section); UnInit(); }
- static DXGI_COLOR_SPACE_TYPE GetDXGIColorSpace(CRenderBuffer*, bool);
+ static DXGI_COLOR_SPACE_TYPE GetDXGIColorSpaceSource(CRenderBuffer* view, bool supportHDR);
+ static DXGI_COLOR_SPACE_TYPE GetDXGIColorSpaceTarget(CRenderBuffer* view);
protected:
bool ReInit();
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp
index 930ff1e396..020fb83b83 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp
@@ -13,7 +13,6 @@
#include "VideoRenderers/windows/RendererBase.h"
#include "cores/VideoPlayer/VideoRenderers/VideoShaders/dither.h"
#include "filesystem/File.h"
-#include "rendering/dx/DeviceResources.h"
#include "rendering/dx/RenderContext.h"
#include "utils/MemUtils.h"
#include "utils/log.h"
@@ -228,7 +227,7 @@ bool COutputShader::Create(bool useLUT, bool useDithering, int ditherDepth, bool
{
m_useLut = useLUT;
m_ditherDepth = ditherDepth;
- m_toneMapping = toneMapping;
+ m_toneMapping = toneMapping && !DX::Windowing()->IsHDROutput();
CWinShader::CreateVertexBuffer(4, sizeof(Vertex));
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp
index e48e2e4b31..4e52d1cede 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/WinRenderer.cpp
@@ -210,8 +210,9 @@ void CWinRenderer::RenderUpdate(int index, int index2, bool clear, unsigned int
DX::Windowing()->SetAlphaBlendEnable(alpha < 255);
ManageRenderArea();
- m_renderer->Render(index, index2, DX::Windowing()->GetBackBuffer(),
- m_sourceRect, m_destRect, GetScreenRect(), flags);
+ m_renderer->Render(index, index2, DX::Windowing()->GetBackBuffer(), m_sourceRect, m_destRect,
+ GetScreenRect(), flags);
+ DX::Windowing()->SetAlphaBlendEnable(true);
}
bool CWinRenderer::RenderCapture(CRenderCapture* capture)
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp
index 7b23f4b058..8ef572e6e0 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.cpp
@@ -10,10 +10,13 @@
#include "DVDCodecs/Video/DVDVideoCodec.h"
#include "DVDCodecs/Video/DXVA.h"
+#include "ServiceBroker.h"
#include "VideoRenderers/BaseRenderer.h"
#include "VideoRenderers/RenderFlags.h"
#include "cores/VideoPlayer/Buffers/VideoBuffer.h"
#include "rendering/dx/RenderContext.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
#include "utils/MemUtils.h"
#include "utils/log.h"
#include "windowing/GraphicContext.h"
@@ -131,6 +134,13 @@ CRendererBase::CRendererBase(CVideoSettings& videoSettings)
CRendererBase::~CRendererBase()
{
+ if (DX::Windowing()->IsHDROutput())
+ {
+ CLog::LogF(LOGDEBUG, "Restoring SDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ if (m_AutoSwitchHDR)
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR OFF
+ }
Flush(false);
}
@@ -163,6 +173,29 @@ bool CRendererBase::Configure(const VideoPicture& picture, float fps, unsigned o
m_fps = fps;
m_renderOrientation = orientation;
+ m_lastHdr10 = {};
+ m_iCntMetaData = 0;
+ m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ m_AutoSwitchHDR = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ DX::Windowing()->SETTING_WINSYSTEM_IS_HDR_DISPLAY) &&
+ DX::Windowing()->IsHDRDisplay();
+
+ // Auto switch HDR only if supported and "Settings/Player/Use HDR display capabilities" = ON
+ if (m_AutoSwitchHDR)
+ {
+ // Stream is HDR10 or HLG or Rec.2020 (wide color)
+ if (picture.color_primaries == AVCOL_PRI_BT2020)
+ {
+ if (!DX::Windowing()->IsHDROutput())
+ DX::Windowing()->ToggleHDR(); // Toggle disply HDR ON
+ }
+ else // Stream is SDR
+ {
+ if (DX::Windowing()->IsHDROutput())
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR OFF
+ }
+ }
+
return true;
}
@@ -196,6 +229,8 @@ void CRendererBase::Render(CD3DTexture& target, const CRect& sourceRect, const C
return;
}
+ ProcessHDR(buf);
+
if (m_viewWidth != static_cast<unsigned>(viewRect.Width()) ||
m_viewHeight != static_cast<unsigned>(viewRect.Height()))
{
@@ -393,7 +428,7 @@ void CRendererBase::CheckVideoParameters()
CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex];
bool toneMap = false;
- if (m_videoSettings.m_ToneMapMethod != VS_TONEMAPMETHOD_OFF)
+ if (m_videoSettings.m_ToneMapMethod != VS_TONEMAPMETHOD_OFF && !DX::Windowing()->IsHDROutput())
{
if (buf->hasLightMetadata || buf->hasDisplayMetadata && buf->displayMetadata.has_luminance)
toneMap = true;
@@ -445,3 +480,117 @@ AVPixelFormat CRendererBase::GetAVFormat(DXGI_FORMAT dxgi_format)
return AV_PIX_FMT_NONE;
}
}
+
+DXGI_HDR_METADATA_HDR10 CRendererBase::GetDXGIHDR10MetaData(CRenderBuffer* rb)
+{
+ DXGI_HDR_METADATA_HDR10 hdr10 = {};
+
+ if (rb->displayMetadata.has_primaries)
+ {
+ hdr10.RedPrimary[0] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[0][0].num);
+ hdr10.RedPrimary[1] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[0][1].num);
+ hdr10.GreenPrimary[0] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[1][0].num);
+ hdr10.GreenPrimary[1] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[1][1].num);
+ hdr10.BluePrimary[0] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[2][0].num);
+ hdr10.BluePrimary[1] = static_cast<uint16_t>(rb->displayMetadata.display_primaries[2][1].num);
+ hdr10.WhitePoint[0] = static_cast<uint16_t>(rb->displayMetadata.white_point[0].num);
+ hdr10.WhitePoint[1] = static_cast<uint16_t>(rb->displayMetadata.white_point[1].num);
+ }
+ if (rb->displayMetadata.has_luminance)
+ {
+ hdr10.MaxMasteringLuminance = static_cast<uint32_t>(rb->displayMetadata.max_luminance.num);
+ hdr10.MinMasteringLuminance = static_cast<uint32_t>(rb->displayMetadata.min_luminance.num);
+ }
+ if (rb->hasLightMetadata)
+ {
+ hdr10.MaxContentLightLevel = static_cast<uint16_t>(rb->lightMetadata.MaxCLL);
+ hdr10.MaxFrameAverageLightLevel = static_cast<uint16_t>(rb->lightMetadata.MaxFALL);
+ }
+
+ return hdr10;
+}
+
+void CRendererBase::ProcessHDR(CRenderBuffer* rb)
+{
+ if (m_AutoSwitchHDR && rb->primaries == AVCOL_PRI_BT2020 && !DX::Windowing()->IsHDROutput())
+ {
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR ON
+ }
+
+ if (!DX::Windowing()->IsHDROutput())
+ return;
+
+ // HDR10
+ if (rb->color_transfer == AVCOL_TRC_SMPTE2084 && rb->primaries == AVCOL_PRI_BT2020)
+ {
+ DXGI_HDR_METADATA_HDR10 hdr10 = GetDXGIHDR10MetaData(rb);
+ if (m_HdrType == HDR_TYPE::HDR_HDR10)
+ {
+ // Only Sets HDR10 metadata if differs from previous
+ if (0 != std::memcmp(&hdr10, &m_lastHdr10, sizeof(hdr10)))
+ {
+ // Sets HDR10 metadata only
+ DX::Windowing()->SetHdrMetaData(hdr10);
+ m_lastHdr10 = hdr10;
+ }
+ }
+ else
+ {
+ // Sets HDR10 metadata and enables HDR10 color space (switch to HDR rendering)
+ DX::Windowing()->SetHdrMetaData(hdr10);
+ CLog::LogF(LOGINFO, "Switching to HDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+ m_HdrType = HDR_TYPE::HDR_HDR10;
+ m_lastHdr10 = hdr10;
+ }
+ m_iCntMetaData = 0;
+ }
+ // HLG
+ else if (rb->color_transfer == AVCOL_TRC_ARIB_STD_B67 && rb->primaries == AVCOL_PRI_BT2020)
+ {
+ if (m_HdrType != HDR_TYPE::HDR_HLG)
+ {
+ // Switch to HLG rendering
+ CLog::LogF(LOGINFO, "Switching to HLG rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020);
+ m_HdrType = HDR_TYPE::HDR_HLG;
+ }
+ }
+ // Rec. 2020
+ else if (rb->primaries == AVCOL_PRI_BT2020)
+ {
+ if (m_HdrType != HDR_TYPE::HDR_REC2020)
+ {
+ // Switch to Rec.2020 rendering
+ CLog::LogF(LOGINFO, "Switching to Rec.2020 rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020);
+ m_HdrType = HDR_TYPE::HDR_REC2020;
+ }
+ }
+ else
+ {
+ if (m_HdrType == HDR_TYPE::HDR_HDR10)
+ {
+ m_iCntMetaData++;
+ if (m_iCntMetaData > 60)
+ {
+ // If more than 60 frames are received without HDR10 metadata switch to SDR rendering
+ CLog::LogF(LOGINFO, "Switching to SDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ m_iCntMetaData = 0;
+ if (m_AutoSwitchHDR)
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR OFF
+ }
+ }
+ if (m_HdrType == HDR_TYPE::HDR_HLG || m_HdrType == HDR_TYPE::HDR_REC2020)
+ {
+ // Switch to SDR rendering
+ CLog::LogF(LOGINFO, "Switching to SDR rendering");
+ DX::Windowing()->SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ if (m_AutoSwitchHDR)
+ DX::Windowing()->ToggleHDR(); // Toggle display HDR OFF
+ }
+ }
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h
index e6083cffdc..786428b016 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererBase.h
@@ -7,14 +7,16 @@
*/
#pragma once
-#include "cores/VideoSettings.h"
-#include "guilib/D3DResource.h"
#include "VideoRenderers/ColorManager.h"
#include "VideoRenderers/RenderInfo.h"
#include "VideoRenderers/VideoShaders/WinVideoFilter.h"
+#include "cores/VideoSettings.h"
+#include "guilib/D3DResource.h"
-#include <d3d11.h>
#include <vector>
+
+#include <d3d11.h>
+#include <dxgi1_5.h>
extern "C" {
#include <libavutil/mastering_display_metadata.h>
}
@@ -42,6 +44,14 @@ enum RenderMethod
RENDER_SW = 0x03,
};
+enum class HDR_TYPE : uint32_t
+{
+ HDR_NONE_SDR = 0x00,
+ HDR_HDR10 = 0x01,
+ HDR_HLG = 0x02,
+ HDR_REC2020 = 0x03
+};
+
class CRenderBuffer
{
public:
@@ -121,6 +131,7 @@ public:
static DXGI_FORMAT GetDXGIFormat(const VideoPicture &picture);
static DXGI_FORMAT GetDXGIFormat(CVideoBuffer* videoBuffer);
static AVPixelFormat GetAVFormat(DXGI_FORMAT dxgi_format);
+ static DXGI_HDR_METADATA_HDR10 GetDXGIHDR10MetaData(CRenderBuffer* rb);
protected:
explicit CRendererBase(CVideoSettings& videoSettings);
@@ -131,6 +142,8 @@ protected:
bool CreateRenderBuffer(int index);
void DeleteRenderBuffer(int index);
+ void ProcessHDR(CRenderBuffer* rb);
+
virtual void RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint (&destPoints)[4], uint32_t flags) = 0;
virtual void FinalOutput(CD3DTexture& source, CD3DTexture& target, const CRect& sourceRect, const CPoint(&destPoints)[4]);
@@ -145,7 +158,7 @@ protected:
bool m_useDithering = false;
bool m_cmsOn = false;
bool m_clutLoaded = false;
-
+
int m_iBufferIndex = 0;
int m_iNumBuffers = 0;
int m_iBuffersRequired = 0;
@@ -167,4 +180,9 @@ protected:
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_pLUTView;
CVideoSettings& m_videoSettings;
std::map<int, CRenderBuffer*> m_renderBuffers;
+
+ DXGI_HDR_METADATA_HDR10 m_lastHdr10 = {};
+ HDR_TYPE m_HdrType = HDR_TYPE::HDR_NONE_SDR;
+ int m_iCntMetaData = 0;
+ bool m_AutoSwitchHDR = false;
};
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp
index 7b8f3fd4ac..31cfd02ca5 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp
@@ -110,14 +110,13 @@ bool CRendererDXVA::Configure(const VideoPicture& picture, float fps, unsigned o
bool CRendererDXVA::NeedBuffer(int idx)
{
- if (m_renderBuffers[idx]->IsLoaded())
+ if (m_renderBuffers[idx]->IsLoaded() && m_renderBuffers[idx]->pictureFlags & DVP_FLAG_INTERLACED)
{
- const int numPast = m_processor->PastRefs();
- if (m_renderBuffers[idx]->pictureFlags & DVP_FLAG_INTERLACED &&
- m_renderBuffers[idx]->frameIdx + numPast * 2 >=
- m_renderBuffers[m_iBufferIndex]->frameIdx)
+ if (m_renderBuffers[idx]->frameIdx + (m_processor->PastRefs() * 2u) >=
+ m_renderBuffers[m_iBufferIndex]->frameIdx)
return true;
}
+
return false;
}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp
index 04bdc8504d..9111e208a6 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp
@@ -132,7 +132,14 @@ void CRendererShaders::UpdateVideoFilters()
if (!m_colorShader)
{
m_colorShader = std::make_unique<CYUV2RGBShader>();
- if (!m_colorShader->Create(m_format, AVCOL_PRI_BT709, m_srcPrimaries))
+
+ AVColorPrimaries dstPrimaries = AVCOL_PRI_BT709;
+
+ if (DX::Windowing()->IsHDROutput() &&
+ (m_srcPrimaries == AVCOL_PRI_BT709 || m_srcPrimaries == AVCOL_PRI_BT2020))
+ dstPrimaries = m_srcPrimaries;
+
+ if (!m_colorShader->Create(m_format, dstPrimaries, m_srcPrimaries))
{
// we are in a big trouble
CLog::LogF(LOGERROR, "unable to create YUV->RGB shader, rendering is not possible");
diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp
index 881f8a606b..5aca5fb913 100644
--- a/xbmc/cores/paplayer/PAPlayer.cpp
+++ b/xbmc/cores/paplayer/PAPlayer.cpp
@@ -358,7 +358,7 @@ bool PAPlayer::QueueNextFileEx(const CFileItem &file, bool fadeIn)
return false;
}
- /* yield our time so that the main PAP thread doesnt stall */
+ /* yield our time so that the main PAP thread doesn't stall */
CThread::Sleep(1);
}
@@ -503,7 +503,7 @@ inline bool PAPlayer::PrepareStream(StreamInfo *si)
if (!QueueData(si))
break;
- /* yield our time so that the main PAP thread doesnt stall */
+ /* yield our time so that the main PAP thread doesn't stall */
CThread::Sleep(1);
}
diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.cpp b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
index c5e4983260..27d4c19e39 100644
--- a/xbmc/cores/paplayer/VideoPlayerCodec.cpp
+++ b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
@@ -66,13 +66,11 @@ void VideoPlayerCodec::SetPassthroughStreamType(CAEStreamInfo::DataType streamT
bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
{
- const std::string &strFile = file.GetPath();
-
// take precaution if Init()ialized earlier
if (m_bInited)
{
// keep things as is if Init() was done with known strFile
- if (m_strFileName == strFile)
+ if (m_strFileName == file.GetDynPath())
return true;
// got differing filename, so cleanup before starting over
@@ -81,19 +79,13 @@ bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
m_nDecodedLen = 0;
- std::string strFileToOpen = strFile;
-
- CURL urlFile(strFile);
- if (urlFile.IsProtocol("shout") )
- strFileToOpen.replace(0, 8, "http://");
-
CFileItem fileitem(file);
fileitem.SetMimeType(m_strContentType);
fileitem.SetMimeTypeForInternetFile();
m_pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, fileitem);
if (!m_pInputStream)
{
- CLog::Log(LOGERROR, "%s: Error creating input stream for %s", __FUNCTION__, strFileToOpen.c_str());
+ CLog::Log(LOGERROR, "{}: Error creating input stream for {}", __FUNCTION__, file.GetDynPath());
return false;
}
@@ -101,7 +93,7 @@ bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
//! convey CFileItem::ContentLookup() into Open()
if (!m_pInputStream->Open())
{
- CLog::Log(LOGERROR, "%s: Error opening file %s", __FUNCTION__, strFileToOpen.c_str());
+ CLog::Log(LOGERROR, "{}: Error opening file {}", __FUNCTION__, file.GetDynPath());
if (m_pInputStream.use_count() > 1)
throw std::runtime_error("m_pInputStream reference count is greater than 1");
m_pInputStream.reset();
@@ -186,7 +178,7 @@ bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
m_strContentType == "audio/ape")
strFallbackFileExtension = "ape";
CTagLoaderTagLib tagLoaderTagLib;
- tagLoaderTagLib.Load(strFile, m_tag, strFallbackFileExtension);
+ tagLoaderTagLib.Load(file.GetDynPath(), m_tag, strFallbackFileExtension);
// we have to decode initial data in order to get channels/samplerate
// for sanity - we read no more than 10 packets
@@ -280,7 +272,7 @@ bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
m_bitsPerSample = CAEUtil::DataFormatToBits(m_format.m_dataFormat);
}
- m_strFileName = strFile;
+ m_strFileName = file.GetDynPath();
m_bInited = true;
return true;
diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp
index b0ec34e001..54f36484ad 100644
--- a/xbmc/dbwrappers/mysqldataset.cpp
+++ b/xbmc/dbwrappers/mysqldataset.cpp
@@ -218,7 +218,8 @@ int MysqlDatabase::connect(bool create_new) {
char sqlcmd[512];
int ret;
- sprintf(sqlcmd, "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", db.c_str());
+ snprintf(sqlcmd, sizeof(sqlcmd),
+ "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", db.c_str());
if ( (ret=query_with_reconnect(sqlcmd)) != MYSQL_OK )
{
throw DbErrors("Can't create new database: '%s' (%d)", db.c_str(), ret);
@@ -276,7 +277,7 @@ int MysqlDatabase::drop() {
throw DbErrors("Can't drop database: no active connection...");
char sqlcmd[512];
int ret;
- sprintf(sqlcmd,"DROP DATABASE `%s`", db.c_str());
+ snprintf(sqlcmd, sizeof(sqlcmd), "DROP DATABASE `%s`", db.c_str());
if ( (ret=query_with_reconnect(sqlcmd)) != MYSQL_OK )
{
throw DbErrors("Can't drop database: '%s' (%d)", db.c_str(), ret);
@@ -313,7 +314,8 @@ int MysqlDatabase::copy(const char *backup_name) {
}
// create the new database
- sprintf(sql, "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", backup_name);
+ snprintf(sql, sizeof(sql), "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci",
+ backup_name);
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
{
mysql_free_result(res);
@@ -326,8 +328,7 @@ int MysqlDatabase::copy(const char *backup_name) {
while ( (row=mysql_fetch_row(res)) != NULL )
{
// copy the table definition
- sprintf(sql, "CREATE TABLE `%s`.%s LIKE %s",
- backup_name, row[0], row[0]);
+ snprintf(sql, sizeof(sql), "CREATE TABLE `%s`.%s LIKE %s", backup_name, row[0], row[0]);
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
{
@@ -336,7 +337,7 @@ int MysqlDatabase::copy(const char *backup_name) {
}
// copy the table data
- sprintf(sql, "INSERT INTO `%s`.%s SELECT * FROM %s",
+ snprintf(sql, sizeof(sql), "INSERT INTO `%s`.%s SELECT * FROM %s",
backup_name, row[0], row[0]);
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
@@ -366,10 +367,10 @@ int MysqlDatabase::drop_analytics(void) {
throw DbErrors("Can't connect to database: '%s'",db.c_str());
// getting a list of indexes in the database
- sprintf(sql, "SELECT DISTINCT table_name, index_name"
- " FROM information_schema.statistics"
- " WHERE index_name != 'PRIMARY' AND"
- " table_schema = '%s'", db.c_str());
+ snprintf(sql, sizeof(sql), "SELECT DISTINCT table_name, index_name "
+ "FROM information_schema.statistics "
+ "WHERE index_name != 'PRIMARY' AND table_schema = '%s'",
+ db.c_str());
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
throw DbErrors("Can't determine list of indexes to drop.");
@@ -381,7 +382,7 @@ int MysqlDatabase::drop_analytics(void) {
{
while ( (row=mysql_fetch_row(res)) != NULL )
{
- sprintf(sql, "ALTER TABLE `%s`.%s DROP INDEX %s", db.c_str(), row[0], row[1]);
+ snprintf(sql, sizeof(sql), "ALTER TABLE `%s`.%s DROP INDEX %s", db.c_str(), row[0], row[1]);
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
{
@@ -393,9 +394,8 @@ int MysqlDatabase::drop_analytics(void) {
}
// next topic is a views list
- sprintf(sql, "SELECT table_name"
- " FROM information_schema.views"
- " WHERE table_schema = '%s'", db.c_str());
+ snprintf(sql, sizeof(sql),
+ "SELECT table_name FROM information_schema.views WHERE table_schema = '%s'", db.c_str());
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
throw DbErrors("Can't determine list of views to drop.");
@@ -406,7 +406,7 @@ int MysqlDatabase::drop_analytics(void) {
while ( (row=mysql_fetch_row(res)) != NULL )
{
/* we do not need IF EXISTS because these views are exist */
- sprintf(sql, "DROP VIEW `%s`.%s", db.c_str(), row[0]);
+ snprintf(sql, sizeof(sql), "DROP VIEW `%s`.%s", db.c_str(), row[0]);
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
{
@@ -418,9 +418,9 @@ int MysqlDatabase::drop_analytics(void) {
}
// triggers
- sprintf(sql, "SELECT trigger_name"
- " FROM information_schema.triggers"
- " WHERE event_object_schema = '%s'", db.c_str());
+ snprintf(sql, sizeof(sql),
+ "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'",
+ db.c_str());
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
throw DbErrors("Can't determine list of triggers to drop.");
@@ -430,7 +430,7 @@ int MysqlDatabase::drop_analytics(void) {
{
while ( (row=mysql_fetch_row(res)) != NULL )
{
- sprintf(sql, "DROP TRIGGER `%s`.%s", db.c_str(), row[0]);
+ snprintf(sql, sizeof(sql), "DROP TRIGGER `%s`.%s", db.c_str(), row[0]);
if ( (ret=query_with_reconnect(sql)) != MYSQL_OK )
{
@@ -442,9 +442,7 @@ int MysqlDatabase::drop_analytics(void) {
}
// Native functions
- sprintf(sql,
- "SELECT routine_name "
- "FROM information_schema.routines "
+ snprintf(sql, sizeof(sql), "SELECT routine_name FROM information_schema.routines "
"WHERE routine_type = 'FUNCTION' and routine_schema = '%s'",
db.c_str());
if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
@@ -456,7 +454,7 @@ int MysqlDatabase::drop_analytics(void) {
{
while ((row = mysql_fetch_row(res)) != NULL)
{
- sprintf(sql, "DROP FUNCTION `%s`.%s", db.c_str(), row[0]);
+ snprintf(sql, sizeof(sql), "DROP FUNCTION `%s`.%s", db.c_str(), row[0]);
if ((ret = query_with_reconnect(sql)) != MYSQL_OK)
{
@@ -494,7 +492,7 @@ long MysqlDatabase::nextid(const char* sname) {
int id;/*,nrow,ncol;*/
MYSQL_RES* res;
char sqlcmd[512];
- sprintf(sqlcmd,"select nextid from %s where seq_name = '%s'",seq_table, sname);
+ snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'", seq_table, sname);
CLog::Log(LOGDEBUG,"MysqlDatabase::nextid will request");
if ((last_err = query_with_reconnect(sqlcmd)) != 0)
{
@@ -506,7 +504,8 @@ long MysqlDatabase::nextid(const char* sname) {
if (mysql_num_rows(res) == 0)
{
id = 1;
- sprintf(sqlcmd, "insert into %s (nextid,seq_name) values (%d,'%s')", seq_table, id, sname);
+ snprintf(sqlcmd, sizeof(sqlcmd), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
+ seq_table, id, sname);
mysql_free_result(res);
if ((last_err = query_with_reconnect(sqlcmd)) != 0) return DB_UNEXPECTED_RESULT;
return id;
@@ -519,7 +518,8 @@ long MysqlDatabase::nextid(const char* sname) {
unsigned long *lengths;
lengths = mysql_fetch_lengths(res);
CLog::Log(LOGINFO, "Next id is [%.*s] ", (int)lengths[0], row[0]);
- sprintf(sqlcmd, "update %s set nextid=%d where seq_name = '%s'", seq_table, id, sname);
+ snprintf(sqlcmd, sizeof(sqlcmd), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'",
+ seq_table, id, sname);
mysql_free_result(res);
if ((last_err = query_with_reconnect(sqlcmd)) != 0) return DB_UNEXPECTED_RESULT;
return id;
diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp
index 803c27fa52..fe5ba449c5 100644
--- a/xbmc/dbwrappers/sqlitedataset.cpp
+++ b/xbmc/dbwrappers/sqlitedataset.cpp
@@ -473,19 +473,22 @@ long SqliteDatabase::nextid(const char* sname) {
int id;/*,nrow,ncol;*/
result_set res;
char sqlcmd[512];
- sprintf(sqlcmd,"select nextid from %s where seq_name = '%s'",sequence_table.c_str(), sname);
+ snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'",
+ sequence_table.c_str(), sname);
if ((last_err = sqlite3_exec(getHandle(),sqlcmd,&callback,&res,NULL)) != SQLITE_OK) {
return DB_UNEXPECTED_RESULT;
}
if (res.records.empty()) {
id = 1;
- sprintf(sqlcmd,"insert into %s (nextid,seq_name) values (%d,'%s')",sequence_table.c_str(),id,sname);
+ snprintf(sqlcmd, sizeof(sqlcmd), "INSERT INTO %s (nextid,seq_name) VALUES (%d,'%s')",
+ sequence_table.c_str(), id, sname);
if ((last_err = sqlite3_exec(conn,sqlcmd,NULL,NULL,NULL)) != SQLITE_OK) return DB_UNEXPECTED_RESULT;
return id;
}
else {
id = res.records[0]->at(0).get_asInt()+1;
- sprintf(sqlcmd,"update %s set nextid=%d where seq_name = '%s'",sequence_table.c_str(),id,sname);
+ snprintf(sqlcmd, sizeof(sqlcmd), "UPDATE %s SET nextid=%d WHERE seq_name = '%s'",
+ sequence_table.c_str(), id, sname);
if ((last_err = sqlite3_exec(conn,sqlcmd,NULL,NULL,NULL)) != SQLITE_OK) return DB_UNEXPECTED_RESULT;
return id;
}
diff --git a/xbmc/filesystem/AddonsDirectory.cpp b/xbmc/filesystem/AddonsDirectory.cpp
index 5cbe7dd4aa..f90e68c912 100644
--- a/xbmc/filesystem/AddonsDirectory.cpp
+++ b/xbmc/filesystem/AddonsDirectory.cpp
@@ -52,12 +52,6 @@ const auto CATEGORY_GAME_PROVIDERS = "category.gameproviders";
const auto CATEGORY_GAME_RESOURCES = "category.gameresources";
const auto CATEGORY_GAME_SUPPORT_ADDONS = "category.gamesupport";
-const std::set<TYPE> dependencyTypes = {
- ADDON_SCRAPER_LIBRARY,
- ADDON_SCRIPT_LIBRARY,
- ADDON_SCRIPT_MODULE,
-};
-
const std::set<TYPE> infoProviderTypes = {
ADDON_SCRAPER_ALBUMS,
ADDON_SCRAPER_ARTISTS,
@@ -144,15 +138,9 @@ static bool IsGameAddon(const AddonPtr& addon)
IsGameSupportAddon(addon);
}
-static bool IsDependencyType(TYPE type)
-{
- return dependencyTypes.find(type) != dependencyTypes.end();
-}
-
static bool IsUserInstalled(const AddonPtr& addon)
{
- return std::find_if(dependencyTypes.begin(), dependencyTypes.end(),
- [&](TYPE type) { return addon->HasType(type); }) == dependencyTypes.end();
+ return !CAddonType::IsDependencyType(addon->MainType());
}
static bool IsOrphaned(const AddonPtr& addon, const VECADDONS& all)
@@ -349,8 +337,9 @@ static void GenerateMainCategoryListing(const CURL& path, const VECADDONS& addon
* subtypes (audio, video, app or/and game) and not needed to show
* together in a Script or Plugin list
*/
- if (!IsInfoProviderType(type) && !IsLookAndFeelType(type) && !IsDependencyType(type) &&
- !IsGameType(type) && type != ADDON_SCRIPT && type != ADDON_PLUGIN)
+ if (!IsInfoProviderType(type) && !IsLookAndFeelType(type) &&
+ !CAddonType::IsDependencyType(type) && !IsGameType(type) && type != ADDON_SCRIPT &&
+ type != ADDON_PLUGIN)
uncategorized.insert(static_cast<TYPE>(i));
}
GenerateTypeListing(path, uncategorized, addons, items);
diff --git a/xbmc/filesystem/CurlFile.cpp b/xbmc/filesystem/CurlFile.cpp
index 3a02d7551f..872cc781ba 100644
--- a/xbmc/filesystem/CurlFile.cpp
+++ b/xbmc/filesystem/CurlFile.cpp
@@ -538,7 +538,8 @@ void CCurlFile::SetCommonOptions(CReadState* state, bool failOnError /* = true *
else
{
g_curlInterface.easy_setopt(h, CURLOPT_REFERER, NULL);
- g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, CURL_ON);
+ // Do not send referer header on redirects (same behaviour as ffmpeg and browsers)
+ g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, CURL_OFF);
}
// setup any requested authentication
@@ -1585,7 +1586,7 @@ int8_t CCurlFile::CReadState::FillBuffer(unsigned int want)
fd_set fdexcep;
// only attempt to fill buffer if transactions still running and buffer
- // doesnt exceed required size already
+ // doesn't exceed required size already
while (m_buffer.getMaxReadSize() < want && m_buffer.getMaxWriteSize() > 0 )
{
if (m_cancelled)
diff --git a/xbmc/guilib/GUIColorManager.cpp b/xbmc/guilib/GUIColorManager.cpp
index 1ffcc547ee..823409e0b7 100644
--- a/xbmc/guilib/GUIColorManager.cpp
+++ b/xbmc/guilib/GUIColorManager.cpp
@@ -65,7 +65,7 @@ bool CGUIColorManager::LoadXML(CXBMCTinyXML &xmlDoc)
std::string strValue = pRootElement->Value();
if (strValue != std::string("colors"))
{
- CLog::Log(LOGERROR, "color file doesnt start with <colors>");
+ CLog::Log(LOGERROR, "color file doesn't start with <colors>");
return false;
}
diff --git a/xbmc/guilib/GUIFontManager.cpp b/xbmc/guilib/GUIFontManager.cpp
index 8bf25ab8e7..a7c49764c8 100644
--- a/xbmc/guilib/GUIFontManager.cpp
+++ b/xbmc/guilib/GUIFontManager.cpp
@@ -356,7 +356,7 @@ void GUIFontManager::LoadFonts(const std::string& fontSet)
TiXmlElement* pRootElement = xmlDoc.RootElement();
if (!pRootElement || pRootElement->ValueStr() != "fonts")
{
- CLog::Log(LOGERROR, "file %s doesnt start with <fonts>", strPath.c_str());
+ CLog::Log(LOGERROR, "file %s doesn't start with <fonts>", strPath.c_str());
return;
}
// Resolve includes in Font.xml
@@ -385,11 +385,11 @@ void GUIFontManager::LoadFonts(const std::string& fontSet)
// no fontset was loaded, try the first
if (!firstFont.empty())
{
- CLog::Log(LOGWARNING, "file doesnt have <fontset> with name '%s', defaulting to first fontset", fontSet.c_str());
+ CLog::Log(LOGWARNING, "file doesn't have <fontset> with name '%s', defaulting to first fontset", fontSet.c_str());
LoadFonts(firstFont);
}
else
- CLog::Log(LOGERROR, "file '%s' doesnt have a valid <fontset>", strPath.c_str());
+ CLog::Log(LOGERROR, "file '%s' doesn't have a valid <fontset>", strPath.c_str());
}
void GUIFontManager::LoadFonts(const TiXmlNode* fontNode)
diff --git a/xbmc/guilib/VisibleEffect.cpp b/xbmc/guilib/VisibleEffect.cpp
index 0afbf68151..470cfd4539 100644
--- a/xbmc/guilib/VisibleEffect.cpp
+++ b/xbmc/guilib/VisibleEffect.cpp
@@ -427,7 +427,7 @@ void CAnimation::Animate(unsigned int time, bool startAnim)
m_start = time;
m_currentProcess = ANIM_PROCESS_REVERSE;
}
- // reset the queued state once we've rendered to ensure allocation has occured
+ // reset the queued state once we've rendered to ensure allocation has occurred
m_queuedProcess = ANIM_PROCESS_NONE;
// Update our animation process
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabel.cpp b/xbmc/guilib/guiinfo/GUIInfoLabel.cpp
index fb0fc180b2..23201146b2 100644
--- a/xbmc/guilib/guiinfo/GUIInfoLabel.cpp
+++ b/xbmc/guilib/guiinfo/GUIInfoLabel.cpp
@@ -258,7 +258,7 @@ void CGUIInfoLabel::Parse(const std::string &label, int context)
if (info == 0)
info = infoMgr.RegisterSkinVariableString(g_SkinInfo->CreateSkinVariable(params[0], context));
if (info == 0) // skinner didn't define this conditional label!
- CLog::Log(LOGWARNING, "Label Formating: $VAR[%s] is not defined", params[0].c_str());
+ CLog::Log(LOGWARNING, "Label Formatting: $VAR[%s] is not defined", params[0].c_str());
}
else
info = infoMgr.TranslateString(params[0]);
diff --git a/xbmc/input/actions/ActionIDs.h b/xbmc/input/actions/ActionIDs.h
index e68d280282..760228d30a 100644
--- a/xbmc/input/actions/ActionIDs.h
+++ b/xbmc/input/actions/ActionIDs.h
@@ -327,6 +327,8 @@
#define ACTION_VOLUME_SET 245
#define ACTION_TOGGLE_COMMSKIP 246
+#define ACTION_HDR_TOGGLE 247 //!< Toggle display HDR on/off
+
#define ACTION_PLAYER_RESET 248 //!< Send a reset command to the active game
#define ACTION_TOGGLE_FONT 249 //!< Toggle font. Used in TextViewer dialog
diff --git a/xbmc/input/actions/ActionTranslator.cpp b/xbmc/input/actions/ActionTranslator.cpp
index 8731459326..877bd3b9ab 100644
--- a/xbmc/input/actions/ActionTranslator.cpp
+++ b/xbmc/input/actions/ActionTranslator.cpp
@@ -201,6 +201,9 @@ static const std::map<ActionName, ActionID> ActionMappings = {
{"togglestereomode", ACTION_STEREOMODE_TOGGLE},
{"stereomodetomono", ACTION_STEREOMODE_TOMONO},
+ // HDR display support
+ {"hdrtoggle", ACTION_HDR_TOGGLE},
+
// PVR actions
{"channelup", ACTION_CHANNEL_UP},
{"channeldown", ACTION_CHANNEL_DOWN},
diff --git a/xbmc/input/joysticks/generic/FeatureHandling.h b/xbmc/input/joysticks/generic/FeatureHandling.h
index 4010781dab..096bcd1789 100644
--- a/xbmc/input/joysticks/generic/FeatureHandling.h
+++ b/xbmc/input/joysticks/generic/FeatureHandling.h
@@ -36,7 +36,7 @@ public:
virtual ~CJoystickFeature() = default;
/*!
- * \brief A digital motion has occured
+ * \brief A digital motion has occurred
*
* \param source The source of the motion. Must be digital (button or hat)
* \param bPressed True for press motion, false for release motion
@@ -46,7 +46,7 @@ public:
virtual bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) = 0;
/*!
- * \brief An analog motion has occured
+ * \brief An analog motion has occurred
*
* \param source The source of the motion. Must be a semiaxis
* \param magnitude The magnitude of the press or motion in the interval [0.0, 1.0]
@@ -61,7 +61,7 @@ public:
virtual bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) = 0;
/*!
- * \brief Process the motions that have occured since the last invocation
+ * \brief Process the motions that have occurred since the last invocation
*
* This allows features with motion on multiple driver primitives to call
* their handler once all driver primitives are accounted for.
diff --git a/xbmc/input/touch/ITouchInputHandler.h b/xbmc/input/touch/ITouchInputHandler.h
index 15ca0f0e8f..12758395f2 100644
--- a/xbmc/input/touch/ITouchInputHandler.h
+++ b/xbmc/input/touch/ITouchInputHandler.h
@@ -52,7 +52,7 @@ public:
* \param event The actual touch event (abort, down, up, move)
* \param x The x coordinate (with sub-pixel) of the touch
* \param y The y coordinate (with sub-pixel) of the touch
- * \param time The time (in nanoseconds) when this touch occured
+ * \param time The time (in nanoseconds) when this touch occurred
* \param pointer The number of the touch pointer which caused this event (default 0)
* \param size The size of the touch pointer (with sub-pixel) (default 0.0)
*
@@ -76,7 +76,7 @@ public:
* \param pointer The number of the touch pointer which caused this event (default 0)
* \param x The x coordinate (with sub-pixel) of the touch
* \param y The y coordinate (with sub-pixel) of the touch
- * \param time The time (in nanoseconds) when this touch occured
+ * \param time The time (in nanoseconds) when this touch occurred
* \param size The size of the touch pointer (with sub-pixel) (default 0.0)
*
* \return True if the pointer was updated otherwise false.
diff --git a/xbmc/input/touch/generic/IGenericTouchGestureDetector.h b/xbmc/input/touch/generic/IGenericTouchGestureDetector.h
index da9d86869e..7e2cb88365 100644
--- a/xbmc/input/touch/generic/IGenericTouchGestureDetector.h
+++ b/xbmc/input/touch/generic/IGenericTouchGestureDetector.h
@@ -66,7 +66,7 @@ public:
virtual bool OnTouchMove(unsigned int index, const Pointer& pointer) { return false; }
/*!
* \brief An active touch pointer's values have been updated but no event has
- * occured.
+ * occurred.
*
* \param index Index of the given touch pointer
* \param pointer Touch pointer that has changed
diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp
index cce8c4a0f1..ba3cd99b97 100644
--- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp
+++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp
@@ -403,7 +403,7 @@ static int PlayMedia(const std::vector<std::string>& params)
if (StringUtils::EqualsNoCase(params[i], "isdir"))
item.m_bIsFolder = true;
else if (params[i] == "1") // set fullscreen or windowed
- CMediaSettings::GetInstance().SetVideoStartWindowed(true);
+ CMediaSettings::GetInstance().SetMediaStartWindowed(true);
else if (StringUtils::EqualsNoCase(params[i], "resume"))
{
// force the item to resume (if applicable) (see CApplication::PlayMedia)
@@ -586,12 +586,12 @@ static int Seek(const std::vector<std::string>& params)
/// ,
/// Plays the media. This can be a playlist\, music\, or video file\, directory\,
/// plugin or an Url. The optional parameter "\,isdir" can be used for playing
-/// a directory. "\,1" will start a video in a preview window\, instead of
-/// fullscreen. If media is a playlist\, you can use playoffset=xx where xx is
+/// a directory. "\,1" will start the media without switching to fullscreen.
+/// If media is a playlist\, you can use playoffset=xx where xx is
/// the position to start playback from.
/// @param[in] media URL to media to play (optional).
/// @param[in] isdir Set "isdir" if media is a directory (optional).
-/// @param[in] fullscreen Set "1" to start playback in fullscreen (optional).
+/// @param[in] windowed Set "1" to start playback without switching to fullscreen (optional).
/// @param[in] resume Set "resume" to force resuming (optional).
/// @param[in] noresume Set "noresume" to force not resuming (optional).
/// @param[in] playeroffset Set "playoffset=<offset>" to start playback from a given position in a playlist (optional).
diff --git a/xbmc/interfaces/json-rpc/AddonsOperations.cpp b/xbmc/interfaces/json-rpc/AddonsOperations.cpp
index 52d04a2689..9ac5e164d3 100644
--- a/xbmc/interfaces/json-rpc/AddonsOperations.cpp
+++ b/xbmc/interfaces/json-rpc/AddonsOperations.cpp
@@ -150,15 +150,27 @@ JSONRPC_STATUS CAddonsOperations::SetAddonEnabled(const std::string &method, ITr
return InvalidParams;
bool disabled = false;
+ AddonDisabledReason disabledReason;
if (parameterObject["enabled"].isBoolean())
+ {
disabled = !parameterObject["enabled"].asBoolean();
+ disabledReason =
+ static_cast<AddonDisabledReason>(parameterObject["disabledReason"].asInteger());
+ }
// we need to toggle the current disabled state of the addon
else if (parameterObject["enabled"].isString())
+ {
disabled = !CServiceBroker::GetAddonMgr().IsAddonDisabled(id);
+ disabledReason =
+ static_cast<AddonDisabledReason>(parameterObject["disabledReason"].asInteger());
+ }
else
+ {
return InvalidParams;
+ }
- bool success = disabled ? CServiceBroker::GetAddonMgr().DisableAddon(id) : CServiceBroker::GetAddonMgr().EnableAddon(id);
+ bool success = disabled ? CServiceBroker::GetAddonMgr().DisableAddon(id, disabledReason)
+ : CServiceBroker::GetAddonMgr().EnableAddon(id);
return success ? ACK : InvalidParams;
}
diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json
index 856dda7490..079eb7efea 100644
--- a/xbmc/interfaces/json-rpc/schema/methods.json
+++ b/xbmc/interfaces/json-rpc/schema/methods.json
@@ -694,7 +694,7 @@
},
"AudioLibrary.GetArtists": {
"type": "method",
- "description": "Retrieve all artists. For backward compatibility by default this implicity does not include those that only contribute other roles, however absolutely all artists can be returned using allroles=true",
+ "description": "Retrieve all artists. For backward compatibility by default this implicitly does not include those that only contribute other roles, however absolutely all artists can be returned using allroles=true",
"transport": "Response",
"permission": "ReadData",
"params": [
@@ -837,9 +837,9 @@
{ "$ref": "List.Filter.Songs" }
]
},
- { "name": "includesingles", "type": "boolean", "default": true, "description": "Only songs from albums are returned when false, but overidden when singlesonly parameter is true" },
+ { "name": "includesingles", "type": "boolean", "default": true, "description": "Only songs from albums are returned when false, but overridden when singlesonly parameter is true" },
{ "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than default of excluding other contributors. When true it overrides any role filter value." },
- { "name": "singlesonly", "type": "boolean", "default": false, "description": "Only singles are returned when true, and overides includesingles parameter" }
+ { "name": "singlesonly", "type": "boolean", "default": false, "description": "Only singles are returned when true, and overrides includesingles parameter" }
],
"returns": {
"type": "object",
diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json
index bf755dc5bf..21a2347ed1 100644
--- a/xbmc/interfaces/json-rpc/schema/types.json
+++ b/xbmc/interfaces/json-rpc/schema/types.json
@@ -397,6 +397,10 @@
"label": { "type": "string", "required": true }
}
},
+ "Item.CustomProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "Global.String.NotEmpty" }
+ },
"Media.Details.Base": {
"extends": "Item.Details.Base",
"properties": {
@@ -1527,7 +1531,8 @@
"bitrate": { "type": "integer" },
"samplerate": { "type": "integer" },
"channels": { "type": "integer"},
- "albumstatus": { "type": "string" }
+ "albumstatus": { "type": "string" },
+ "customproperties": { "$ref": "Item.CustomProperties" }
}
},
"List.Fields.All": {
@@ -1550,7 +1555,7 @@
"contributors", "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist",
"userrating", "votes", "sortartist", "musicbrainzreleasegroupid", "mediapath", "dynpath",
"isboxset", "totaldiscs", "disctitle", "releasedate", "originaldate", "bpm",
- "bitrate", "samplerate", "channels", "albumstatus", "datemodified", "datenew"]
+ "bitrate", "samplerate", "channels", "albumstatus", "datemodified", "datenew", "customproperties"]
}
},
"List.Item.All": {
@@ -1582,7 +1587,7 @@
"episodeguide", "uniqueid", "dateadded", "size", "lastmodified", "mimetype",
"specialsortseason", "specialsortepisode", "sortartist", "musicbrainzreleasegroupid",
"isboxset", "totaldiscs", "disctitle", "releasedate", "originaldate", "bpm",
- "bitrate", "samplerate", "channels", "datemodified", "datenew"]
+ "bitrate", "samplerate", "channels", "datemodified", "datenew", "customproperties"]
}
},
"List.Item.File": {
diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt
index 6fb712b06f..1b9bbc7425 100644
--- a/xbmc/interfaces/json-rpc/schema/version.txt
+++ b/xbmc/interfaces/json-rpc/schema/version.txt
@@ -1 +1 @@
-JSONRPC_VERSION 11.11.0
+JSONRPC_VERSION 11.12.0
diff --git a/xbmc/interfaces/legacy/Player.cpp b/xbmc/interfaces/legacy/Player.cpp
index c15472f210..0ce107bf97 100644
--- a/xbmc/interfaces/legacy/Player.cpp
+++ b/xbmc/interfaces/legacy/Player.cpp
@@ -75,7 +75,7 @@ namespace XBMCAddon
if (!item.empty())
{
// set fullscreen or windowed
- CMediaSettings::GetInstance().SetVideoStartWindowed(windowed);
+ CMediaSettings::GetInstance().SetMediaStartWindowed(windowed);
const AddonClass::Ref<xbmcgui::ListItem> listitem(plistitem);
@@ -101,7 +101,7 @@ namespace XBMCAddon
XBMC_TRACE;
DelayedCallGuard dc(languageHook);
// set fullscreen or windowed
- CMediaSettings::GetInstance().SetVideoStartWindowed(windowed);
+ CMediaSettings::GetInstance().SetMediaStartWindowed(windowed);
// play current file in playlist
if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() != iPlayList)
@@ -116,7 +116,7 @@ namespace XBMCAddon
if (playlist != NULL)
{
// set fullscreen or windowed
- CMediaSettings::GetInstance().SetVideoStartWindowed(windowed);
+ CMediaSettings::GetInstance().SetMediaStartWindowed(windowed);
// play a python playlist (a playlist from playlistplayer.cpp)
iPlayList = playlist->getPlayListId();
diff --git a/xbmc/interfaces/python/swig.cpp b/xbmc/interfaces/python/swig.cpp
index 2d70b6ec17..d0a0699d63 100644
--- a/xbmc/interfaces/python/swig.cpp
+++ b/xbmc/interfaces/python/swig.cpp
@@ -138,7 +138,7 @@ namespace PythonBindings
std::string msg;
std::string type, value, traceback;
if (!ParsePythonException(type, value, traceback))
- UncheckedException::SetMessage("Strange: No Python exception occured");
+ UncheckedException::SetMessage("Strange: No Python exception occurred");
else
SetMessage(type, value, traceback);
}
diff --git a/xbmc/music/Artist.cpp b/xbmc/music/Artist.cpp
index 3b692a941f..4a787cf812 100644
--- a/xbmc/music/Artist.cpp
+++ b/xbmc/music/Artist.cpp
@@ -118,17 +118,17 @@ bool CArtist::Load(const TiXmlElement *artist, bool append, bool prioritise)
// Discography
const TiXmlElement* node = artist->FirstChildElement("album");
+ if (node)
+ discography.clear();
while (node)
{
- const TiXmlNode* title = node->FirstChild("title");
- if (title && title->FirstChild())
+ if (node->FirstChild())
{
- std::string strTitle = title->FirstChild()->Value();
- std::string strYear;
- const TiXmlNode* year = node->FirstChild("year");
- if (year && year->FirstChild())
- strYear = year->FirstChild()->Value();
- discography.emplace_back(strTitle, strYear);
+ CDiscoAlbum album;
+ XMLUtils::GetString(node, "title", album.strAlbum);
+ XMLUtils::GetString(node, "year", album.strYear);
+ XMLUtils::GetString(node, "musicbrainzreleasegroupid", album.strReleaseGroupMBID);
+ discography.push_back(album);
}
node = node->NextSiblingElement("album");
}
@@ -215,16 +215,11 @@ bool CArtist::Save(TiXmlNode *node, const std::string &tag, const std::string& s
for (const auto& it : discography)
{
// add a <album> tag
- TiXmlElement cast("album");
- TiXmlNode *node = artist->InsertEndChild(cast);
- TiXmlElement title("title");
- TiXmlNode *titleNode = node->InsertEndChild(title);
- TiXmlText name(it.first);
- titleNode->InsertEndChild(name);
- TiXmlElement year("year");
- TiXmlNode *yearNode = node->InsertEndChild(year);
- TiXmlText name2(it.second);
- yearNode->InsertEndChild(name2);
+ TiXmlElement discoElement("album");
+ TiXmlNode* node = artist->InsertEndChild(discoElement);
+ XMLUtils::SetString(node, "title", it.strAlbum);
+ XMLUtils::SetString(node, "year", it.strYear);
+ XMLUtils::SetString(node, "musicbrainzreleasegroupid", it.strReleaseGroupMBID);
}
return true;
diff --git a/xbmc/music/Artist.h b/xbmc/music/Artist.h
index d5e71186a4..9bb617bdd6 100644
--- a/xbmc/music/Artist.h
+++ b/xbmc/music/Artist.h
@@ -22,6 +22,14 @@ class TiXmlNode;
class CAlbum;
class CMusicDatabase;
+class CDiscoAlbum
+{
+public:
+ std::string strAlbum;
+ std::string strYear;
+ std::string strReleaseGroupMBID;
+};
+
class CArtist
{
public:
@@ -105,7 +113,7 @@ public:
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;
+ std::vector<CDiscoAlbum> discography;
CDateTime dateAdded; // From related file creation or modification times, or when (re-)scanned
CDateTime dateUpdated; // Time db record Last modified
CDateTime dateNew; // Time db record created
diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp
index ac8266c305..3639722ee8 100644
--- a/xbmc/music/MusicDatabase.cpp
+++ b/xbmc/music/MusicDatabase.cpp
@@ -212,7 +212,8 @@ void CMusicDatabase::CreateTables()
m_pDS->exec("CREATE TABLE infosetting (idSetting INTEGER PRIMARY KEY, strScraperPath TEXT, strSettings TEXT)");
CLog::Log(LOGINFO, "create discography table");
- m_pDS->exec("CREATE TABLE discography (idArtist integer, strAlbum text, strYear text)");
+ m_pDS->exec("CREATE TABLE discography (idArtist integer, strAlbum text, strYear text, "
+ "strReleaseGroupMBID TEXT)");
CLog::Log(LOGINFO, "create art table");
m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
@@ -1555,7 +1556,7 @@ int CMusicDatabase::AddGenre(std::string& strGenre)
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
- // doesnt exists, add it
+ // doesn't exists, add it
strSQL=PrepareSQL("INSERT INTO genre (idGenre, strGenre) values( NULL, '%s' )", strGenre.c_str());
m_pDS->exec(strSQL);
@@ -1604,7 +1605,7 @@ bool CMusicDatabase::UpdateArtist(const CArtist& artist)
DeleteArtistDiscography(artist.idArtist);
for (const auto &disc : artist.discography)
{
- AddArtistDiscography(artist.idArtist, disc.first, disc.second);
+ AddArtistDiscography(artist.idArtist, disc);
}
// Set current artwork (held in art table)
@@ -1781,6 +1782,20 @@ int CMusicDatabase::UpdateArtist(int idArtist,
if (idArtist < 0)
return -1;
+ // Check another artist with this mbid not already exist (an alias for example)
+ bool useMBIDNull = strMusicBrainzArtistID.empty();
+ bool isScrapedMBID = bScrapedMBID;
+ std::string artistname;
+ int idArtistMbid = GetArtistFromMBID(strMusicBrainzArtistID, artistname);
+ if (idArtistMbid > 0 && idArtistMbid != idArtist)
+ {
+ CLog::Log(LOGDEBUG, "{0}: Updating {4} (Id: {5}) mbid {1} already assigned to {2} (Id: {3})",
+ __FUNCTION__, strMusicBrainzArtistID.c_str(), artistname.c_str(), idArtistMbid,
+ strArtist.c_str(), idArtist);
+ useMBIDNull = true;
+ isScrapedMBID = false;
+ }
+
std::string strSQL;
strSQL = PrepareSQL("UPDATE artist SET "
" strArtist = '%s', "
@@ -1799,7 +1814,7 @@ int CMusicDatabase::UpdateArtist(int idArtist,
strBiography.c_str(), strDied.c_str(), strDisbanded.c_str(),
strYearsActive.c_str(), strImage.c_str(), strFanart.c_str(),
CDateTime::GetUTCDateTime().GetAsDBDateTime().c_str(), bScrapedMBID);
- if (strMusicBrainzArtistID.empty())
+ if (useMBIDNull)
strSQL += PrepareSQL(", strMusicBrainzArtistID = NULL");
else
strSQL += PrepareSQL(", strMusicBrainzArtistID = '%s'", strMusicBrainzArtistID.c_str());
@@ -1821,6 +1836,16 @@ bool CMusicDatabase::UpdateArtistScrapedMBID(int idArtist, const std::string& st
if (strMusicBrainzArtistID.empty() || idArtist < 0)
return false;
+ // Check another artist with this mbid not already exist (an alias for example)
+ std::string artistname;
+ int idArtistMbid = GetArtistFromMBID(strMusicBrainzArtistID, artistname);
+ if (idArtistMbid > 0 && idArtistMbid != idArtist)
+ {
+ CLog::Log(LOGDEBUG, "{0}: Artist mbid {1} already assigned to {2} (Id: {3})", __FUNCTION__,
+ strMusicBrainzArtistID.c_str(), artistname.c_str(), idArtistMbid);
+ return false;
+ }
+
// Set scraped artist Musicbrainz ID for a previously added artist with no MusicBrainz ID
std::string strSQL;
strSQL = PrepareSQL("UPDATE artist SET strMusicBrainzArtistID = '%s', bScrapedMBID = 1 "
@@ -1871,8 +1896,10 @@ bool CMusicDatabase::GetArtist(int idArtist, CArtist &artist, bool fetchAll /* =
while (!m_pDS->eof())
{
const dbiplus::sql_record* const record = m_pDS->get_sql_record();
-
- artist.discography.emplace_back(record->at(discographyOffset + 1).get_asString(), record->at(discographyOffset + 2).get_asString());
+ CDiscoAlbum discoAlbum;
+ discoAlbum.strAlbum = record->at(discographyOffset + 1).get_asString();
+ discoAlbum.strYear = record->at(discographyOffset + 2).get_asString();
+ discoAlbum.strReleaseGroupMBID = record->at(discographyOffset + 3).get_asString();
m_pDS->next();
}
}
@@ -1926,6 +1953,40 @@ int CMusicDatabase::GetLastArtist()
return static_cast<int>(strtol(lastArtist.c_str(), NULL, 10));
}
+int CMusicDatabase::GetArtistFromMBID(const std::string& strMusicBrainzArtistID,
+ std::string& artistname)
+{
+ if (strMusicBrainzArtistID.empty())
+ return -1;
+
+ std::string strSQL;
+ try
+ {
+ if (nullptr == m_pDB || nullptr == m_pDS2)
+ return -1;
+ // Match on MusicBrainz ID, definitively unique
+ strSQL =
+ PrepareSQL("SELECT idArtist, strArtist FROM artist WHERE strMusicBrainzArtistID = '%s'",
+ strMusicBrainzArtistID.c_str());
+ if (!m_pDS2->query(strSQL))
+ return -1;
+ int idArtist = -1;
+ if (m_pDS2->num_rows() > 0)
+ {
+ idArtist = m_pDS2->fv("idArtist").get_asInt();
+ artistname = m_pDS2->fv("strArtist").get_asString();
+ }
+ m_pDS2->close();
+ return idArtist;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "CMusicDatabase::{0} - failed to execute {1}", __FUNCTION__,
+ strSQL.c_str());
+ }
+ return -1;
+}
+
bool CMusicDatabase::HasArtistBeenScraped(int idArtist)
{
std::string strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE idArtist = %i AND lastScraped IS NULL", idArtist);
@@ -1938,12 +1999,13 @@ bool CMusicDatabase::ClearArtistLastScrapedTime(int idArtist)
return ExecuteQuery(strSQL);
}
-int CMusicDatabase::AddArtistDiscography(int idArtist, const std::string& strAlbum, const std::string& strYear)
+int CMusicDatabase::AddArtistDiscography(int idArtist, const CDiscoAlbum& discoAlbum)
{
- std::string strSQL=PrepareSQL("INSERT INTO discography (idArtist, strAlbum, strYear) values(%i, '%s', '%s')",
- idArtist,
- strAlbum.c_str(),
- strYear.c_str());
+ std::string strSQL = PrepareSQL("INSERT INTO discography "
+ "(idArtist, strAlbum, strYear, strReleaseGroupMBID) "
+ "VALUES(%i, '%s', '%s', '%s')",
+ idArtist, discoAlbum.strAlbum.c_str(), discoAlbum.strYear.c_str(),
+ discoAlbum.strReleaseGroupMBID.c_str());
return ExecuteQuery(strSQL);
}
@@ -1962,19 +2024,64 @@ bool CMusicDatabase::GetArtistDiscography(int idArtist, CFileItemList& items)
if (nullptr == m_pDS)
return false;
- // Combine entries from discography and album tables
- // When title in both, album entry will be before disco entry
+ /* Combine entries from discography and album tables
+ Can not use CREATE TEMPORARY TABLE as MySQL does not support updates of table using
+ correlated subqueries to a temp table. An updatable join to temp table would work in MySQL
+ but SQLite not support updatable joins.
+ */
+ m_pDS->exec("CREATE TABLE tempDisco "
+ "(strAlbum TEXT, iYear INTEGER, mbid TEXT, idAlbum INTEGER)");
+
std::string strSQL;
- strSQL = PrepareSQL("SELECT strAlbum, "
- "CAST(discography.strYear AS INTEGER) AS iYear, -1 AS idAlbum "
- "FROM discography "
- "WHERE discography.idArtist = %i "
- "UNION "
- "SELECT strAlbum, CAST(strReleaseDate AS INTEGER) AS iYear, album.idAlbum "
- "FROM album JOIN album_artist ON album_artist.idAlbum = album.idAlbum "
- "WHERE album_artist.idArtist = %i "
- "ORDER BY iYear, strAlbum, idAlbum DESC",
- idArtist, idArtist);
+ strSQL = PrepareSQL("INSERT INTO tempDisco(strAlbum, iYear, mbid, idAlbum) "
+ "SELECT strAlbum, CAST(discography.strYear AS INTEGER) AS iYear, "
+ "strReleaseGroupMBID, NULL "
+ "FROM discography WHERE idArtist = %i",
+ idArtist);
+ m_pDS->exec(strSQL);
+
+ // Match albums on release group mbid, if multi-releases then first used
+ strSQL = "UPDATE tempDisco SET idAlbum = (SELECT album.idAlbum FROM album "
+ "WHERE album.strReleaseGroupMBID = tempDisco.mbid "
+ "AND album.strReleaseGroupMBID IS NOT NULL)";
+ m_pDS->exec(strSQL);
+ // Match remaining to albums by artist on title and year
+ strSQL = PrepareSQL("UPDATE tempDisco SET idAlbum = (SELECT album.idAlbum FROM album "
+ "JOIN album_artist ON album_artist.idAlbum = album.idAlbum "
+ "WHERE album_artist.idArtist = %i "
+ "AND NOT EXISTS(SELECT 1 FROM tempDisco AS td "
+ "WHERE td.idAlbum = album.idAlbum) "
+ "AND CAST(strOrigReleaseDate AS INTEGER) = tempDisco.iYear "
+ "AND album.strAlbum = tempDisco.strAlbum) "
+ "WHERE tempDisco.idAlbum is NULL",
+ idArtist);
+ m_pDS->exec(strSQL);
+ // Match remaining to albums by artist on title only
+ strSQL = PrepareSQL("UPDATE tempDisco SET idAlbum = (SELECT album.idAlbum FROM album "
+ "JOIN album_artist ON album_artist.idAlbum = album.idAlbum "
+ "WHERE album_artist.idArtist = %i "
+ "AND NOT EXISTS(SELECT 1 FROM tempDisco AS td "
+ "WHERE td.idAlbum = album.idAlbum) "
+ "AND album.strAlbum = tempDisco.strAlbum) "
+ "WHERE tempDisco.idAlbum is NULL",
+ idArtist);
+ m_pDS->exec(strSQL);
+ // Use year from album table, when matched by name only it could be different
+ strSQL = PrepareSQL("UPDATE tempDisco "
+ "SET iYear = (SELECT CAST(album.strOrigReleaseDate AS INTEGER) FROM album "
+ "WHERE album.idAlbum = tempDisco.idAlbum) "
+ "WHERE tempDisco.idAlbum > 0");
+ m_pDS->exec(strSQL);
+
+ // Combine distinctly with albums by artist that are not in discography
+ strSQL =
+ PrepareSQL("SELECT strAlbum, iYear, idAlbum FROM tempDisco "
+ "UNION "
+ "SELECT strAlbum, CAST(strOrigReleaseDate AS INTEGER) AS iYear, album.idAlbum "
+ "FROM album JOIN album_artist ON album_artist.idAlbum = album.idAlbum "
+ "WHERE album_artist.idArtist = %i "
+ "ORDER BY iYear, strAlbum, idAlbum",
+ idArtist);
if (!m_pDS->query(strSQL))
return false;
@@ -1985,40 +2092,31 @@ bool CMusicDatabase::GetArtistDiscography(int idArtist, CFileItemList& items)
return true;
}
- std::string strAlbum;
- std::string strLastAlbum;
- int iLastID = -1;
while (!m_pDS->eof())
{
int idAlbum = m_pDS->fv("idAlbum").get_asInt();
- strAlbum = m_pDS->fv("strAlbum").get_asString();
+ if (idAlbum == 0)
+ idAlbum = -1;
+ std::string strAlbum = m_pDS->fv("strAlbum").get_asString();
if (!strAlbum.empty())
{
- if (strAlbum.compare(strLastAlbum) != 0)
- { // Save new title (from album or discography)
CFileItemPtr pItem(new CFileItem(strAlbum));
pItem->SetLabel2(m_pDS->fv("iYear").get_asString());
pItem->GetMusicInfoTag()->SetDatabaseId(idAlbum, MediaTypeAlbum);
-
items.Add(pItem);
- strLastAlbum = strAlbum;
- iLastID = idAlbum;
- }
- else if (idAlbum > 0 && iLastID < 0)
- { // Amend previously saved discography item to set album ID
- items[items.Size() - 1]->GetMusicInfoTag()->SetDatabaseId(idAlbum, MediaTypeAlbum);
- }
}
m_pDS->next();
}
// cleanup
m_pDS->close();
+ m_pDS->exec("DROP TABLE tempDisco");
return true;
}
catch (...)
{
+ m_pDS->exec("DROP TABLE tempDisco");
CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
}
return false;
@@ -2594,7 +2692,7 @@ int CMusicDatabase::AddPath(const std::string& strPath1)
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
- // doesnt exists, add it
+ // doesn't exists, add it
strSQL=PrepareSQL("insert into path (idPath, strPath) values( NULL, '%s' )", strPath.c_str());
m_pDS->exec(strSQL);
@@ -4211,7 +4309,7 @@ bool CMusicDatabase::LookupCDDBInfo(bool bRequery/*=false*/)
else
{
pCdInfo->SetNoCDDBInfo();
- // ..no, an error occured, display it to the user
+ // ..no, an error occurred, display it to the user
std::string strErrorText = StringUtils::Format("[%d] %s", cddb.getLastError(), cddb.getLastErrorText());
HELPERS::ShowOKDialogLines(CVariant{255}, CVariant{257}, CVariant{std::move(strErrorText)}, CVariant{0});
}
@@ -8208,7 +8306,7 @@ void CMusicDatabase::UpdateTables(int version)
}
catch (...)
{
- CLog::Log(LOGERROR, "Migrating specific artist scraper settings has failed, settings not transfered");
+ CLog::Log(LOGERROR, "Migrating specific artist scraper settings has failed, settings not transferred");
}
try
{
@@ -8219,7 +8317,7 @@ void CMusicDatabase::UpdateTables(int version)
}
catch (...)
{
- CLog::Log(LOGERROR, "Migrating specific album scraper settings has failed, settings not transfered");
+ CLog::Log(LOGERROR, "Migrating specific album scraper settings has failed, settings not transferred");
}
try
{
@@ -8234,7 +8332,7 @@ void CMusicDatabase::UpdateTables(int version)
}
catch (...)
{
- CLog::Log(LOGERROR, "Migrating album and artist scraper settings has failed, settings not transfered");
+ CLog::Log(LOGERROR, "Migrating album and artist scraper settings has failed, settings not transferred");
}
m_pDS->exec("DROP TABLE content_temp");
@@ -8452,6 +8550,10 @@ void CMusicDatabase::UpdateTables(int version)
m_pDS->exec(PrepareSQL("UPDATE artist SET dateNew = '%s'", strUTCNow.c_str()));
m_pDS->exec("UPDATE artist SET dateModified = dateNew");
}
+ if (version < 79)
+ {
+ m_pDS->exec("ALTER TABLE discography ADD strReleaseGroupMBID TEXT");
+ }
// Set the verion of tag scanning required.
// Not every schema change requires the tags to be rescanned, set to the highest schema version
@@ -8473,7 +8575,7 @@ void CMusicDatabase::UpdateTables(int version)
int CMusicDatabase::GetSchemaVersion() const
{
- return 78;
+ return 79;
}
int CMusicDatabase::GetMusicNeedsTagScan()
diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h
index 8fada1a898..21c898ba87 100644
--- a/xbmc/music/MusicDatabase.h
+++ b/xbmc/music/MusicDatabase.h
@@ -327,6 +327,7 @@ public:
bool GetArtist(int idArtist, CArtist& artist, bool fetchAll = false);
bool GetArtistExists(int idArtist);
int GetLastArtist();
+ int GetArtistFromMBID(const std::string& strMusicBrainzArtistID, std::string& artistname);
int UpdateArtist(int idArtist,
const std::string& strArtist, const std::string& strSortName,
const std::string& strMusicBrainzArtistID, bool bScrapedMBID,
@@ -343,7 +344,7 @@ public:
void SetTranslateBlankArtist(bool translate) { m_translateBlankArtist = translate; }
bool HasArtistBeenScraped(int idArtist);
bool ClearArtistLastScrapedTime(int idArtist);
- int AddArtistDiscography(int idArtist, const std::string& strAlbum, const std::string& strYear);
+ int AddArtistDiscography(int idArtist, const CDiscoAlbum& discoAlbum);
bool DeleteArtistDiscography(int idArtist);
bool GetArtistDiscography(int idArtist, CFileItemList& items);
@@ -669,7 +670,7 @@ public:
/*! \brief Check if music files need all tags rescanning regardless of file being unchanged
because the tag processing has changed (which may happen without db version changes) since they
where last scanned.
- \return -1 if an error occured, 0 if no scan is needed, or the version number of tags if not the same as current.
+ \return -1 if an error occurred, 0 if no scan is needed, or the version number of tags if not the same as current.
*/
virtual int GetMusicNeedsTagScan();
diff --git a/xbmc/network/WakeOnAccess.cpp b/xbmc/network/WakeOnAccess.cpp
index b4824be357..4e24fb06e9 100644
--- a/xbmc/network/WakeOnAccess.cpp
+++ b/xbmc/network/WakeOnAccess.cpp
@@ -811,7 +811,7 @@ void CWakeOnAccess::LoadFromXML()
TiXmlElement* pRootElement = xmlDoc.RootElement();
if (StringUtils::CompareNoCase(pRootElement->Value(), "onaccesswakeup"))
{
- CLog::Log(LOGERROR, "%s - XML file %s doesnt contain <onaccesswakeup>", __FUNCTION__, GetSettingFile().c_str());
+ CLog::Log(LOGERROR, "%s - XML file %s doesn't contain <onaccesswakeup>", __FUNCTION__, GetSettingFile().c_str());
return;
}
diff --git a/xbmc/network/WebServer.cpp b/xbmc/network/WebServer.cpp
index 7371bfa073..427ca38c70 100644
--- a/xbmc/network/WebServer.cpp
+++ b/xbmc/network/WebServer.cpp
@@ -38,14 +38,18 @@
#define MAX_POST_BUFFER_SIZE 2048
-#define PAGE_FILE_NOT_FOUND "<html><head><title>File not found</title></head><body>File not found</body></html>"
-#define NOT_SUPPORTED "<html><head><title>Not Supported</title></head><body>The method you are trying to use is not supported by this server</body></html>"
+#define PAGE_FILE_NOT_FOUND \
+ "<html><head><title>File not found</title></head><body>File not found</body></html>"
+#define NOT_SUPPORTED \
+ "<html><head><title>Not Supported</title></head><body>The method you are trying to use is not " \
+ "supported by this server</body></html>"
#define HEADER_VALUE_NO_CACHE "no-cache"
-#define HEADER_NEWLINE "\r\n"
+#define HEADER_NEWLINE "\r\n"
-typedef struct {
+typedef struct
+{
std::shared_ptr<XFILE::CFile> file;
CHttpRanges ranges;
size_t rangeCountTotal;
@@ -67,7 +71,7 @@ CWebServer::CWebServer()
m_logger(CServiceBroker::GetLogging().GetLogger("CWebServer"))
{
#if defined(TARGET_DARWIN)
- void *stack_addr;
+ void* stack_addr;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_getstack(&attr, &stack_addr, &m_thread_stacksize);
@@ -94,16 +98,16 @@ static MHD_Response* create_response(size_t size, const void* data, int free, in
return MHD_create_response_from_buffer(size, const_cast<void*>(data), mode);
}
-int CWebServer::AskForAuthentication(const HTTPRequest& request) const
+MHD_RESULT CWebServer::AskForAuthentication(const HTTPRequest& request) const
{
- struct MHD_Response *response = create_response(0, nullptr, MHD_NO, MHD_NO);
+ struct MHD_Response* response = create_response(0, nullptr, MHD_NO, MHD_NO);
if (!response)
{
m_logger->error("unable to create HTTP Unauthorized response");
return MHD_NO;
}
- int ret = AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close");
+ MHD_RESULT ret = AddHeader(response, MHD_HTTP_HEADER_CONNECTION, "close");
if (!ret)
{
m_logger->error("unable to prepare HTTP Unauthorized response");
@@ -113,8 +117,12 @@ int CWebServer::AskForAuthentication(const HTTPRequest& request) const
LogResponse(request, MHD_HTTP_UNAUTHORIZED);
- ret =
- MHD_queue_basic_auth_fail_response(request.connection, CCompileInfo::GetAppName(), response);
+ // This MHD_RESULT cast is only necessary for libmicrohttpd 0.9.71
+ // The return type of MHD_queue_basic_auth_fail_response was fixed for future versions
+ // See
+ // https://git.gnunet.org/libmicrohttpd.git/commit/?id=860b42e9180da4dcd7e8690a3fcdb4e37e5772c5
+ ret = static_cast<MHD_RESULT>(
+ MHD_queue_basic_auth_fail_response(request.connection, CCompileInfo::GetAppName(), response));
MHD_destroy_response(response);
return ret;
@@ -144,10 +152,14 @@ bool CWebServer::IsAuthenticated(const HTTPRequest& request) const
return authenticated;
}
-int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
- const char *url, const char *method,
- const char *version, const char *upload_data,
- size_t *upload_data_size, void **con_cls)
+MHD_RESULT CWebServer::AnswerToConnection(void* cls,
+ struct MHD_Connection* connection,
+ const char* url,
+ const char* method,
+ const char* version,
+ const char* upload_data,
+ size_t* upload_data_size,
+ void** con_cls)
{
if (cls == nullptr || con_cls == nullptr || *con_cls == nullptr)
{
@@ -155,7 +167,7 @@ int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
return MHD_NO;
}
- CWebServer *webServer = reinterpret_cast<CWebServer*>(cls);
+ CWebServer* webServer = reinterpret_cast<CWebServer*>(cls);
if (webServer == nullptr)
{
s_logger->error("invalid request received");
@@ -164,15 +176,22 @@ int CWebServer::AnswerToConnection(void *cls, struct MHD_Connection *connection,
ConnectionHandler* connectionHandler = reinterpret_cast<ConnectionHandler*>(*con_cls);
HTTPMethod methodType = GetHTTPMethod(method);
- HTTPRequest request = { webServer, connection, connectionHandler->fullUri, url, methodType, version };
+ HTTPRequest request = {webServer, connection, connectionHandler->fullUri,
+ url, methodType, version};
if (connectionHandler->isNew)
webServer->LogRequest(request);
- return webServer->HandlePartialRequest(connection, connectionHandler, request, upload_data, upload_data_size, con_cls);
+ return webServer->HandlePartialRequest(connection, connectionHandler, request, upload_data,
+ upload_data_size, con_cls);
}
-int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, ConnectionHandler* connectionHandler, const HTTPRequest& request, const char *upload_data, size_t *upload_data_size, void **con_cls)
+MHD_RESULT CWebServer::HandlePartialRequest(struct MHD_Connection* connection,
+ ConnectionHandler* connectionHandler,
+ const HTTPRequest& request,
+ const char* upload_data,
+ size_t* upload_data_size,
+ void** con_cls)
{
std::unique_ptr<ConnectionHandler> conHandler(connectionHandler);
@@ -205,17 +224,18 @@ int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, Connecti
if (handler->GetLastModifiedDate(lastModified) && lastModified.IsValid())
{
// handle If-Modified-Since or If-Unmodified-Since
- std::string ifModifiedSince = HTTPRequestHandlerUtils::GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MODIFIED_SINCE);
- std::string ifUnmodifiedSince = HTTPRequestHandlerUtils::GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE);
+ std::string ifModifiedSince = HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MODIFIED_SINCE);
+ std::string ifUnmodifiedSince = HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE);
CDateTime ifModifiedSinceDate;
CDateTime ifUnmodifiedSinceDate;
// handle If-Modified-Since (but only if the response is cacheable)
- if (cacheable &&
- ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince) &&
- lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
+ if (cacheable && ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince) &&
+ lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
{
- struct MHD_Response *response = create_response(0, nullptr, MHD_NO, MHD_NO);
+ struct MHD_Response* response = create_response(0, nullptr, MHD_NO, MHD_NO);
if (response == nullptr)
{
m_logger->error("failed to create a HTTP 304 response");
@@ -226,7 +246,7 @@ int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, Connecti
}
// handle If-Unmodified-Since
else if (ifUnmodifiedSinceDate.SetFromRFC1123DateTime(ifUnmodifiedSince) &&
- lastModified.GetAsUTCDateTime() > ifUnmodifiedSinceDate)
+ lastModified.GetAsUTCDateTime() > ifUnmodifiedSinceDate)
return SendErrorResponse(request, MHD_HTTP_PRECONDITION_FAILED, request.method);
}
@@ -240,7 +260,8 @@ int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, Connecti
// as ownership of the connection handler is passed to libmicrohttpd we must not destroy it
SetupPostDataProcessing(request, conHandler.get(), handler, con_cls);
- // as ownership of the connection handler has been passed to libmicrohttpd we must not destroy it
+ // as ownership of the connection handler has been passed to libmicrohttpd we must not
+ // destroy it
conHandler.release();
return MHD_YES;
@@ -258,7 +279,8 @@ int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, Connecti
// process additional / remaining POST data
if (ProcessPostData(request, conHandler.get(), upload_data, upload_data_size, con_cls))
{
- // as ownership of the connection handler has been passed to libmicrohttpd we must not destroy it
+ // as ownership of the connection handler has been passed to libmicrohttpd we must not
+ // destroy it
conHandler.release();
return MHD_YES;
@@ -275,7 +297,8 @@ int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, Connecti
return HandleRequest(conHandler->requestHandler);
}
- // it's unusual to get more than one call to AnswerToConnection for none-POST requests, but let's handle it anyway
+ // it's unusual to get more than one call to AnswerToConnection for none-POST requests, but
+ // let's handle it anyway
auto requestHandler = FindRequestHandler(request);
if (requestHandler != nullptr)
return HandleRequest(requestHandler);
@@ -285,15 +308,20 @@ int CWebServer::HandlePartialRequest(struct MHD_Connection *connection, Connecti
return SendErrorResponse(request, MHD_HTTP_NOT_FOUND, request.method);
}
-int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
- const char *filename, const char *content_type,
- const char *transfer_encoding, const char *data, uint64_t off,
- size_t size)
+MHD_RESULT CWebServer::HandlePostField(void* cls,
+ enum MHD_ValueKind kind,
+ const char* key,
+ const char* filename,
+ const char* content_type,
+ const char* transfer_encoding,
+ const char* data,
+ uint64_t off,
+ size_t size)
{
- ConnectionHandler *conHandler = (ConnectionHandler *)cls;
+ ConnectionHandler* conHandler = (ConnectionHandler*)cls;
- if (conHandler == nullptr || conHandler->requestHandler == nullptr ||
- key == nullptr || data == nullptr || size == 0)
+ if (conHandler == nullptr || conHandler->requestHandler == nullptr || key == nullptr ||
+ data == nullptr || size == 0)
{
s_logger->error("unable to handle HTTP POST field");
return MHD_NO;
@@ -303,21 +331,21 @@ int CWebServer::HandlePostField(void *cls, enum MHD_ValueKind kind, const char *
return MHD_YES;
}
-int CWebServer::HandleRequest(const std::shared_ptr<IHTTPRequestHandler>& handler)
+MHD_RESULT CWebServer::HandleRequest(const std::shared_ptr<IHTTPRequestHandler>& handler)
{
if (handler == nullptr)
return MHD_NO;
HTTPRequest request = handler->GetRequest();
- int ret = handler->HandleRequest();
+ MHD_RESULT ret = handler->HandleRequest();
if (ret == MHD_NO)
{
m_logger->error("failed to handle HTTP request for {}", request.pathUrl);
return SendErrorResponse(request, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
}
- const HTTPResponseDetails &responseDetails = handler->GetResponseDetails();
- struct MHD_Response *response = nullptr;
+ const HTTPResponseDetails& responseDetails = handler->GetResponseDetails();
+ struct MHD_Response* response = nullptr;
switch (responseDetails.type)
{
case HTTPNone:
@@ -340,7 +368,8 @@ int CWebServer::HandleRequest(const std::shared_ptr<IHTTPRequestHandler>& handle
break;
case HTTPError:
- ret = CreateErrorResponse(request.connection, responseDetails.status, request.method, response);
+ ret =
+ CreateErrorResponse(request.connection, responseDetails.status, request.method, response);
break;
default:
@@ -357,13 +386,15 @@ int CWebServer::HandleRequest(const std::shared_ptr<IHTTPRequestHandler>& handle
return FinalizeRequest(handler, responseDetails.status, response);
}
-int CWebServer::FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& handler, int responseStatus, struct MHD_Response *response)
+MHD_RESULT CWebServer::FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& handler,
+ int responseStatus,
+ struct MHD_Response* response)
{
if (handler == nullptr || response == nullptr)
return MHD_NO;
- const HTTPRequest &request = handler->GetRequest();
- const HTTPResponseDetails &responseDetails = handler->GetResponseDetails();
+ const HTTPRequest& request = handler->GetRequest();
+ const HTTPResponseDetails& responseDetails = handler->GetResponseDetails();
// if the request handler has set a content type and it hasn't been set as a header, add it
if (!responseDetails.contentType.empty())
@@ -389,7 +420,8 @@ int CWebServer::FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& hand
// if the response can't be cached or the maximum age is 0 force the client not to cache
if (!handler->CanBeCached() || maxAge == 0)
- handler->AddResponseHeader(MHD_HTTP_HEADER_CACHE_CONTROL, "private, max-age=0, " HEADER_VALUE_NO_CACHE);
+ handler->AddResponseHeader(MHD_HTTP_HEADER_CACHE_CONTROL,
+ "private, max-age=0, " HEADER_VALUE_NO_CACHE);
else
{
// create the value of the Cache-Control header
@@ -426,14 +458,14 @@ int CWebServer::FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& hand
return SendResponse(request, responseStatus, response);
}
-std::shared_ptr<IHTTPRequestHandler> CWebServer::FindRequestHandler(const HTTPRequest& request) const
+std::shared_ptr<IHTTPRequestHandler> CWebServer::FindRequestHandler(
+ const HTTPRequest& request) const
{
// look for a IHTTPRequestHandler which can take care of the current request
auto requestHandlerIt = std::find_if(m_requestHandlers.cbegin(), m_requestHandlers.cend(),
- [&request](const IHTTPRequestHandler* requestHandler)
- {
- return requestHandler->CanHandleRequest(request);
- });
+ [&request](const IHTTPRequestHandler* requestHandler) {
+ return requestHandler->CanHandleRequest(request);
+ });
// we found a matching IHTTPRequestHandler so let's get a new instance for this request
if (requestHandlerIt != m_requestHandlers.cend())
@@ -445,7 +477,8 @@ std::shared_ptr<IHTTPRequestHandler> CWebServer::FindRequestHandler(const HTTPRe
bool CWebServer::IsRequestCacheable(const HTTPRequest& request) const
{
// handle Cache-Control
- std::string cacheControl = HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL);
+ std::string cacheControl = HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CACHE_CONTROL);
if (!cacheControl.empty())
{
std::vector<std::string> cacheControls = StringUtils::Split(cacheControl, ",");
@@ -460,23 +493,26 @@ bool CWebServer::IsRequestCacheable(const HTTPRequest& request) const
}
// handle Pragma
- std::string pragma = HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_PRAGMA);
+ std::string pragma = HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_PRAGMA);
if (pragma.compare(HEADER_VALUE_NO_CACHE) == 0)
return false;
return true;
}
-bool CWebServer::IsRequestRanged(const HTTPRequest& request, const CDateTime &lastModified) const
+bool CWebServer::IsRequestRanged(const HTTPRequest& request, const CDateTime& lastModified) const
{
// parse the Range header and store it in the request object
CHttpRanges ranges;
- bool ranged = ranges.Parse(HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE));
+ bool ranged = ranges.Parse(HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE));
// handle If-Range header but only if the Range header is present
if (ranged && lastModified.IsValid())
{
- std::string ifRange = HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_RANGE);
+ std::string ifRange = HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_RANGE);
if (!ifRange.empty() && lastModified.IsValid())
{
CDateTime ifRangeDate;
@@ -492,25 +528,33 @@ bool CWebServer::IsRequestRanged(const HTTPRequest& request, const CDateTime &la
return !ranges.IsEmpty();
}
-void CWebServer::SetupPostDataProcessing(const HTTPRequest& request, ConnectionHandler *connectionHandler, std::shared_ptr<IHTTPRequestHandler> handler, void **con_cls) const
+void CWebServer::SetupPostDataProcessing(const HTTPRequest& request,
+ ConnectionHandler* connectionHandler,
+ std::shared_ptr<IHTTPRequestHandler> handler,
+ void** con_cls) const
{
connectionHandler->requestHandler = handler;
- // we might need to handle the POST data ourselves which is done in the next call to AnswerToConnection
+ // we might need to handle the POST data ourselves which is done in the next call to
+ // AnswerToConnection
*con_cls = connectionHandler;
// get the content-type of the POST data
- const auto contentType = HTTPRequestHandlerUtils::GetRequestHeaderValue(request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
+ const auto contentType = HTTPRequestHandlerUtils::GetRequestHeaderValue(
+ request.connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
if (contentType.empty())
return;
- // if the content-type is neither application/x-ww-form-urlencoded nor multipart/form-data we need to handle it ourselves
+ // if the content-type is neither application/x-ww-form-urlencoded nor multipart/form-data we need
+ // to handle it ourselves
if (!StringUtils::EqualsNoCase(contentType, MHD_HTTP_POST_ENCODING_FORM_URLENCODED) &&
!StringUtils::EqualsNoCase(contentType, MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA))
return;
// otherwise we can use MHD's POST processor
- connectionHandler->postprocessor = MHD_create_post_processor(request.connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField, static_cast<void*>(connectionHandler));
+ connectionHandler->postprocessor = MHD_create_post_processor(
+ request.connection, MAX_POST_BUFFER_SIZE, &CWebServer::HandlePostField,
+ static_cast<void*>(connectionHandler));
// MHD doesn't seem to be able to handle this post request
if (connectionHandler->postprocessor == nullptr)
@@ -520,7 +564,11 @@ void CWebServer::SetupPostDataProcessing(const HTTPRequest& request, ConnectionH
}
}
-bool CWebServer::ProcessPostData(const HTTPRequest& request, ConnectionHandler *connectionHandler, const char *upload_data, size_t *upload_data_size, void **con_cls) const
+bool CWebServer::ProcessPostData(const HTTPRequest& request,
+ ConnectionHandler* connectionHandler,
+ const char* upload_data,
+ size_t* upload_data_size,
+ void** con_cls) const
{
if (connectionHandler->requestHandler == nullptr)
{
@@ -543,10 +591,12 @@ bool CWebServer::ProcessPostData(const HTTPRequest& request, ConnectionHandler *
bool postDataHandled = false;
// either use MHD's POST processor
if (connectionHandler->postprocessor != nullptr)
- postDataHandled = MHD_post_process(connectionHandler->postprocessor, upload_data, *upload_data_size) == MHD_YES;
+ postDataHandled = MHD_post_process(connectionHandler->postprocessor, upload_data,
+ *upload_data_size) == MHD_YES;
// or simply copy the data to the handler
else if (connectionHandler->requestHandler != nullptr)
- postDataHandled = connectionHandler->requestHandler->AddPostData(upload_data, *upload_data_size);
+ postDataHandled =
+ connectionHandler->requestHandler->AddPostData(upload_data, *upload_data_size);
// abort if the received POST data couldn't be handled
if (!postDataHandled)
@@ -566,7 +616,7 @@ bool CWebServer::ProcessPostData(const HTTPRequest& request, ConnectionHandler *
return true;
}
-void CWebServer::FinalizePostDataProcessing(ConnectionHandler *connectionHandler) const
+void CWebServer::FinalizePostDataProcessing(ConnectionHandler* connectionHandler) const
{
if (connectionHandler->postprocessor == nullptr)
return;
@@ -574,13 +624,14 @@ void CWebServer::FinalizePostDataProcessing(ConnectionHandler *connectionHandler
MHD_destroy_post_processor(connectionHandler->postprocessor);
}
-int CWebServer::CreateMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const
+MHD_RESULT CWebServer::CreateMemoryDownloadResponse(
+ const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response*& response) const
{
if (handler == nullptr)
return MHD_NO;
- const HTTPRequest &request = handler->GetRequest();
- const HTTPResponseDetails &responseDetails = handler->GetResponseDetails();
+ const HTTPRequest& request = handler->GetRequest();
+ const HTTPResponseDetails& responseDetails = handler->GetResponseDetails();
HttpResponseRanges responseRanges = handler->GetResponseData();
// check if the response is completely empty
@@ -589,7 +640,7 @@ int CWebServer::CreateMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestH
// check if the response contains more ranges than the request asked for
if ((request.ranges.IsEmpty() && responseRanges.size() > 1) ||
- (!request.ranges.IsEmpty() && responseRanges.size() > request.ranges.Size()))
+ (!request.ranges.IsEmpty() && responseRanges.size() > request.ranges.Size()))
{
m_logger->warn("response contains more ranges ({}) than the request asked for ({})",
static_cast<int>(responseRanges.size()),
@@ -615,33 +666,38 @@ int CWebServer::CreateMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestH
switch (responseDetails.type)
{
- case HTTPMemoryDownloadNoFreeNoCopy:
- return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, false, false, response);
+ case HTTPMemoryDownloadNoFreeNoCopy:
+ return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength,
+ false, false, response);
- case HTTPMemoryDownloadNoFreeCopy:
- return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, false, true, response);
+ case HTTPMemoryDownloadNoFreeCopy:
+ return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength,
+ false, true, response);
- case HTTPMemoryDownloadFreeNoCopy:
- return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, true, false, response);
+ case HTTPMemoryDownloadFreeNoCopy:
+ return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength,
+ true, false, response);
- case HTTPMemoryDownloadFreeCopy:
- return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength, true, true, response);
+ case HTTPMemoryDownloadFreeCopy:
+ return CreateMemoryDownloadResponse(request.connection, responseData, responseDataLength,
+ true, true, response);
- default:
- return SendErrorResponse(request, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
+ default:
+ return SendErrorResponse(request, MHD_HTTP_INTERNAL_SERVER_ERROR, request.method);
}
}
return CreateRangedMemoryDownloadResponse(handler, response);
}
-int CWebServer::CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const
+MHD_RESULT CWebServer::CreateRangedMemoryDownloadResponse(
+ const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response*& response) const
{
if (handler == nullptr)
return MHD_NO;
- const HTTPRequest &request = handler->GetRequest();
- const HTTPResponseDetails &responseDetails = handler->GetResponseDetails();
+ const HTTPRequest& request = handler->GetRequest();
+ const HTTPResponseDetails& responseDetails = handler->GetResponseDetails();
HttpResponseRanges responseRanges = handler->GetResponseData();
// if there's no or only one range this is not the right place
@@ -673,8 +729,10 @@ int CWebServer::CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRe
// adjust the HTTP status of the response
handler->SetResponseStatus(MHD_HTTP_PARTIAL_CONTENT);
// add Content-Range header
- handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_RANGE,
- HttpRangeUtils::GenerateContentRangeHeaderValue(firstRangePosition, lastRangePosition, responseDetails.totalLength));
+ handler->AddResponseHeader(
+ MHD_HTTP_HEADER_CONTENT_RANGE,
+ HttpRangeUtils::GenerateContentRangeHeaderValue(firstRangePosition, lastRangePosition,
+ responseDetails.totalLength));
// generate a multipart boundary
std::string multipartBoundary = HttpRangeUtils::GenerateMultipartBoundary();
@@ -685,7 +743,8 @@ int CWebServer::CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRe
handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_TYPE, contentType);
// generate the multipart boundary with the Content-Type header field
- std::string multipartBoundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType);
+ std::string multipartBoundaryWithHeader =
+ HttpRangeUtils::GenerateMultipartBoundaryWithHeader(multipartBoundary, contentType);
std::string result;
// add all the ranges to the result
@@ -695,14 +754,18 @@ int CWebServer::CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRe
if (range != ranges.begin())
result += HEADER_NEWLINE;
- // generate and append the multipart boundary with the full header (Content-Type and Content-Length)
- result += HttpRangeUtils::GenerateMultipartBoundaryWithHeader(multipartBoundaryWithHeader, &*range);
+ // generate and append the multipart boundary with the full header (Content-Type and
+ // Content-Length)
+ result +=
+ HttpRangeUtils::GenerateMultipartBoundaryWithHeader(multipartBoundaryWithHeader, &*range);
// and append the data of the range
- result.append(static_cast<const char*>(range->GetData()), static_cast<size_t>(range->GetLength()));
+ result.append(static_cast<const char*>(range->GetData()),
+ static_cast<size_t>(range->GetLength()));
// check if we need to free the range data
- if (responseDetails.type == HTTPMemoryDownloadFreeNoCopy || responseDetails.type == HTTPMemoryDownloadFreeCopy)
+ if (responseDetails.type == HTTPMemoryDownloadFreeNoCopy ||
+ responseDetails.type == HTTPMemoryDownloadFreeCopy)
free(const_cast<void*>(range->GetData()));
}
@@ -713,10 +776,13 @@ int CWebServer::CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRe
StringUtils::Format("{}", static_cast<uint64_t>(result.size())));
// finally create the response
- return CreateMemoryDownloadResponse(request.connection, result.c_str(), result.size(), false, true, response);
+ return CreateMemoryDownloadResponse(request.connection, result.c_str(), result.size(), false,
+ true, response);
}
-int CWebServer::CreateRedirect(struct MHD_Connection *connection, const std::string &strURL, struct MHD_Response *&response) const
+MHD_RESULT CWebServer::CreateRedirect(struct MHD_Connection* connection,
+ const std::string& strURL,
+ struct MHD_Response*& response) const
{
response = create_response(0, nullptr, MHD_NO, MHD_NO);
if (response == nullptr)
@@ -729,13 +795,14 @@ int CWebServer::CreateRedirect(struct MHD_Connection *connection, const std::str
return MHD_YES;
}
-int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const
+MHD_RESULT CWebServer::CreateFileDownloadResponse(
+ const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response*& response) const
{
if (handler == nullptr)
return MHD_NO;
- const HTTPRequest &request = handler->GetRequest();
- const HTTPResponseDetails &responseDetails = handler->GetResponseDetails();
+ const HTTPRequest& request = handler->GetRequest();
+ const HTTPResponseDetails& responseDetails = handler->GetResponseDetails();
HttpResponseRanges responseRanges = handler->GetResponseData();
std::shared_ptr<XFILE::CFile> file = std::make_shared<XFILE::CFile>();
@@ -777,7 +844,8 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
if (!request.ranges.IsEmpty())
context->ranges = request.ranges;
else
- HTTPRequestHandlerUtils::GetRequestedRanges(request.connection, fileLength, context->ranges);
+ HTTPRequestHandlerUtils::GetRequestedRanges(request.connection, fileLength,
+ context->ranges);
}
uint64_t firstPosition = 0;
@@ -789,7 +857,8 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
{
handler->SetResponseStatus(MHD_HTTP_PARTIAL_CONTENT);
- // we need to remember that we are ranged because the range length might change and won't be reliable anymore for length comparisons
+ // we need to remember that we are ranged because the range length might change and won't be
+ // reliable anymore for length comparisons
ranged = true;
context->ranges.GetFirstPosition(firstPosition);
@@ -801,7 +870,8 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
// remember the total length
totalLength = context->ranges.GetLength();
- // adjust the MIME type and range length in case of multiple ranges which requires multipart boundaries
+ // adjust the MIME type and range length in case of multiple ranges which requires multipart
+ // boundaries
if (context->rangeCountTotal > 1)
{
context->boundary = HttpRangeUtils::GenerateMultipartBoundary();
@@ -809,14 +879,19 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
// build part of the boundary with the optional Content-Type header
// "--<boundary>\r\nContent-Type: <content-type>\r\n
- context->boundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundary, context->contentType);
+ context->boundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(
+ context->boundary, context->contentType);
context->boundaryEnd = HttpRangeUtils::GenerateMultipartBoundaryEnd(context->boundary);
// for every range, we need to add a boundary with header
- for (HttpRanges::const_iterator range = context->ranges.Begin(); range != context->ranges.End(); ++range)
+ for (HttpRanges::const_iterator range = context->ranges.Begin();
+ range != context->ranges.End(); ++range)
{
- // we need to temporarily add the Content-Range header to the boundary to be able to determine the length
- std::string completeBoundaryWithHeader = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader, &*range);
+ // we need to temporarily add the Content-Range header to the boundary to be able to
+ // determine the length
+ std::string completeBoundaryWithHeader =
+ HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader,
+ &*range);
totalLength += completeBoundaryWithHeader.size();
// add a newline before any new multipart boundary
@@ -831,10 +906,9 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
context->ranges.GetFirstPosition(context->writePosition);
// create the response object
- response = MHD_create_response_from_callback(totalLength, 2048,
- &CWebServer::ContentReaderCallback,
- context.get(),
- &CWebServer::ContentReaderFreeCallback);
+ response =
+ MHD_create_response_from_callback(totalLength, 2048, &CWebServer::ContentReaderCallback,
+ context.get(), &CWebServer::ContentReaderFreeCallback);
if (response == nullptr)
{
m_logger->error("failed to create a HTTP response for {} to be filled from{}",
@@ -846,7 +920,9 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
// add Content-Range header
if (ranged)
- handler->AddResponseHeader(MHD_HTTP_HEADER_CONTENT_RANGE, HttpRangeUtils::GenerateContentRangeHeaderValue(firstPosition, lastPosition, fileLength));
+ handler->AddResponseHeader(
+ MHD_HTTP_HEADER_CONTENT_RANGE,
+ HttpRangeUtils::GenerateContentRangeHeaderValue(firstPosition, lastPosition, fileLength));
}
else
{
@@ -868,10 +944,13 @@ int CWebServer::CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHan
return MHD_YES;
}
-int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response) const
+MHD_RESULT CWebServer::CreateErrorResponse(struct MHD_Connection* connection,
+ int responseType,
+ HTTPMethod method,
+ struct MHD_Response*& response) const
{
size_t payloadSize = 0;
- const void *payload = nullptr;
+ const void* payload = nullptr;
if (method != HEAD)
{
@@ -879,12 +958,12 @@ int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int respo
{
case MHD_HTTP_NOT_FOUND:
payloadSize = strlen(PAGE_FILE_NOT_FOUND);
- payload = (const void *)PAGE_FILE_NOT_FOUND;
+ payload = (const void*)PAGE_FILE_NOT_FOUND;
break;
case MHD_HTTP_NOT_IMPLEMENTED:
payloadSize = strlen(NOT_SUPPORTED);
- payload = (const void *)NOT_SUPPORTED;
+ payload = (const void*)NOT_SUPPORTED;
break;
}
}
@@ -899,9 +978,15 @@ int CWebServer::CreateErrorResponse(struct MHD_Connection *connection, int respo
return MHD_YES;
}
-int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection, const void *data, size_t size, bool free, bool copy, struct MHD_Response *&response) const
+MHD_RESULT CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection* connection,
+ const void* data,
+ size_t size,
+ bool free,
+ bool copy,
+ struct MHD_Response*& response) const
{
- response = create_response(size, const_cast<void*>(data), free ? MHD_YES : MHD_NO, copy ? MHD_YES : MHD_NO);
+ response = create_response(size, const_cast<void*>(data), free ? MHD_YES : MHD_NO,
+ copy ? MHD_YES : MHD_NO);
if (response == nullptr)
{
m_logger->error("failed to create a HTTP download response");
@@ -911,29 +996,33 @@ int CWebServer::CreateMemoryDownloadResponse(struct MHD_Connection *connection,
return MHD_YES;
}
-int CWebServer::SendResponse(const HTTPRequest& request, int responseStatus, MHD_Response *response) const
+MHD_RESULT CWebServer::SendResponse(const HTTPRequest& request,
+ int responseStatus,
+ MHD_Response* response) const
{
LogResponse(request, responseStatus);
- int ret = MHD_queue_response(request.connection, responseStatus, response);
+ MHD_RESULT ret = MHD_queue_response(request.connection, responseStatus, response);
MHD_destroy_response(response);
return ret;
}
-int CWebServer::SendErrorResponse(const HTTPRequest& request, int errorType, HTTPMethod method) const
+MHD_RESULT CWebServer::SendErrorResponse(const HTTPRequest& request,
+ int errorType,
+ HTTPMethod method) const
{
- struct MHD_Response *response = nullptr;
- int ret = CreateErrorResponse(request.connection, errorType, method, response);
+ struct MHD_Response* response = nullptr;
+ MHD_RESULT ret = CreateErrorResponse(request.connection, errorType, method, response);
if (ret == MHD_NO)
return MHD_NO;
return SendResponse(request, errorType, response);
}
-void* CWebServer::UriRequestLogger(void *cls, const char *uri)
+void* CWebServer::UriRequestLogger(void* cls, const char* uri)
{
- CWebServer *webServer = reinterpret_cast<CWebServer*>(cls);
+ CWebServer* webServer = reinterpret_cast<CWebServer*>(cls);
// log the full URI
if (webServer == nullptr)
@@ -953,9 +1042,9 @@ void CWebServer::LogRequest(const char* uri) const
m_logger->debug("request received for {}", uri);
}
-ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, size_t max)
+ssize_t CWebServer::ContentReaderCallback(void* cls, uint64_t pos, char* buf, size_t max)
{
- HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
+ HttpFileDownloadContext* context = (HttpFileDownloadContext*)cls;
if (context == nullptr || context->file == nullptr)
return -1;
@@ -997,7 +1086,8 @@ ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, si
}
// put together the boundary for the current range
- std::string boundary = HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader, &range);
+ std::string boundary =
+ HttpRangeUtils::GenerateMultipartBoundaryWithHeader(context->boundaryWithHeader, &range);
// copy the boundary into the buffer
memcpy(buf, boundary.c_str(), boundary.size());
@@ -1018,7 +1108,8 @@ ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, si
maximum = std::min(maximum, end - context->writePosition + 1);
// seek to the position if necessary
- if (context->file->GetPosition() < 0 || context->writePosition != static_cast<uint64_t>(context->file->GetPosition()))
+ if (context->file->GetPosition() < 0 ||
+ context->writePosition != static_cast<uint64_t>(context->file->GetPosition()))
context->file->Seek(context->writePosition);
// read data from the file
@@ -1047,9 +1138,9 @@ ssize_t CWebServer::ContentReaderCallback(void *cls, uint64_t pos, char *buf, si
return written;
}
-void CWebServer::ContentReaderFreeCallback(void *cls)
+void CWebServer::ContentReaderFreeCallback(void* cls)
{
- HttpFileDownloadContext *context = (HttpFileDownloadContext *)cls;
+ HttpFileDownloadContext* context = (HttpFileDownloadContext*)cls;
delete context;
if (CServiceBroker::GetLogging().CanLogComponent(LOGWEBSERVER))
@@ -1064,7 +1155,10 @@ static Logger GetMhdLogger()
}
// local helper
-static void panicHandlerForMHD(void* unused, const char* file, unsigned int line, const char *reason)
+static void panicHandlerForMHD(void* unused,
+ const char* file,
+ unsigned int line,
+ const char* reason)
{
GetMhdLogger()->critical("serious error: reason \"{}\" in file \"{}\" at line {}",
reason ? reason : "", file ? file : "", line);
@@ -1093,7 +1187,7 @@ static void logFromMHD(void* unused, const char* fmt, va_list ap)
}
}
-bool CWebServer::LoadCert(std::string &skey, std::string &scert)
+bool CWebServer::LoadCert(std::string& skey, std::string& scert)
{
XFILE::CFile file;
XFILE::auto_buffer buf;
@@ -1137,63 +1231,52 @@ struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port)
MHD_set_panic_func(&panicHandlerForMHD, nullptr);
- if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_WEBSERVERSSL) &&
- MHD_is_feature_supported(MHD_FEATURE_SSL) == MHD_YES &&
- LoadCert(m_key, m_cert))
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_SERVICES_WEBSERVERSSL) &&
+ MHD_is_feature_supported(MHD_FEATURE_SSL) == MHD_YES && LoadCert(m_key, m_cert))
// SSL enabled
- return MHD_start_daemon(flags |
- // one thread per connection
- // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
- // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
- MHD_USE_THREAD_PER_CONNECTION
+ return MHD_start_daemon(
+ flags |
+ // one thread per connection
+ // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
+ // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
+ MHD_USE_THREAD_PER_CONNECTION
#if (MHD_VERSION >= 0x00095207)
- | MHD_USE_INTERNAL_POLLING_THREAD /* MHD_USE_THREAD_PER_CONNECTION must be used only with MHD_USE_INTERNAL_POLLING_THREAD since 0.9.54 */
+ |
+ MHD_USE_INTERNAL_POLLING_THREAD /* MHD_USE_THREAD_PER_CONNECTION must be used only with
+ MHD_USE_INTERNAL_POLLING_THREAD since 0.9.54 */
#endif
- | MHD_USE_DEBUG /* Print MHD error messages to log */
- | MHD_USE_SSL
- ,
- port,
- 0,
- 0,
- &CWebServer::AnswerToConnection,
- this,
-
- MHD_OPTION_CONNECTION_LIMIT, 512,
- MHD_OPTION_CONNECTION_TIMEOUT, timeout,
- MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this,
- MHD_OPTION_EXTERNAL_LOGGER, &logFromMHD, 0,
- MHD_OPTION_THREAD_STACK_SIZE, m_thread_stacksize,
- MHD_OPTION_HTTPS_MEM_KEY, m_key.c_str(),
- MHD_OPTION_HTTPS_MEM_CERT, m_cert.c_str(),
- MHD_OPTION_HTTPS_PRIORITIES, ciphers,
- MHD_OPTION_END);
+ | MHD_USE_DEBUG /* Print MHD error messages to log */
+ | MHD_USE_SSL,
+ port, 0, 0, &CWebServer::AnswerToConnection, this,
+
+ MHD_OPTION_CONNECTION_LIMIT, 512, MHD_OPTION_CONNECTION_TIMEOUT, timeout,
+ MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this,
+ MHD_OPTION_EXTERNAL_LOGGER, &logFromMHD, 0, MHD_OPTION_THREAD_STACK_SIZE,
+ m_thread_stacksize, MHD_OPTION_HTTPS_MEM_KEY, m_key.c_str(), MHD_OPTION_HTTPS_MEM_CERT,
+ m_cert.c_str(), MHD_OPTION_HTTPS_PRIORITIES, ciphers, MHD_OPTION_END);
// No SSL
- return MHD_start_daemon(flags |
- // one thread per connection
- // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
- // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
- MHD_USE_THREAD_PER_CONNECTION
+ return MHD_start_daemon(
+ flags |
+ // one thread per connection
+ // WARNING: set MHD_OPTION_CONNECTION_TIMEOUT to something higher than 1
+ // otherwise on libmicrohttpd 0.4.4-1 it spins a busy loop
+ MHD_USE_THREAD_PER_CONNECTION
#if (MHD_VERSION >= 0x00095207)
- | MHD_USE_INTERNAL_POLLING_THREAD /* MHD_USE_THREAD_PER_CONNECTION must be used only with MHD_USE_INTERNAL_POLLING_THREAD since 0.9.54 */
+ | MHD_USE_INTERNAL_POLLING_THREAD /* MHD_USE_THREAD_PER_CONNECTION must be used only with
+ MHD_USE_INTERNAL_POLLING_THREAD since 0.9.54 */
#endif
- | MHD_USE_DEBUG /* Print MHD error messages to log */
- ,
- port,
- 0,
- 0,
- &CWebServer::AnswerToConnection,
- this,
-
- MHD_OPTION_CONNECTION_LIMIT, 512,
- MHD_OPTION_CONNECTION_TIMEOUT, timeout,
- MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this,
- MHD_OPTION_EXTERNAL_LOGGER, &logFromMHD, 0,
- MHD_OPTION_THREAD_STACK_SIZE, m_thread_stacksize,
- MHD_OPTION_END);
+ | MHD_USE_DEBUG /* Print MHD error messages to log */
+ ,
+ port, 0, 0, &CWebServer::AnswerToConnection, this,
+
+ MHD_OPTION_CONNECTION_LIMIT, 512, MHD_OPTION_CONNECTION_TIMEOUT, timeout,
+ MHD_OPTION_URI_LOG_CALLBACK, &CWebServer::UriRequestLogger, this, MHD_OPTION_EXTERNAL_LOGGER,
+ &logFromMHD, 0, MHD_OPTION_THREAD_STACK_SIZE, m_thread_stacksize, MHD_OPTION_END);
}
-bool CWebServer::Start(uint16_t port, const std::string &username, const std::string &password)
+bool CWebServer::Start(uint16_t port, const std::string& username, const std::string& password)
{
SetCredentials(username, password);
if (!m_running)
@@ -1250,7 +1333,7 @@ bool CWebServer::WebServerSupportsSSL()
return MHD_is_feature_supported(MHD_FEATURE_SSL) == MHD_YES;
}
-void CWebServer::SetCredentials(const std::string &username, const std::string &password)
+void CWebServer::SetCredentials(const std::string& username, const std::string& password)
{
CSingleLock lock(m_critSection);
@@ -1259,7 +1342,7 @@ void CWebServer::SetCredentials(const std::string &username, const std::string &
m_authenticationRequired = !m_authenticationPassword.empty();
}
-void CWebServer::RegisterRequestHandler(IHTTPRequestHandler *handler)
+void CWebServer::RegisterRequestHandler(IHTTPRequestHandler* handler)
{
if (handler == nullptr)
return;
@@ -1270,15 +1353,18 @@ void CWebServer::RegisterRequestHandler(IHTTPRequestHandler *handler)
m_requestHandlers.push_back(handler);
std::sort(m_requestHandlers.begin(), m_requestHandlers.end(),
- [](IHTTPRequestHandler* lhs, IHTTPRequestHandler* rhs) { return rhs->GetPriority() < lhs->GetPriority(); });
+ [](IHTTPRequestHandler* lhs, IHTTPRequestHandler* rhs) {
+ return rhs->GetPriority() < lhs->GetPriority();
+ });
}
-void CWebServer::UnregisterRequestHandler(IHTTPRequestHandler *handler)
+void CWebServer::UnregisterRequestHandler(IHTTPRequestHandler* handler)
{
if (handler == nullptr)
return;
- m_requestHandlers.erase(std::remove(m_requestHandlers.begin(), m_requestHandlers.end(), handler), m_requestHandlers.end());
+ m_requestHandlers.erase(std::remove(m_requestHandlers.begin(), m_requestHandlers.end(), handler),
+ m_requestHandlers.end());
}
void CWebServer::LogRequest(const HTTPRequest& request) const
@@ -1287,9 +1373,11 @@ void CWebServer::LogRequest(const HTTPRequest& request) const
return;
std::multimap<std::string, std::string> headerValues;
- HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_HEADER_KIND, headerValues);
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_HEADER_KIND,
+ headerValues);
std::multimap<std::string, std::string> getValues;
- HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_GET_ARGUMENT_KIND, getValues);
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_GET_ARGUMENT_KIND,
+ getValues);
m_logger->debug(" [IN] {} {} {}", request.version, GetHTTPMethod(request.method),
request.pathUrlFull);
@@ -1313,7 +1401,8 @@ void CWebServer::LogResponse(const HTTPRequest& request, int responseStatus) con
return;
std::multimap<std::string, std::string> headerValues;
- HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_HEADER_KIND, headerValues);
+ HTTPRequestHandlerUtils::GetRequestHeaderValues(request.connection, MHD_HEADER_KIND,
+ headerValues);
m_logger->debug("[OUT] {} {} {}", request.version, responseStatus, request.pathUrlFull);
@@ -1321,7 +1410,7 @@ void CWebServer::LogResponse(const HTTPRequest& request, int responseStatus) con
m_logger->debug("[OUT] {}: {}", header.first, header.second);
}
-std::string CWebServer::CreateMimeTypeFromExtension(const char *ext)
+std::string CWebServer::CreateMimeTypeFromExtension(const char* ext)
{
if (strcmp(ext, ".kar") == 0)
return "audio/midi";
@@ -1331,10 +1420,12 @@ std::string CWebServer::CreateMimeTypeFromExtension(const char *ext)
return CMime::GetMimeType(ext);
}
-int CWebServer::AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value) const
+MHD_RESULT CWebServer::AddHeader(struct MHD_Response* response,
+ const std::string& name,
+ const std::string& value) const
{
if (response == nullptr || name.empty())
- return 0;
+ return MHD_NO;
if (CServiceBroker::GetLogging().CanLogComponent(LOGWEBSERVER))
m_logger->debug("[OUT] {}: {}", name, value);
diff --git a/xbmc/network/WebServer.h b/xbmc/network/WebServer.h
index 70d2853234..a87597ff51 100644
--- a/xbmc/network/WebServer.h
+++ b/xbmc/network/WebServer.h
@@ -57,17 +57,17 @@ protected:
virtual void LogRequest(const char* uri) const;
- virtual int HandlePartialRequest(struct MHD_Connection *connection, ConnectionHandler* connectionHandler, const HTTPRequest& request,
+ virtual MHD_RESULT HandlePartialRequest(struct MHD_Connection *connection, ConnectionHandler* connectionHandler, const HTTPRequest& request,
const char *upload_data, size_t *upload_data_size, void **con_cls);
- virtual int HandleRequest(const std::shared_ptr<IHTTPRequestHandler>& handler);
- virtual int FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& handler, int responseStatus, struct MHD_Response *response);
+ virtual MHD_RESULT HandleRequest(const std::shared_ptr<IHTTPRequestHandler>& handler);
+ virtual MHD_RESULT FinalizeRequest(const std::shared_ptr<IHTTPRequestHandler>& handler, int responseStatus, struct MHD_Response *response);
private:
struct MHD_Daemon* StartMHD(unsigned int flags, int port);
std::shared_ptr<IHTTPRequestHandler> FindRequestHandler(const HTTPRequest& request) const;
- int AskForAuthentication(const HTTPRequest& request) const;
+ MHD_RESULT AskForAuthentication(const HTTPRequest& request) const;
bool IsAuthenticated(const HTTPRequest& request) const;
bool IsRequestCacheable(const HTTPRequest& request) const;
@@ -77,18 +77,18 @@ private:
bool ProcessPostData(const HTTPRequest& request, ConnectionHandler *connectionHandler, const char *upload_data, size_t *upload_data_size, void **con_cls) const;
void FinalizePostDataProcessing(ConnectionHandler *connectionHandler) const;
- int CreateMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const;
- int CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const;
+ MHD_RESULT CreateMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const;
+ MHD_RESULT CreateRangedMemoryDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const;
- int CreateRedirect(struct MHD_Connection *connection, const std::string &strURL, struct MHD_Response *&response) const;
- int CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const;
- int CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response) const;
- int CreateMemoryDownloadResponse(struct MHD_Connection *connection, const void *data, size_t size, bool free, bool copy, struct MHD_Response *&response) const;
+ MHD_RESULT CreateRedirect(struct MHD_Connection *connection, const std::string &strURL, struct MHD_Response *&response) const;
+ MHD_RESULT CreateFileDownloadResponse(const std::shared_ptr<IHTTPRequestHandler>& handler, struct MHD_Response *&response) const;
+ MHD_RESULT CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response) const;
+ MHD_RESULT CreateMemoryDownloadResponse(struct MHD_Connection *connection, const void *data, size_t size, bool free, bool copy, struct MHD_Response *&response) const;
- int SendResponse(const HTTPRequest& request, int responseStatus, MHD_Response *response) const;
- int SendErrorResponse(const HTTPRequest& request, int errorType, HTTPMethod method) const;
+ MHD_RESULT SendResponse(const HTTPRequest& request, int responseStatus, MHD_Response *response) const;
+ MHD_RESULT SendErrorResponse(const HTTPRequest& request, int errorType, HTTPMethod method) const;
- int AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value) const;
+ MHD_RESULT AddHeader(struct MHD_Response *response, const std::string &name, const std::string &value) const;
void LogRequest(const HTTPRequest& request) const;
void LogResponse(const HTTPRequest& request, int responseStatus) const;
@@ -101,11 +101,11 @@ private:
static ssize_t ContentReaderCallback (void *cls, uint64_t pos, char *buf, size_t max);
static void ContentReaderFreeCallback(void *cls);
- static int AnswerToConnection (void *cls, struct MHD_Connection *connection,
+ static MHD_RESULT AnswerToConnection (void *cls, struct MHD_Connection *connection,
const char *url, const char *method,
const char *version, const char *upload_data,
size_t *upload_data_size, void **con_cls);
- static int HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
+ static MHD_RESULT HandlePostField(void *cls, enum MHD_ValueKind kind, const char *key,
const char *filename, const char *content_type,
const char *transfer_encoding, const char *data, uint64_t off,
size_t size);
diff --git a/xbmc/network/cddb.cpp b/xbmc/network/cddb.cpp
index 4966567d78..5272369f4e 100644
--- a/xbmc/network/cddb.cpp
+++ b/xbmc/network/cddb.cpp
@@ -495,7 +495,7 @@ void Xcddb::parseData(const char *buffer)
std::map<std::string, std::string>::const_iterator it = keywords.find(strKeyword);
if (it != keywords.end())
- strValue = it->second + strValue; // keyword occured before, concatenate
+ strValue = it->second + strValue; // keyword occurred before, concatenate
else
keywordsOrder.push_back(strKeyword);
diff --git a/xbmc/network/httprequesthandler/HTTPFileHandler.cpp b/xbmc/network/httprequesthandler/HTTPFileHandler.cpp
index 10b6cf6b41..8bd4ef6f3b 100644
--- a/xbmc/network/httprequesthandler/HTTPFileHandler.cpp
+++ b/xbmc/network/httprequesthandler/HTTPFileHandler.cpp
@@ -24,7 +24,7 @@ CHTTPFileHandler::CHTTPFileHandler(const HTTPRequest &request)
m_lastModified()
{ }
-int CHTTPFileHandler::HandleRequest()
+MHD_RESULT CHTTPFileHandler::HandleRequest()
{
return !m_url.empty() ? MHD_YES : MHD_NO;
}
diff --git a/xbmc/network/httprequesthandler/HTTPFileHandler.h b/xbmc/network/httprequesthandler/HTTPFileHandler.h
index 6baa7acdd5..1e68afb1ce 100644
--- a/xbmc/network/httprequesthandler/HTTPFileHandler.h
+++ b/xbmc/network/httprequesthandler/HTTPFileHandler.h
@@ -19,7 +19,7 @@ class CHTTPFileHandler : public IHTTPRequestHandler
public:
~CHTTPFileHandler() override = default;
- int HandleRequest() override;
+ MHD_RESULT HandleRequest() override;
bool CanHandleRanges() const override { return m_canHandleRanges; }
bool CanBeCached() const override { return m_canBeCached; }
diff --git a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp
index fdcce67c99..5b6783b7dd 100644
--- a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp
+++ b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp
@@ -105,7 +105,7 @@ bool CHTTPImageTransformationHandler::CanHandleRequest(const HTTPRequest &reques
options.find(TRANSFORMATION_OPTION_HEIGHT) != options.end());
}
-int CHTTPImageTransformationHandler::HandleRequest()
+MHD_RESULT CHTTPImageTransformationHandler::HandleRequest()
{
if (m_response.type == HTTPError)
return MHD_YES;
diff --git a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h
index 886351f320..6d8732df9b 100644
--- a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h
+++ b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.h
@@ -23,7 +23,7 @@ public:
IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPImageTransformationHandler(request); }
bool CanHandleRequest(const HTTPRequest &request)const override;
- int HandleRequest() override;
+ MHD_RESULT HandleRequest() override;
bool CanHandleRanges() const override { return true; }
bool CanBeCached() const override { return true; }
diff --git a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp
index 49f8d94f95..5e6d72d89b 100644
--- a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp
+++ b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.cpp
@@ -27,7 +27,7 @@ bool CHTTPJsonRpcHandler::CanHandleRequest(const HTTPRequest &request) const
return (request.pathUrl.compare("/jsonrpc") == 0);
}
-int CHTTPJsonRpcHandler::HandleRequest()
+MHD_RESULT CHTTPJsonRpcHandler::HandleRequest()
{
CHTTPClient client(m_request.method);
bool isRequest = false;
diff --git a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h
index a4a0767b3d..88d4496a55 100644
--- a/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h
+++ b/xbmc/network/httprequesthandler/HTTPJsonRpcHandler.h
@@ -24,7 +24,7 @@ public:
IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPJsonRpcHandler(request); }
bool CanHandleRequest(const HTTPRequest &request) const override;
- int HandleRequest() override;
+ MHD_RESULT HandleRequest() override;
HttpResponseRanges GetResponseData() const override;
diff --git a/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp b/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp
index fb8037ac01..dd64ccfd36 100644
--- a/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp
+++ b/xbmc/network/httprequesthandler/HTTPPythonHandler.cpp
@@ -114,7 +114,7 @@ bool CHTTPPythonHandler::CanHandleRequest(const HTTPRequest &request) const
return true;
}
-int CHTTPPythonHandler::HandleRequest()
+MHD_RESULT CHTTPPythonHandler::HandleRequest()
{
if (m_response.type == HTTPError || m_response.type == HTTPRedirect)
return MHD_YES;
diff --git a/xbmc/network/httprequesthandler/HTTPPythonHandler.h b/xbmc/network/httprequesthandler/HTTPPythonHandler.h
index 03c150693f..166430e68d 100644
--- a/xbmc/network/httprequesthandler/HTTPPythonHandler.h
+++ b/xbmc/network/httprequesthandler/HTTPPythonHandler.h
@@ -25,7 +25,7 @@ public:
bool CanBeCached() const override { return false; }
bool GetLastModifiedDate(CDateTime &lastModified) const override;
- int HandleRequest() override;
+ MHD_RESULT HandleRequest() override;
HttpResponseRanges GetResponseData() const override { return m_responseRanges; }
diff --git a/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp
index 00c15b98df..240449a1ba 100644
--- a/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp
+++ b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.cpp
@@ -62,7 +62,7 @@ bool HTTPRequestHandlerUtils::GetRequestedRanges(struct MHD_Connection *connecti
return ranges.Parse(GetRequestHeaderValue(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE), totalLength);
}
-int HTTPRequestHandlerUtils::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
+MHD_RESULT HTTPRequestHandlerUtils::FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
{
if (cls == nullptr || key == nullptr)
return MHD_NO;
@@ -73,7 +73,7 @@ int HTTPRequestHandlerUtils::FillArgumentMap(void *cls, enum MHD_ValueKind kind,
return MHD_YES;
}
-int HTTPRequestHandlerUtils::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
+MHD_RESULT HTTPRequestHandlerUtils::FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
{
if (cls == nullptr || key == nullptr)
return MHD_NO;
diff --git a/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h
index 3af2f571fb..d02b5c1163 100644
--- a/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h
+++ b/xbmc/network/httprequesthandler/HTTPRequestHandlerUtils.h
@@ -25,6 +25,6 @@ public:
private:
HTTPRequestHandlerUtils() = delete;
- static int FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value);
- static int FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value);
+ static MHD_RESULT FillArgumentMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value);
+ static MHD_RESULT FillArgumentMultiMap(void *cls, enum MHD_ValueKind kind, const char *key, const char *value);
};
diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp
index 589c721ed8..7ceac820f3 100644
--- a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp
+++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.cpp
@@ -19,7 +19,7 @@ bool CHTTPWebinterfaceAddonsHandler::CanHandleRequest(const HTTPRequest &request
return (request.pathUrl.compare("/addons") == 0 || request.pathUrl.compare("/addons/") == 0);
}
-int CHTTPWebinterfaceAddonsHandler::HandleRequest()
+MHD_RESULT CHTTPWebinterfaceAddonsHandler::HandleRequest()
{
m_responseData = ADDON_HEADER;
ADDON::VECADDONS addons;
diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h
index f2ab1491d4..20a44ecffe 100644
--- a/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h
+++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceAddonsHandler.h
@@ -21,7 +21,7 @@ public:
IHTTPRequestHandler* Create(const HTTPRequest &request) const override { return new CHTTPWebinterfaceAddonsHandler(request); }
bool CanHandleRequest(const HTTPRequest &request) const override;
- int HandleRequest() override;
+ MHD_RESULT HandleRequest() override;
HttpResponseRanges GetResponseData() const override;
diff --git a/xbmc/network/httprequesthandler/IHTTPRequestHandler.h b/xbmc/network/httprequesthandler/IHTTPRequestHandler.h
index e62795f3fa..cee03b2efb 100644
--- a/xbmc/network/httprequesthandler/IHTTPRequestHandler.h
+++ b/xbmc/network/httprequesthandler/IHTTPRequestHandler.h
@@ -22,6 +22,12 @@
#include <sys/socket.h>
#include <sys/types.h>
+#if MHD_VERSION >= 0x00097002
+using MHD_RESULT = MHD_Result;
+#else
+using MHD_RESULT = int;
+#endif
+
class CDateTime;
class CWebServer;
@@ -114,7 +120,7 @@ public:
*
* \return MHD_NO if a severe error has occurred otherwise MHD_YES.
*/
- virtual int HandleRequest() = 0;
+ virtual MHD_RESULT HandleRequest() = 0;
/*!
* \brief Whether the HTTP response could also be provided in ranges.
diff --git a/xbmc/platform/win32/WIN32Util.cpp b/xbmc/platform/win32/WIN32Util.cpp
index b84cd4f802..734f6bb971 100644
--- a/xbmc/platform/win32/WIN32Util.cpp
+++ b/xbmc/platform/win32/WIN32Util.cpp
@@ -44,13 +44,18 @@ using namespace MEDIA_DETECT;
#ifdef TARGET_WINDOWS_STORE
#include "platform/win10/AsyncHelpers.h"
+
#include <ppltasks.h>
+#include <winrt/Windows.Devices.Display.Core.h>
#include <winrt/Windows.Devices.Power.h>
#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.Graphics.Display.Core.h>
#include <winrt/Windows.Storage.h>
using namespace winrt::Windows::Devices::Power;
+using namespace winrt::Windows::Devices::Display::Core;
using namespace winrt::Windows::Graphics::Display;
+using namespace winrt::Windows::Graphics::Display::Core;
using namespace winrt::Windows::Storage;
#endif
@@ -1229,3 +1234,281 @@ bool CWIN32Util::SetThreadLocalLocale(bool enable /* = true */)
return _configthreadlocale(param) != -1;
}
+HDR_STATUS CWIN32Util::ToggleWindowsHDR(DXGI_MODE_DESC& modeDesc)
+{
+ HDR_STATUS status = HDR_STATUS::HDR_TOGGLE_FAILED;
+
+#ifdef TARGET_WINDOWS_STORE
+ auto hdmiDisplayInfo = HdmiDisplayInformation::GetForCurrentView();
+
+ if (hdmiDisplayInfo == nullptr)
+ return status;
+
+ auto current = hdmiDisplayInfo.GetCurrentDisplayMode();
+
+ auto newColorSp = (current.ColorSpace() == HdmiDisplayColorSpace::BT2020)
+ ? HdmiDisplayColorSpace::BT709
+ : HdmiDisplayColorSpace::BT2020;
+
+ auto modes = hdmiDisplayInfo.GetSupportedDisplayModes();
+
+ // Browse over all modes available like the current (resolution and refresh)
+ // but reciprocals HDR (color space and transfer).
+ // NOTE: transfer for HDR is here "fake HDR" (EotfSdr) to be
+ // able render SRD content with HDR ON, same as Windows HDR switch does.
+ // GUI-skin is SDR. The real HDR mode is activated later when playback begins.
+ for (const auto& mode : modes)
+ {
+ if (mode.ColorSpace() == newColorSp &&
+ mode.ResolutionHeightInRawPixels() == current.ResolutionHeightInRawPixels() &&
+ mode.ResolutionWidthInRawPixels() == current.ResolutionWidthInRawPixels() &&
+ mode.StereoEnabled() == false && fabs(mode.RefreshRate() - current.RefreshRate()) < 0.0001)
+ {
+ if (current.ColorSpace() == HdmiDisplayColorSpace::BT2020) // HDR is ON
+ {
+ CLog::LogF(LOGINFO, "Toggle Windows HDR Off (ON => OFF).");
+ if (Wait(hdmiDisplayInfo.RequestSetCurrentDisplayModeAsync(mode,
+ HdmiDisplayHdrOption::None)))
+ status = HDR_STATUS::HDR_OFF;
+ }
+ else // HDR is OFF
+ {
+ CLog::LogF(LOGINFO, "Toggle Windows HDR On (OFF => ON).");
+ if (Wait(hdmiDisplayInfo.RequestSetCurrentDisplayModeAsync(mode,
+ HdmiDisplayHdrOption::EotfSdr)))
+ status = HDR_STATUS::HDR_ON;
+ }
+ break;
+ }
+ }
+#else
+ uint32_t pathCount = 0;
+ uint32_t modeCount = 0;
+
+ MONITORINFOEXW mi = {};
+ mi.cbSize = sizeof(mi);
+ GetMonitorInfoW(MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY), &mi);
+ const std::wstring deviceNameW = mi.szDevice;
+
+ if (ERROR_SUCCESS == GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount))
+ {
+ std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
+ std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
+
+ if (ERROR_SUCCESS == QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(),
+ &modeCount, modes.data(), nullptr))
+ {
+ DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO getColorInfo = {};
+ getColorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
+ getColorInfo.header.size = sizeof(getColorInfo);
+
+ DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE setColorState = {};
+ setColorState.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
+ setColorState.header.size = sizeof(setColorState);
+
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME getSourceName = {};
+ getSourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+ getSourceName.header.size = sizeof(getSourceName);
+
+ // Only try to toggle display currently used by Kodi
+ for (const auto& path : paths)
+ {
+ getSourceName.header.adapterId.HighPart = path.sourceInfo.adapterId.HighPart;
+ getSourceName.header.adapterId.LowPart = path.sourceInfo.adapterId.LowPart;
+ getSourceName.header.id = path.sourceInfo.id;
+
+ if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&getSourceName.header))
+ {
+ const std::wstring sourceNameW = getSourceName.viewGdiDeviceName;
+ if (deviceNameW == sourceNameW)
+ {
+ const auto& mode = modes.at(path.targetInfo.modeInfoIdx);
+
+ getColorInfo.header.adapterId.HighPart = mode.adapterId.HighPart;
+ getColorInfo.header.adapterId.LowPart = mode.adapterId.LowPart;
+ getColorInfo.header.id = mode.id;
+
+ setColorState.header.adapterId.HighPart = mode.adapterId.HighPart;
+ setColorState.header.adapterId.LowPart = mode.adapterId.LowPart;
+ setColorState.header.id = mode.id;
+
+ if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&getColorInfo.header))
+ {
+ if (getColorInfo.advancedColorSupported)
+ {
+ if (getColorInfo.advancedColorEnabled) // HDR is ON
+ {
+ setColorState.enableAdvancedColor = FALSE;
+ status = HDR_STATUS::HDR_OFF;
+ CLog::LogF(LOGINFO, "Toggle Windows HDR Off (ON => OFF).");
+ }
+ else // HDR is OFF
+ {
+ setColorState.enableAdvancedColor = TRUE;
+ status = HDR_STATUS::HDR_ON;
+ CLog::LogF(LOGINFO, "Toggle Windows HDR On (OFF => ON).");
+ }
+ if (ERROR_SUCCESS != DisplayConfigSetDeviceInfo(&setColorState.header))
+ status = HDR_STATUS::HDR_TOGGLE_FAILED;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Restores previous graphics mode before toggle HDR
+ if (status != HDR_STATUS::HDR_TOGGLE_FAILED && modeDesc.RefreshRate.Denominator != 0)
+ {
+ float fps = static_cast<float>(modeDesc.RefreshRate.Numerator) /
+ static_cast<float>(modeDesc.RefreshRate.Denominator);
+ int32_t est;
+ DEVMODEW devmode = {};
+ devmode.dmSize = sizeof(devmode);
+ devmode.dmPelsWidth = modeDesc.Width;
+ devmode.dmPelsHeight = modeDesc.Height;
+ devmode.dmDisplayFrequency = static_cast<uint32_t>(fps);
+ if (modeDesc.ScanlineOrdering &&
+ modeDesc.ScanlineOrdering != DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE)
+ devmode.dmDisplayFlags = DM_INTERLACED;
+ devmode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
+ est = ChangeDisplaySettingsExW(deviceNameW.c_str(), &devmode, nullptr, CDS_FULLSCREEN, nullptr);
+ if (est == DISP_CHANGE_SUCCESSFUL)
+ CLog::LogF(LOGDEBUG, "Previous graphics mode restored OK");
+ else
+ CLog::LogF(LOGERROR, "Previous graphics mode cannot be restored (error# {})", est);
+ }
+#endif
+
+ return status;
+}
+
+HDR_STATUS CWIN32Util::GetWindowsHDRStatus()
+{
+ bool advancedColorSupported = false;
+ bool advancedColorEnabled = false;
+ HDR_STATUS status = HDR_STATUS::HDR_UNSUPPORTED;
+
+#ifdef TARGET_WINDOWS_STORE
+ auto displayInformation = DisplayInformation::GetForCurrentView();
+
+ if (displayInformation)
+ {
+ auto advancedColorInfo = displayInformation.GetAdvancedColorInfo();
+
+ if (advancedColorInfo)
+ {
+ if (advancedColorInfo.CurrentAdvancedColorKind() == AdvancedColorKind::HighDynamicRange)
+ {
+ advancedColorSupported = true;
+ advancedColorEnabled = true;
+ }
+ }
+ }
+ // Try to find out if the display supports HDR even if Windows HDR switch is OFF
+ if (!advancedColorEnabled)
+ {
+ auto displayManager = DisplayManager::Create(DisplayManagerOptions::None);
+
+ if (displayManager)
+ {
+ auto targets = displayManager.GetCurrentTargets();
+
+ for (const auto& target : targets)
+ {
+ if (target.IsConnected())
+ {
+ auto displayMonitor = target.TryGetMonitor();
+ if (displayMonitor.MaxLuminanceInNits() >= 400.0f)
+ {
+ advancedColorSupported = true;
+ break;
+ }
+ }
+ }
+ displayManager.Close();
+ }
+ }
+#else
+ uint32_t pathCount = 0;
+ uint32_t modeCount = 0;
+
+ MONITORINFOEXW mi = {};
+ mi.cbSize = sizeof(mi);
+ GetMonitorInfoW(MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY), &mi);
+ const std::wstring deviceNameW = mi.szDevice;
+
+ if (ERROR_SUCCESS == GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount))
+ {
+ std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
+ std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
+
+ if (ERROR_SUCCESS == QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(),
+ &modeCount, modes.data(), 0))
+ {
+ DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO getColorInfo = {};
+ getColorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
+ getColorInfo.header.size = sizeof(getColorInfo);
+
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME getSourceName = {};
+ getSourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+ getSourceName.header.size = sizeof(getSourceName);
+
+ for (const auto& path : paths)
+ {
+ getSourceName.header.adapterId.HighPart = path.sourceInfo.adapterId.HighPart;
+ getSourceName.header.adapterId.LowPart = path.sourceInfo.adapterId.LowPart;
+ getSourceName.header.id = path.sourceInfo.id;
+
+ if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&getSourceName.header))
+ {
+ const std::wstring sourceNameW = getSourceName.viewGdiDeviceName;
+ if (g_hWnd == nullptr || deviceNameW == sourceNameW)
+ {
+ const auto& mode = modes.at(path.targetInfo.modeInfoIdx);
+
+ getColorInfo.header.adapterId.HighPart = mode.adapterId.HighPart;
+ getColorInfo.header.adapterId.LowPart = mode.adapterId.LowPart;
+ getColorInfo.header.id = mode.id;
+
+ if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&getColorInfo.header))
+ {
+ if (getColorInfo.advancedColorEnabled)
+ advancedColorEnabled = true;
+
+ if (getColorInfo.advancedColorSupported)
+ advancedColorSupported = true;
+ }
+
+ if (g_hWnd != nullptr)
+ break;
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ if (!advancedColorSupported)
+ {
+ status = HDR_STATUS::HDR_UNSUPPORTED;
+ if (CServiceBroker::IsServiceManagerUp())
+ CLog::LogF(LOGDEBUG, "Display is not HDR capable or cannot be detected");
+ }
+ else if (advancedColorSupported && !advancedColorEnabled)
+ {
+ status = HDR_STATUS::HDR_OFF;
+ if (CServiceBroker::IsServiceManagerUp())
+ CLog::LogF(LOGDEBUG, "Display HDR capable and current HDR status is OFF");
+ }
+ else if (advancedColorSupported && advancedColorEnabled)
+ {
+ status = HDR_STATUS::HDR_ON;
+ if (CServiceBroker::IsServiceManagerUp())
+ CLog::LogF(LOGDEBUG, "Display HDR capable and current HDR status is ON");
+ }
+
+ return status;
+}
diff --git a/xbmc/platform/win32/WIN32Util.h b/xbmc/platform/win32/WIN32Util.h
index c46e73ebbc..cb110183bd 100644
--- a/xbmc/platform/win32/WIN32Util.h
+++ b/xbmc/platform/win32/WIN32Util.h
@@ -8,11 +8,14 @@
#pragma once
+#include "HDRStatus.h"
#include "URL.h"
#include "utils/Geometry.h"
#include <vector>
+#include <dxgi1_5.h>
+
#define BONJOUR_EVENT ( WM_USER + 0x100 ) // Message sent to the Window when a Bonjour event occurs.
#define BONJOUR_BROWSER_EVENT ( WM_USER + 0x110 )
@@ -63,4 +66,8 @@ public:
static std::string WUSysMsg(DWORD dwError);
static bool SetThreadLocalLocale(bool enable = true);
+
+ // HDR display support
+ static HDR_STATUS ToggleWindowsHDR(DXGI_MODE_DESC& modeDesc);
+ static HDR_STATUS GetWindowsHDRStatus();
};
diff --git a/xbmc/powermanagement/IPowerSyscall.h b/xbmc/powermanagement/IPowerSyscall.h
index f3bf1ae465..b3f215516c 100644
--- a/xbmc/powermanagement/IPowerSyscall.h
+++ b/xbmc/powermanagement/IPowerSyscall.h
@@ -55,7 +55,7 @@ public:
PumpPowerEvents is called from Application Thread and the platform implementation may signal
power related events back to xbmc through the callback.
- return true if an event occured and false if not.
+ return true if an event occurred and false if not.
\param callback the callback to signal to
*/
diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp
index f90292df14..b905fa7b25 100644
--- a/xbmc/pvr/PVRManager.cpp
+++ b/xbmc/pvr/PVRManager.cpp
@@ -542,7 +542,7 @@ void CPVRManager::Process()
}
catch (...)
{
- CLog::LogF(LOGERROR, "An error occured while trying to execute the last PVR update job, trying to recover");
+ CLog::LogF(LOGERROR, "An error occurred while trying to execute the last PVR update job, trying to recover");
bRestart = true;
}
diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp
index ceebbf8ec9..d29a4492fc 100644
--- a/xbmc/pvr/addons/PVRClients.cpp
+++ b/xbmc/pvr/addons/PVRClients.cpp
@@ -160,7 +160,8 @@ void CPVRClients::UpdateAddons(const std::string& changedAddonId /*= ""*/)
CLog::LogF(LOGERROR, "Failed to create add-on %s, status = %d", addon.first->Name().c_str(), status);
if (status == ADDON_STATUS_PERMANENT_FAILURE)
{
- CServiceBroker::GetAddonMgr().DisableAddon(addon.first->ID());
+ CServiceBroker::GetAddonMgr().DisableAddon(addon.first->ID(),
+ AddonDisabledReason::PERMANENT_FAILURE);
CJobManager::GetInstance().AddJob(new CPVREventLogJob(true, true, addon.first->Name(), g_localizeStrings.Get(24070), addon.first->Icon()), nullptr);
}
}
diff --git a/xbmc/pvr/epg/Epg.h b/xbmc/pvr/epg/Epg.h
index 2e1293d296..2e6e1ac487 100644
--- a/xbmc/pvr/epg/Epg.h
+++ b/xbmc/pvr/epg/Epg.h
@@ -134,7 +134,7 @@ namespace PVR
std::shared_ptr<CPVREpgInfoTag> GetTagNext() const;
/*!
- * @brief Get the event that occured previously
+ * @brief Get the event that occurred previously
* @return The previous event or NULL if it wasn't found.
*/
std::shared_ptr<CPVREpgInfoTag> GetTagPrevious() const;
diff --git a/xbmc/pvr/epg/EpgTagsContainer.h b/xbmc/pvr/epg/EpgTagsContainer.h
index f3ef0ad726..6a3ca24ce5 100644
--- a/xbmc/pvr/epg/EpgTagsContainer.h
+++ b/xbmc/pvr/epg/EpgTagsContainer.h
@@ -116,7 +116,7 @@ public:
std::shared_ptr<CPVREpgInfoTag> GetNextStartingTag() const;
/*!
- * @brief Get the event that occured previously
+ * @brief Get the event that occurred previously
* @return The tag or nullptr if no tag was found.
*/
std::shared_ptr<CPVREpgInfoTag> GetLastEndedTag() const;
diff --git a/xbmc/pvr/guilib/PVRGUIActions.cpp b/xbmc/pvr/guilib/PVRGUIActions.cpp
index e9749071f4..356984da74 100644
--- a/xbmc/pvr/guilib/PVRGUIActions.cpp
+++ b/xbmc/pvr/guilib/PVRGUIActions.cpp
@@ -1247,7 +1247,7 @@ namespace PVR
void CPVRGUIActions::CheckAndSwitchToFullscreen(bool bFullscreen) const
{
- CMediaSettings::GetInstance().SetVideoStartWindowed(!bFullscreen);
+ CMediaSettings::GetInstance().SetMediaStartWindowed(!bFullscreen);
if (bFullscreen)
{
diff --git a/xbmc/rendering/dx/DeviceResources.cpp b/xbmc/rendering/dx/DeviceResources.cpp
index 681de567a8..df2d89a663 100644
--- a/xbmc/rendering/dx/DeviceResources.cpp
+++ b/xbmc/rendering/dx/DeviceResources.cpp
@@ -7,18 +7,22 @@
*/
#include "DeviceResources.h"
+
#include "DirectXHelper.h"
#include "RenderContext.h"
+#include "ServiceBroker.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIWindowManager.h"
-#include "windowing/GraphicContext.h"
#include "messaging/ApplicationMessenger.h"
-#include "platform/win32/CharsetConverter.h"
-#include "ServiceBroker.h"
#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
-#include "utils/log.h"
#include "utils/SystemInfo.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include "platform/win32/CharsetConverter.h"
+#include "platform/win32/WIN32Util.h"
#ifdef _DEBUG
#include <dxgidebug.h>
@@ -309,9 +313,10 @@ void DX::DeviceResources::CreateDeviceResources()
// Don't forget to declare your application's minimum required feature level in its
// description. All applications are assumed to support 9.1 unless otherwise stated.
std::vector<D3D_FEATURE_LEVEL> featureLevels;
+ if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10))
+ featureLevels.push_back(D3D_FEATURE_LEVEL_12_0);
if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8))
featureLevels.push_back(D3D_FEATURE_LEVEL_11_1);
-
featureLevels.push_back(D3D_FEATURE_LEVEL_11_0);
featureLevels.push_back(D3D_FEATURE_LEVEL_10_1);
featureLevels.push_back(D3D_FEATURE_LEVEL_10_0);
@@ -513,11 +518,13 @@ void DX::DeviceResources::ResizeBuffers()
CLog::LogF(LOGDEBUG, "resize buffers.");
- bool bHWStereoEnabled = RENDER_STEREO_MODE_HARDWAREBASED == CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
+ bool bHWStereoEnabled = RENDER_STEREO_MODE_HARDWAREBASED ==
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
bool windowed = true;
+ bool isHdrEnabled = false;
HRESULT hr = E_FAIL;
+ DXGI_SWAP_CHAIN_DESC1 scDesc = {};
- DXGI_SWAP_CHAIN_DESC1 scDesc = { 0 };
if (m_swapChain)
{
BOOL bFullcreen = 0;
@@ -529,6 +536,7 @@ void DX::DeviceResources::ResizeBuffers()
// check if swapchain needs to be recreated
m_swapChain->GetDesc1(&scDesc);
+
if ((scDesc.Stereo == TRUE) != bHWStereoEnabled)
{
// check fullscreen state and go to windowing if necessary
@@ -536,17 +544,20 @@ void DX::DeviceResources::ResizeBuffers()
{
m_swapChain->SetFullscreenState(false, nullptr); // mandatory before releasing swapchain
}
-
m_swapChain = nullptr;
m_deferrContext->Flush();
m_d3dContext->Flush();
}
}
+ isHdrEnabled = (HDR_STATUS::HDR_ON == CWIN32Util::GetWindowsHDRStatus());
+
if (m_swapChain != nullptr)
{
// If the swap chain already exists, resize it.
m_swapChain->GetDesc1(&scDesc);
+ isHdrEnabled ? scDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM
+ : scDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
hr = m_swapChain->ResizeBuffers(
scDesc.BufferCount,
lround(m_outputSize.Width),
@@ -569,27 +580,31 @@ void DX::DeviceResources::ResizeBuffers()
else
{
// Otherwise, create a new one using the same adapter as the existing Direct3D device.
- DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
+ DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = lround(m_outputSize.Width);
swapChainDesc.Height = lround(m_outputSize.Height);
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc.Stereo = bHWStereoEnabled;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
- swapChainDesc.BufferCount = 3 * (1 + bHWStereoEnabled);
- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
+ // HDR 60 fps needs 6 buffers to avoid frame drops but in Windows 10 it's good for all
+ swapChainDesc.BufferCount =
+ (m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_12_0) ? 6 : 3 * (1 + bHWStereoEnabled);
+ swapChainDesc.SwapEffect = (m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_12_0)
+ ? DXGI_SWAP_EFFECT_FLIP_DISCARD
+ : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc.Flags = windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
- DXGI_SWAP_CHAIN_FULLSCREEN_DESC scFSDesc = { 0 }; // unused for uwp
+ DXGI_SWAP_CHAIN_FULLSCREEN_DESC scFSDesc = {}; // unused for uwp
scFSDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
scFSDesc.Windowed = windowed;
ComPtr<IDXGISwapChain1> swapChain;
- if ( m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_11_0
- && !bHWStereoEnabled
- && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bTry10bitOutput)
+ if (m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_11_0 && !bHWStereoEnabled &&
+ (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bTry10bitOutput ||
+ isHdrEnabled))
{
swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain);
@@ -597,6 +612,7 @@ void DX::DeviceResources::ResizeBuffers()
{
CLog::LogF(LOGWARNING, "creating 10bit swapchain failed, fallback to 8bit.");
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ swapChainDesc.BufferCount = 3 * (1 + bHWStereoEnabled);
}
}
@@ -615,7 +631,8 @@ void DX::DeviceResources::ResizeBuffers()
hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); CHECK_ERR();
// fallback to split_horizontal mode.
- CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode(RENDER_STEREO_MODE_SPLIT_HORIZONTAL);
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode(
+ RENDER_STEREO_MODE_SPLIT_HORIZONTAL);
}
if (FAILED(hr))
@@ -624,6 +641,13 @@ void DX::DeviceResources::ResizeBuffers()
return;
}
+ m_IsHDROutput = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) && isHdrEnabled;
+
+ const int bits = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8;
+
+ CLog::LogF(LOGINFO, "{} bit swapchain is used with {} buffers and {} output", bits,
+ swapChainDesc.BufferCount, m_IsHDROutput ? "HDR" : "SDR");
+
hr = swapChain.As(&m_swapChain); CHECK_ERR();
m_stereoEnabled = bHWStereoEnabled;
@@ -633,6 +657,8 @@ void DX::DeviceResources::ResizeBuffers()
hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR();
dxgiDevice->SetMaximumFrameLatency(1);
}
+
+ CLog::LogF(LOGDEBUG, "end resize buffers.");
}
// These resources need to be recreated every time the window size is changed.
@@ -1097,3 +1123,126 @@ void DX::DeviceResources::Trim() const
}
#endif
+
+void DX::DeviceResources::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const
+{
+ ComPtr<IDXGISwapChain4> swapChain4;
+
+ if (m_swapChain == nullptr)
+ return;
+
+ if (SUCCEEDED(m_swapChain.As(&swapChain4)))
+ {
+ if (SUCCEEDED(swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10), &hdr10)))
+ {
+ CLog::LogF(LOGDEBUG,
+ "(raw) RP {} {} | GP {} {} | BP {} {} | WP {} {} | Max ML {} | min ML "
+ "{} | Max CLL {} | Max FALL {}",
+ hdr10.RedPrimary[0], hdr10.RedPrimary[1], hdr10.GreenPrimary[0],
+ hdr10.GreenPrimary[1], hdr10.BluePrimary[0], hdr10.BluePrimary[1],
+ hdr10.WhitePoint[0], hdr10.WhitePoint[1], hdr10.MaxMasteringLuminance,
+ hdr10.MinMasteringLuminance, hdr10.MaxContentLightLevel,
+ hdr10.MaxFrameAverageLightLevel);
+
+ constexpr double FACTOR_1 = 50000.0;
+ constexpr double FACTOR_2 = 10000.0;
+ const double RP_0 = static_cast<double>(hdr10.RedPrimary[0]) / FACTOR_1;
+ const double RP_1 = static_cast<double>(hdr10.RedPrimary[1]) / FACTOR_1;
+ const double GP_0 = static_cast<double>(hdr10.GreenPrimary[0]) / FACTOR_1;
+ const double GP_1 = static_cast<double>(hdr10.GreenPrimary[1]) / FACTOR_1;
+ const double BP_0 = static_cast<double>(hdr10.BluePrimary[0]) / FACTOR_1;
+ const double BP_1 = static_cast<double>(hdr10.BluePrimary[1]) / FACTOR_1;
+ const double WP_0 = static_cast<double>(hdr10.WhitePoint[0]) / FACTOR_1;
+ const double WP_1 = static_cast<double>(hdr10.WhitePoint[1]) / FACTOR_1;
+ const double Max_ML = static_cast<double>(hdr10.MaxMasteringLuminance) / FACTOR_2;
+ const double min_ML = static_cast<double>(hdr10.MinMasteringLuminance) / FACTOR_2;
+
+ CLog::LogF(LOGINFO,
+ "RP {:0.3f} {:0.3f} | GP {:0.3f} {:0.3f} | BP {:0.3f} {:0.3f} | WP {:0.3f} "
+ "{:0.3f} | Max ML {:0.0f} | min ML {:0.3f} | Max CLL {} | Max FALL {}",
+ RP_0, RP_1, GP_0, GP_1, BP_0, BP_1, WP_0, WP_1, Max_ML, min_ML,
+ hdr10.MaxContentLightLevel, hdr10.MaxFrameAverageLightLevel);
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "DXGI SetHDRMetaData failed");
+ }
+ }
+}
+
+void DX::DeviceResources::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const
+{
+ ComPtr<IDXGISwapChain3> swapChain3;
+
+ if (m_swapChain == nullptr)
+ return;
+
+ if (SUCCEEDED(m_swapChain.As(&swapChain3)))
+ {
+ DXGI_COLOR_SPACE_TYPE cs = colorSpace;
+ if (DX::Windowing()->UseLimitedColor())
+ {
+ switch (cs)
+ {
+ case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709:
+ cs = DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709;
+ break;
+ case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
+ cs = DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020;
+ break;
+ case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020:
+ cs = DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020;
+ break;
+ case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020:
+ cs = DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020;
+ break;
+ }
+ }
+ if (SUCCEEDED(swapChain3->SetColorSpace1(cs)))
+ {
+ CLog::LogF(LOGDEBUG, "DXGI SetColorSpace1 success");
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "DXGI SetColorSpace1 failed");
+ }
+ }
+}
+
+HDR_STATUS DX::DeviceResources::ToggleHDR()
+{
+ DXGI_MODE_DESC md = {};
+
+ if (m_swapChain)
+ GetDisplayMode(&md);
+
+ // Toggle display HDR
+ DX::Windowing()->SetAlteringWindow(true);
+
+ HDR_STATUS hdrStatus = CWIN32Util::ToggleWindowsHDR(md);
+
+ // Kill swapchain
+ if (m_swapChain && hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED)
+ {
+ CLog::LogF(LOGDEBUG, "Re-create swapchain due HDR <-> SDR switch");
+ BOOL bFullcreen = 0;
+ m_swapChain->GetFullscreenState(&bFullcreen, nullptr);
+ if (!!bFullcreen)
+ m_swapChain->SetFullscreenState(false, nullptr);
+ m_swapChain = nullptr;
+ m_deferrContext->Flush();
+ m_d3dContext->Flush();
+ }
+
+ DX::Windowing()->SetAlteringWindow(false);
+
+ // Re-create swapchain
+ if (hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED)
+ {
+ CreateWindowSizeDependentResources();
+
+ DX::Windowing()->NotifyAppFocusChange(true);
+ }
+
+ return hdrStatus;
+}
diff --git a/xbmc/rendering/dx/DeviceResources.h b/xbmc/rendering/dx/DeviceResources.h
index 50a16521a3..b2e0dd2379 100644
--- a/xbmc/rendering/dx/DeviceResources.h
+++ b/xbmc/rendering/dx/DeviceResources.h
@@ -8,19 +8,17 @@
#pragma once
-#include <wrl.h>
-#include <wrl/client.h>
-#include <concrt.h>
-#if defined(TARGET_WINDOWS_STORE)
-#include <dxgi1_3.h>
-#else
-#include <dxgi1_2.h>
-#endif
+#include "DirectXHelper.h"
+#include "HDRStatus.h"
+#include "guilib/D3DResource.h"
+
#include <functional>
#include <memory>
-#include "DirectXHelper.h"
-#include "guilib/D3DResource.h"
+#include <concrt.h>
+#include <dxgi1_5.h>
+#include <wrl.h>
+#include <wrl/client.h>
struct RESOLUTION_INFO;
@@ -80,6 +78,12 @@ namespace DX
bool SetFullScreen(bool fullscreen, RESOLUTION_INFO& res);
+ // HDR display support
+ HDR_STATUS ToggleHDR();
+ void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const;
+ void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const;
+ bool IsHDROutput() const { return m_IsHDROutput; }
+
// DX resources registration
void Register(ID3DResource *resource);
void Unregister(ID3DResource *resource);
@@ -158,7 +162,9 @@ namespace DX
Concurrency::critical_section m_criticalSection;
Concurrency::critical_section m_resourceSection;
std::vector<ID3DResource*> m_resources;
+
bool m_stereoEnabled;
bool m_bDeviceCreated;
+ bool m_IsHDROutput;
};
}
diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp
index 7138b0a639..ab678b9c44 100644
--- a/xbmc/settings/AdvancedSettings.cpp
+++ b/xbmc/settings/AdvancedSettings.cpp
@@ -412,6 +412,7 @@ void CAdvancedSettings::Initialize()
m_stereoscopicregex_tab = "[-. _]h?tab[-. _]";
m_allowUseSeparateDeviceForDecoding = false;
+ m_disableDXVAdiscreteDecoding = false;
m_videoAssFixedWorks = false;
@@ -696,6 +697,8 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file)
m_DXVACheckCompatibilityPresent = XMLUtils::GetBoolean(pElement,"checkdxvacompatibility", m_DXVACheckCompatibility);
XMLUtils::GetBoolean(pElement, "allowdiscretedecoder", m_allowUseSeparateDeviceForDecoding);
+ XMLUtils::GetBoolean(pElement, "disableDXVAdiscretedecoder", m_disableDXVAdiscreteDecoding);
+
//0 = disable fps detect, 1 = only detect on timestamps with uniform spacing, 2 detect on all timestamps
XMLUtils::GetInt(pElement, "fpsdetect", m_videoFpsDetect, 0, 2);
XMLUtils::GetFloat(pElement, "maxtempo", m_maxTempo, 1.5, 2.1);
@@ -799,7 +802,7 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file)
pElement = pRootElement->FirstChildElement("externalplayer");
if (pElement)
{
- CLog::Log(LOGWARNING, "External player configuration has been removed from advancedsettings.xml. It can now be configed in userdata/playercorefactory.xml");
+ CLog::Log(LOGWARNING, "External player configuration has been removed from advancedsettings.xml. It can now be configured in userdata/playercorefactory.xml");
}
pElement = pRootElement->FirstChildElement("slideshow");
if (pElement)
diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h
index 0bb22429c4..dd6e98e65a 100644
--- a/xbmc/settings/AdvancedSettings.h
+++ b/xbmc/settings/AdvancedSettings.h
@@ -356,6 +356,7 @@ class CAdvancedSettings : public ISettingCallback, public ISettingsHandler
std::string m_stereoscopicregex_tab;
bool m_allowUseSeparateDeviceForDecoding;
+ bool m_disableDXVAdiscreteDecoding;
/*!< @brief position behavior of ass subtitles when setting "subtitle position on screen" set to "fixed"
True to show at the fixed position set in video calibration
diff --git a/xbmc/settings/MediaSettings.cpp b/xbmc/settings/MediaSettings.cpp
index 868ba66e25..42059f6667 100644
--- a/xbmc/settings/MediaSettings.cpp
+++ b/xbmc/settings/MediaSettings.cpp
@@ -51,7 +51,7 @@ CMediaSettings::CMediaSettings()
m_videoPlaylistRepeat = false;
m_videoPlaylistShuffle = false;
- m_videoStartWindowed = false;
+ m_mediaStartWindowed = false;
m_additionalSubtitleDirectoryChecked = 0;
m_musicNeedsUpdate = 0;
diff --git a/xbmc/settings/MediaSettings.h b/xbmc/settings/MediaSettings.h
index 23b77eeea8..4f6dab4c3e 100644
--- a/xbmc/settings/MediaSettings.h
+++ b/xbmc/settings/MediaSettings.h
@@ -76,8 +76,8 @@ public:
void SetVideoPlaylistRepeat(bool repeats) { m_videoPlaylistRepeat = repeats; }
void SetVideoPlaylistShuffled(bool shuffled) { m_videoPlaylistShuffle = shuffled; }
- bool DoesVideoStartWindowed() const { return m_videoStartWindowed; }
- void SetVideoStartWindowed(bool windowed) { m_videoStartWindowed = windowed; }
+ bool DoesMediaStartWindowed() const { return m_mediaStartWindowed; }
+ void SetMediaStartWindowed(bool windowed) { m_mediaStartWindowed = windowed; }
int GetAdditionalSubtitleDirectoryChecked() const { return m_additionalSubtitleDirectoryChecked; }
void SetAdditionalSubtitleDirectoryChecked(int checked) { m_additionalSubtitleDirectoryChecked = checked; }
@@ -108,7 +108,7 @@ private:
bool m_videoPlaylistRepeat;
bool m_videoPlaylistShuffle;
- bool m_videoStartWindowed;
+ bool m_mediaStartWindowed;
int m_additionalSubtitleDirectoryChecked;
int m_musicNeedsUpdate; ///< if a database update means an update is required (set to the version number of the db)
diff --git a/xbmc/utils/ColorUtils.cpp b/xbmc/utils/ColorUtils.cpp
index 41a8c15d3f..80319a0b44 100644
--- a/xbmc/utils/ColorUtils.cpp
+++ b/xbmc/utils/ColorUtils.cpp
@@ -15,5 +15,5 @@
UTILS::Color ColorUtils::ChangeOpacity(const UTILS::Color color, const float opacity)
{
int newAlpha = ceil( ((color >> 24) & 0xff) * opacity);
- return color + (newAlpha << 24);
+ return (color & 0x00FFFFFF) | (newAlpha << 24);
};
diff --git a/xbmc/utils/EGLUtils.cpp b/xbmc/utils/EGLUtils.cpp
index d5f2d4864e..01df4ba7e7 100644
--- a/xbmc/utils/EGLUtils.cpp
+++ b/xbmc/utils/EGLUtils.cpp
@@ -364,7 +364,7 @@ bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId, bool
break;
if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, EGL_NATIVE_VISUAL_ID, &id) != EGL_TRUE)
- CEGLUtils::Log(LOGERROR, "failed to query EGL attibute EGL_NATIVE_VISUAL_ID");
+ CEGLUtils::Log(LOGERROR, "failed to query EGL attribute EGL_NATIVE_VISUAL_ID");
if (visualId == id)
break;
@@ -382,7 +382,7 @@ bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId, bool
{
EGLint value{0};
if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, eglAttribute.first, &value) != EGL_TRUE)
- CEGLUtils::Log(LOGERROR, StringUtils::Format("failed to query EGL attibute %s", eglAttribute.second));
+ CEGLUtils::Log(LOGERROR, StringUtils::Format("failed to query EGL attribute %s", eglAttribute.second));
// we only need to print the hex value if it's an actual EGL define
CLog::Log(LOGDEBUG, " %s: %s", eglAttribute.second, (value >= 0x3000 && value <= 0x3200) ? StringUtils::Format("0x%04x", value) : StringUtils::Format("%d", value));
@@ -395,7 +395,7 @@ EGLint CEGLContextUtils::GetConfigAttrib(EGLint attribute) const
{
EGLint value{0};
if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, attribute, &value) != EGL_TRUE)
- CEGLUtils::Log(LOGERROR, "failed to query EGL attibute");
+ CEGLUtils::Log(LOGERROR, "failed to query EGL attribute");
return value;
}
diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp
index bde44660d8..68c7241fc7 100644
--- a/xbmc/video/VideoDatabase.cpp
+++ b/xbmc/video/VideoDatabase.cpp
@@ -1483,7 +1483,7 @@ int CVideoDatabase::AddToTable(const std::string& table, const std::string& firs
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
- // doesnt exists, add it
+ // doesn't exists, add it
strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
m_pDS->exec(strSQL);
int id = (int)m_pDS->lastinsertid();
@@ -1543,7 +1543,7 @@ int CVideoDatabase::AddRatings(int mediaId, const char *mediaType, const RatingM
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
- // doesnt exists, add it
+ // doesn't exists, add it
strSQL = PrepareSQL("INSERT INTO rating (media_id, media_type, rating_type, rating, votes) VALUES (%i, '%s', '%s', %f, %i)", mediaId, mediaType, i.first.c_str(), i.second.rating, i.second.votes);
m_pDS->exec(strSQL);
id = (int)m_pDS->lastinsertid();
@@ -1608,7 +1608,7 @@ int CVideoDatabase::AddUniqueIDs(int mediaId, const char *mediaType, const CVide
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
- // doesnt exists, add it
+ // doesn't exists, add it
strSQL = PrepareSQL("INSERT INTO uniqueid (media_id, media_type, value, type) VALUES (%i, '%s', '%s', '%s')", mediaId, mediaType, i.second.c_str(), i.first.c_str());
m_pDS->exec(strSQL);
id = (int)m_pDS->lastinsertid();
@@ -1696,7 +1696,7 @@ int CVideoDatabase::AddActor(const std::string& name, const std::string& thumbUR
if (m_pDS->num_rows() == 0)
{
m_pDS->close();
- // doesnt exists, add it
+ // doesn't exists, add it
strSQL=PrepareSQL("insert into actor (actor_id, name, art_urls) values(NULL, '%s', '%s')", trimmedName.substr(0,255).c_str(), thumbURLs.c_str());
m_pDS->exec(strSQL);
idActor = (int)m_pDS->lastinsertid();
@@ -1733,7 +1733,7 @@ void CVideoDatabase::AddLinkToActor(int mediaId, const char *mediaType, int acto
actorId, mediaId, mediaType, role.c_str());
if (GetSingleValue(sql).empty())
- { // doesnt exists, add it
+ { // doesn't exists, add it
sql = PrepareSQL("INSERT INTO actor_link (actor_id, media_id, media_type, role, cast_order) VALUES(%i,%i,'%s','%s',%i)", actorId, mediaId, mediaType, role.c_str(), order);
ExecuteQuery(sql);
}
@@ -1745,7 +1745,7 @@ void CVideoDatabase::AddToLinkTable(int mediaId, const std::string& mediaType, c
std::string sql = PrepareSQL("SELECT 1 FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
if (GetSingleValue(sql).empty())
- { // doesnt exists, add it
+ { // doesn't exists, add it
sql = PrepareSQL("INSERT INTO %s_link (%s_id,media_id,media_type) VALUES(%i,%i,'%s')", table.c_str(), key, valueId, mediaId, mediaType.c_str());
ExecuteQuery(sql);
}
diff --git a/xbmc/windowing/WinSystem.h b/xbmc/windowing/WinSystem.h
index bc7cb07cb7..751bbde0f8 100644
--- a/xbmc/windowing/WinSystem.h
+++ b/xbmc/windowing/WinSystem.h
@@ -8,6 +8,7 @@
#pragma once
+#include "HDRStatus.h"
#include "OSScreenSaver.h"
#include "Resolution.h"
#include "VideoSync.h"
@@ -157,6 +158,8 @@ public:
std::shared_ptr<CDPMSSupport> GetDPMSManager();
virtual bool SetHDR(const VideoPicture* videoPicture) { return false; };
virtual bool IsHDRDisplay() { return false; };
+ virtual HDR_STATUS ToggleHDR() { return HDR_STATUS::HDR_UNSUPPORTED; };
+ virtual HDR_STATUS GetOSHDRStatus() { return HDR_STATUS::HDR_UNSUPPORTED; };
static const char* SETTING_WINSYSTEM_IS_HDR_DISPLAY;
diff --git a/xbmc/windowing/X11/WinSystemX11GLESContext.cpp b/xbmc/windowing/X11/WinSystemX11GLESContext.cpp
index 016918df8c..ed72f9444d 100644
--- a/xbmc/windowing/X11/WinSystemX11GLESContext.cpp
+++ b/xbmc/windowing/X11/WinSystemX11GLESContext.cpp
@@ -259,6 +259,8 @@ XVisualInfo* CWinSystemX11GLESContext::GetVisual()
}
XVisualInfo x11_visual_info_template;
+ memset(&x11_visual_info_template, 0, sizeof(XVisualInfo));
+
if (!eglGetConfigAttrib(eglDisplay, eglConfig,
EGL_NATIVE_VISUAL_ID, reinterpret_cast<EGLint*>(&x11_visual_info_template.visualid)))
{
diff --git a/xbmc/windowing/wayland/SeatInputProcessing.h b/xbmc/windowing/wayland/SeatInputProcessing.h
index b40b00fec2..ce1f0ee759 100644
--- a/xbmc/windowing/wayland/SeatInputProcessing.h
+++ b/xbmc/windowing/wayland/SeatInputProcessing.h
@@ -64,7 +64,7 @@ public:
*
* This request is sent in addition to \ref OnEnter for \ref InputType::POINTER.
*
- * \param seatGlobalName numeric Wayland global name of the seat the event occured on
+ * \param seatGlobalName numeric Wayland global name of the seat the event occurred on
* \param pointer pointer instance that needs its cursor set
* \param serial Wayland protocol message serial that must be sent back in set_cursor
*/
diff --git a/xbmc/windowing/win10/WinSystemWin10.h b/xbmc/windowing/win10/WinSystemWin10.h
index 7e99e7e23c..5e72176063 100644
--- a/xbmc/windowing/win10/WinSystemWin10.h
+++ b/xbmc/windowing/win10/WinSystemWin10.h
@@ -90,6 +90,7 @@ public:
// CWinSystemWin10
bool IsAlteringWindow() const { return m_IsAlteringWindow; }
+ void SetAlteringWindow(bool altering) { m_IsAlteringWindow = altering; }
virtual bool DPIChanged(WORD dpi, RECT windowRect) const;
bool IsMinimized() const { return m_bMinimized; }
void SetMinimized(bool minimized) { m_bMinimized = minimized; }
diff --git a/xbmc/windowing/win10/WinSystemWin10DX.cpp b/xbmc/windowing/win10/WinSystemWin10DX.cpp
index 5b2e8d9147..85311cb2ae 100644
--- a/xbmc/windowing/win10/WinSystemWin10DX.cpp
+++ b/xbmc/windowing/win10/WinSystemWin10DX.cpp
@@ -14,6 +14,8 @@
#include "utils/XTimeUtils.h"
#include "utils/log.h"
+#include "platform/win32/WIN32Util.h"
+
std::unique_ptr<CWinSystemBase> CWinSystemBase::CreateWinSystem()
{
return std::make_unique<CWinSystemWin10DX>();
@@ -153,3 +155,33 @@ void CWinSystemWin10DX::UninitHooks()
void CWinSystemWin10DX::InitHooks(IDXGIOutput* pOutput)
{
}
+
+bool CWinSystemWin10DX::IsHDRDisplay()
+{
+ return (CWIN32Util::GetWindowsHDRStatus() != HDR_STATUS::HDR_UNSUPPORTED);
+}
+
+HDR_STATUS CWinSystemWin10DX::GetOSHDRStatus()
+{
+ return CWIN32Util::GetWindowsHDRStatus();
+}
+
+HDR_STATUS CWinSystemWin10DX::ToggleHDR()
+{
+ return m_deviceResources->ToggleHDR();
+}
+
+bool CWinSystemWin10DX::IsHDROutput() const
+{
+ return m_deviceResources->IsHDROutput();
+}
+
+void CWinSystemWin10DX::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const
+{
+ m_deviceResources->SetHdrMetaData(hdr10);
+}
+
+void CWinSystemWin10DX::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const
+{
+ m_deviceResources->SetHdrColorSpace(colorSpace);
+}
diff --git a/xbmc/windowing/win10/WinSystemWin10DX.h b/xbmc/windowing/win10/WinSystemWin10DX.h
index 61c747224c..f83d1c01d3 100644
--- a/xbmc/windowing/win10/WinSystemWin10DX.h
+++ b/xbmc/windowing/win10/WinSystemWin10DX.h
@@ -8,6 +8,7 @@
#pragma once
+#include "HDRStatus.h"
#include "WinSystemWin10.h"
#include "rendering/dx/RenderSystemDX.h"
@@ -62,6 +63,16 @@ public:
void ShowSplash(const std::string& message) override;
+ // HDR OS/display override
+ bool IsHDRDisplay() override;
+ HDR_STATUS ToggleHDR() override;
+ HDR_STATUS GetOSHDRStatus() override;
+
+ // HDR support
+ bool IsHDROutput() const;
+ void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const;
+ void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const;
+
protected:
void SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res) override;
void ReleaseBackBuffer() override;
diff --git a/xbmc/windowing/windows/WinSystemWin32.h b/xbmc/windowing/windows/WinSystemWin32.h
index b5c8cef4ac..539a8f13a1 100644
--- a/xbmc/windowing/windows/WinSystemWin32.h
+++ b/xbmc/windowing/windows/WinSystemWin32.h
@@ -106,6 +106,7 @@ public:
// CWinSystemWin32
HWND GetHwnd() const { return m_hWnd; }
bool IsAlteringWindow() const { return m_IsAlteringWindow; }
+ void SetAlteringWindow(bool altering) { m_IsAlteringWindow = altering; }
virtual bool DPIChanged(WORD dpi, RECT windowRect) const;
bool IsMinimized() const { return m_bMinimized; }
void SetMinimized(bool minimized) { m_bMinimized = minimized; }
diff --git a/xbmc/windowing/windows/WinSystemWin32DX.cpp b/xbmc/windowing/windows/WinSystemWin32DX.cpp
index 2b730154a8..083afb6e85 100644
--- a/xbmc/windowing/windows/WinSystemWin32DX.cpp
+++ b/xbmc/windowing/windows/WinSystemWin32DX.cpp
@@ -19,6 +19,7 @@
#include "windowing/GraphicContext.h"
#include "platform/win32/CharsetConverter.h"
+#include "platform/win32/WIN32Util.h"
#include "system.h"
@@ -380,3 +381,33 @@ HRESULT APIENTRY HookOpenAdapter10_2(D3D10DDIARG_OPENADAPTER *pOpenData)
}
return hr;
}
+
+bool CWinSystemWin32DX::IsHDRDisplay()
+{
+ return (CWIN32Util::GetWindowsHDRStatus() != HDR_STATUS::HDR_UNSUPPORTED);
+}
+
+HDR_STATUS CWinSystemWin32DX::GetOSHDRStatus()
+{
+ return CWIN32Util::GetWindowsHDRStatus();
+}
+
+HDR_STATUS CWinSystemWin32DX::ToggleHDR()
+{
+ return m_deviceResources->ToggleHDR();
+}
+
+bool CWinSystemWin32DX::IsHDROutput() const
+{
+ return m_deviceResources->IsHDROutput();
+}
+
+void CWinSystemWin32DX::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const
+{
+ m_deviceResources->SetHdrMetaData(hdr10);
+}
+
+void CWinSystemWin32DX::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const
+{
+ m_deviceResources->SetHdrColorSpace(colorSpace);
+}
diff --git a/xbmc/windowing/windows/WinSystemWin32DX.h b/xbmc/windowing/windows/WinSystemWin32DX.h
index 78f2f52b51..b57df21be2 100644
--- a/xbmc/windowing/windows/WinSystemWin32DX.h
+++ b/xbmc/windowing/windows/WinSystemWin32DX.h
@@ -8,6 +8,7 @@
#pragma once
+#include "HDRStatus.h"
#include "rendering/dx/RenderSystemDX.h"
#include "windowing/windows/WinSystemWin32.h"
@@ -64,6 +65,16 @@ public:
void FixRefreshRateIfNecessary(const D3D10DDIARG_CREATERESOURCE* pResource) const;
+ // HDR OS/display override
+ bool IsHDRDisplay() override;
+ HDR_STATUS ToggleHDR() override;
+ HDR_STATUS GetOSHDRStatus() override;
+
+ // HDR support
+ bool IsHDROutput() const;
+ void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const;
+ void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const;
+
protected:
void SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res) override;
void ReleaseBackBuffer() override;
diff --git a/xbmc/windows/GUIWindowFileManager.cpp b/xbmc/windows/GUIWindowFileManager.cpp
index e5e0971f53..f5ca1abe8d 100644
--- a/xbmc/windows/GUIWindowFileManager.cpp
+++ b/xbmc/windows/GUIWindowFileManager.cpp
@@ -1245,11 +1245,13 @@ void CGUIWindowFileManager::OnInitWindow()
else if (!bResult0)
{
ShowShareErrorMessage(m_Directory[0]); //show the error message after window is loaded!
+ Update(0, ""); // reset view to root
}
if (!bResult1)
{
ShowShareErrorMessage(m_Directory[1]); //show the error message after window is loaded!
+ Update(1, ""); // reset view to root
}
}