From d8d7799b6cbe20f2d009895d9945dc7a8364ee17 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Sat, 31 Aug 2019 14:25:12 +0100 Subject: [PATCH 1/7] Initial work to add boxsets navigation --- .../resources/strings.po | 21 +- .../skin.estuary/xml/MusicVisualisation.xml | 191 ++-- addons/skin.estuary/xml/Variables.xml | 946 +++++++++--------- addons/skin.estuary/xml/View_50_List.xml | 539 +++++----- addons/skin.estuary/xml/View_54_InfoWall.xml | 938 ++++++++--------- system/library/music/boxsets.xml | 6 + xbmc/GUIInfoManager.cpp | 270 ++--- xbmc/filesystem/MusicDatabaseDirectory.cpp | 8 +- .../MusicDatabaseDirectory/CMakeLists.txt | 6 + .../MusicDatabaseDirectory/DirectoryNode.cpp | 11 + .../MusicDatabaseDirectory/DirectoryNode.h | 7 +- .../DirectoryNodeBoxsetDiscSongs.cpp | 49 + .../DirectoryNodeBoxsetDiscSongs.h | 27 + .../DirectoryNodeBoxsetDiscs.cpp | 50 + .../DirectoryNodeBoxsetDiscs.h | 27 + .../DirectoryNodeBoxsets.cpp | 51 + .../DirectoryNodeBoxsets.h | 27 + .../DirectoryNodeOverview.cpp | 7 +- .../DirectoryNodeSong.cpp | 1 + .../MusicDatabaseDirectory/QueryParams.cpp | 7 + .../MusicDatabaseDirectory/QueryParams.h | 6 +- xbmc/guilib/guiinfo/GUIInfoLabels.h | 5 +- xbmc/guilib/guiinfo/MusicGUIInfo.cpp | 4 + xbmc/music/Album.cpp | 7 +- xbmc/music/Album.h | 2 + xbmc/music/GUIViewStateMusic.cpp | 13 + xbmc/music/MusicDatabase.cpp | 636 +++++++++--- xbmc/music/MusicDatabase.h | 49 +- xbmc/music/MusicDbUrl.cpp | 8 + xbmc/music/Song.cpp | 9 + xbmc/music/Song.h | 3 + xbmc/music/infoscanner/MusicInfoScanner.cpp | 72 +- xbmc/music/infoscanner/MusicInfoScanner.h | 2 + xbmc/music/tags/MusicInfoTag.cpp | 23 + xbmc/music/tags/MusicInfoTag.h | 6 + xbmc/music/tags/TagLoaderTagLib.cpp | 34 + xbmc/music/tags/TagLoaderTagLib.h | 2 +- xbmc/music/windows/GUIWindowMusicBase.cpp | 7 +- xbmc/music/windows/GUIWindowMusicBase.h | 2 +- xbmc/music/windows/GUIWindowMusicNav.cpp | 13 +- 40 files changed, 2471 insertions(+), 1621 deletions(-) create mode 100644 system/library/music/boxsets.xml create mode 100644 xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.cpp create mode 100644 xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.h create mode 100644 xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.cpp create mode 100644 xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.h create mode 100644 xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.cpp create mode 100644 xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 3835cef8d1f4a..4f59f0a89fe69 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -2004,7 +2004,7 @@ msgstr "" #: addons/skin.estuary/xml/Variables.xml msgctxt "#427" -msgid "Disc" +msgid "Disc Number" msgstr "" msgctxt "#428" @@ -2192,7 +2192,11 @@ msgctxt "#470" msgid "Credits" msgstr "" -#empty strings from id 471 to 473 +msgctxt "#471" +msgid "Disc Title" +msgstr "" + +#empty strings from id 472 to 473 msgctxt "#474" msgid "Off" @@ -2389,7 +2393,7 @@ msgctxt "#519" msgid "Launch in..." msgstr "" -#empty string with id 520 +#: empty string 520 #: xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp msgctxt "#521" @@ -12764,7 +12768,8 @@ msgctxt "#20340" msgid "Do you want to remove all items within this path from your library?" msgstr "" -#empty string with id 20341 +#: empty string #20341 + #: xbmc/dialogs/GUIDialogMediaFilter.cpp #: xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp @@ -21145,8 +21150,12 @@ msgctxt "#38073" msgid "Do you want to refresh information for this item now?" msgstr "" -#empty strings from id 38074 to 38099 -#strings 38074 to 38099 reserved for music library +msgctxt "#38074" +msgid "Box-sets" +msgstr "" + +#empty strings from id 38075 to 38099 +#strings 38075 to 38099 reserved for music library #. Description of section #14200 "Player"" #: system/settings/settings.xml diff --git a/addons/skin.estuary/xml/MusicVisualisation.xml b/addons/skin.estuary/xml/MusicVisualisation.xml index 3bbab7da638ef..afb682b5ec819 100644 --- a/addons/skin.estuary/xml/MusicVisualisation.xml +++ b/addons/skin.estuary/xml/MusicVisualisation.xml @@ -1,94 +1,103 @@ - - background - RunScript(script.artistslideshow) - - - FullScreenDimensions - Player.HasAudio - - - DepthBackground - FullScreenDimensions - !Skin.HasSetting(hide_background_fanart) - Conditional - Conditional - - scale - 400 - WindowOpen - WindowClose - $INFO[Player.Art(fanart)] - - - scale - 10000 - true - 600 - yes - $INFO[Window(Visualisation).Property(ArtistSlideshow)] - System.HasAddon(script.artistslideshow) - - - - Conditional - ColoredBackgroundImages - - - 0 - 1080 - OpenClose_Left - - 33 - 200 - Visible_Left - [Player.ShowInfo | Window.IsActive(musicosd)] + !MusicPlayer.Content(livetv) - 500 - 500 - 400 - keep - $INFO[MusicPlayer.Cover] - colors/black.png - 4 - - - -30 - [Player.ShowInfo | Window.IsActive(musicosd)] + ![Window.IsActive(playerprocessinfo) | MusicPlayer.Content(livetv)] - Visible_Left - - 30 - 740 - - 0 - 1600 - 40 - - font60 - black - true - - - 80 - 1600 - 40 - - font37 - black - true - - - 127 - 1600 - 40 - - font45 - black - button_focus - true - - - - - + + background + RunScript(script.artistslideshow) + + + FullScreenDimensions + Player.HasAudio + + + DepthBackground + FullScreenDimensions + !Skin.HasSetting(hide_background_fanart) + Conditional + Conditional + + scale + 400 + WindowOpen + WindowClose + $INFO[Player.Art(fanart)] + + + scale + 10000 + true + 600 + yes + $INFO[Window(Visualisation).Property(ArtistSlideshow)] + System.HasAddon(script.artistslideshow) + + + + Conditional + ColoredBackgroundImages + + + 0 + 1080 + OpenClose_Left + + 33 + 200 + Visible_Left + [Player.ShowInfo | Window.IsActive(musicosd)] + !MusicPlayer.Content(livetv) + 500 + 500 + 400 + keep + $INFO[MusicPlayer.Cover] + colors/black.png + 4 + + + -30 + [Player.ShowInfo | Window.IsActive(musicosd)] + ![Window.IsActive(playerprocessinfo) | MusicPlayer.Content(livetv)] + Visible_Left + + 30 + 740 + + 0 + 1600 + 40 + + font60 + black + true + + + 80 + 1600 + 40 + + font37 + black + true + + + 120 + 1600 + 40 + + font40 + black + true + + + 167 + 1600 + 40 + + font45 + black + button_focus + true + + + + + diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index ab9bae788e96e..7c5417e2b2ef1 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -1,476 +1,476 @@ - - windows/pvr/record.png - windows/pvr/timer.png - windows/pvr/archive.png - - - plugin://plugin.program.autocompletion?info=autocomplete&&id=$INFO[Control.GetLabel(312).index(1)]&&limit=9 - - - $INFO[ListItem.TVShowtitle,,: ]$INFO[ListItem.Season,,x]$INFO[ListItem.Episode,,. ]$INFO[ListItem.Title] - $INFO[ListItem.Artist,, - ]$INFO[ListItem.Title] - $INFO[ListItem.Label] - - - $INFO[ListItem.Duration] - $INFO[ListItem.Duration,, $LOCALIZE[12391]] - - - $INFO[ListItem.FolderPath] - - - - >9 - 9 - 8 - 7 - 6 - 5 - 4 - 3 - 2 - 1 - [COLOR grey]0[/COLOR] - - - $INFO[ListItem.Comment,[B]$LOCALIZE[569][/B][CR][COLOR=white],[/COLOR]] - $INFO[ListItem.Property(Album_Description),[COLOR=white],[/COLOR]] - $INFO[ListItem.Property(Artist_Description),[COLOR=white],[/COLOR]] - - - $INFO[VideoPlayer.TvShowTitle] - $INFO[VideoPlayer.Year]$INFO[VideoPlayer.Genre, - ] - $INFO[VideoPlayer.ChannelName] - $INFO[MusicPlayer.Artist] - - - icons/now-playing/pause.png - icons/now-playing/play.png - - - $INFO[Player.Art(poster)] - $INFO[Player.Art(tvshow.poster)] - DefaultTVShows.png - $INFO[Player.Icon] - - - $INFO[Listitem.Art(poster)] - DefaultFolderBackSquare.png - DefaultAudio.png - DefaultFolderSquare.png - $INFO[ListItem.Thumb] - - - $INFO[Listitem.Art(thumb)] - DefaultArtist.png - DefaultAlbumCover.png - DefaultAudio.png - - - $INFO[Listitem.Art(poster)] - $INFO[ListItem.Icon] - - - $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] - $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] - $INFO[Listitem.Art(poster)] - $INFO[ListItem.Thumb] - $INFO[ListItem.Icon] - - - $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] - $INFO[ListItem.Year] - $LOCALIZE[38026]: $INFO[ListItem.Appearances] - $INFO[ListItem.Label2] - - - $INFO[ListItem.Property(description),,[CR]]$INFO[ListItem.PictureDatetime,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]]$INFO[ListItem.PictureResolution,[COLOR button_focus]$LOCALIZE[169]: [/COLOR],[CR]]$INFO[ListItem.PictureCamMake,[COLOR button_focus]$LOCALIZE[31041]: [/COLOR],[CR]]$INFO[ListItem.PictureCamModel,[COLOR button_focus]$LOCALIZE[21823]: [/COLOR],[CR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_YearsActive),[COLOR button_focus]$LOCALIZE[21898]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_Style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] - $INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(album_label),[COLOR button_focus]$LOCALIZE[21899]: [/COLOR],[CR]]$INFO[ListItem.Property(album_style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Director,[COLOR button_focus]$LOCALIZE[20339]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR button_focus]$LOCALIZE[20417]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] - - - $INFO[ListItem.Size,[COLOR button_focus]$LOCALIZE[289]: [/COLOR],[CR]]$INFO[ListItem.PictureAperture,[COLOR button_focus]$LOCALIZE[21826]: [/COLOR],[CR]]$INFO[ListItem.PictureFocalLen,[COLOR button_focus]$LOCALIZE[21827]: [/COLOR],[CR]]$INFO[ListItem.PictureExpTime,[COLOR button_focus]$LOCALIZE[21830]: [/COLOR],[CR]]$INFO[ListItem.Date,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]] - $INFO[ListItem.Property(artist_description)] - $INFO[ListItem.Property(album_description)] - $INFO[ListItem.Plot] - - - [COLOR=button_focus]$INFO[Container(3).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(3).CurrentPage]/$INFO[Container(3).NumPages][/COLOR] - [COLOR=button_focus]$INFO[Container(6).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(6).CurrentPage]/$INFO[Container(6).NumPages][/COLOR] - - - [COLOR=button_focus]$INFO[Container(450).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(450).CurrentPage]/$INFO[Container(450).NumPages][/COLOR] - [COLOR=button_focus]$INFO[Container(451).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(451).CurrentPage]/$INFO[Container(451).NumPages][/COLOR] - - - DefaultBackBanner.png - $INFO[ListItem.Art(banner)] - $INFO[ListItem.Art(fanart)] - dialogs/dialog-bg-nobo.png - - - special://skin/extras/home-images/movie.jpg - $INFO[ListItem.Art(fanart)] - - - $INFO[ListItem.Addonnews] - $INFO[ListItem.AddonDescription] - $INFO[ListItem.Property(album_description)] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Plot] - $INFO[ListItem.Property(artist_description)] - $INFO[ListItem.Plot] - $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]] - - - $INFO[listitem.TrackNumber, ] - $INFO[listitem.TrackNumber, ,.]$INFO[listitem.Title, ] - - - $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] - DefaultGenre.png - - - $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] - DefaultStudio.png - - - $INFO[ListItem.Property(addon.status)] - $INFO[ListItem.Label2] - $INFO[ListItem.AddonCreator,, - ]$INFO[ListItem.AddonVersion] - - - $LOCALIZE[563] - $LOCALIZE[38018] - $LOCALIZE[16018] - - - $LOCALIZE[31165] - $LOCALIZE[31166] - $LOCALIZE[16018] - - - icons/addonstatus/disable.png - icons/addonstatus/orphan.png - icons/addonstatus/install.png - icons/addonstatus/disable.png - icons/addonstatus/update.png - OverlayWatched.png - OverlayUnwatched.png - - - flags/videoresolution/3D.png - $INFO[ListItem.VideoResolution,flags/videoresolution/,.png] - - - flags/videoresolution/3D.png - $INFO[Container.ListItem.VideoResolution,flags/videoresolution/,.png] - - - $INFO[ListItem.Art(banner)] - $INFO[ListItem.Art(tvshow.banner)] - - - DefaultFolderBackPoster.png - $INFO[Container.Art(season.poster)] - $INFO[Container.Art(tvshow.poster)] - $INFO[Container.Art(tvshow.poster)] - $INFO[Container.ListItem.Art(thumb)] - $INFO[ListItem.Art(tvshow.poster)] - $INFO[ListItem.Art(poster)] - - - $INFO[ListItem.Art(poster)] - $INFO[ListItem.Art(season.poster)] - $INFO[ListItem.Art(tvshow.poster)] - - - $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] - - - - 2x - 4x - 8x - 16x - 32x - - - $LOCALIZE[773][COLOR=grey] $INFO[Player.SeekStepSize][/COLOR] - $LOCALIZE[112] - $LOCALIZE[31039] $VAR[VideoPlayerForwardRewindVar] - $LOCALIZE[31038] $VAR[VideoPlayerForwardRewindVar] - $LOCALIZE[31142]: $INFO[Player.PlaySpeed] - - - [B]$INFO[Player.SeekNumeric(hh:mm:ss)][/B] - $INFO[PVR.EpgEventSeekTime]$INFO[PVR.EpgEventDuration, / ] - $INFO[Player.SeekTime]$INFO[Player.Duration, / ] - $INFO[PVR.EpgEventElapsedTime]$INFO[PVR.EpgEventDuration, / ] - $INFO[Player.Time]$INFO[Player.Duration, / ] - - - $LOCALIZE[31050] - $LOCALIZE[298] - $LOCALIZE[31106] - $LOCALIZE[24012] - $LOCALIZE[31092] - $LOCALIZE[19033] - $LOCALIZE[19019] - $LOCALIZE[19069] - $LOCALIZE[5] - $LOCALIZE[36501] - $LOCALIZE[19059] - $LOCALIZE[264] - $LOCALIZE[31054] - - - $LOCALIZE[31033]$INFO[MusicPlayer.UserRating, : ] - $LOCALIZE[19033] - $LOCALIZE[19019] - $LOCALIZE[19069] - $LOCALIZE[29900] - $LOCALIZE[486]$INFO[Playlist.Repeat, : ] - $LOCALIZE[590]: $LOCALIZE[16041] - $LOCALIZE[590]: $LOCALIZE[16039] - $LOCALIZE[24013] - $LOCALIZE[10004] - - - $LOCALIZE[31129] - $LOCALIZE[31130] - - - dialogs/volume/mute.png - dialogs/volume/volume.png - dialogs/volume/volume2.png - dialogs/volume/volume.png - dialogs/volume/volume1.png - - - - [COLOR grey]$INFO[ListItem.Year, (,)][/COLOR] - - - $INFO[ListItem.Title] - $INFO[ListItem.TVShowTitle]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] - $INFO[ListItem.Title]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] - $INFO[ListItem.Label]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] - - - $LOCALIZE[1024] - $LOCALIZE[208] - - - $INFO[ListItem.Season]$INFO[ListItem.Episode,[COLOR grey]x[/COLOR],: ]$INFO[ListItem.Title] - $INFO[ListItem.Tagline,[I],[/I]] - $INFO[ListItem.Genre] - - - $INFO[ListItem.LastPlayed,$LOCALIZE[568]: ] - $INFO[ListItem.FileNameAndPath] - - - $INFO[SlideShow.Filename] - $INFO[ListItem.Label] - - - $INFO[SlideShow.EXIFtime] - $INFO[ListItem.PictureDateTime] - - - $INFO[VideoPlayer.Title] - $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] - $INFO[VideoPlayer.TVShowTitle]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] - $INFO[VideoPlayer.Title]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] - $LOCALIZE[589] - $LOCALIZE[31000]... - - - $LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length] - $INFO[VideoPlayer.Artist]$INFO[VideoPlayer.Album, - ] - $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] - $INFO[VideoPlayer.ChannelNumberLabel,([COLOR button_focus],[/COLOR]) ]$INFO[VideoPlayer.ChannelName] $INFO[VideoPlayer.EpisodeName, - ] - $INFO[VideoPlayer.Genre] - - - $INFO[Player.Art(tvshow.clearlogo)] - $INFO[Player.Art(clearlogo)] - - - - $INFO[Window(home).Property(infobackground)] - $INFO[Container.ListItem.Art(fanart)] - $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] - - - - $INFO[Window(home).Property(infobackground)] - $INFO[Container.ListItem.Art(fanart)] - $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]power$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]settings$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]favorites$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]search$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] - - - - $INFO[Window(home).Property(infobackground)] - $INFO[Skin.String(MovieGenreFanart.path)]$INFO[ListItem.Label]$INFO[Skin.String(MovieGenreFanart.ext)] - $INFO[ListItem.FolderPath] - $INFO[ListItem.Art(fanart)] - $INFO[Container.Art(tvshow.fanart)] - $INFO[Container.Art(artist.fanart)] - $INFO[Container.Art(fanart)] - - - $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] - $INFO[Skin.String(weatherfanart.path)]$INFO[Window(Weather).Property(Current.FanartCode)]$INFO[Skin.String(weatherfanart.ext)] - $INFO[Skin.String(HomeFanart.path)]weather$INFO[Skin.String(HomeFanart.ext)] - - - windows/pvr/record.png - overlays/watched/OverlayPlaying-List.png - overlays/watched/resume.png - overlays/set.png - overlays/folder.png - $INFO[ListItem.Overlay] - OverlayUnwatched.png - - - windows/pvr/record.png - overlays/set.png - overlays/watched/OverlayPlaying-List.png - overlays/watched/resume.png - windows/pvr/archive.png - $INFO[ListItem.Overlay] - - - - $LOCALIZE[20342] - $LOCALIZE[20389] - $LOCALIZE[20343] - $LOCALIZE[20343] - $LOCALIZE[20343] - $LOCALIZE[3] - - - $LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] - $LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] - - - $LOCALIZE[19020] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] - $LOCALIZE[19021] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] - - - $LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - $LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - - - $LOCALIZE[19020] / $LOCALIZE[19040] - $LOCALIZE[19021] / $LOCALIZE[19040] - $LOCALIZE[19020] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] - $LOCALIZE[19021] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] - - - $LOCALIZE[19020] / $LOCALIZE[137] - $LOCALIZE[19021] / $LOCALIZE[137] - - - $LOCALIZE[15016] - - - button_focus - FFFFFFFF - - - $LOCALIZE[19199] - $LOCALIZE[19024] - $LOCALIZE[19199] - $LOCALIZE[19023] - - - $INFO[Player.Title] - $INFO[MusicPlayer.ChannelName]$INFO[Player.Title, - ] - - - $LOCALIZE[19048] - $LOCALIZE[19174] - $LOCALIZE[19048] - $LOCALIZE[19173] - - - $LOCALIZE[19019] - $LOCALIZE[19069] - $LOCALIZE[19017] - $LOCALIZE[19040] - $LOCALIZE[19138] - $LOCALIZE[137] - - - osd/fullscreen/buttons/repeat-one.png - osd/fullscreen/buttons/repeat-all.png - osd/fullscreen/buttons/repeat-off.png - - - $INFO[VideoPlayer.Title] - $INFO[MusicPlayer.Artist] - - - $LOCALIZE[20373]$INFO[VideoPlayer.Season,: , / ]$LOCALIZE[20359]$INFO[VideoPlayer.Episode,: ] - $INFO[VideoPlayer.Year] - $INFO[VideoPlayer.ChannelName] - [COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ] - - - $INFO[VideoPlayer.TvShowTitle] - $INFO[VideoPlayer.Genre] - $INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR] - - - icons/pvr/PVR-IsRecording.png - icons/pvr/timers/bell.png - icons/pvr/PVR-HasTimerScheduleError.png - icons/pvr/PVR-HasTimerError.png - icons/pvr/PVR-HasTimerScheduleConflict.png - icons/pvr/PVR-HasTimerConflict.png - icons/pvr/PVR-HasRecording.png - icons/pvr/PVR-HasTimerScheduleDisabled.png - icons/pvr/PVR-HasTimerDisabled.png - icons/pvr/PVR-HasTimerSchedule.png - icons/pvr/PVR-HasTimer.png - icons/pvr/PVR-HasArchive.png - - - $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E] - $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ] - - - [COLOR grey]$LOCALIZE[19299]:[/COLOR] $INFO[ListItem.ExpirationDate] $INFO[ListItem.ExpirationTime][CR] - - - HW - SW - - - $LOCALIZE[31136] - - - $INFO[ListItem.Timertype] - $INFO[ListItem.EpisodeName] - - - $LOCALIZE[231] - $INFO[Skin.String(background_overlay),$LOCALIZE[467] ] - - - $LOCALIZE[20045] - $LOCALIZE[20046] - - - $INFO[ListItem.EpgEventIcon] - $INFO[ListItem.Icon] - + + windows/pvr/record.png + windows/pvr/timer.png + windows/pvr/archive.png + + + plugin://plugin.program.autocompletion?info=autocomplete&&id=$INFO[Control.GetLabel(312).index(1)]&&limit=9 + + + $INFO[ListItem.TVShowtitle,,: ]$INFO[ListItem.Season,,x]$INFO[ListItem.Episode,,. ]$INFO[ListItem.Title] + $INFO[ListItem.Artist,, - ]$INFO[ListItem.Title] + $INFO[ListItem.Label] + + + $INFO[ListItem.Duration] + $INFO[ListItem.Duration,, $LOCALIZE[12391]] + + + $INFO[ListItem.FolderPath] + + + + >9 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + [COLOR grey]0[/COLOR] + + + $INFO[ListItem.Comment,[B]$LOCALIZE[569][/B][CR][COLOR=white],[/COLOR]] + $INFO[ListItem.Property(Album_Description),[COLOR=white],[/COLOR]] + $INFO[ListItem.Property(Artist_Description),[COLOR=white],[/COLOR]] + + + $INFO[VideoPlayer.TvShowTitle] + $INFO[VideoPlayer.Year]$INFO[VideoPlayer.Genre, - ] + $INFO[VideoPlayer.ChannelName] + $INFO[MusicPlayer.Artist] + + + icons/now-playing/pause.png + icons/now-playing/play.png + + + $INFO[Player.Art(poster)] + $INFO[Player.Art(tvshow.poster)] + DefaultTVShows.png + $INFO[Player.Icon] + + + $INFO[Listitem.Art(poster)] + DefaultFolderBackSquare.png + DefaultAudio.png + DefaultFolderSquare.png + $INFO[ListItem.Thumb] + + + $INFO[Listitem.Art(thumb)] + DefaultArtist.png + DefaultAlbumCover.png + DefaultAudio.png + + + $INFO[Listitem.Art(poster)] + $INFO[ListItem.Icon] + + + $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] + $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] + $INFO[Listitem.Art(poster)] + $INFO[ListItem.Thumb] + $INFO[ListItem.Icon] + + + $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] + $INFO[ListItem.Year] + $LOCALIZE[38026]: $INFO[ListItem.Appearances] + $INFO[ListItem.Label2] + + + $INFO[ListItem.Property(description),,[CR]]$INFO[ListItem.PictureDatetime,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]]$INFO[ListItem.PictureResolution,[COLOR button_focus]$LOCALIZE[169]: [/COLOR],[CR]]$INFO[ListItem.PictureCamMake,[COLOR button_focus]$LOCALIZE[31041]: [/COLOR],[CR]]$INFO[ListItem.PictureCamModel,[COLOR button_focus]$LOCALIZE[21823]: [/COLOR],[CR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_YearsActive),[COLOR button_focus]$LOCALIZE[21898]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_Style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] + $INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(album_label),[COLOR button_focus]$LOCALIZE[21899]: [/COLOR],[CR]]$INFO[ListItem.Property(album_style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Director,[COLOR button_focus]$LOCALIZE[20339]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR button_focus]$LOCALIZE[20417]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] + + + $INFO[ListItem.Size,[COLOR button_focus]$LOCALIZE[289]: [/COLOR],[CR]]$INFO[ListItem.PictureAperture,[COLOR button_focus]$LOCALIZE[21826]: [/COLOR],[CR]]$INFO[ListItem.PictureFocalLen,[COLOR button_focus]$LOCALIZE[21827]: [/COLOR],[CR]]$INFO[ListItem.PictureExpTime,[COLOR button_focus]$LOCALIZE[21830]: [/COLOR],[CR]]$INFO[ListItem.Date,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]] + $INFO[ListItem.Property(artist_description)] + $INFO[ListItem.Property(album_description)] + $INFO[ListItem.Plot] + + + [COLOR=button_focus]$INFO[Container(3).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(3).CurrentPage]/$INFO[Container(3).NumPages][/COLOR] + [COLOR=button_focus]$INFO[Container(6).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(6).CurrentPage]/$INFO[Container(6).NumPages][/COLOR] + + + [COLOR=button_focus]$INFO[Container(450).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(450).CurrentPage]/$INFO[Container(450).NumPages][/COLOR] + [COLOR=button_focus]$INFO[Container(451).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(451).CurrentPage]/$INFO[Container(451).NumPages][/COLOR] + + + DefaultBackBanner.png + $INFO[ListItem.Art(banner)] + $INFO[ListItem.Art(fanart)] + dialogs/dialog-bg-nobo.png + + + special://skin/extras/home-images/movie.jpg + $INFO[ListItem.Art(fanart)] + + + $INFO[ListItem.Addonnews] + $INFO[ListItem.AddonDescription] + $INFO[ListItem.Property(album_description)] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Plot] + $INFO[ListItem.Property(artist_description)] + $INFO[ListItem.Plot] + $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]] + + + $INFO[listitem.TrackNumber, ] + $INFO[listitem.TrackNumber, ,.]$INFO[listitem.Title, ] + + + $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] + DefaultGenre.png + + + $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] + DefaultStudio.png + + + $INFO[ListItem.Property(addon.status)] + $INFO[ListItem.Label2] + $INFO[ListItem.AddonCreator,, - ]$INFO[ListItem.AddonVersion] + + + $LOCALIZE[563] + $LOCALIZE[38018] + $LOCALIZE[16018] + + + $LOCALIZE[31165] + $LOCALIZE[31166] + $LOCALIZE[16018] + + + icons/addonstatus/disable.png + icons/addonstatus/orphan.png + icons/addonstatus/install.png + icons/addonstatus/disable.png + icons/addonstatus/update.png + OverlayWatched.png + OverlayUnwatched.png + + + flags/videoresolution/3D.png + $INFO[ListItem.VideoResolution,flags/videoresolution/,.png] + + + flags/videoresolution/3D.png + $INFO[Container.ListItem.VideoResolution,flags/videoresolution/,.png] + + + $INFO[ListItem.Art(banner)] + $INFO[ListItem.Art(tvshow.banner)] + + + DefaultFolderBackPoster.png + $INFO[Container.Art(season.poster)] + $INFO[Container.Art(tvshow.poster)] + $INFO[Container.Art(tvshow.poster)] + $INFO[Container.ListItem.Art(thumb)] + $INFO[ListItem.Art(tvshow.poster)] + $INFO[ListItem.Art(poster)] + + + $INFO[ListItem.Art(poster)] + $INFO[ListItem.Art(season.poster)] + $INFO[ListItem.Art(tvshow.poster)] + + + $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] + + + + 2x + 4x + 8x + 16x + 32x + + + $LOCALIZE[773][COLOR=grey] $INFO[Player.SeekStepSize][/COLOR] + $LOCALIZE[112] + $LOCALIZE[31039] $VAR[VideoPlayerForwardRewindVar] + $LOCALIZE[31038] $VAR[VideoPlayerForwardRewindVar] + $LOCALIZE[31142]: $INFO[Player.PlaySpeed] + + + [B]$INFO[Player.SeekNumeric(hh:mm:ss)][/B] + $INFO[PVR.EpgEventSeekTime]$INFO[PVR.EpgEventDuration, / ] + $INFO[Player.SeekTime]$INFO[Player.Duration, / ] + $INFO[PVR.EpgEventElapsedTime]$INFO[PVR.EpgEventDuration, / ] + $INFO[Player.Time]$INFO[Player.Duration, / ] + + + $LOCALIZE[31050] + $LOCALIZE[298] + $LOCALIZE[31106] + $LOCALIZE[24012] + $LOCALIZE[31092] + $LOCALIZE[19033] + $LOCALIZE[19019] + $LOCALIZE[19069] + $LOCALIZE[5] + $LOCALIZE[36501] + $LOCALIZE[19059] + $LOCALIZE[264] + $LOCALIZE[31054] + + + $LOCALIZE[31033]$INFO[MusicPlayer.UserRating, : ] + $LOCALIZE[19033] + $LOCALIZE[19019] + $LOCALIZE[19069] + $LOCALIZE[29900] + $LOCALIZE[486]$INFO[Playlist.Repeat, : ] + $LOCALIZE[590]: $LOCALIZE[16041] + $LOCALIZE[590]: $LOCALIZE[16039] + $LOCALIZE[24013] + $LOCALIZE[10004] + + + $LOCALIZE[31129] + $LOCALIZE[31130] + + + dialogs/volume/mute.png + dialogs/volume/volume.png + dialogs/volume/volume2.png + dialogs/volume/volume.png + dialogs/volume/volume1.png + + + + [COLOR grey]$INFO[ListItem.Year, (,)][/COLOR] + + + $INFO[ListItem.Title] + $INFO[ListItem.TVShowTitle]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] + $INFO[ListItem.Title]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] + $INFO[ListItem.Label]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] + + + $LOCALIZE[1024] + $LOCALIZE[208] + + + $INFO[ListItem.Season]$INFO[ListItem.Episode,[COLOR grey]x[/COLOR],: ]$INFO[ListItem.Title] + $INFO[ListItem.Tagline,[I],[/I]] + $INFO[ListItem.Genre] + + + $INFO[ListItem.LastPlayed,$LOCALIZE[568]: ] + $INFO[ListItem.FileNameAndPath] + + + $INFO[SlideShow.Filename] + $INFO[ListItem.Label] + + + $INFO[SlideShow.EXIFtime] + $INFO[ListItem.PictureDateTime] + + + $INFO[VideoPlayer.Title] + $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] + $INFO[VideoPlayer.TVShowTitle]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] + $INFO[VideoPlayer.Title]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] + $LOCALIZE[589] + $LOCALIZE[31000]... + + + $LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length] + $INFO[VideoPlayer.Artist]$INFO[VideoPlayer.Album, - ] + $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] + $INFO[VideoPlayer.ChannelNumberLabel,([COLOR button_focus],[/COLOR]) ]$INFO[VideoPlayer.ChannelName] $INFO[VideoPlayer.EpisodeName, - ] + $INFO[VideoPlayer.Genre] + + + $INFO[Player.Art(tvshow.clearlogo)] + $INFO[Player.Art(clearlogo)] + + + + $INFO[Window(home).Property(infobackground)] + $INFO[Container.ListItem.Art(fanart)] + $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] + + + + $INFO[Window(home).Property(infobackground)] + $INFO[Container.ListItem.Art(fanart)] + $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]power$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]settings$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]favorites$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]search$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] + + + + $INFO[Window(home).Property(infobackground)] + $INFO[Skin.String(MovieGenreFanart.path)]$INFO[ListItem.Label]$INFO[Skin.String(MovieGenreFanart.ext)] + $INFO[ListItem.FolderPath] + $INFO[ListItem.Art(fanart)] + $INFO[Container.Art(tvshow.fanart)] + $INFO[Container.Art(artist.fanart)] + $INFO[Container.Art(fanart)] + + + $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] + $INFO[Skin.String(weatherfanart.path)]$INFO[Window(Weather).Property(Current.FanartCode)]$INFO[Skin.String(weatherfanart.ext)] + $INFO[Skin.String(HomeFanart.path)]weather$INFO[Skin.String(HomeFanart.ext)] + + + windows/pvr/record.png + overlays/watched/OverlayPlaying-List.png + overlays/watched/resume.png + overlays/set.png + overlays/folder.png + $INFO[ListItem.Overlay] + OverlayUnwatched.png + + + windows/pvr/record.png + overlays/set.png + overlays/watched/OverlayPlaying-List.png + overlays/watched/resume.png + windows/pvr/archive.png + $INFO[ListItem.Overlay] + + + + $LOCALIZE[20342] + $LOCALIZE[20389] + $LOCALIZE[20343] + $LOCALIZE[20343] + $LOCALIZE[20343] + $LOCALIZE[3] + + + $LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] + $LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] + + + $LOCALIZE[19020] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] + $LOCALIZE[19021] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] + + + $LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] + $LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] + + + $LOCALIZE[19020] / $LOCALIZE[19040] + $LOCALIZE[19021] / $LOCALIZE[19040] + $LOCALIZE[19020] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] + $LOCALIZE[19021] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] + + + $LOCALIZE[19020] / $LOCALIZE[137] + $LOCALIZE[19021] / $LOCALIZE[137] + + + $LOCALIZE[15016] + + + button_focus + FFFFFFFF + + + $LOCALIZE[19199] - $LOCALIZE[19024] + $LOCALIZE[19199] - $LOCALIZE[19023] + + + $INFO[Player.Title] + $INFO[MusicPlayer.ChannelName]$INFO[Player.Title, - ] + + + $LOCALIZE[19048] - $LOCALIZE[19174] + $LOCALIZE[19048] - $LOCALIZE[19173] + + + $LOCALIZE[19019] + $LOCALIZE[19069] + $LOCALIZE[19017] + $LOCALIZE[19040] + $LOCALIZE[19138] + $LOCALIZE[137] + + + osd/fullscreen/buttons/repeat-one.png + osd/fullscreen/buttons/repeat-all.png + osd/fullscreen/buttons/repeat-off.png + + + $INFO[VideoPlayer.Title] + $INFO[MusicPlayer.Artist] + + + $LOCALIZE[20373]$INFO[VideoPlayer.Season,: , / ]$LOCALIZE[20359]$INFO[VideoPlayer.Episode,: ] + $INFO[VideoPlayer.Year] + $INFO[VideoPlayer.ChannelName] + [COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ] + + + $INFO[VideoPlayer.TvShowTitle] + $INFO[VideoPlayer.Genre] + $INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR] + + + icons/pvr/PVR-IsRecording.png + icons/pvr/timers/bell.png + icons/pvr/PVR-HasTimerScheduleError.png + icons/pvr/PVR-HasTimerError.png + icons/pvr/PVR-HasTimerScheduleConflict.png + icons/pvr/PVR-HasTimerConflict.png + icons/pvr/PVR-HasRecording.png + icons/pvr/PVR-HasTimerScheduleDisabled.png + icons/pvr/PVR-HasTimerDisabled.png + icons/pvr/PVR-HasTimerSchedule.png + icons/pvr/PVR-HasTimer.png + icons/pvr/PVR-HasArchive.png + + + $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E] + $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ] + + + [COLOR grey]$LOCALIZE[19299]:[/COLOR] $INFO[ListItem.ExpirationDate] $INFO[ListItem.ExpirationTime][CR] + + + HW + SW + + + $LOCALIZE[31136] + + + $INFO[ListItem.Timertype] + $INFO[ListItem.EpisodeName] + + + $LOCALIZE[231] + $INFO[Skin.String(background_overlay),$LOCALIZE[467] ] + + + $LOCALIZE[20045] + $LOCALIZE[20046] + + + $INFO[ListItem.EpgEventIcon] + $INFO[ListItem.Icon] + diff --git a/addons/skin.estuary/xml/View_50_List.xml b/addons/skin.estuary/xml/View_50_List.xml index 3591a9bfde50a..c6bbde8f84311 100644 --- a/addons/skin.estuary/xml/View_50_List.xml +++ b/addons/skin.estuary/xml/View_50_List.xml @@ -1,272 +1,273 @@ - - - OpenClose_Right - 596 - Control.IsVisible(50) - Visible_Right - - - - - - - DepthContentPanel - - -20 - 634 - - - - - - - - 20 - list_y_offset - 12 - list_y_offset - 50 - 50 - vertical - conditional - - - DepthContentPopout - 38 - 36 - 120 - 124 - 200 - keep - $VAR[InfoWallThumbVar] - - - 310 - 936 - - - - - - - - - - 0 - 0 - - - $PARAM[left] - $PARAM[right] - list_y_offset - list_y_offset - 5 - 6 - 500 - vertical - $PARAM[list_id]600 - 9000 - $PARAM[list_id]600 - $PARAM[list_id] - $PARAM[list_id] - list - Container.Content(movies) | Container.Content(sets) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(games) | Window.IsActive(MyPlaylist.xml) - - - 0 - 0 - 0 - 0 - lists/focus.png - Control.HasFocus($PARAM[list_id]) - - - 70 - 70 - 0 - 0 - center - true - font27 - - text_shadow - - - 0 - 0 - 100 - 20 - right - center - font12 - - Focus - text_shadow - - - 21 - 50% - 32 - 32 - $VAR[ListWatchedIconVar] - Focus - - - 21 - 50% - 32 - 32 - $VAR[ListWatchedIconVar] - Focus - VisibleChange - !Control.HasFocus($PARAM[list_id]) - - - - - 70 - 70 - 0 - 0 - center - font27 - - text_shadow - - - 20 - 20 - 0 - 0 - right - center - font12 - - grey - text_shadow - - - 21 - 50% - 32 - 32 - $VAR[ListWatchedIconVar] - - - - - - - - false - - - DepthContentPanel - - - - - - 30 - 140 - 530 - 470 - keep - 300 - $VAR[IconWallThumbVar] - !String.IsEqual(ListItem.DbType,episode) - - - 30 - 140 - 530 - 330 - keep - 300 - $INFO[ListItem.Icon] - String.IsEqual(ListItem.DbType,episode) - - - 273 - 590 - RatingCircle - conditional - - - !Container.Content() | !String.isempty(ListItem.Plot) - 30 - - 640 - 525 - 117 - !System.HasModalDialog + Skin.HasSetting(AutoScroll) - - !String.IsEqual(ListItem.DbType,episode) - - - 485 - 525 - 96 - !System.HasModalDialog + Skin.HasSetting(AutoScroll) - - String.IsEqual(ListItem.DbType,episode) - - - - 20 - 640 - ListItem.IsCollection + String.IsEmpty(ListItem.Plot) - - - - - - - - - - !ListItem.IsCollection + String.IsEmpty(Control.GetLabel(15500)) + Control.IsVisible(15500) - - 30 - 460 - 530 - 413 - center - center - font27 - 80FFFFFF - - !ListItem.IsParentFolder - !Integer.IsGreater(Container(42000).NumItems,0) + !Integer.IsGreater(Container(43000).NumItems,0) - !Container.Content() | !String.isempty(ListItem.Plot) - - - 20 - 640 - !String.IsEmpty(ListItem.DBID) - - - - - - - - - - - - - - - - - - - - - - - + + + OpenClose_Right + 596 + Control.IsVisible(50) + Visible_Right + + + + + + + DepthContentPanel + + -20 + 634 + + + + + + + + 20 + list_y_offset + 12 + list_y_offset + 50 + 50 + vertical + conditional + + + DepthContentPopout + 38 + 36 + 120 + 124 + 200 + keep + $VAR[InfoWallThumbVar] + + + 310 + 936 + + + + + + + + + + 0 + 0 + + + $PARAM[left] + $PARAM[right] + list_y_offset + list_y_offset + 5 + 6 + 500 + vertical + $PARAM[list_id]600 + 9000 + $PARAM[list_id]600 + $PARAM[list_id] + $PARAM[list_id] + list + Container.Content(movies) | Container.Content(sets) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(games) | Window.IsActive(MyPlaylist.xml) + + + 0 + 0 + 0 + 0 + lists/focus.png + Control.HasFocus($PARAM[list_id]) + + + 70 + 70 + 0 + 0 + center + true + font27 + + text_shadow + + + 0 + 0 + 100 + 20 + right + center + font12 + + Focus + text_shadow + + + 21 + 50% + 32 + 32 + $VAR[ListWatchedIconVar] + Focus + + + 21 + 50% + 32 + 32 + $VAR[ListWatchedIconVar] + Focus + VisibleChange + !Control.HasFocus($PARAM[list_id]) + + + + + 70 + 70 + 0 + 0 + center + font27 + + text_shadow + + + 20 + 20 + 0 + 0 + right + center + font12 + + grey + text_shadow + + + 21 + 50% + 32 + 32 + $VAR[ListWatchedIconVar] + + + + + + + + false + + + DepthContentPanel + + + + + + 30 + 140 + 530 + 470 + keep + 300 + $VAR[IconWallThumbVar] + !String.IsEqual(ListItem.DbType,episode) + + + 30 + 140 + 530 + 330 + keep + 300 + $INFO[ListItem.Icon] + String.IsEqual(ListItem.DbType,episode) + + + 273 + 590 + RatingCircle + conditional + + + !Container.Content() | !String.isempty(ListItem.Plot) + 30 + + 640 + 525 + 117 + !System.HasModalDialog + Skin.HasSetting(AutoScroll) + + !String.IsEqual(ListItem.DbType,episode) + + + 485 + 525 + 96 + !System.HasModalDialog + Skin.HasSetting(AutoScroll) + + String.IsEqual(ListItem.DbType,episode) + + + + 20 + 640 + ListItem.IsCollection + String.IsEmpty(ListItem.Plot) + + + + + + + + + + !ListItem.IsCollection + String.IsEmpty(Control.GetLabel(15500)) + Control.IsVisible(15500) + + 30 + 460 + 530 + 413 + center + center + font27 + 80FFFFFF + + !ListItem.IsParentFolder + !Integer.IsGreater(Container(42000).NumItems,0) + !Integer.IsGreater(Container(43000).NumItems,0) + !Container.Content() | !String.isempty(ListItem.Plot) + + + 20 + 640 + !String.IsEmpty(ListItem.DBID) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/skin.estuary/xml/View_54_InfoWall.xml b/addons/skin.estuary/xml/View_54_InfoWall.xml index 7aa4047fef4d8..c1300199d2d56 100644 --- a/addons/skin.estuary/xml/View_54_InfoWall.xml +++ b/addons/skin.estuary/xml/View_54_InfoWall.xml @@ -1,467 +1,477 @@ - - false - - - -10 - 376 - 380 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - -14 - -4 - 384 - 388 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 20 - 10 - 336 - 300 - $VAR[InfoWallThumbVar] - keep - - - 20 - 318 - 338 - 20 - 20 - center - center - font12 - - - - - - DefaultFolder.png - $INFO[ListItem.Artist] - $INFO[ListItem.Title] - $INFO[ListItem.Label] - false - - - 0 - 316 - 386 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - -4 - -4 - 324 - 394 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 0 - 0 - 316 - 316 - $VAR[InfoWallThumbVar] - keep - 20 - - - 28 - 289 - 262 - 80 - font12 - center - center - - !String.IsEqual(ListItem.DBType,album) - - - String.IsEqual(ListItem.DBType,album) - - 29 - 300 - 260 - - font10 - text_shadow - $PARAM[focused] - center - - - 29 - 328 - 260 - - font12 - text_shadow - $PARAM[focused] - center - - - - 133 - 2 - RatingCircle - - - - - $INFO[ListItem.Title] - $INFO[ListItem.TVShowTitle] - $INFO[ListItem.Season,,x]$INFO[ListItem.Episode] - DefaultTVShows.png - false - - - 10 - 316 - 288 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - 6 - -4 - 324 - 296 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 0 - 10 - 316 - 218 - $VAR[InfoWallThumbVar] - scale - 20 - - - 20 - 138 - 276 - 70 - overlays/overlayfade.png - !ListItem.IsParentFolder - - - 31 - 178 - 260 - - font20_title - text_shadow - right - - - 20 - 175 - 32 - 32 - $VAR[WallWatchedIconVar] - - - 28 - 202 - 262 - 80 - font12 - center - center - - Window.IsActive(videos) - - - !Window.IsActive(videos) - - 29 - 210 - 260 - - font12 - text_shadow - $PARAM[focused] - center - - - 29 - 240 - 260 - - font10 - text_shadow - $PARAM[focused] - center - - - - 20 - 258 - 276 - 1 - - progress/texturebg_alt_white.png - ListItem.PercentPlayed - !Integer.IsEqual(ListItem.PercentPlayed,0) - - - 134 - 8 - RatingCircle - - - - - false - - - String.IsEmpty(ListItem.Art(poster)) - - 15 - -10 - 290 - 400 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - 15 - -10 - 290 - 400 - colors/grey.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 40 - 244 - 242 - 120 - font27 - center - center - - $PARAM[focused] - !ListItem.IsParentFolder - - - 24 - -1 - 272 - 270 - $INFO[ListItem.Icon] - scale - 20 - - - - !String.IsEmpty(ListItem.Art(poster)) - - 11 - -14 - 298 - 408 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 15 - -10 - 290 - 400 - $INFO[ListItem.Art(poster)] - scale - overlays/shadow.png - 20 - - - 35 - 290 - 80 - 80 - overlays/overlay-bg.png - Listitem.IsCollection | ListItem.IsPlaying | Integer.IsGreater(ListItem.Playcount,0) - - - - String.IsEqual(ListItem.DBtype,tvshow) - 320 - - 35 - 0 - 250 - 50 - overlays/overlayfade.png - !String.IsEmpty(ListItem.Art(poster)) - - - 0 - 20 - 244 - - font20_title - text_shadow - right - - - 254 - 23 - 24 - 24 - lists/played-total.png - - - - 35 - 338 - 32 - 32 - $VAR[WallWatchedIconVar] - - - 135 - -8 - RatingCircle - - - 35 - 350 - 250 - 1 - - progress/texturebg_alt_white.png - ListItem.PercentPlayed - !Integer.IsEqual(ListItem.PercentPlayed,0) - - - - - - OpenClose_Right - 100 - 0 - Control.IsVisible(54) - Visible_Right - - 490 - 0 - 15 - 100% - 9000 - 531 - 54 - 9000 - 54 - icon - 2 - 531 - 500 - Container.Content(artists) | Container.Content(albums) | Container.Content(sets) | Container.Content(movies) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes) | Container.Content(musicvideos) | Container.Content(images) | Container.Content(videos) | Container.Content(games) - - - 30 - 120 - InfoWallMovieLayout - - - - - DepthContentPopout - 30 - Focus - UnFocus - 120 - - - - - - - - 64 - 110 - - - - - - - - - DepthContentPopout - Focus - UnFocus - 64 - 110 - - - - - - - - - 150 - 40 - InfoWallMusicLayout - - - - - DepthContentPopout - Focus - UnFocus - 150 - 40 - - - - - - - - 0 - 150 - InfoWallPictureLayout - - - - - DepthContentPopout - 0 - 150 - Focus - UnFocus - - - - - - - - + + false + + + -10 + 376 + 380 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + -14 + -4 + 384 + 388 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 20 + 10 + 336 + 300 + $VAR[InfoWallThumbVar] + keep + + + 20 + 318 + 338 + 20 + 20 + center + center + font12 + + + + + + DefaultFolder.png + $INFO[ListItem.Artist] + $INFO[ListItem.Title] + $INFO[ListItem.Label] + false + + + 0 + 316 + 386 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + -4 + -4 + 324 + 394 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 0 + 0 + 316 + 316 + $VAR[InfoWallThumbVar] + keep + 20 + + + 10 + 10 + 40 + 40 + overlays/set.png + String.IsEqual(Listitem.IsBoxset,1) + + + 28 + 289 + 262 + 80 + font12 + center + center + + !String.IsEqual(ListItem.DBType,album) + + + String.IsEqual(ListItem.DBType,album) + + 29 + 300 + 260 + + + font10 + text_shadow + $PARAM[focused] + center + + + 29 + 328 + 260 + + font12 + text_shadow + $PARAM[focused] + center + + + + + 133 + 2 + RatingCircle + + + + + $INFO[ListItem.Title] + $INFO[ListItem.TVShowTitle] + $INFO[ListItem.Season,,x]$INFO[ListItem.Episode] + DefaultTVShows.png + false + + + 10 + 316 + 288 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + 6 + -4 + 324 + 296 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 0 + 10 + 316 + 218 + $VAR[InfoWallThumbVar] + scale + 20 + + + 20 + 138 + 276 + 70 + overlays/overlayfade.png + !ListItem.IsParentFolder + + + 31 + 178 + 260 + + font20_title + text_shadow + right + + + 20 + 175 + 32 + 32 + $VAR[WallWatchedIconVar] + + + 28 + 202 + 262 + 80 + font12 + center + center + + Window.IsActive(videos) + + + !Window.IsActive(videos) + + 29 + 210 + 260 + + font12 + text_shadow + $PARAM[focused] + center + + + 29 + 240 + 260 + + font10 + text_shadow + $PARAM[focused] + center + + + + 20 + 258 + 276 + 1 + + progress/texturebg_alt_white.png + ListItem.PercentPlayed + !Integer.IsEqual(ListItem.PercentPlayed,0) + + + 134 + 8 + RatingCircle + + + + + false + + + String.IsEmpty(ListItem.Art(poster)) + + 15 + -10 + 290 + 400 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + 15 + -10 + 290 + 400 + colors/grey.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 40 + 244 + 242 + 120 + font27 + center + center + + $PARAM[focused] + !ListItem.IsParentFolder + + + 24 + -1 + 272 + 270 + $INFO[ListItem.Icon] + scale + 20 + + + + !String.IsEmpty(ListItem.Art(poster)) + + 11 + -14 + 298 + 408 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 15 + -10 + 290 + 400 + $INFO[ListItem.Art(poster)] + scale + overlays/shadow.png + 20 + + + 35 + 290 + 80 + 80 + overlays/overlay-bg.png + Listitem.IsCollection | ListItem.IsPlaying | Integer.IsGreater(ListItem.Playcount,0) + + + + String.IsEqual(ListItem.DBtype,tvshow) + 320 + + 35 + 0 + 250 + 50 + overlays/overlayfade.png + !String.IsEmpty(ListItem.Art(poster)) + + + 0 + 20 + 244 + + font20_title + text_shadow + right + + + 254 + 23 + 24 + 24 + lists/played-total.png + + + + 35 + 338 + 32 + 32 + $VAR[WallWatchedIconVar] + + + 135 + -8 + RatingCircle + + + 35 + 350 + 250 + 1 + + progress/texturebg_alt_white.png + ListItem.PercentPlayed + !Integer.IsEqual(ListItem.PercentPlayed,0) + + + + + + OpenClose_Right + 100 + 0 + Control.IsVisible(54) + Visible_Right + + 490 + 0 + 15 + 100% + 9000 + 531 + 54 + 9000 + 54 + icon + 2 + 531 + 500 + Container.Content(artists) | Container.Content(albums) | Container.Content(sets) | Container.Content(movies) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes) | Container.Content(musicvideos) | Container.Content(images) | Container.Content(videos) | Container.Content(games) + + + 30 + 120 + InfoWallMovieLayout + + + + + DepthContentPopout + 30 + Focus + UnFocus + 120 + + + + + + + + 64 + 110 + + + + + + + + + DepthContentPopout + Focus + UnFocus + 64 + 110 + + + + + + + + + 150 + 40 + InfoWallMusicLayout + + + + + DepthContentPopout + Focus + UnFocus + 150 + 40 + + + + + + + + 0 + 150 + InfoWallPictureLayout + + + + + DepthContentPopout + 0 + 150 + Focus + UnFocus + + + + + + + + diff --git a/system/library/music/boxsets.xml b/system/library/music/boxsets.xml new file mode 100644 index 0000000000000..34eb84d826df9 --- /dev/null +++ b/system/library/music/boxsets.xml @@ -0,0 +1,6 @@ + + + + DefaultSets.png + musicdb://boxsets/ + diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 5f0fac8fdb570..dcad902b23e28 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -86,14 +86,14 @@ typedef struct /// /// Skins can use infolabels with $INFO[infolabel] or the \ tag. Scripts /// can read infolabels with xbmc.getInfoLabel('infolabel'). -/// +/// /// @todo [docs] Improve the description and create links for functions /// @todo [docs] Separate boolean conditions from infolabels /// @todo [docs] Order items alphabetically within subsections for a better search experience /// @todo [docs] Order subsections alphabetically /// @todo [docs] Use links instead of bold values for infolabels/bools /// so we can use a link to point users when providing help -/// +/// /// \page modules__infolabels_boolean_conditions @@ -139,7 +139,7 @@ typedef struct /// _boolean_, /// @return **True** if the info is empty. /// @param info - infolabel -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -153,7 +153,7 @@ typedef struct /// @return **True** if the info is equal to the given string. /// @param info - infolabel /// @param string - comparison string -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -167,7 +167,7 @@ typedef struct /// @return **True** if the info starts with the given substring. /// @param info - infolabel /// @param substring - substring to check -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -181,7 +181,7 @@ typedef struct /// @return **True** if the info ends with the given substring. /// @param info - infolabel /// @param substring - substring to check -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -195,7 +195,7 @@ typedef struct /// @return **True** if the info contains the given substring. /// @param info - infolabel /// @param substring - substring to check -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -550,7 +550,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.FilenameAndPath`, /// \anchor Player_FilenameAndPath /// _string_, -/// @return The full path with filename of the currently +/// @return The full path with filename of the currently /// playing song or movie ///

/// } @@ -583,14 +583,14 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.ChannelPreviewActive`, /// \anchor Player_ChannelPreviewActive /// _boolean_, -/// @return **True** if PVR channel preview is active (used +/// @return **True** if PVR channel preview is active (used /// channel tag different from played tag) ///

/// } /// \table_row3{ `Player.TempoEnabled`, /// \anchor Player_TempoEnabled /// _boolean_, -/// @return **True** if player supports tempo (i.e. speed up/down normal +/// @return **True** if player supports tempo (i.e. speed up/down normal /// playback speed) ///


/// @skinning_v17 **[New Boolean Condition]** \link Player_TempoEnabled `Player.TempoEnabled`\endlink @@ -608,9 +608,9 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.PlaySpeed`, /// \anchor Player_PlaySpeed /// _string_, -/// @return The player playback speed with the format `%1.2f` (1.00 means normal +/// @return The player playback speed with the format `%1.2f` (1.00 means normal /// playback speed). -/// @note For Tempo\, the default range is 0.80 - 1.50 (it can be changed +/// @note For Tempo\, the default range is 0.80 - 1.50 (it can be changed /// in advanced settings). If \ref Player_PlaySpeed "Player.PlaySpeed" returns a value different from 1.00 /// and \ref Player_IsTempo "Player.IsTempo" is false it means the player is in ff/rw mode. ///

@@ -618,7 +618,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.HasResolutions`, /// \anchor Player_HasResolutions /// _boolean_, -/// @return **True** if the player is allowed to switch resolution and refresh rate +/// @return **True** if the player is allowed to switch resolution and refresh rate /// (i.e. if whitelist modes are configured in Kodi's System/Display settings) ///


/// @skinning_v18 **[New Boolean Condition]** \link Player_HasResolutions `Player.HasResolutions`\endlink @@ -627,7 +627,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.HasPrograms`, /// \anchor Player_HasPrograms /// _boolean_, -/// @return **True** if the media file being played has programs\, i.e. groups of streams. +/// @return **True** if the media file being played has programs\, i.e. groups of streams. /// @note Ex: if a media file has multiple streams (quality\, channels\, etc) a program represents /// a particular stream combo. ///

@@ -648,7 +648,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// the icon will be returned\, if available. ///


/// @skinning_v18 **[New Infolabel]** \link Player_Icon `Player.Icon`\endlink -///

+///

/// } /// \table_row3{ `Player.Cutlist`, /// \anchor Player_Cutlist @@ -1115,7 +1115,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// _boolean_, /// @return **True** if PVR is supported from Kodi. /// @note normally always true -/// +/// /// } /// \table_row3{ `System.HasPVRAddon`, /// \anchor System_HasPVRAddon @@ -1403,7 +1403,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// \table_row3{ `System.FriendlyName`, /// \anchor System_FriendlyName /// _string_, -/// @return The Kodi instance name. +/// @return The Kodi instance name. /// @note It will auto append (%hostname%) in case /// the device name was not changed. eg. "Kodi (htpc)" ///

@@ -1575,7 +1575,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// \table_row3{ `System.GetBool(boolean)`, /// \anchor System_GetBool /// _string_, -/// @return The value of any standard system boolean setting. +/// @return The value of any standard system boolean setting. /// @note Will not work with settings in advancedsettings.xml ///

/// } @@ -2010,9 +2010,9 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// _string_, /// @return The name of the dj who remixed the selected song. /// @todo So maybe rather than a row each have one entry for Role.XXXXX with composer\, arranger etc. as listed values -/// @note MusicPlayer.Property(Role.any_custom_role) also works\, +/// @note MusicPlayer.Property(Role.any_custom_role) also works\, /// where any_custom_role could be an instrument violin or some other production activity e.g. sound engineer. -/// The roles listed (composer\, arranger etc.) are standard ones but there are many possible. +/// The roles listed (composer\, arranger etc.) are standard ones but there are many possible. /// Music file tagging allows for the musicians and all other people involved in the recording to be added\, Kodi /// will gathers and stores that data\, and it is availlable to GUI. ///


@@ -2076,7 +2076,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// _string_, /// @return Artist(s) of the song which has an offset `number` with respect /// to the start of the playlist. -/// @param number - the offset of the song with respect to +/// @param number - the offset of the song with respect to /// the start of the playlist ///

/// } @@ -2098,7 +2098,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// @return The sortname of the currently playing Artist. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Sortname `MusicPlayer.Property(Artist_Sortname)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Type)`, /// \anchor MusicPlayer_Property_Artist_Type @@ -2107,7 +2107,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// group\, orchestra\, choir etc. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Type `MusicPlayer.Property(Artist_Type)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Gender)`, /// \anchor MusicPlayer_Property_Artist_Gender @@ -2116,7 +2116,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// female\, other. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Gender `MusicPlayer.Property(Artist_Gender)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Disambiguation)`, /// \anchor MusicPlayer_Property_Artist_Disambiguation @@ -2125,7 +2125,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// from others with the same name. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Disambiguation `MusicPlayer.Property(Artist_Disambiguation)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Born)`, /// \anchor MusicPlayer_Property_Artist_Born @@ -2275,7 +2275,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// @return The scraped rating of the currently playing song (1-10). ///


/// @skinning_v17 **[New Infolabel]** \link MusicPlayer_UserRating `MusicPlayer.UserRating`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Votes`, /// \anchor MusicPlayer_Votes @@ -2414,7 +2414,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// _string_, /// @return The track number of the song with an offset `number` /// with respect to start of the playlist. -/// @param number - The offset number of the song with respect +/// @param number - The offset number of the song with respect /// to start of the playlist ///

/// } @@ -2540,6 +2540,8 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, { "samplerate", MUSICPLAYER_SAMPLERATE }, { "codec", MUSICPLAYER_CODEC }, { "discnumber", MUSICPLAYER_DISC_NUMBER }, + { "discname", MUSICPLAYER_DISC_NAME }, + { "isboxset", MUSICPLAYER_IS_BOXSET }, { "rating", MUSICPLAYER_RATING }, { "ratingandvotes", MUSICPLAYER_RATING_AND_VOTES }, { "userrating", MUSICPLAYER_USER_RATING }, @@ -3126,7 +3128,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, /// - original (Shrink to the original resolution) ///


/// @skinning_v18 **[New Infolabel]** \link RetroPlayer_StretchMode `RetroPlayer.StretchMode`\endlink -///

+///

/// } /// \table_row3{ `RetroPlayer.VideoRotation`, /// \anchor RetroPlayer_VideoRotation @@ -3140,7 +3142,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, /// - 270 (Shown in the GUI as 90 degrees) ///


/// @skinning_v18 **[New Infolabel]** \link RetroPlayer_VideoRotation `RetroPlayer.VideoRotation`\endlink -///

+///

/// } /// \table_end /// @@ -3339,14 +3341,14 @@ const infomap mediacontainer[] = {{ "hasfiles", CONTAINER_HASFILES }, /// \table_row3{ `Container(id).NumItems`, /// \anchor Container_NumItems /// _integer_, -/// @return The number of items in the container or grouplist with given id excluding parent folder item. +/// @return The number of items in the container or grouplist with given id excluding parent folder item. /// @note If no id is specified it grabs the current container. ///

/// } /// \table_row3{ `Container(id).NumAllItems`, /// \anchor Container_NumAllItems /// _integer_, -/// @return The number of all items in the container or grouplist with given id including parent folder item. +/// @return The number of all items in the container or grouplist with given id including parent folder item. /// @note If no id is specified it grabs the current container. ///


/// @skinning_v18 **[New Infolabel]** \link Container_NumAllItems `Container(id).NumAllItems`\endlink @@ -3356,7 +3358,7 @@ const infomap mediacontainer[] = {{ "hasfiles", CONTAINER_HASFILES }, /// \anchor Container_NumNonFolderItems /// _integer_, /// @return The Number of items in the container or grouplist with given id excluding all folder items. -/// @note **Example:** pvr recordings folders\, parent ".." folder). +/// @note **Example:** pvr recordings folders\, parent ".." folder). /// If no id is specified it grabs the current container. ///


/// @skinning_v18 **[New Infolabel]** \link Container_NumNonFolderItems `Container(id).NumNonFolderItems`\endlink @@ -3375,7 +3377,7 @@ const infomap mediacontainer[] = {{ "hasfiles", CONTAINER_HASFILES }, /// @return **True** if the user is currently scrolling through the container /// with id (or current container if id is omitted). /// @note This is slightly delayed from the actual scroll start. Use -/// \ref Container_OnScrollNext "Container(id).OnScrollNext" or +/// \ref Container_OnScrollNext "Container(id).OnScrollNext" or /// \ref Container_OnScrollPrevious "Container(id).OnScrollPrevious" to trigger animations /// immediately on scroll. ///

@@ -3471,7 +3473,7 @@ const infomap container_bools[] ={{ "onnext", CONTAINER_MOVE_NEXT }, /// \table_row3{ `Container(id).CurrentItem`, /// \anchor Container_CurrentItem /// _integer_, -/// @return The current item in the container or grouplist with given id. +/// @return The current item in the container or grouplist with given id. /// @note If no id is specified it grabs the current container. ///


/// @skinning_v15 **[New Infolabel]** \link Container_CurrentItem `Container(id).CurrentItem`\endlink @@ -3535,8 +3537,8 @@ const infomap container_ints[] = {{ "row", CONTAINER_ROW }, /// @return the same as \link Container_ListItem_property `Container(id).ListItem(offset).Property` \endlink /// but it won't wrap. /// @param offset - The offset for the listitem. -/// @note That means if the last item of a list is focused\, `ListItemNoWrap(1)` -/// will be empty while `ListItem(1)` will return the first item of the list. +/// @note That means if the last item of a list is focused\, `ListItemNoWrap(1)` +/// will be empty while `ListItem(1)` will return the first item of the list. /// `Property` has to be replaced with `Label`\, `Label2`\, `Icon` etc. /// @note **Example:** `Container(50).ListitemNoWrap(1).Plot` ///

@@ -3563,15 +3565,15 @@ const infomap container_ints[] = {{ "row", CONTAINER_ROW }, /// \anchor Container_Content_parameter /// _string_, /// @return **True** if the current container you are in contains the following: -/// - files -/// - songs +/// - files +/// - songs /// - artists -/// - albums +/// - albums /// - movies /// - tvshows /// - seasons -/// - episodes -/// - musicvideos +/// - episodes +/// - musicvideos /// - genres /// - years /// - actors @@ -3634,7 +3636,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.Icon`, /// \anchor ListItem_Icon /// _string_, -/// @return The thumbnail (if it exists) of the currently selected item in a list or thumb control. +/// @return The thumbnail (if it exists) of the currently selected item in a list or thumb control. /// @note If no thumbnail image exists\, it will show the icon. ///

/// } @@ -4312,9 +4314,9 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.PictureExpTime`, /// \anchor ListItem_PictureExpTime /// _string_, -/// @return The exposure time of the selected picture\, in seconds. +/// @return The exposure time of the selected picture\, in seconds. /// @note This is the value of the EXIF ExposureTime tag (hex code 0x829A). -/// If the ExposureTime tag is not found\, the ShutterSpeedValue tag (hex code 0x9201) +/// If the ExposureTime tag is not found\, the ShutterSpeedValue tag (hex code 0x9201) /// might be used. ///

/// } @@ -4337,7 +4339,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.PictureFocusDist`, /// \anchor ListItem_PictureFocusDist /// _string_, -/// @return The focal length of the lens\, in mm. +/// @return The focal length of the lens\, in mm. /// @note This is the value of the EXIF FocalLength tag (hex code 0x920A). /// } /// \table_row3{ `ListItem.PictureGPSLat`, @@ -4352,7 +4354,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureGPSLon /// _string_, /// @return The longitude where the selected picture was taken (degrees\, -/// minutes\, seconds East or West). +/// minutes\, seconds East or West). /// @note This is the value of the EXIF GPSInfo.GPSLongitude and GPSInfo.GPSLongitudeRef tags. ///

/// } @@ -4433,7 +4435,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureLongDate /// _string_, /// @return Only the localized date of the selected picture. The long form of -/// the date is used. +/// the date is used. /// @note The value of the EXIF DateTimeOriginal tag (hex code /// 0x9003) is preferred. If the DateTimeOriginal tag is not found\, the /// value of DateTimeDigitized (hex code 0x9004) or of DateTime (hex code @@ -4446,7 +4448,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureLongDatetime /// _string_, /// @return The date/timestamp of the selected picture. The localized long -/// form of the date and time is used. +/// form of the date and time is used. /// @note The value of the EXIF DateTimeOriginal /// tag (hex code 0x9003) is preferred. if the DateTimeOriginal tag is not /// found\, the value of DateTimeDigitized (hex code 0x9004) or of DateTime @@ -4460,7 +4462,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// possible values are: /// - "Center weight" /// - "Spot" -/// - "Matrix" +/// - "Matrix" /// @note This is the value of the EXIF MeteringMode tag (hex code 0x9207). ///


/// @skinning_v13 **[New Infolabel]** \link ListItem_PictureMeteringMode `ListItem.PictureMeteringMode`\endlink @@ -4479,11 +4481,11 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureOrientation /// _string_, /// @return The orientation of the selected picture. Possible values are: -/// - "Top Left" +/// - "Top Left" /// - "Top Right" /// - "Left Top" /// - "Right Bottom" -/// - etc +/// - etc /// @note This is the value of the EXIF Orientation tag (hex code 0x0112). ///


/// @skinning_v13 **[New Infolabel]** \link ListItem_PictureOrientation `ListItem.PictureOrientation`\endlink @@ -4684,8 +4686,8 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.Rating([name])`, /// \anchor ListItem_Rating /// _string_, -/// @return The scraped rating of the currently selected item in a container (1-10). -/// @param name - [opt] you can specify the name of the scraper to retrieve a specific rating\, +/// @return The scraped rating of the currently selected item in a container (1-10). +/// @param name - [opt] you can specify the name of the scraper to retrieve a specific rating\, /// for use in dialogvideoinfo.xml. ///


/// @skinning_v18 **[Infolabel Updated]** \link ListItem_Rating `ListItem.Rating([name])`\endlink replaces @@ -4701,7 +4703,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the set the movie is part of. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Set `ListItem.Set`\endlink -///

+///

/// } /// \table_row3{ `ListItem.SetId`, /// \anchor ListItem_SetId @@ -4709,7 +4711,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The id of the set the movie is part of. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_SetId `ListItem.SetId`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Status`, /// \anchor ListItem_Status @@ -4724,7 +4726,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @note For use with tv shows. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Status `ListItem.Status`\endlink -///

+///

/// } /// \table_row3{ `ListItem.EndTimeResume`, /// \anchor ListItem_EndTimeResume @@ -4732,7 +4734,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return Returns the time a video will end if you resume it\, instead of playing it from the beginning. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_EndTimeResume `ListItem.EndTimeResume`\endlink -///

+///

/// } /// \table_row3{ `ListItem.UserRating`, /// \anchor ListItem_UserRating @@ -4754,7 +4756,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v17 **[Infolabel Updated]** \link ListItem_Votes `ListItem.Votes([name])`\endlink /// add optional param name to specify the scrapper. /// @skinning_v13 **[New Infolabel]** \link ListItem_Votes `ListItem.Votes`\endlink -///

+///

/// } /// \table_row3{ `ListItem.RatingAndVotes([name])`, /// \anchor ListItem_RatingAndVotes @@ -4767,7 +4769,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v17 **[New Infolabel]** \link ListItem_RatingAndVotes `ListItem.RatingAndVotes([name])`\endlink /// @skinning_v17 **[Infolabel Updated]** \link ListItem_RatingAndVotes `ListItem.RatingAndVotes`\endlink /// now available for albums/songs. -///

+///

/// } /// \table_row3{ `ListItem.Mood`, /// \anchor ListItem_Mood @@ -4775,7 +4777,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The mood of the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Mood `ListItem.Mood`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Mpaa`, /// \anchor ListItem_Mpaa @@ -4815,7 +4817,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// _string_, /// @return The database type of the \ref ListItem_DBID "ListItem.DBID" for videos (movie\, set\, /// genre\, actor\, tvshow\, season\, episode). It does not return any value -/// for the music library. +/// for the music library. /// @note Beware with season\, the "*all seasons" entry does /// give a DBTYPE "season" and a DBID\, but you can't get the details of that /// entry since it's a virtual entry in the Video Library. @@ -4890,7 +4892,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The summary of current Video in a container. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Tag `ListItem.Tag`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Tagline`, /// \anchor ListItem_Tagline @@ -4987,7 +4989,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_VideoResolution /// _string_, /// @return The resolution of the currently selected video. Possible values: -/// - 480 +/// - 480 /// - 576 /// - 540 /// - 720 @@ -5103,7 +5105,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.Property(SubtitleLanguage.[n])`, /// \anchor ListItem_Property_SubtitleLanguage /// _string_, -/// @return The subtitle language of the currently selected video +/// @return The subtitle language of the currently selected video /// @param n - the number of the subtitle (values: see \ref ListItem_SubtitleLanguage "ListItem.SubtitleLanguage") ///


/// @skinning_v16 **[New Infolabel]** \link ListItem_Property_SubtitleLanguage `ListItem.Property(SubtitleLanguage.[n])`\endlink @@ -5524,7 +5526,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// - artist.fanart - the artist fanart of an album or song item. /// - album.thumb - the album thumb (cover) of a song item. /// - artist[n].* - in case a song has multiple artists\, a digit is added to the art type for the 2nd artist onwards -/// e.g `Listitem.Art(artist1.thumb)` gives the thumb of the 2nd artist of a song. +/// e.g `Listitem.Art(artist1.thumb)` gives the thumb of the 2nd artist of a song. /// - albumartist[n].* - n case a song has multiple album artists\, a digit is added to the art type for the 2nd artist /// onwards e.g `Listitem.Art(artist1.thumb)` gives the thumb of the 2nd artist of a song. ///

@@ -5532,7 +5534,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, ///


/// @skinning_v18 **[Infolabel Updated]** \link ListItem_Art_Type `ListItem.Art(type)`\endlink add artist[n].* and /// albumartist[n].* as possible targets for type -///

+///

/// } /// \table_row3{ `ListItem.Platform`, /// \anchor ListItem_Platform @@ -5540,7 +5542,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game platform (e.g. "Atari 2600") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Platform `ListItem.Platform`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Genres`, /// \anchor ListItem_Genres @@ -5548,7 +5550,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game genres (e.g. "["Action"\,"Strategy"]") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Genres `ListItem.Genres`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Publisher`, /// \anchor ListItem_Publisher @@ -5556,7 +5558,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game publisher (e.g. "Nintendo") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Publisher `ListItem.Publisher`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Developer`, /// \anchor ListItem_Developer @@ -5564,7 +5566,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game developer (e.g. "Square") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Developer `ListItem.Developer`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Overview`, /// \anchor ListItem_Overview @@ -5572,7 +5574,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game overview/summary (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Overview `ListItem.Overview`\endlink -///

+///

/// } /// \table_row3{ `ListItem.GameClient`, /// \anchor ListItem_GameClient @@ -5581,7 +5583,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// (e.g. game.libretro.fceumm) (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_GameClient `ListItem.GameClient`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(propname)`, /// \anchor ListItem_Property_Propname @@ -5596,7 +5598,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who composed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Composer `ListItem.Property(Role.Composer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Conductor)`, /// \anchor ListItem_Property_Role_Conductor @@ -5604,7 +5606,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who conducted the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Conductor `ListItem.Property(Role.Conductor)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Orchestra)`, /// \anchor ListItem_Property_Role_Orchestra @@ -5612,7 +5614,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the orchestra performing the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Orchestra `ListItem.Property(Role.Orchestra)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Lyricist)`, /// \anchor ListItem_Property_Role_Lyricist @@ -5620,7 +5622,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who wrote the lyrics of the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Lyricist `ListItem.Property(Role.Lyricist)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Remixer)`, /// \anchor ListItem_Property_Role_Remixer @@ -5628,7 +5630,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who remixed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Remixer `ListItem.Property(Role.Remixer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Arranger)`, /// \anchor ListItem_Property_Role_Arranger @@ -5636,7 +5638,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who arranged the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Arranger `ListItem.Property(Role.Arranger)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Engineer)`, /// \anchor ListItem_Property_Role_Engineer @@ -5644,7 +5646,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who was the engineer of the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Engineer `ListItem.Property(Role.Engineer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Producer)`, /// \anchor ListItem_Property_Role_Producer @@ -5652,7 +5654,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who produced the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Producer `ListItem.Property(Role.Producer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.DJMixer)`, /// \anchor ListItem_Property_Role_DJMixer @@ -5660,7 +5662,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the dj who remixed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_DJMixer `ListItem.Property(Role.DJMixer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Mixer)`, /// \anchor ListItem_Property_Role_Mixer @@ -5668,7 +5670,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who mixed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_DJMixer `ListItem.Property(Role.DJMixer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Game.VideoFilter)`, /// \anchor ListItem_Property_Game_VideoFilter @@ -5679,7 +5681,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// for the possible values. ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Property_Game_VideoFilter `ListItem.Property(Game.VideoFilter)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Game.StretchMode)`, /// \anchor ListItem_Property_Game_StretchMode @@ -5690,7 +5692,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// for the possible values. ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Property_Game_StretchMode `ListItem.Property(Game.StretchMode)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Game.VideoRotation)`, /// \anchor ListItem_Property_Game_VideoRotation @@ -5701,7 +5703,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// for the possible values. ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Property_Game_VideoRotation `ListItem.Property(Game.VideoRotation)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.ParentalRating`, /// \anchor ListItem_ParentalRating @@ -5735,6 +5737,8 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "genre", LISTITEM_GENRE }, { "contributors", LISTITEM_CONTRIBUTORS }, { "contributorandrole", LISTITEM_CONTRIBUTOR_AND_ROLE }, + { "discname", LISTITEM_DISC_NAME }, + { "isboxset", LISTITEM_IS_BOXSET }, { "director", LISTITEM_DIRECTOR }, { "filename", LISTITEM_FILENAME }, { "filenameandpath", LISTITEM_FILENAME_AND_PATH }, @@ -6095,7 +6099,7 @@ const infomap skin_labels[] = {{ "currenttheme", SKIN_THEME }, /// \anchor Window_IsModalDialogTopmost /// _boolean_, /// @return **True** if the dialog with id or title _dialog_ is on top of the -/// modal dialog stack +/// modal dialog stack /// @note Excludes fade out time on dialogs ///

/// } @@ -6162,7 +6166,7 @@ const infomap skin_labels[] = {{ "currenttheme", SKIN_THEME }, /// - 36Hour.%i.OutlookIcon /// - Weekend.%i.OutlookIcon /// - Hourly.%i.OutlookIcon -/// +/// /// previously the openweathermap addon would provide the full\, hardcoded path to the icon /// ie. `resource://resource.images.weathericons.default/28.png` /// to make it easier for skins to work with custom icon sets\, it now will return the filename only @@ -6769,7 +6773,7 @@ const infomap playlist[] = {{ "length", PLAYLIST_LENGTH }, /// @return The currently entered channel number while in numeric channel input mode\, an empty string otherwise. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_ChannelNumberInput `PVR.ChannelNumberInput`\endlink -///

+///

/// } /// \table_row3{ `PVR.CanRecordPlayingChannel`, /// \anchor PVR_CanRecordPlayingChannel @@ -6803,7 +6807,7 @@ const infomap playlist[] = {{ "length", PLAYLIST_LENGTH }, /// @return The percentage of the current play position within the PVR timeshift progress. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressPlayPos `PVR.TimeshiftProgressPlayPos`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressEpgStart`, /// \anchor PVR_TimeshiftProgressEpgStart @@ -6972,7 +6976,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_EpgEventRemainingTime `PVR.EpgEventRemainingTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.EpgEventRemainingTime(format)`, /// \anchor PVR_EpgEventRemainingTime_format @@ -6990,7 +6994,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_EpgEventSeekTime `PVR.EpgEventSeekTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.EpgEventSeekTime(format)`, /// \anchor PVR_EpgEventSeekTime_format @@ -7008,7 +7012,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_EpgEventFinishTime `PVR.EpgEventFinishTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.EpgEventFinishTime(format)`, /// \anchor PVR_EpgEventFinishTime_format @@ -7090,7 +7094,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressDuration `PVR.TimeshiftProgressDuration`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressDuration(format)`, /// \anchor PVR_TimeshiftProgressDuration_format @@ -7108,7 +7112,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressStartTime `PVR.TimeshiftProgressStartTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressStartTime(format)`, /// \anchor PVR_TimeshiftProgressStartTime_format @@ -7126,7 +7130,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressEndTime `PVR.TimeshiftProgressEndTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressEndTime(format)`, /// \anchor PVR_TimeshiftProgressEndTime_format @@ -7155,7 +7159,7 @@ const infomap pvr_times[] = {{ "epgeventduration", PVR_EPG_EVENT_DURA /// \page modules__infolabels_boolean_conditions /// \subsection modules__infolabels_boolean_conditions_RDS RDS /// @note Only supported if both the PVR backend and the Kodi client support RDS. -/// +/// /// \table_start /// \table_h3{ Labels, Type, Description } /// \table_row3{ `RDS.HasRds`, @@ -7696,7 +7700,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Author /// _string_, /// @return The name of the person involved in writing about the current -/// picture. +/// picture. /// @note This is the value of the IPTC Writer tag (hex code 0x7A). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Author `Slideshow.Author`\endlink @@ -7705,7 +7709,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.Byline`, /// \anchor Slideshow_Byline /// _string_, -/// @return The name of the person who created the current picture. +/// @return The name of the person who created the current picture. /// @note This is the value of the IPTC Byline tag (hex code 0x50). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Byline `Slideshow.Byline`\endlink @@ -7714,7 +7718,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.BylineTitle`, /// \anchor Slideshow_BylineTitle /// _string_, -/// @return The title of the person who created the current picture. +/// @return The title of the person who created the current picture. /// @note This is the value of the IPTC BylineTitle tag (hex code 0x55). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_BylineTitle `Slideshow.BylineTitle`\endlink @@ -7731,14 +7735,14 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_CameraModel /// _string_, /// @return The manufacturer's model name or number of the camera used to take -/// the current picture. +/// the current picture. /// @note This is the value of the EXIF Model tag (hex code 0x0110). ///

/// } /// \table_row3{ `Slideshow.Caption`, /// \anchor Slideshow_Caption /// _string_, -/// @return A description of the current picture. +/// @return A description of the current picture. /// @note This is the value of the IPTC Caption tag (hex code 0x78). ///

/// } @@ -7755,7 +7759,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_CCDWidth /// _string_, /// @return The width of the CCD in the camera used to take the current -/// picture. +/// picture. /// @note This is calculated from three EXIF tags (0xA002 * 0xA210 / 0xA20e). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_CCDWidth `Slideshow.CCDWidth`\endlink @@ -7783,7 +7787,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.CopyrightNotice`, /// \anchor Slideshow_CopyrightNotice /// _string_, -/// @return The copyright notice of the current picture. +/// @return The copyright notice of the current picture. /// @note This is the value of the IPTC Copyright tag (hex code 0x74). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_CopyrightNotice `Slideshow.CopyrightNotice`\endlink @@ -7802,7 +7806,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_CountryCode /// _string_, /// @return The country code of the country where the current picture was -/// taken. +/// taken. /// @note This is the value of the IPTC CountryCode tag (hex code 0x64). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_CountryCode `Slideshow.CountryCode`\endlink @@ -7811,7 +7815,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.Credit`, /// \anchor Slideshow_Credit /// _string_, -/// @return Who provided the current picture. +/// @return Who provided the current picture. /// @note This is the value of the IPTC Credit tag (hex code 0x6E). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Credit `Slideshow.Credit`\endlink @@ -7829,8 +7833,8 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.EXIFComment`, /// \anchor Slideshow_EXIFComment /// _string_, -/// @return A description of the current picture. -/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). +/// @return A description of the current picture. +/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). /// This is the same value as \ref Slideshow_SlideComment "Slideshow.SlideComment". ///

/// } @@ -7851,7 +7855,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_EXIFDescription /// _string_, /// @return A short description of the current picture. The SlideComment\, -/// EXIFComment or Caption values might contain a longer description. +/// EXIFComment or Caption values might contain a longer description. /// @note This is the value of the EXIF ImageDescription tag (hex code 0x010E). ///

/// } @@ -7859,7 +7863,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_EXIFSoftware /// _string_, /// @return The name and version of the firmware used by the camera that took -/// the current picture. +/// the current picture. /// @note This is the value of the EXIF Software tag (hex code 0x0131). ///

/// } @@ -7867,10 +7871,10 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_EXIFTime /// _string_, /// @return The date/timestamp of the current picture. The localized short -/// form of the date and time is used. -/// @note The value of the EXIF DateTimeOriginal tag (hex code 0x9003) is -/// preferred. If the DateTimeOriginal tag is not found\, the value of -/// DateTimeDigitized (hex code 0x9004) or of DateTime (hex code 0x0132) +/// form of the date and time is used. +/// @note The value of the EXIF DateTimeOriginal tag (hex code 0x9003) is +/// preferred. If the DateTimeOriginal tag is not found\, the value of +/// DateTimeDigitized (hex code 0x9004) or of DateTime (hex code 0x0132) /// might be used. ///

/// } @@ -7883,7 +7887,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// - "Program (Auto)" /// - "Aperture priority (Semi-Auto)" /// - "Shutter priority (semi-auto)" -/// - etc... +/// - etc... /// @note This is the value of the EXIF ExposureProgram tag /// (hex code 0x8822). ///


@@ -7950,7 +7954,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.FocalLength`, /// \anchor Slideshow_FocalLength /// _string_, -/// @return The focal length of the lens\, in mm. +/// @return The focal length of the lens\, in mm. /// @note This is the value of the EXIF FocalLength tag (hex code 0x920A). ///

/// } @@ -8005,8 +8009,8 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Latitude /// _string_, /// @return The latitude where the current picture was taken (degrees\, -/// minutes\, seconds North or South). -/// @note This is the value of the EXIF GPSInfo.GPSLatitude and +/// minutes\, seconds North or South). +/// @note This is the value of the EXIF GPSInfo.GPSLatitude and /// GPSInfo.GPSLatitudeRef tags. ///

/// } @@ -8028,7 +8032,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_LongEXIFDate /// _string_, /// @return Only the localized date of the current picture. The long form of -/// the date is used. +/// the date is used. /// @note The value of the EXIF DateTimeOriginal tag (hex code /// 0x9003) is preferred. If the DateTimeOriginal tag is not found\, the /// value of DateTimeDigitized (hex code 0x9004) or of DateTime (hex code @@ -8055,7 +8059,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// _string_, /// @return The longitude where the current picture was taken (degrees\, /// minutes\, seconds East or West). -/// @note This is the value of the EXIF GPSInfo.GPSLongitude and +/// @note This is the value of the EXIF GPSInfo.GPSLongitude and /// GPSInfo.GPSLongitudeRef tags. ///

/// } @@ -8128,8 +8132,8 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.SlideComment`, /// \anchor Slideshow_SlideComment /// _string_, -/// @return A description of the current picture. -/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). +/// @return A description of the current picture. +/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). /// This is the same value as \ref Slideshow_EXIFComment "Slideshow.EXIFComment". ///

/// } @@ -8171,7 +8175,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Sublocation /// _string_, /// @return The location within a city where the current picture was taken - -/// might indicate the nearest landmark. +/// might indicate the nearest landmark. /// @note This is the value of the IPTC SubLocation tag (hex code 0x5C). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Sublocation `Slideshow.Sublocation`\endlink @@ -8181,7 +8185,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_SupplementalCategories /// _string_, /// @return The supplemental category codes to further refine the subject of the -/// current picture. +/// current picture. /// @note This is the value of the IPTC SuppCategory tag (hex /// code 0x14). ///


@@ -8192,7 +8196,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_TimeCreated /// _string_, /// @return The time when the intellectual content of the current picture was -/// created\, rather than when the picture was created. +/// created\, rather than when the picture was created. /// @note This is the value of the IPTC TimeCreated tag (hex code 0x3C). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_TimeCreated `Slideshow.TimeCreated`\endlink @@ -8202,7 +8206,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_TransmissionReference /// _string_, /// @return A code representing the location of original transmission of the -/// current picture. +/// current picture. /// @note This is the value of the IPTC TransmissionReference tag /// (hex code 0x67). ///


@@ -8213,7 +8217,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Urgency /// _string_, /// @return The urgency of the current picture. Values are 1-9. The 1 is most -/// urgent. +/// urgent. /// @note Some image management programs use urgency to indicate picture /// rating\, where urgency 1 is 5 stars and urgency 5 is 1 star. Urgencies /// 6-9 are not used for rating. This is the value of the IPTC Urgency tag @@ -8466,7 +8470,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// \subsection modules_rm_infolabels_booleans_v18 Kodi v18 (Leia) /// /// @skinning_v18 **[Removed Infolabels]** The following infolabels have been removed: -/// - `Listitem.Property(artistthumbs)`, `Listitem.Property(artistthumb)` - use +/// - `Listitem.Property(artistthumbs)`, `Listitem.Property(artistthumb)` - use /// \link ListItem_Art_Type `ListItem.Art(type)`\endlink with albumartist[n].* or artist[n].* as type /// - `ADSP.ActiveStreamType` /// - `ADSP.DetectedStreamType` @@ -8474,10 +8478,10 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ADSP.MasterInfo` /// - `ADSP.MasterOwnIcon` /// - `ADSP.MasterOverrideIcon` -/// - `ListItem.ChannelNumber`, `ListItem.SubChannelNumber`, `MusicPlayer.ChannelNumber`, +/// - `ListItem.ChannelNumber`, `ListItem.SubChannelNumber`, `MusicPlayer.ChannelNumber`, /// `MusicPlayer.SubChannelNumber`, `VideoPlayer.ChannelNumber`, /// `VideoPlayer.SubChannelNumber`. Please use the following alternatives -/// \link ListItem_ChannelNumberLabel `ListItem.ChannelNumberLabel` \endlink, +/// \link ListItem_ChannelNumberLabel `ListItem.ChannelNumberLabel` \endlink, /// \link MusicPlayer_ChannelNumberLabel `MusicPlayer.ChannelNumberLabel` \endlink /// \link VideoPlayer_ChannelNumberLabel `VideoPlayer.ChannelNumberLabel` \endlink from now on. /// @@ -8518,7 +8522,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ADSP.HasOutputResample` /// - `ADSP.MasterActive` /// - `System.HasModalDialog` -/// +/// /// @skinning_v16 **[New Infolabels]** The following infolabels were added: /// - `ADSP.ActiveStreamType` /// - `ADSP.DetectedStreamType` @@ -8526,7 +8530,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ADSP.MasterInfo` /// - `ADSP.MasterOwnIcon` /// - `ADSP.MasterOverrideIcon` -/// +/// /// @skinning_v16 **[Removed Boolean Conditions]** The following infobols were removed: /// - `System.Platform.ATV2` @@ -8538,12 +8542,12 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ListItem.SubChannelNumber` /// - `MusicPlayer.SubChannelNumber` /// - `VideoPlayer.SubChannelNumber` -/// +/// ///
/// \subsection modules_rm_infolabels_booleans_v13 XBMC v13 (Gotham) /// @skinning_v13 **[Removed Infolabels]** The following infolabels were removed: /// - `Network.SubnetAddress` -/// +/// ///
// Crazy part, to use tableofcontents must it be on end /// \page modules__infolabels_boolean_conditions diff --git a/xbmc/filesystem/MusicDatabaseDirectory.cpp b/xbmc/filesystem/MusicDatabaseDirectory.cpp index 68f32530df559..fc539730dd294 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory.cpp @@ -121,7 +121,6 @@ bool CMusicDatabaseDirectory::IsAllItem(const std::string& strDirectory) bool CMusicDatabaseDirectory::GetLabel(const std::string& strDirectory, std::string& strLabel) { strLabel = ""; - std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strDirectory); std::unique_ptr pNode(CDirectoryNode::ParseURL(path)); if (!pNode.get()) @@ -207,6 +206,9 @@ bool CMusicDatabaseDirectory::GetLabel(const std::string& strDirectory, std::str case NODE_TYPE_ALBUM_COMPILATIONS_SONGS: strLabel = g_localizeStrings.Get(521); break; + case NODE_TYPE_BOXSETS: + strLabel = g_localizeStrings.Get(38074); // box-sets + break; case NODE_TYPE_OVERVIEW: strLabel = ""; break; @@ -214,7 +216,6 @@ bool CMusicDatabaseDirectory::GetLabel(const std::string& strDirectory, std::str return false; } } - return true; } @@ -229,6 +230,7 @@ bool CMusicDatabaseDirectory::ContainsSongs(const std::string &path) if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_TOP100_SONGS) return true; if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_SONG_TOP100) return true; if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_SONG) return true; + if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_BOXSET_DISC_SONGS) return true; return false; } @@ -290,6 +292,8 @@ std::string CMusicDatabaseDirectory::GetIcon(const std::string &strDirectory) return "DefaultMusicTop100Songs.png"; case NODE_TYPE_YEAR: return "DefaultMusicYears.png"; + case NODE_TYPE_BOXSETS: + return "DefaultSets.png"; case NODE_TYPE_ALBUM_COMPILATIONS: return "DefaultMusicCompilations.png"; default: diff --git a/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt b/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt index 6de6b45ab1625..8844871380d66 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt +++ b/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt @@ -8,6 +8,9 @@ set(SOURCES DirectoryNodeAlbumCompilations.cpp DirectoryNodeAlbumTop100.cpp DirectoryNodeAlbumTop100Song.cpp DirectoryNodeArtist.cpp + DirectoryNodeBoxsets.cpp + DirectoryNodeBoxsetDiscs.cpp + DirectoryNodeBoxsetDiscSongs.cpp DirectoryNode.cpp DirectoryNodeGrouped.cpp DirectoryNodeOverview.cpp @@ -31,6 +34,9 @@ set(HEADERS DirectoryNode.h DirectoryNodeAlbumTop100.h DirectoryNodeAlbumTop100Song.h DirectoryNodeArtist.h + DirectoryNodeBoxsets.h + DirectoryNodeBoxsetDiscs.h + DirectoryNodeBoxsetDiscSongs.h DirectoryNodeGrouped.h DirectoryNodeOverview.h DirectoryNodeRoot.h diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp index 73224c89fea32..6eb18b40c8add 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp @@ -18,6 +18,9 @@ #include "DirectoryNodeAlbumTop100.h" #include "DirectoryNodeAlbumTop100Song.h" #include "DirectoryNodeArtist.h" +#include "DirectoryNodeBoxsets.h" +#include "DirectoryNodeBoxsetDiscs.h" +#include "DirectoryNodeBoxsetDiscSongs.h" #include "DirectoryNodeGrouped.h" #include "DirectoryNodeOverview.h" #include "DirectoryNodeRoot.h" @@ -32,6 +35,7 @@ #include "URL.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" +#include "utils/log.h" using namespace XFILE::MUSICDATABASEDIRECTORY; @@ -91,6 +95,7 @@ void CDirectoryNode::GetDatabaseInfo(const std::string& strPath, CQueryParams& p // Create a node object CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent) { +// CLog::Log(LOGNOTICE, "Creating a node of value %i", Type); switch (Type) { case NODE_TYPE_ROOT: @@ -102,6 +107,12 @@ CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const std::string& st case NODE_TYPE_ROLE: case NODE_TYPE_YEAR: return new CDirectoryNodeGrouped(Type, strName, pParent); + case NODE_TYPE_BOXSETS: + return new CDirectoryNodeBoxsets(strName, pParent); + case NODE_TYPE_BOXSET_DISCS: + return new CDirectoryNodeBoxsetDiscs(strName, pParent); + case NODE_TYPE_BOXSET_DISC_SONGS: + return new CDirectoryNodeBoxsetDiscSongs(strName, pParent); case NODE_TYPE_ARTIST: return new CDirectoryNodeArtist(strName, pParent); case NODE_TYPE_ALBUM: diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h index 016aef49a5b6f..a4aad738fd1a2 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h @@ -42,7 +42,10 @@ namespace XFILE NODE_TYPE_YEAR, NODE_TYPE_YEAR_ALBUM, NODE_TYPE_YEAR_SONG, - NODE_TYPE_SINGLES + NODE_TYPE_SINGLES, + NODE_TYPE_BOXSETS, //23 + NODE_TYPE_BOXSET_DISCS, + NODE_TYPE_BOXSET_DISC_SONGS } NODE_TYPE; typedef struct { @@ -90,5 +93,3 @@ namespace XFILE }; } } - - diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.cpp new file mode 100644 index 0000000000000..9828ba3372050 --- /dev/null +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2018 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. + */ + +#include "DirectoryNodeBoxsetDiscSongs.h" + +#include "QueryParams.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" + +using namespace XFILE::MUSICDATABASEDIRECTORY; + +CDirectoryNodeBoxsetDiscSongs::CDirectoryNodeBoxsetDiscSongs(const std::string& strName, CDirectoryNode* pParent) + : CDirectoryNode(NODE_TYPE_BOXSET_DISC_SONGS, strName, pParent) +{ + +} + +NODE_TYPE CDirectoryNodeBoxsetDiscSongs::GetChildType() const +{ + return NODE_TYPE_BOXSET_DISC_SONGS; +} +std::string CDirectoryNodeBoxsetDiscSongs::GetLocalizedName() const +{ + + if (GetID() == -1) + return g_localizeStrings.Get(15102); // All Albums + return ""; +} + +bool CDirectoryNodeBoxsetDiscSongs::GetContent(CFileItemList& items) const +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return false; + + CQueryParams params; + CollectQueryParams(params); + + bool bSuccess=musicdatabase.GetBoxsetDiscSongs(BuildPath(), items); + + musicdatabase.Close(); + + return bSuccess; +} diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.h new file mode 100644 index 0000000000000..f196fab3e4d29 --- /dev/null +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscSongs.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2018 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 + +#include "DirectoryNode.h" + +namespace XFILE +{ + namespace MUSICDATABASEDIRECTORY + { + class CDirectoryNodeBoxsetDiscSongs : public CDirectoryNode + { + public: + CDirectoryNodeBoxsetDiscSongs(const std::string& strName, CDirectoryNode* pParent); + protected: + NODE_TYPE GetChildType() const override; + bool GetContent(CFileItemList& items) const override; + std::string GetLocalizedName() const override; + }; + } +} diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.cpp new file mode 100644 index 0000000000000..48b0535f3b3c4 --- /dev/null +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2018 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. + */ + +#include "DirectoryNodeBoxsetDiscs.h" + +#include "QueryParams.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" + +using namespace XFILE::MUSICDATABASEDIRECTORY; + +CDirectoryNodeBoxsetDiscs::CDirectoryNodeBoxsetDiscs(const std::string& strName, CDirectoryNode* pParent) + : CDirectoryNode(NODE_TYPE_BOXSET_DISCS, strName, pParent) +{ + +} + +NODE_TYPE CDirectoryNodeBoxsetDiscs::GetChildType() const +{ + return NODE_TYPE_BOXSET_DISC_SONGS; +} + +std::string CDirectoryNodeBoxsetDiscs::GetLocalizedName() const +{ + + if (GetID() == -1) + return g_localizeStrings.Get(15102); // All Albums + return ""; +} + +bool CDirectoryNodeBoxsetDiscs::GetContent(CFileItemList& items) const +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return false; + + CQueryParams params; + CollectQueryParams(params); + + bool bSuccess=musicdatabase.GetBoxsetDiscs(BuildPath(), items, params.GetAlbumId()); + + musicdatabase.Close(); + + return bSuccess; +} diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.h new file mode 100644 index 0000000000000..4ad43c800cbb2 --- /dev/null +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsetDiscs.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2018 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 + +#include "DirectoryNode.h" + +namespace XFILE +{ + namespace MUSICDATABASEDIRECTORY + { + class CDirectoryNodeBoxsetDiscs : public CDirectoryNode + { + public: + CDirectoryNodeBoxsetDiscs(const std::string& strName, CDirectoryNode* pParent); + protected: + NODE_TYPE GetChildType() const override; + bool GetContent(CFileItemList& items) const override; + std::string GetLocalizedName() const override; + }; + } +} diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.cpp new file mode 100644 index 0000000000000..b768ab0b8b0ac --- /dev/null +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2018 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. + */ + +#include "DirectoryNodeBoxsets.h" + +#include "QueryParams.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" + +using namespace XFILE::MUSICDATABASEDIRECTORY; + +CDirectoryNodeBoxsets::CDirectoryNodeBoxsets(const std::string& strName, CDirectoryNode* pParent) + : CDirectoryNode(NODE_TYPE_BOXSETS, strName, pParent) +{ + +} + +NODE_TYPE CDirectoryNodeBoxsets::GetChildType() const +{ + return NODE_TYPE_BOXSET_DISCS; +} +std::string CDirectoryNodeBoxsets::GetLocalizedName() const +{ + if (GetID() == -1) + return g_localizeStrings.Get(15102); // All Albums + CMusicDatabase db; + if (db.Open()) + return db.GetAlbumById(GetID()); + return ""; +} + +bool CDirectoryNodeBoxsets::GetContent(CFileItemList& items) const +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return false; + + CQueryParams params; + CollectQueryParams(params); + + bool bSuccess=musicdatabase.GetBoxsetsAlbums(BuildPath(), items); + + musicdatabase.Close(); + + return bSuccess; +} diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.h new file mode 100644 index 0000000000000..3d929c4abde83 --- /dev/null +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeBoxsets.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2018 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 + +#include "DirectoryNode.h" + +namespace XFILE +{ + namespace MUSICDATABASEDIRECTORY + { + class CDirectoryNodeBoxsets : public CDirectoryNode + { + public: + CDirectoryNodeBoxsets(const std::string& strName, CDirectoryNode* pParent); + protected: + NODE_TYPE GetChildType() const override; + bool GetContent(CFileItemList& items) const override; + std::string GetLocalizedName() const override; + }; + } +} diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp index 25072e426e9a9..4732a8f43db13 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp @@ -30,7 +30,8 @@ namespace XFILE { NODE_TYPE_ALBUM_COMPILATIONS, "compilations", 521 }, { NODE_TYPE_ROLE, "roles", 38033 }, { NODE_TYPE_SOURCE, "sources", 39031 }, - }; + { NODE_TYPE_BOXSETS, "boxsets", 38074 }, + }; }; }; @@ -65,13 +66,15 @@ bool CDirectoryNodeOverview::GetContent(CFileItemList& items) const bool hasSingles = (musicDatabase.GetSinglesCount() > 0); bool hasCompilations = (musicDatabase.GetCompilationAlbumsCount() > 0); - + bool hasBoxsets = (musicDatabase.GetBoxsetsCount() > 0); for (unsigned int i = 0; i < sizeof(OverviewChildren) / sizeof(Node); ++i) { if (i == 3 && !hasSingles) continue; if (i == 9 && !hasCompilations) continue; + if (i == 12 && !hasBoxsets) + continue; CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(OverviewChildren[i].label))); std::string strDir = StringUtils::Format("%s/", OverviewChildren[i].id.c_str()); diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp index 4c43f907721ba..8de9bc4878fa7 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp @@ -10,6 +10,7 @@ #include "QueryParams.h" #include "music/MusicDatabase.h" +#include "utils/log.h" using namespace XFILE::MUSICDATABASEDIRECTORY; diff --git a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp index 6c991863bfd6f..79a03a774e089 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp @@ -19,6 +19,8 @@ CQueryParams::CQueryParams() m_idGenre=-1; m_idSong=-1; m_year=-1; + m_disc=-1; + } void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeName) @@ -36,12 +38,16 @@ void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeN case NODE_TYPE_ARTIST: m_idArtist=idDb; break; + case NODE_TYPE_BOXSET_DISCS: + m_disc=idDb; + break; case NODE_TYPE_ALBUM_RECENTLY_PLAYED: case NODE_TYPE_ALBUM_RECENTLY_ADDED: case NODE_TYPE_ALBUM_COMPILATIONS: case NODE_TYPE_ALBUM_TOP100: case NODE_TYPE_ALBUM: case NODE_TYPE_YEAR_ALBUM: + case NODE_TYPE_BOXSETS: m_idAlbum=idDb; break; case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS: @@ -51,6 +57,7 @@ void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeN case NODE_TYPE_YEAR_SONG: case NODE_TYPE_SONG: case NODE_TYPE_SONG_TOP100: + case NODE_TYPE_BOXSET_DISC_SONGS: m_idSong=idDb; default: break; diff --git a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h index 75f95986dcfde..0fb0187c6b159 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h +++ b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h @@ -23,7 +23,7 @@ namespace XFILE long GetGenreId() { return m_idGenre; } long GetSongId() { return m_idSong; } long GetYear() { return m_year; } - + long GetDisc() { return m_idAlbum; } protected: void SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeName); @@ -34,8 +34,8 @@ namespace XFILE long m_idGenre; long m_idSong; long m_year; + long m_disc; +// long m_discNo <- temp index generated by MusicDatabase::GetBoxsetDiscs() }; } } - - diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index de027949b97bb..6d85ad3efc47b 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -194,7 +194,8 @@ #define MUSICPLAYER_CONTRIBUTOR_AND_ROLE 240 #define MUSICPLAYER_DBID 241 #define MUSICPLAYER_PROPERTY 242 - +#define MUSICPLAYER_DISC_NAME 243 +#define MUSICPLAYER_IS_BOXSET 244 #define VIDEOPLAYER_AUDIO_BITRATE 248 #define VIDEOPLAYER_VIDEO_BITRATE 249 #define VIDEOPLAYER_TITLE 250 @@ -877,9 +878,7 @@ #define LISTITEM_ISPLAYABLE (LISTITEM_START + 186) #define LISTITEM_FILENAME_NO_EXTENSION (LISTITEM_START + 187) #define LISTITEM_CURRENTITEM (LISTITEM_START + 188) - #define LISTITEM_END (LISTITEM_START + 2500) - #define CONDITIONAL_LABEL_START (LISTITEM_END + 1) // 37501 #define CONDITIONAL_LABEL_END 39999 diff --git a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp index 84b61babe412f..a2675c88a5a4b 100644 --- a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp @@ -142,6 +142,10 @@ bool CMusicGUIInfo::GetLabel(std::string& value, const CFileItem *item, int cont return true; } break; + case MUSICPLAYER_DISC_NAME: + case LISTITEM_DISC_NAME: + value = tag->GetDiscSubtitle(); + return true; case MUSICPLAYER_ARTIST: case LISTITEM_ARTIST: value = tag->GetArtistString(); diff --git a/xbmc/music/Album.cpp b/xbmc/music/Album.cpp index 9e9c1cc876415..f720d3e949ce4 100644 --- a/xbmc/music/Album.cpp +++ b/xbmc/music/Album.cpp @@ -54,6 +54,8 @@ CAlbum::CAlbum(const CFileItem& item) strType = tag.GetMusicBrainzReleaseType(); bCompilation = tag.GetCompilation(); iTimesPlayed = 0; + bBoxedSet = tag.GetBoxset(); + dateAdded.Reset(); lastPlayed.Reset(); releaseType = tag.GetAlbumReleaseType(); @@ -292,6 +294,8 @@ void CAlbum::MergeScrapedAlbum(const CAlbum& source, bool override /* = true */) if (override) bCompilation = source.bCompilation; // iTimesPlayed = source.iTimesPlayed; // times played is derived from songs + if (override) + bBoxedSet = source.bBoxedSet; if ((override && !source.strArtistSort.empty()) || strArtistSort.empty()) strArtistSort = source.strArtistSort; @@ -480,6 +484,7 @@ bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise) XMLUtils::GetStringArray(album, "mood", moods, prioritise, itemSeparator); XMLUtils::GetStringArray(album, "theme", themes, prioritise, itemSeparator); XMLUtils::GetBoolean(album, "compilation", bCompilation); + XMLUtils::GetBoolean(album, "boxset", bBoxedSet); XMLUtils::GetString(album,"review",strReview); XMLUtils::GetString(album,"releasedate",m_strDateOfRelease); @@ -594,6 +599,7 @@ bool CAlbum::Save(TiXmlNode *node, const std::string &tag, const std::string& st XMLUtils::SetStringArray(album, "mood", moods); XMLUtils::SetStringArray(album, "theme", themes); XMLUtils::SetBoolean(album, "compilation", bCompilation); + XMLUtils::SetBoolean(album, "boxset", bBoxedSet); XMLUtils::SetString(album, "review", strReview); XMLUtils::SetString(album, "type", strType); @@ -637,4 +643,3 @@ bool CAlbum::Save(TiXmlNode *node, const std::string &tag, const std::string& st return true; } - diff --git a/xbmc/music/Album.h b/xbmc/music/Album.h index 875addf26d318..35bb20eab3b4c 100644 --- a/xbmc/music/Album.h +++ b/xbmc/music/Album.h @@ -55,6 +55,7 @@ class CAlbum iUserrating = -1; iVotes = -1; iYear = -1; + bBoxedSet = false; bCompilation = false; iTimesPlayed = 0; dateAdded.Reset(); @@ -155,6 +156,7 @@ class CAlbum int iUserrating = -1; int iVotes = -1; int iYear = -1; + bool bBoxedSet = false; bool bCompilation = false; int iTimesPlayed = 0; CDateTime dateAdded; diff --git a/xbmc/music/GUIViewStateMusic.cpp b/xbmc/music/GUIViewStateMusic.cpp index 67b89a0ec5a98..32f00443e57d5 100644 --- a/xbmc/music/GUIViewStateMusic.cpp +++ b/xbmc/music/GUIViewStateMusic.cpp @@ -138,6 +138,16 @@ CGUIViewStateMusicDatabase::CGUIViewStateMusicDatabase(const CFileItemList& item SetSortOrder(SortOrderNone); } break; + //case NODE_TYPE_BOXSETS: + //{ + //AddSortMethod(SortByNone, 38074, LABEL_MASKS("%F", "", "%G", "")); // Filename, empty | Genre, empty + //SetSortMethod(SortByPlaycount); + + //SetViewAsControl(DEFAULT_VIEW_LIST); + + //SetSortOrder(SortOrderNone); + //} + //break; case NODE_TYPE_YEAR: { AddSortMethod(SortByLabel, 562, LABEL_MASKS("%F", "", "%Y", "")); // Filename, empty | Year, empty @@ -162,6 +172,8 @@ CGUIViewStateMusicDatabase::CGUIViewStateMusicDatabase(const CFileItemList& item case NODE_TYPE_ALBUM_COMPILATIONS: case NODE_TYPE_ALBUM: case NODE_TYPE_YEAR_ALBUM: + case NODE_TYPE_BOXSETS: +// case NODE_TYPE_BOXSET_DISCS: { // album AddSortMethod(SortByAlbum, sortAttribute, 558, LABEL_MASKS("%F", "", strAlbum, "%A")); // Filename, empty | Userdefined (default=%B), Artist @@ -255,6 +267,7 @@ CGUIViewStateMusicDatabase::CGUIViewStateMusicDatabase(const CFileItemList& item case NODE_TYPE_ALBUM_TOP100_SONGS: case NODE_TYPE_YEAR_SONG: case NODE_TYPE_SONG: + case NODE_TYPE_BOXSET_DISC_SONGS: { AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS(strTrack, "%D")); // Userdefined, Duration| empty, empty AddSortMethod(SortByTitle, sortAttribute, 556, LABEL_MASKS("%T - %A", "%D")); // Title, Artist, Duration| empty, empty diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 4e6d4d5a7540d..bf68f452aeb18 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -8,6 +8,7 @@ #include "MusicDatabase.h" +#include #include "Album.h" #include "Application.h" #include "Artist.h" @@ -142,6 +143,7 @@ void CMusicDatabase::CreateTables() " strReleaseGroupMBID text, " " strArtistDisp text, strArtistSort text, strGenres text, " " iYear integer, " + " bBoxedSet INTEGER NOT NULL DEFAULT 0, " " bCompilation integer not null default '0', " " strMoods text, strStyles text, strThemes text, " " strReview text, strImage text, strLabel text, " @@ -183,7 +185,7 @@ void CMusicDatabase::CreateTables() " idAlbum integer, idPath integer, " " strArtistDisp text, strArtistSort text, strGenres text, strTitle varchar(512), " " iTrack integer, iDuration integer, iYear integer, " - " strFileName text, strMusicBrainzTrackID text, " + " strDiscSubtitle text, strFileName text, strMusicBrainzTrackID text, " " iTimesPlayed integer, iStartOffset integer, iEndOffset integer, " " lastplayed varchar(20) default NULL, " " rating FLOAT NOT NULL DEFAULT 0, votes INTEGER NOT NULL DEFAULT 0, " @@ -210,6 +212,7 @@ void CMusicDatabase::CreateTables() CLog::Log(LOGINFO, "create versiontagscan table"); m_pDS->exec("CREATE TABLE versiontagscan (idVersion INTEGER, iNeedsScan INTEGER, lastscanned VARCHAR(20))"); m_pDS->exec(PrepareSQL("INSERT INTO versiontagscan (idVersion, iNeedsScan) values(%i, 0)", GetSchemaVersion())); + } void CMusicDatabase::CreateAnalytics() @@ -283,7 +286,7 @@ void CMusicDatabase::CreateAnalytics() " DELETE FROM source_path WHERE source_path.idSource = old.idSource;" " DELETE FROM album_source WHERE album_source.idSource = old.idSource;" " END"); - + // we create views last to ensure all indexes are rolled in CreateViews(); @@ -300,6 +303,7 @@ void CMusicDatabase::CreateViews() " strTitle, " " iTrack, iDuration, " " song.iYear AS iYear, " + " song.strDiscSubtitle as strDiscSubtitle, " " strFileName, " " strMusicBrainzTrackID, " " iTimesPlayed, iStartOffset, iEndOffset, " @@ -312,6 +316,7 @@ void CMusicDatabase::CreateViews() " strAlbum, " " strPath, " " album.bCompilation AS bCompilation," + " album.bBoxedSet AS bBoxedSet, " " album.strArtistDisp AS strAlbumArtists," " album.strArtistSort AS strAlbumArtistSort," " album.strReleaseType AS strAlbumReleaseType," @@ -334,6 +339,7 @@ void CMusicDatabase::CreateViews() " album.strArtistSort AS strArtistSort, " " album.strGenres AS strGenres, " " album.iYear AS iYear, " + " album.bBoxedSet AS bBoxedSet, " " album.strMoods AS strMoods, " " album.strStyles AS strStyles, " " strThemes, " @@ -423,6 +429,7 @@ bool CMusicDatabase::AddAlbum(CAlbum& album, int idSource) album.GetAlbumArtistSort(), album.GetGenreString(), album.iYear, + album.bBoxedSet, album.strLabel, album.strType, album.bCompilation, album.releaseType); @@ -450,6 +457,7 @@ bool CMusicDatabase::AddAlbum(CAlbum& album, int idSource) song->GetArtistSort(), song->genre, song->iTrack, song->iDuration, song->iYear, + song->strDiscSubtitle, song->iTimesPlayed, song->iStartOffset, song->iEndOffset, song->lastPlayed, @@ -511,7 +519,8 @@ bool CMusicDatabase::UpdateAlbum(CAlbum& album) album.strReview, album.thumbURL.m_xml.c_str(), album.strLabel, album.strType, - album.fRating, album.iUserrating, album.iVotes, album.iYear, album.bCompilation, album.releaseType, + album.fRating, album.iUserrating, album.iVotes, album.iYear, album.bBoxedSet, + album.bCompilation, album.releaseType, album.bScrapedMBID); if (!album.bArtistSongMerge) @@ -557,6 +566,7 @@ int CMusicDatabase::AddSong(const int idAlbum, const std::string &artistDisp, const std::string &artistSort, const std::vector& genres, int iTrack, int iDuration, int iYear, + const std::string& strDiscSubtitle, const int iTimesPlayed, int iStartOffset, int iEndOffset, const CDateTime& dtLastPlayed, float rating, int userrating, int votes, const ReplayGain& replayGain) @@ -596,16 +606,17 @@ int CMusicDatabase::AddSong(const int idAlbum, m_pDS->close(); strSQL=PrepareSQL("INSERT INTO song (" "idSong,idAlbum,idPath,strArtistDisp," - "strTitle,iTrack,iDuration,iYear,strFileName," + "strTitle,iTrack,iDuration,iYear, strDiscSubtitle, strFileName," "strMusicBrainzTrackID, strArtistSort, " "iTimesPlayed,iStartOffset, " "iEndOffset,lastplayed,rating,userrating,votes,comment,mood,strReplayGain" - ") values (NULL, %i, %i, '%s', '%s', %i, %i, %i, '%s'", + ") values (NULL, %i, %i, '%s', '%s', %i, %i, %i,'%s', '%s'", idAlbum, idPath, artistDisp.c_str(), strTitle.c_str(), iTrack, iDuration, iYear, + strDiscSubtitle.c_str(), strFileName.c_str()); if (strMusicBrainzTrackID.empty()) @@ -632,10 +643,9 @@ int CMusicDatabase::AddSong(const int idAlbum, idSong = m_pDS->fv("idSong").get_asInt(); m_pDS->close(); UpdateSong( idSong, strTitle, strMusicBrainzTrackID, strPathAndFileName, strComment, strMood, strThumb, - artistDisp, artistSort, genres, iTrack, iDuration, iYear, iTimesPlayed, iStartOffset, iEndOffset, + artistDisp, artistSort, genres, iTrack, iDuration, iYear, strDiscSubtitle, iTimesPlayed, iStartOffset, iEndOffset, dtLastPlayed, rating, userrating, votes, replayGain); } - if (!strThumb.empty()) SetArtForItem(idSong, MediaTypeSong, "thumb", strThumb); @@ -716,6 +726,7 @@ bool CMusicDatabase::UpdateSong(CSong& song, bool bArtists /*= true*/) song.iTrack, song.iDuration, song.iYear, + song.strDiscSubtitle, song.iTimesPlayed, song.iStartOffset, song.iEndOffset, @@ -760,6 +771,7 @@ int CMusicDatabase::UpdateSong(int idSong, const std::string &artistDisp, const std::string &artistSort, const std::vector& genres, int iTrack, int iDuration, int iYear, + const std::string& strDiscSubtitle, int iTimesPlayed, int iStartOffset, int iEndOffset, const CDateTime& dtLastPlayed, float rating, int userrating, int votes, const ReplayGain& replayGain) @@ -810,9 +822,9 @@ int CMusicDatabase::UpdateSong(int idSong, int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& strMusicBrainzAlbumID, const std::string& strReleaseGroupMBID, const std::string& strArtist, const std::string& strArtistSort, - const std::string& strGenre, int year, + const std::string& strGenre, int year, bool bBoxedSet, const std::string& strRecordLabel, const std::string& strType, - bool bCompilation, CAlbum::ReleaseType releaseType) + bool bCompilation, CAlbum::ReleaseType releaseType ) { std::string strSQL; try @@ -833,13 +845,14 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str { m_pDS->close(); // doesnt exists, add it - strSQL = PrepareSQL("INSERT INTO album (idAlbum, strAlbum, strArtistDisp, strGenres, iYear, " + strSQL = PrepareSQL("INSERT INTO album (idAlbum, strAlbum, strArtistDisp, strGenres, iYear, bBoxedSet, " "strLabel, strType, bCompilation, strReleaseType, strMusicBrainzAlbumID, strReleaseGroupMBID, strArtistSort) " - "values(NULL, '%s', '%s', '%s', %i, '%s', '%s', %i, '%s'", + "values(NULL, '%s', '%s', '%s', %i, %i, '%s', '%s', %i, '%s'", strAlbum.c_str(), strArtist.c_str(), strGenre.c_str(), year, + bBoxedSet, strRecordLabel.c_str(), strType.c_str(), bCompilation, @@ -859,7 +872,6 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str strSQL += PrepareSQL(", '%s'", strArtistSort.c_str()); strSQL += ")"; m_pDS->exec(strSQL); - return (int)m_pDS->lastinsertid(); } else @@ -871,9 +883,13 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str stored for it. Most values here should be the same across all songs anyway, but it does mean that if there's any inconsistencies then only the last folders information will be taken. + NOTE - This means for boxset albums that are multi-folder albums, only the last disc name will be + stored and Kodi will not recognize it as a set UNLESS the appropriate tag is set. + We make sure we clear out the link tables (album artists, album sources) and we reset the last scraped time to make sure that online metadata is re-fetched. */ int idAlbum = m_pDS->fv("idAlbum").get_asInt(); +// bool isBoxset = m_pDS->fv("bBoxedSet").get_asInt() == 1; m_pDS->close(); strSQL = "UPDATE album SET "; @@ -888,11 +904,12 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str else strSQL += PrepareSQL(" strArtistSort = '%s'", strArtistSort.c_str()); - strSQL += PrepareSQL(", strGenres = '%s', iYear=%i, strLabel = '%s', strType = '%s', " + strSQL += PrepareSQL(", strGenres = '%s', iYear=%i, bBoxedSet=%i, strLabel = '%s', strType = '%s', " "bCompilation=%i, strReleaseType = '%s', lastScraped = NULL " "WHERE idAlbum=%i", strGenre.c_str(), year, + bBoxedSet, strRecordLabel.c_str(), strType.c_str(), bCompilation, @@ -921,7 +938,8 @@ int CMusicDatabase::UpdateAlbum(int idAlbum, const std::string& strThemes, const std::string& strReview, const std::string& strImage, const std::string& strLabel, const std::string& strType, - float fRating, int iUserrating, int iVotes, int iYear, bool bCompilation, + float fRating, int iUserrating, int iVotes, int iYear, bool bBoxedSet, + bool bCompilation, CAlbum::ReleaseType releaseType, bool bScrapedMBID) { @@ -934,13 +952,13 @@ int CMusicDatabase::UpdateAlbum(int idAlbum, " strMoods = '%s', strStyles = '%s', strThemes = '%s', " " strReview = '%s', strImage = '%s', strLabel = '%s', " " strType = '%s', fRating = %f, iUserrating = %i, iVotes = %i," - " iYear = %i, bCompilation = %i, strReleaseType = '%s', " + " iYear = %i, bBoxedSet = %i, bCompilation = %i, strReleaseType = '%s', " " lastScraped = '%s', bScrapedMBID = %i", strAlbum.c_str(), strArtist.c_str(), strGenre.c_str(), strMoods.c_str(), strStyles.c_str(), strThemes.c_str(), strReview.c_str(), strImage.c_str(), strLabel.c_str(), strType.c_str(), fRating, iUserrating, iVotes, - iYear, bCompilation, + iYear, bBoxedSet, bCompilation, CAlbum::ReleaseTypeToString(releaseType).c_str(), CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str(), bScrapedMBID); @@ -958,7 +976,6 @@ int CMusicDatabase::UpdateAlbum(int idAlbum, strSQL += PrepareSQL(", strArtistSort = '%s'", strArtistSort.c_str()); strSQL += PrepareSQL(" WHERE idAlbum = %i", idAlbum); - bool status = ExecuteQuery(strSQL); if (status) AnnounceUpdate(MediaTypeAlbum, idAlbum); @@ -1072,6 +1089,26 @@ bool CMusicDatabase::HasAlbumBeenScraped(int idAlbum) return GetSingleValue(strSQL).empty(); } +bool CMusicDatabase::RemoveBoxset(int albumId) +{ + std::string strSQL; + bool off = false; + try + { + strSQL=PrepareSQL("UPDATE album SET bBoxedSet = %i WHERE idAlbum = %i", off, albumId); + CLog::Log(LOGDEBUG, "%s - query (%s)", __FUNCTION__, strSQL.c_str()); + if (!ExecuteQuery(strSQL)) + return false; + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "musicdatabase:unable to remove boxset (%s)", strSQL.c_str()); + } + return false; +} + + int CMusicDatabase::AddGenre(std::string& strGenre) { std::string strSQL; @@ -1380,7 +1417,7 @@ bool CMusicDatabase::GetArtist(int idArtist, CArtist &artist, bool fetchAll /* = if (fetchAll) strSQL = PrepareSQL("SELECT * FROM artistview LEFT JOIN discography ON artistview.idArtist = discography.idArtist WHERE artistview.idArtist = %i", idArtist); else - // Same fields as artistview, but don't fetch dateadded when value not + // Same fields as artistview, but don't fetch dateadded when value not // needed. MySQL very slow for view with subquery column with aggregate //! @todo replace with artistview once dateadded is column of artist table strSQL = PrepareSQL("SELECT " @@ -1893,6 +1930,35 @@ bool CMusicDatabase::GetSongsByArtist(int idArtist, std::vector &songs) return false; }; +bool CMusicDatabase::GetSongByDiscSubtitleAndAlbum(const std::string& strDiscSubtitle, int idAlbum) +{ + try + { + std::string strSQL; + strSQL = PrepareSQL("SELECT idSong FROM song WHERE strDiscSubtitle = '%s' AND idAlbum = %i", strDiscSubtitle, idAlbum); + if (!m_pDS->query(strSQL)) + return false; + if (m_pDS->num_rows() == 0) + { + m_pDS->close(); + return false; + } + + while (!m_pDS->eof()) + { +// songs.push_back(m_pDS->fv("idSong").get_asInt()); + m_pDS->next(); + } + m_pDS->close(); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s(%i, %s) failed", __FUNCTION__, idAlbum, strDiscSubtitle); + } + return false; +}; + bool CMusicDatabase::GetArtistsBySong(int idSong, std::vector &artists) { try @@ -2175,6 +2241,7 @@ void CMusicDatabase::GetFileItemFromDataset(const dbiplus::sql_record* const rec stTime.wYear = static_cast(record->at(song_iYear).get_asInt()); item->GetMusicInfoTag()->SetReleaseDate(stTime); item->GetMusicInfoTag()->SetTitle(record->at(song_strTitle).get_asString()); + item->GetMusicInfoTag()->SetDiscSubtitle(record->at(song_strDiscSubtitle).get_asString()); item->SetLabel(record->at(song_strTitle).get_asString()); item->m_lStartOffset = record->at(song_iStartOffset).get_asInt64(); item->SetProperty("item_start", item->m_lStartOffset); @@ -2191,6 +2258,7 @@ void CMusicDatabase::GetFileItemFromDataset(const dbiplus::sql_record* const rec std::string strRealPath = URIUtils::AddFileToFolder(record->at(song_strPath).get_asString(), record->at(song_strFileName).get_asString()); item->GetMusicInfoTag()->SetURL(strRealPath); item->GetMusicInfoTag()->SetCompilation(record->at(song_bCompilation).get_asInt() == 1); + item->GetMusicInfoTag()->SetBoxset(record->at(song_bBoxedSet).get_asInt() == 1); // get the album artist string from songview (not the album_artist and artist tables) item->GetMusicInfoTag()->SetAlbumArtist(record->at(song_strAlbumArtists).get_asString()); item->GetMusicInfoTag()->SetAlbumReleaseType(CAlbum::ReleaseTypeFromString(record->at(song_strAlbumReleaseType).get_asString())); @@ -2270,6 +2338,7 @@ CAlbum CMusicDatabase::GetAlbumFromDataset(const dbiplus::sql_record* const reco album.iUserrating = record->at(offset + album_iUserrating).get_asInt(); album.iVotes = record->at(offset + album_iVotes).get_asInt(); album.iYear = record->at(offset + album_iYear).get_asInt(); + album.bBoxedSet = record->at(offset + album_bBoxedSet).get_asInt() == 1; album.strReview = record->at(offset + album_strReview).get_asString(); album.styles = StringUtils::Split(record->at(offset + album_strStyles).get_asString(), itemSeparator); album.moods = StringUtils::Split(record->at(offset + album_strMoods).get_asString(), itemSeparator); @@ -3738,14 +3807,14 @@ bool CMusicDatabase::GetGenresNav(const std::string& strBaseDir, CFileItemList& { if (extFilter.where.find("artistview") != std::string::npos) { - extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); - extFilter.AppendJoin("JOIN songview ON songview.idSong = song_genre.idSong"); - extFilter.AppendJoin("JOIN song_artist ON song_artist.idSong = songview.idSong"); + extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); + extFilter.AppendJoin("JOIN songview ON songview.idSong = song_genre.idSong"); + extFilter.AppendJoin("JOIN song_artist ON song_artist.idSong = songview.idSong"); extFilter.AppendJoin("JOIN artistview ON artistview.idArtist = song_artist.idArtist"); } else if (extFilter.where.find("songview") != std::string::npos) { - extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); + extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); extFilter.AppendJoin("JOIN songview ON songview.idSong = song_genre.idSong"); } else if (extFilter.where.find("albumview") != std::string::npos) @@ -3933,6 +4002,101 @@ bool CMusicDatabase::GetSourcesNav(const std::string& strBaseDir, CFileItemList& return false; } +bool CMusicDatabase::GetBoxsetsNav(const std::string& strBaseDir, CFileItemList& items, const Filter& filter) +{ + CLog::Log(LOGNOTICE, "GetBoxsetsNav - StrBaseDir = [%s]", strBaseDir.c_str()); +// GetBoxsetsAlbums(strBaseDir, items); +//return true; + + try + { + unsigned int querytime = 0; + unsigned int time = XbmcThreads::SystemClockMillis(); + int total = -1; + + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + Filter extFilter = filter; + CMusicDbUrl musicUrl; + SortDescription sorting; + if (!musicUrl.FromString(strBaseDir) ) + return false; + + // get boxsets from albumlist + std::string strSQL = "SELECT albumview.* FROM albumview"; + extFilter.AppendWhere("albumview.bBoxedSet = 1"); + + if (!BuildSQL(strSQL, extFilter, strSQL)) + return false; + + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); + querytime = XbmcThreads::SystemClockMillis(); + if (!m_pDS->query(strSQL)) + return false; + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound == 0) + { + m_pDS->close(); + return true; + } + querytime = XbmcThreads::SystemClockMillis() - querytime; + + // store the total value of items as a property + if (total < iRowsFound) + total = iRowsFound; + items.SetProperty("total", total); + + + DatabaseResults results; + results.reserve(iRowsFound); + + // Random order with limits already applied in SQL, just fetch results from dataset +// sorting = sortDescription; +// if (limitedInSQL && sortDescription.sortBy == SortByRandom) +// sorting.sortBy = SortByNone; +// if (!SortUtils::SortFromDataset(sorting, MediaTypeAlbum, m_pDS, results)) +// return false; + + // get data from returned rows + items.Reserve(results.size()); + const dbiplus::query_data &data = m_pDS->get_result_set().records; + for (const auto &i : results) + { + unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger(); + const dbiplus::sql_record* const record = data.at(targetRow); + + try + { + CMusicDbUrl itemUrl = musicUrl; + std::string path = StringUtils::Format("%i/", record->at(album_idAlbum).get_asInt()); + itemUrl.AppendPath(path); + + CFileItemPtr pItem(new CFileItem(itemUrl.ToString(), GetAlbumFromDataset(record))); + pItem->SetIconImage("DefaultAlbumCover.png"); + items.Add(pItem); + } + catch (...) + { + m_pDS->close(); + CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size()); + } + } + + // cleanup + m_pDS->close(); + CLog::Log(LOGDEBUG, "{0}: Time to fill list with albums {1}ms query took {2}ms", + __FUNCTION__, XbmcThreads::SystemClockMillis() - time, querytime); + return true; + } + catch (...) + { + m_pDS->close(); + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filter.where.c_str()); + } + return false; +} + bool CMusicDatabase::GetYearsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */) { try @@ -4265,7 +4429,7 @@ bool CMusicDatabase::GetArtistsByWhere(const std::string& strBaseDir, const Filt // run query CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); querytime = XbmcThreads::SystemClockMillis(); - if (!m_pDS->query(strSQL)) + if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); if (iRowsFound == 0) @@ -4299,7 +4463,7 @@ bool CMusicDatabase::GetArtistsByWhere(const std::string& strBaseDir, const Filt sorting.sortBy = SortByNone; if (!SortUtils::SortFromDataset(sorting, MediaTypeArtist, m_pDS, results)) return false; - + // get data from returned rows items.Reserve(results.size()); const dbiplus::query_data &data = m_pDS->get_result_set().records; @@ -4439,7 +4603,7 @@ bool CMusicDatabase::GetAlbumsByWhere(const std::string &baseDir, const Filter & // run query CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); querytime = XbmcThreads::SystemClockMillis(); - if (!m_pDS->query(strSQL)) + if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); if (iRowsFound == 0) @@ -4889,7 +5053,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - // Count number of artists that satisfy selection criteria + // Count number of artists that satisfy selection criteria //(includes xsp limits from filter, but not sort limits) total = static_cast(strtol(GetSingleValue("SELECT COUNT(1) FROM artist " + strSQLExtra, m_pDS).c_str(), NULL, 10)); @@ -4953,7 +5117,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, std::string strSQL; // Setup fields to query, and album field number mapping - // Find first join field (isSong) in JSONtoDBArtist for offset + // Find first join field (isSong) in JSONtoDBArtist for offset int index_firstjoin = -1; for (unsigned int i = 0; i < NUM_ARTIST_FIELDS; i++) { @@ -4985,7 +5149,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, { if (JSONtoDBArtist[i].bSimple) { - // Store indexes of requested artist table and scalar subquery fields + // Store indexes of requested artist table and scalar subquery fields // to be output, and -1 when not output to JSON if (!foundJSON) dbfieldindex.emplace_back(-1); @@ -5035,7 +5199,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } else joinFilter.AppendOrder("a1.idArtist"); - joinFilter.AppendGroup("a1.idArtist"); + joinFilter.AppendGroup("a1.idArtist"); // Album artists and song artists if (joinLayout.GetFetch(joinToArtist_isalbumartist) || joinLayout.GetFetch(joinToArtist_idSourceAlbum) || @@ -5059,7 +5223,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, // Sources if (joinLayout.GetFetch(joinToArtist_idSourceAlbum)) - { // Left join as source may have been removed but leaving lib entries + { // Left join as source may have been removed but leaving lib entries albumArtistFilter.AppendJoin("LEFT JOIN album_source ON album_source.idAlbum = album_artist.idAlbum"); albumArtistFilter.AppendGroup("album_source.idSource"); albumArtistFilter.AppendField(JSONtoDBArtist[index_firstjoin + joinToArtist_idSourceAlbum].SQL); @@ -5120,8 +5284,8 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, // Roles if (roleidfilter == 1 && !joinLayout.GetFetch(joinToArtist_strRole)) - // Only looking at album and song artists not other roles (default), - // so filter dataset rows likewise. + // Only looking at album and song artists not other roles (default), + // so filter dataset rows likewise. songArtistFilter.AppendWhere("song_artist.idRole = 1"); else if (joinLayout.GetFetch(joinToArtist_strRole) || // "roles" field (bJoinSongArtist && @@ -5176,7 +5340,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, if (bJoinArt) { // Left join as artist may not have any art joinFilter.AppendJoin("LEFT JOIN art ON art.media_id = a1.idArtist AND art.media_type = 'artist'"); - joinLayout.SetField(joinToArtist_idArt, JSONtoDBArtist[index_firstjoin + joinToArtist_idArt].SQL, + joinLayout.SetField(joinToArtist_idArt, JSONtoDBArtist[index_firstjoin + joinToArtist_idArt].SQL, joinLayout.GetOutput(joinToArtist_idArt)); joinLayout.SetField(joinToArtist_artType, JSONtoDBArtist[index_firstjoin + joinToArtist_artType].SQL); joinLayout.SetField(joinToArtist_artURL, JSONtoDBArtist[index_firstjoin + joinToArtist_artURL].SQL); @@ -5201,15 +5365,15 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, // Adjust where in the results record the join fields are allowing for the // inline view fields (Quicker than finding field by name every time) - // idArtist + other artist fields + // idArtist + other artist fields joinLayout.AdjustRecordNumbers(1 + dbfieldindex.size()); // Build full query // When have multiple value joins e.g. song genres, use inline view - // SELECT a1.*, FROM - // (SELECT FROM artist + + ) AS a1 + // SELECT a1.*, FROM + // (SELECT FROM artist + + ) AS a1 // + - // Don't use prepareSQL - confuses arttype = 'thumb' filter + // Don't use prepareSQL - confuses arttype = 'thumb' filter strSQL = "SELECT " + extFilter.fields + " FROM artist " + strSQLExtra; if (joinLayout.HasFilterFields()) @@ -5338,7 +5502,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } } - // Sources - gathered via both album_artist and song_artist (with role = 1) + // Sources - gathered via both album_artist and song_artist (with role = 1) if (joinLayout.GetFetch(joinToArtist_idSourceAlbum)) { if ((bAlbumArtistRow && joinLayout.GetRecNo(joinToArtist_idSourceAlbum) > -1 && @@ -5362,7 +5526,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, { sourceId = record->at(joinLayout.GetRecNo(joinToArtist_idSourceSong)).get_asInt(); // Song artist row may repeat sources found via album artist - // Already have that source? + // Already have that source? for (const auto& i : sourceidlist) if (i == sourceId) { @@ -5412,7 +5576,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, bool found(false); if (newgenre) { - // Already have that genre? + // Already have that genre? for (const auto& i : genreidlist) if (i == genreId) { @@ -5430,7 +5594,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } } } - // Roles - gathered via song_artist roleid rows + // Roles - gathered via song_artist roleid rows if (joinLayout.GetFetch(joinToArtist_idRole)) { if (!bAlbumArtistRow && roleId != idRoleRow) @@ -5439,7 +5603,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, roleId = idRoleRow; if (joinLayout.GetOutput(joinToArtist_strRole)) { - // Already have that role? + // Already have that role? bool found(false); for (const auto& i : roleidlist) if (i == roleId) @@ -5460,9 +5624,9 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } } // Art - if (bJoinArt && !bArtDone && + if (bJoinArt && !bArtDone && !record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_isNull() && - record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt() > 0 && + record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt() > 0 && artId != record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt()) { artId = record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt(); @@ -5542,7 +5706,7 @@ static const translateJSONField JSONtoDBAlbum[] = { Using albmview, rather than album table, as view has scalar subqueries for playcount, dateadded and lastplayed already defined. Needed as MySQL does not support use of scalar subquery field alias names in where clauses (they - have to be repeated) and these fields can be used by filter rules. + have to be repeated) and these fields can be used by filter rules. Using this view is no slower than the album table as these scalar fields are only calculated (slowing query) when field is in field list. */ @@ -5550,10 +5714,10 @@ static const translateJSONField JSONtoDBAlbum[] = { static const size_t NUM_ALBUM_FIELDS = sizeof(JSONtoDBAlbum) / sizeof(translateJSONField); -bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, const std::string &baseDir, +bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, const std::string &baseDir, CVariant& result, int& total, const SortDescription &sortDescription /* = SortDescription() */) { - + if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; @@ -5574,7 +5738,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - // Count number of albums that satisfy selection criteria + // Count number of albums that satisfy selection criteria // (includes xsp limits from filter, but not sort limits) // Use albumview as filter rules in where clause may use scalar query fields total = static_cast(strtol(GetSingleValue("SELECT COUNT(1) FROM albumview " + strSQLExtra, m_pDS).c_str(), nullptr, 10)); @@ -5673,7 +5837,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c // Natural number case insensitve sort extFilter.AppendOrder(AlphanumericSortSQL(name, sortDescription.sortOrder)); } - else if (name.compare("strAlbum") == 0 || + else if (name.compare("strAlbum") == 0 || name.compare("strType") == 0 || name.compare("strGenres") == 0) // Natural number case insensitve sort @@ -5681,11 +5845,11 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c else extFilter.AppendOrder(name + DESC); } - + std::string strSQL; // Setup fields to query, and album field number mapping - // Find idArtist in JSONtoDBAlbum, offset of first join field + // Find idArtist in JSONtoDBAlbum, offset of first join field int index_idArtist = -1; for (unsigned int i = 0; i < NUM_ALBUM_FIELDS; i++) { @@ -5694,7 +5858,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c index_idArtist = i; break; } - } + } Filter joinFilter; DatasetLayout joinLayout(static_cast(joinToAlbum_enumCount)); extFilter.AppendField("albumview.idAlbum"); // ID "albumid" in JSON @@ -5718,7 +5882,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c { if (JSONtoDBAlbum[i].bSimple) { - // Store indexes of requested album table and scalar subquery fields + // Store indexes of requested album table and scalar subquery fields // to be output, and -1 when not output to JSON if (!foundJSON) dbfieldindex.emplace_back(-1); @@ -5726,7 +5890,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c dbfieldindex.emplace_back(i); // Field from scaler subquery if (!JSONtoDBAlbum[i].SQL.empty()) - { + { if (JSONtoDBAlbum[i].fieldDB == "artistsortname") extFilter.AppendField(artistsortSQL); else @@ -5783,12 +5947,12 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c joinFilter.AppendJoin("JOIN album_artist ON album_artist.idAlbum = a1.idAlbum"); joinFilter.AppendGroup("album_artist.idArtist"); joinFilter.AppendOrder("album_artist.iOrder"); - // Ensure idArtist is queried + // Ensure idArtist is queried if (!joinLayout.GetFetch(joinToAlbum_idArtist)) joinLayout.SetField(joinToAlbum_idArtist, JSONtoDBAlbum[index_idArtist + joinToAlbum_idArtist].SQL); } - // artist table needed for strArtist or MBID - // (album_artist.strArtist can be an alias or spelling variation) + // artist table needed for strArtist or MBID + // (album_artist.strArtist can be an alias or spelling variation) if (joinLayout.GetFetch(joinToAlbum_strArtist) || joinLayout.GetFetch(joinToAlbum_strArtistMBID)) joinFilter.AppendJoin("JOIN artist ON artist.idArtist = album_artist.idArtist"); @@ -5808,16 +5972,16 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c if (joinLayout.HasFilterFields()) if (!BuildSQL(strSQLJoin, joinFilter, strSQLJoin)) return false; - + // Adjust where in the results record the join fields are allowing for the // inline view fields (Quicker than finding field by name every time) - // idAlbum + other album fields + // idAlbum + other album fields joinLayout.AdjustRecordNumbers(1 + dbfieldindex.size()); - + // Build full query // When have multiple value joins (artists or song genres) use inline view - // SELECT a1.*, FROM - // (SELECT FROM albumview + + ) AS a1 + // SELECT a1.*, FROM + // (SELECT FROM albumview + + ) AS a1 // // Don't use prepareSQL - confuses releasetype = 'album' filter and group_concat separator @@ -5827,7 +5991,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c strSQL = "(" + strSQL + ") AS a1 "; strSQL = "SELECT a1.*, " + joinLayout.GetFields() + " FROM " + strSQL + strSQLJoin; } - + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); // run query unsigned int time = XbmcThreads::SystemClockMillis(); @@ -5856,7 +6020,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c const dbiplus::sql_record* const record = m_pDS->get_sql_record(); if (m_pDS->eof() || albumId != record->at(0).get_asInt()) - { + { // Store previous or last album if (!albumObj.empty()) { @@ -5875,7 +6039,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c result["albums"].append(albumObj); - albumObj.clear(); + albumObj.clear(); artistId = -1; bSongGenreDone = false; } @@ -5933,7 +6097,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c if (joinLayout.GetOutput(joinToAlbum_strArtistMBID) && joinLayout.GetRecNo(joinToAlbum_strArtistMBID) > -1) albumObj["musicbrainzalbumartistid"].append(record->at(joinLayout.GetRecNo(joinToAlbum_strArtistMBID)).get_asString()); } - } + } } if (!bSongGenreDone && joinLayout.GetRecNo(joinToAlbum_idSongGenre) > -1 && joinLayout.GetRecNo(joinToAlbum_strSongGenre) > -1 && @@ -5982,13 +6146,13 @@ static const translateJSONField JSONtoDBSong[] = { { "userrating", "unsigned", true, "song.userrating", "" }, { "mood", "array", true, "mood", "" }, { "dateadded", "string", true, "dateAdded", "" }, - { "file", "string", true, "strPathFile", "CONCAT(path.strPath, strFilename) AS strPathFile" }, + { "file", "string", true, "strPathFile", "CONCAT(path.strPath, strFilename) AS strPathFile" }, { "", "string", true, "strPath", "path.strPath AS strPath" }, { "album", "string", true, "strAlbum", "album.strAlbum AS strAlbum" }, { "albumreleasetype", "string", true, "strAlbumReleaseType", "album.strReleaseType AS strAlbumReleaseType" }, { "musicbrainzalbumid", "string", true, "strMusicBrainzAlbumID", "album.strMusicBrainzAlbumID AS strMusicBrainzAlbumID" }, - // JOIN fields (multivalue), same order as _JoinToSongFields + // JOIN fields (multivalue), same order as _JoinToSongFields { "albumartistid", "array", false, "idAlbumArtist", "album_artist.idArtist AS idAlbumArtist" }, { "albumartist", "array", false, "strAlbumArtist", "albumartist.strArtist AS strAlbumArtist" }, { "musicbrainzalbumartistid", "array", false, "strAlbumArtistMBID", "albumartist.strMusicBrainzArtistID AS strAlbumArtistMBID" }, @@ -6008,19 +6172,19 @@ static const translateJSONField JSONtoDBSong[] = { { "displayconductor", "string", false, "Role_Conductor", "song_artist.idRole AS Role_Conductor" }, { "displayorchestra", "string", false, "Role_Orchestra", "song_artist.idRole AS Role_Orchestra" }, { "displaylyricist", "string", false, "Role_Lyricist", "song_artist.idRole AS Role_Lyricist" }, - + // Scalar subquery fields { "track", "integer", true, "track", "(iTrack & 0xffff) AS track" }, { "disc", "integer", true, "disc", "(iTrack >> 16) AS disc" }, { "sourceid", "string", true, "sourceid", "(SELECT GROUP_CONCAT(album_source.idSource SEPARATOR '; ') FROM album_source WHERE album_source.idAlbum = song.idAlbum) AS sources" }, { "", "", true, "artistsortname", "CASE WHEN song.strArtistSort IS NOT NULL THEN song.strArtistSort ELSE song.strArtistDisp END AS artistsortname"} - /* + /* Song "thumbnail", "fanart" and "art" fields of JSON schema are fetched using thumbloader and separate queries to allow for fallback strategy "lyrics"?? Can be set for an item (by addons) but not held in db so AudioLibrary.GetSongs() never fills this field despite being in schema - FROM ( SELECT * FROM song + FROM ( SELECT * FROM song JOIN album ON album.idAlbum = song.idAlbum JOIN path ON path.idPath = song.idPath) AS sv JOIN album_artist ON album_artist.idAlbum = song.idAlbum @@ -6056,7 +6220,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - // Count number of songs that satisfy selection criteria + // Count number of songs that satisfy selection criteria // (includes xsp limits from filter, but not sort limits) // Use songview as filter rules in where clause may use album and path JOIN fields total = static_cast(strtol(GetSingleValue("SELECT COUNT(1) FROM songview " + strSQLExtra, m_pDS).c_str(), nullptr, 10)); @@ -6101,7 +6265,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co orderfields.emplace_back("strAlbum"); orderfields.emplace_back("song.strArtistDisp"); orderfields.emplace_back("song.iTrack"); - } + } else if (sortDescription.sortBy == SortByArtist) { orderfields.emplace_back("song.strArtistDisp"); @@ -6166,7 +6330,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co orderfields.emplace_back("iDuration"); // Always sort by id to define order when other fields same - if (sortDescription.sortBy != SortByRandom) + if (sortDescription.sortBy != SortByRandom) orderfields.emplace_back("song.idSong"); // Fill inline view filter order fields, and build sort scalar subquery SQL @@ -6176,15 +6340,15 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co //Add field for adjusted name sorting using sort name and ignoring articles if (name.compare("song.strArtistDisp") == 0) { - artistsortSQL = SortnameBuildSQL("artistsortname", sortDescription.sortAttributes, + artistsortSQL = SortnameBuildSQL("artistsortname", sortDescription.sortAttributes, "song.strArtistDisp", "song.strArtistSort"); if (!artistsortSQL.empty()) name = "artistsortname"; // Natural number case insensitve sort extFilter.AppendOrder(AlphanumericSortSQL(name, sortDescription.sortOrder)); } - else if (name.compare("strTitle") == 0 || - name.compare("strAlbum") == 0 || + else if (name.compare("strTitle") == 0 || + name.compare("strAlbum") == 0 || name.compare("song.strGenres") == 0) // Natural number case insensitve sort extFilter.AppendOrder(AlphanumericSortSQL(name, sortDescription.sortOrder)); @@ -6196,7 +6360,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co std::string strSQL; // Setup fields to query, and song field number mapping - // Find idAlbumArtist in JSONtoDBSong, offset of first join field + // Find idAlbumArtist in JSONtoDBSong, offset of first join field int index_idAlbumArtist = -1; for (unsigned int i = 0; i < NUM_SONG_FIELDS; i++) { @@ -6205,7 +6369,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co index_idAlbumArtist = i; break; } - } + } Filter joinFilter; DatasetLayout joinLayout(static_cast(joinToSongs_enumCount)); extFilter.AppendField("song.idSong"); // ID "songid" in JSON @@ -6230,7 +6394,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { if (JSONtoDBSong[i].bSimple) { - // Store indexes of requested album table and scalar subquery fields + // Store indexes of requested album table and scalar subquery fields // to be output, and -1 when not output to JSON if (!foundJSON) dbfieldindex.emplace_back(-1); @@ -6238,7 +6402,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co dbfieldindex.emplace_back(i); // Field from scaler subquery if (!JSONtoDBSong[i].SQL.empty()) - { + { if (JSONtoDBSong[i].fieldDB == "artistsortname") extFilter.AppendField(artistsortSQL); else @@ -6261,7 +6425,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co } } } - // Build matching list of role id for "displaycomposer", "displayconductor", + // Build matching list of role id for "displaycomposer", "displayconductor", // "displayorchestra", "displaylyricist" for (const auto& name : rolefieldlist) { @@ -6284,7 +6448,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { // All songs have one path so inner join sufficient extFilter.AppendJoin("JOIN path ON path.idPath = song.idPath"); } - + // Build JOIN, WHERE, ORDER BY and LIMIT for inline view strSQLExtra = ""; if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) @@ -6296,7 +6460,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { strSQLExtra += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart); } - + // Setup multivalue JOINs, GROUP BY and ORDER BY bool bJoinSongArtist(false); bool bJoinAlbumArtist(false); @@ -6313,7 +6477,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co else joinFilter.AppendOrder("sv.idSong"); joinFilter.AppendGroup("sv.idSong"); - + // Album artists if (joinLayout.GetFetch(joinToSongs_idAlbumArtist) || joinLayout.GetFetch(joinToSongs_strAlbumArtist) || @@ -6326,17 +6490,17 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co // Ensure idAlbumArtist is queried for processing repeats if (!joinLayout.GetFetch(joinToSongs_idAlbumArtist)) { - joinLayout.SetField(joinToSongs_idAlbumArtist, + joinLayout.SetField(joinToSongs_idAlbumArtist, JSONtoDBSong[index_idAlbumArtist + joinToSongs_idAlbumArtist].SQL); - } + } // Ensure song.IdAlbum is field of the inline view for join if (fields.find("albumid") == fields.end()) { extFilter.AppendField("song.idAlbum"); //Prefer lookup JSONtoDBSong[XXX].dbField); dbfieldindex.emplace_back(-1); } - // artist table needed for strArtist or MBID - // (album_artist.strArtist can be an alias or spelling variation) + // artist table needed for strArtist or MBID + // (album_artist.strArtist can be an alias or spelling variation) if (joinLayout.GetFetch(joinToSongs_strAlbumArtistMBID) || joinLayout.GetFetch(joinToSongs_strAlbumArtist)) joinFilter.AppendJoin("JOIN artist AS albumartist ON albumartist.idArtist = album_artist.idArtist"); } @@ -6358,7 +6522,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co joinFilter.AppendGroup("song_artist.idArtist"); joinFilter.AppendOrder("song_artist.iOrder"); } - else + else { // Ensure idRole is queried if (!joinLayout.GetFetch(joinToSongs_idRole)) @@ -6386,7 +6550,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { // Get just roles for "displaycomposer", "displayconductor" etc. std::string where; for (size_t i = 0; i < roleidlist.size(); i++) - { + { int idRole = roleidlist[i]; if (idRole <= 1) continue; @@ -6408,16 +6572,16 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co joinLayout.SetField(joinToSongs_idArtist, JSONtoDBSong[index_idAlbumArtist + joinToSongs_idArtist].SQL); } - // artist table needed for strArtist or MBID - // (song_artist.strArtist can be an alias or spelling variation) + // artist table needed for strArtist or MBID + // (song_artist.strArtist can be an alias or spelling variation) if (joinLayout.GetFetch(joinToSongs_strArtistMBID) || joinLayout.GetFetch(joinToSongs_strArtist)) joinFilter.AppendJoin("JOIN artist AS songartist ON songartist.idArtist = song_artist.idArtist"); - } + } // Genre ids if (joinLayout.GetFetch(joinToSongs_idGenre)) { // song genre ids (strGenre demormalised in song table) - // Left join as songs may not have genre + // Left join as songs may not have genre joinFilter.AppendJoin("LEFT JOIN song_genre ON song_genre.idSong = sv.idSong"); joinFilter.AppendGroup("song_genre.idGenre"); joinFilter.AppendOrder("song_genre.iOrder"); @@ -6436,8 +6600,8 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co // Build full query // When have multiple value joins use inline view - // SELECT sv.*, FROM - // (SELECT FROM song + + ) AS sv + // SELECT sv.*, FROM + // (SELECT FROM song + + ) AS sv // // + // Don't use prepareSQL - confuses releasetype = 'album' filter and group_concat separator @@ -6447,7 +6611,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co strSQL = "("+ strSQL + ") AS sv "; strSQL = "SELECT sv.*, " + joinLayout.GetFields() + " FROM " + strSQL + strSQLJoin; } - + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); // Run query @@ -6630,7 +6794,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co contributor["role"] = record->at(joinLayout.GetRecNo(joinToSongs_strRole)).get_asString(); contributor["roleid"] = roleId; contributor["artistid"] = record->at(joinLayout.GetRecNo(joinToSongs_idArtist)).get_asInt(); - songObj["contributors"].append(contributor); + songObj["contributors"].append(contributor); } // "displaycomposer", "displayconductor" etc. for (size_t i = 0; i < roleidlist.size(); i++) @@ -6669,7 +6833,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co std::string CMusicDatabase::GetIgnoreArticleSQL(const std::string& strField) { - /* + /* Make SQL clause from ignore article list. Group tokens the same length together, for example : WHEN strArtist LIKE 'the ' OR strArtist LIKE 'the.' strArtist LIKE 'the_' ESCAPE '_' @@ -6722,7 +6886,7 @@ std::string CMusicDatabase::SortnameBuildSQL(const std::string& strAlias, const /* Build SQL for sort name scalar subquery from sort attributes and ignore article list. For example : - CASE WHEN strArtistSort IS NOT NULL THEN strArtistSort + CASE WHEN strArtistSort IS NOT NULL THEN strArtistSort WHEN strField LIKE 'the ' OR strField LIKE 'the_' ESCAPE '_' THEN SUBSTR(strArtist, 5) WHEN strField LIKE 'LIKE 'an.' strField LIKE 'an_' ESCAPE '_' THEN SUBSTR(strArtist, 4) ELSE strField @@ -7921,10 +8085,10 @@ int CMusicDatabase::UpdateSource(const std::string& strOldName, const std::strin if (NULL == m_pDB.get()) return -1; if (NULL == m_pDS.get()) return -1; - // Get details of named old source + // Get details of named old source if (!strOldName.empty()) { - strSQL = PrepareSQL("SELECT idSource, strMultipath FROM source WHERE strName LIKE '%s'", + strSQL = PrepareSQL("SELECT idSource, strMultipath FROM source WHERE strName LIKE '%s'", strOldName.c_str()); if (!m_pDS->query(strSQL)) return -1; @@ -8041,7 +8205,7 @@ bool CMusicDatabase::AddAlbumSources(int idAlbum, const std::string& strPath) if (!strPath.empty()) { - // Find sources related to album using album path + // Find sources related to album using album path strSQL = PrepareSQL("SELECT DISTINCT idSource FROM source_path " "WHERE SUBSTR('%s', 1, LENGTH(strPath)) = strPath", strPath.c_str()); if (!m_pDS->query(strSQL)) @@ -8054,7 +8218,7 @@ bool CMusicDatabase::AddAlbumSources(int idAlbum, const std::string& strPath) m_pDS->close(); } else - { + { // Find sources using song paths, check each source path individually if (NULL == m_pDS2.get()) return false; strSQL = "SELECT idSource, strPath FROM source_path"; @@ -8082,7 +8246,7 @@ bool CMusicDatabase::AddAlbumSources(int idAlbum, const std::string& strPath) { AddAlbumSource(idAlbum, idSource); } - + return true; } catch (...) @@ -8161,7 +8325,7 @@ bool CMusicDatabase::MigrateSources() std::string strSQL; try - { + { // Fill source and source paths tables for (const auto& source : sources) { @@ -8180,7 +8344,7 @@ bool CMusicDatabase::MigrateSources() idSource, idPath, path.c_str()); m_pDS->exec(strSQL); ++idPath; - } + } } return true; @@ -8198,12 +8362,12 @@ bool CMusicDatabase::MigrateSources() VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); if (CheckSources(sources)) return true; - + try - { + { // Empty sources table (related link tables removed by trigger); ExecuteQuery("DELETE FROM source"); - + // Fill source table, and album sources for (const auto& source : sources) AddSource(source.strName, source.strPath, source.vecPaths); @@ -8321,11 +8485,12 @@ bool CMusicDatabase::GetSourcesByArtist(int idArtist, CFileItem* item) m_pDS->close(); return true; } + } CVariant artistSources(CVariant::VariantTypeArray); while (!m_pDS->eof()) - { + { artistSources.push_back(m_pDS->fv("idSource").get_asInt()); m_pDS->next(); } @@ -8364,14 +8529,14 @@ bool CMusicDatabase::GetSourcesByAlbum(int idAlbum, CFileItem* item) } m_pDS->close(); } - else + else { //! @todo: handle singles, or don't waste time checking songs // Album does have any sources, may be a single?? // Check via song paths, check each source path individually // usually fewer source paths than songs m_pDS->close(); - + if (NULL == m_pDS2.get()) return false; strSQL = "SELECT idSource, strPath FROM source_path"; if (!m_pDS->query(strSQL)) @@ -8393,7 +8558,7 @@ bool CMusicDatabase::GetSourcesByAlbum(int idAlbum, CFileItem* item) m_pDS->close(); } - + item->SetProperty("sourceid", albumSources); return true; } @@ -8813,13 +8978,13 @@ bool CMusicDatabase::GetGenresJSON(CFileItemList& items, bool bSources) extFilter.AppendJoin("LEFT JOIN album_source on album_source.idAlbum = album.idAlbum"); extFilter.AppendOrder("genre.strGenre"); extFilter.AppendOrder("album_source.idSource"); - } + } extFilter.AppendWhere("genre.strGenre != ''"); std::string strSQLExtra; if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - + strSQL = PrepareSQL(strSQL.c_str(), extFilter.fields.c_str()) + strSQLExtra; // run query @@ -8851,7 +9016,7 @@ bool CMusicDatabase::GetGenresJSON(CFileItemList& items, bool bSources) items[items.Size() - 1].get()->SetProperty("sourceid", genreSources); genreSources.clear(); } - idGenre = m_pDS->fv("genre.idGenre").get_asInt(); + idGenre = m_pDS->fv("genre.idGenre").get_asInt(); std::string strGenre = m_pDS->fv("genre.strGenre").get_asString(); CFileItemPtr pItem(new CFileItem(strGenre)); pItem->GetMusicInfoTag()->SetTitle(strGenre); @@ -8888,6 +9053,180 @@ bool CMusicDatabase::GetGenresJSON(CFileItemList& items, bool bSources) return false; } +bool CMusicDatabase::GetBoxsetsAlbums(const std::string& strBaseDir, CFileItemList& items) +{ + CMusicDbUrl musicUrl; + + if (!musicUrl.FromString(strBaseDir)) + return false; + + musicUrl.AddOption("boxset", true); + + Filter filter; + return GetAlbumsByWhere(musicUrl.ToString(), filter, items); +} + +bool CMusicDatabase::GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList& items, int idAlbum, const SortDescription &sortDescription) +{ + // Need to get the single album - must only be one album holding a boxset + // then split the disc names from strDiscTitles, count how many there are + // and loop around that many times to create a view with 'i' number of boxes, + // titled by the subtitles + + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + std::string strSQL; + strSQL=PrepareSQL("SELECT DISTINCT strDiscSubtitle FROM song where song.idAlbum = %i ORDER BY iTrack", idAlbum); + // run query + if (!m_pDS->query(strSQL)) return false; + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound < 2) // can't only be one disc title for a set + { + m_pDS->close(); + return -1; + } + std::vector titlevector; + while (!m_pDS->eof()) + { + titlevector.push_back(m_pDS->fv("strDiscSubtitle").get_asString().c_str()); + m_pDS->next(); + } + m_pDS->close(); + items.SetProperty("total", iRowsFound); + + CMusicDbUrl musicUrl; + std::string temp_path; + temp_path = "musicdb://boxsets/"; + musicUrl.FromString(strBaseDir); + musicUrl.RemoveOption("boxset"); + temp_path += StringUtils::Format("%i/", idAlbum); + + strSQL=PrepareSQL("SELECT albumview.* FROM albumview WHERE albumview.idAlbum = %i", idAlbum); + if (!m_pDS->query(strSQL)) return false; + int iAlbumsFound = m_pDS->num_rows(); + if (iAlbumsFound != 1) // must only be one album + { + m_pDS->close(); + return -1; + } + DatabaseResults results; + results.reserve(iAlbumsFound); + if (!SortUtils::SortFromDataset(sortDescription, MediaTypeSong, m_pDS, results)) + return false; + const dbiplus::sql_record* const record = m_pDS->get_sql_record(); + CMusicDbUrl itemUrl; + new CFileItem(itemUrl.ToString(), GetAlbumFromDataset(record)); + + for (const auto &disctitle : titlevector) // loop to construct the discs for the gui + { + CFileItemPtr pItem(new CFileItem(itemUrl.ToString() , GetAlbumFromDataset(record))); + std::string path; + path = temp_path; + path += StringUtils::Format("%s/", disctitle.c_str()); + pItem->SetPath(path); + pItem->SetLabel(disctitle.c_str()); + pItem->SetIconImage("DefaultAlbumCover.png"); + items.Add(pItem); + } + } + catch (...) + { + m_pDS->close(); + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + m_pDS->close(); + return true; +} + +bool CMusicDatabase::GetBoxsetDiscSongs(const std::string& strBaseDir, CFileItemList& items) +{ + CMusicDbUrl musicUrl; + int idAlbum; + Filter filter; + // Parse out the album ID from the base directory + unsigned int start = strBaseDir.find("boxsets/"); + if (start == std::string::npos) + idAlbum = -1; + unsigned int end = strBaseDir.find("/", start + 9); // 9 is 'boxsets/' + 1 + std::string tmp = strBaseDir.substr(start + 8, end - 18); // 8 = 'boxsets/', 18 = 'musicdb://boxsets/' + idAlbum = atoi(tmp.c_str()); + start = end + 1; // set start to end of albumID field + '/' + end = strBaseDir.find("/", start + 1); + std::string strDiscName = strBaseDir.substr(start, (strBaseDir.length() -1) - start); + CLog::Log(LOGNOTICE, "%s - Album ID [%i] strDiscName [%s]", __FUNCTION__, idAlbum, strDiscName.c_str()); + if (idAlbum == -1) + return false; + if (m_pDB.get() == NULL || m_pDS.get() == NULL) + return false; + + try + { + int total = -1; + std::string strSQL = PrepareSQL("SELECT songview.* from songview WHERE songview.strDiscSubtitle LIKE '%s' AND songview.idAlbum = %i ", strDiscName.c_str(), idAlbum) ; + SortDescription sorting; + CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str()); + // run query + if (!m_pDS->query(strSQL)) + return false; + + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound == 0) + { + m_pDS->close(); + return true; + } + + // store the total value of items as a property + if (total < iRowsFound) + total = iRowsFound; + items.SetProperty("total", total); + + DatabaseResults results; + results.reserve(iRowsFound); + if (!SortUtils::SortFromDataset(sorting, MediaTypeSong, m_pDS, results)) + return false; + + // get data from returned rows + items.Reserve(results.size()); + const dbiplus::query_data &data = m_pDS->get_result_set().records; + int count = 0; + for (const auto &i : results) + { + unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger(); + const dbiplus::sql_record* const record = data.at(targetRow); + + try + { + CFileItemPtr item(new CFileItem); + GetFileItemFromDataset(record, item.get(), musicUrl); + // HACK for sorting by database returned order + item->m_iprogramCount = ++count; + items.Add(item); + } + catch (...) + { + m_pDS->close(); + CLog::Log(LOGERROR, "%s: out of memory loading query: %s", __FUNCTION__, filter.where.c_str()); + return (items.Size() > 0); + } + } + + // cleanup + m_pDS->close(); + return true; + } + catch (...) + { + // cleanup + m_pDS->close(); + CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, filter.where.c_str()); + return false; + } + return true; +} + bool CMusicDatabase::GetCompilationAlbums(const std::string& strBaseDir, CFileItemList& items) { CMusicDbUrl musicUrl; @@ -8912,6 +9251,11 @@ bool CMusicDatabase::GetCompilationSongs(const std::string& strBaseDir, CFileIte return GetSongsFullByWhere(musicUrl.ToString(), filter, items, SortDescription(), true); } +int CMusicDatabase::GetBoxsetsCount() +{ + return strtol(GetSingleValue("album", "count(idAlbum)", "bBoxedSet = 1").c_str(), NULL, 10); +} + int CMusicDatabase::GetCompilationAlbumsCount() { return strtol(GetSingleValue("album", "count(idAlbum)", "bCompilation = 1").c_str(), NULL, 10); @@ -9488,7 +9832,7 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog if (!settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS) && !settings.IsItemExported(ELIBEXPORT_SONGARTISTS) && !settings.IsItemExported(ELIBEXPORT_OTHERARTISTS) && - !settings.IsItemExported(ELIBEXPORT_ALBUMS) && + !settings.IsItemExported(ELIBEXPORT_ALBUMS) && !settings.IsItemExported(ELIBEXPORT_SONGS)) return; @@ -9515,7 +9859,7 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog else if (settings.IsArtistFoldersOnly() || (settings.IsToLibFolders() && settings.IsArtists())) { // Exporting artist folders only, or artist NFO or art to library folders - // need Artist Information Folder defined. + // need Artist Information Folder defined. // (Album NFO and art goes to music folders) strFolder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); if (strFolder.empty()) @@ -9523,9 +9867,9 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog } // - bool artistfoldersonly; + bool artistfoldersonly; artistfoldersonly = settings.IsArtistFoldersOnly() || - ((settings.IsToLibFolders() || settings.IsSeparateFiles()) && + ((settings.IsToLibFolders() || settings.IsSeparateFiles()) && settings.m_skipnfo && !settings.m_artwork); int iFailCount = 0; @@ -9911,7 +10255,7 @@ bool CMusicDatabase::ExportSongHistory(TiXmlNode* pNode, CGUIDialogProgress* pro } void CMusicDatabase::ImportFromXML(const std::string& xmlFile, CGUIDialogProgress* progressDialog) -{ +{ try { if (NULL == m_pDB.get()) return; @@ -10020,7 +10364,7 @@ void CMusicDatabase::ImportFromXML(const std::string& xmlFile, CGUIDialogProgres } bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int total, CGUIDialogProgress* progressDialog) -{ +{ bool bHistSongExists = false; try { @@ -10029,21 +10373,21 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot return false; TiXmlElement* root = xmlDoc.RootElement(); - if (!root) + if (!root) return false; TiXmlElement* entry = root->FirstChildElement(); int current = 0; - + if (progressDialog) { progressDialog->SetLine(1, CVariant{38350}); //"Importing song playback history" progressDialog->SetLine(2, CVariant{ "" }); } - + // As can be many songs do in db, not song at a time which would be slow // Convert xml entries into a SQL bulk insert statement - std::string strSQL; + std::string strSQL; entry = root->FirstChildElement(); while (entry) { @@ -10062,7 +10406,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot int iVotes; std::string strSQLSong; if (strnicmp(entry->Value(), "song", 4) == 0) - { + { XMLUtils::GetString(entry, "artistdesc", strArtistDisp); XMLUtils::GetString(entry, "title", strTitle); XMLUtils::GetInt(entry, "track", iTrack); @@ -10084,7 +10428,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot if (rating > 10.f) rating = 10.f; fRating = rating; - } + } XMLUtils::GetInt(entry, "votes", iVotes); const TiXmlElement* userrating = entry->FirstChildElement("userrating"); if (userrating) @@ -10161,7 +10505,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot if (progressDialog) { - progressDialog->SetLine(2, CVariant{38351}); //"Matching data" + progressDialog->SetLine(2, CVariant{38351}); //"Matching data" progressDialog->SetLine(3, CVariant{ "" }); progressDialog->Progress(); if (progressDialog->IsCanceled()) @@ -10172,7 +10516,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot } BeginTransaction(); - // Match albums first on mbid then artist string and album title, setting idAlbum + // Match albums first on mbid then artist string and album title, setting idAlbum strSQL = "UPDATE HistSong " "SET idAlbum = (SELECT album.idAlbum FROM album " "WHERE album.strMusicBrainzAlbumID = HistSong.strMusicBrainzAlbumID) " @@ -10247,9 +10591,9 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot } } - /* Update song table using the song ids we have matched. + /* Update song table using the song ids we have matched. Use correlated subqueries as SQLite does not support updatable joins. - MySQL requires HistSong table not to be defined temporary for this. + MySQL requires HistSong table not to be defined temporary for this. */ BeginTransaction(); // Times played and last played date(when count is greater) @@ -10260,14 +10604,14 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot "WHERE EXISTS(SELECT 1 FROM HistSong WHERE " "HistSong.idSong = song.idSong AND HistSong.iTimesPlayed > song.iTimesPlayed)"; m_pDS->exec(strSQL); - + // User rating strSQL = "UPDATE song SET userrating = " "(SELECT userrating FROM HistSong WHERE HistSong.idSong = song.idSong) " "WHERE EXISTS(SELECT 1 FROM HistSong WHERE " "HistSong.idSong = song.idSong AND HistSong.userrating > 0)"; m_pDS->exec(strSQL); - + // Rating and votes strSQL = "UPDATE song SET rating = " "(SELECT rating FROM HistSong WHERE HistSong.idSong = song.idSong), " @@ -10297,7 +10641,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot progressDialog->SetLine(2, CVariant{ 331 }); progressDialog->Progress(); } - Compress(false); + Compress(false); // Write event log entry // "Importing song history {1} of {2} songs matched", total - unmatched, total) @@ -10366,6 +10710,8 @@ void CMusicDatabase::SetPropertiesFromAlbum(CFileItem& item, const CAlbum& album item.SetProperty("album_userrating", album.iUserrating); if (album.iVotes > 0) item.SetProperty("album_votes", album.iVotes); + if (album.bBoxedSet ) + item.SetProperty("album_bBoxedSet", true); item.SetProperty("album_releasetype", CAlbum::ReleaseTypeToString(album.releaseType)); } @@ -10817,7 +11163,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription For artists these rules are combined because they apply via album and song and so we need to ensure all criteria are met via the same album or song. - 1) Some artists may be only album artists, so for all artists (with linked + 1) Some artists may be only album artists, so for all artists (with linked albums or songs) we need to check both album_artist and song_artist tables. 2) Role is determined from song_artist table, so even if looking for album artists only we find those that also have a specific role e.g. which album artist is a @@ -10826,8 +11172,8 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription b) When not album artists only and a specific role wanted then only the song_artist table is checked. c) When album artists only and role = 1 (an "artist") then only the album_artist - table is checked. - */ + table is checked. + */ std::string albumArtistSQL, songArtistSQL; ExistsSubQuery albumArtistSub("album_artist", "album_artist.idArtist = artistview.idArtist"); // Prepare album artist subquery SQL @@ -10843,7 +11189,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription albumArtistSub.AppendWhere(PrepareSQL("EXISTS(SELECT 1 FROM album_source " "WHERE album_source.idSource = %i " "AND album_source.idAlbum = album_artist.idAlbum)", idSource)); - } + } } if (idRole <= 1 && idGenre > 0) { // Check genre of songs of album using nested subquery @@ -10867,7 +11213,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription "WHERE album_source.idSource = %i " "AND album_source.idAlbum = song.idAlbum))", idGenre, idSource)); } - else + else { if (idGenre > 0) { @@ -10886,7 +11232,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription songArtistSub.AppendJoin("JOIN song ON song.idSong = song_artist.idSong"); songArtistSub.param = "song_artist.idArtist = album_artist.idArtist"; songArtistSub.AppendWhere("song.idAlbum = album_artist.idAlbum"); - } + } } // Build filter clause from subqueries @@ -10932,6 +11278,10 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription if (option != options.end()) filter.AppendWhere(PrepareSQL("albumview.bCompilation = %i", option->second.asBoolean() ? 1 : 0)); + option = options.find("boxset"); + if (option != options.end()) + filter.AppendWhere(PrepareSQL("albumview.bBoxedSet = %i", option->second.asBoolean() ? 1 : 0)); + if (idSource > 0) filter.AppendWhere(PrepareSQL("EXISTS(SELECT 1 FROM album_source " "WHERE album_source.idAlbum = albumview.idAlbum AND album_source.idSource = %i)", idSource)); diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index bcd9e4ccc3f50..1d76c00415e9f 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -128,6 +128,7 @@ class CMusicDatabase : public CDatabase \param iTrack [in] the track number and disc number of the song \param iDuration [in] the duration of the song \param iYear [in] the year of the song + \param strDiscSubtitle [in] subtitle of a disc if it belongs to a box-set \param iTimesPlayed [in] the number of times the song has been played \param iStartOffset [in] the start offset of the song (when using a single audio file with a .cue) \param iEndOffset [in] the end offset of the song (when using a single audio file with .cue) @@ -148,6 +149,7 @@ class CMusicDatabase : public CDatabase const std::string &artistDisp, const std::string &artistSort, const std::vector& genres, int iTrack, int iDuration, int iYear, + const std::string& strDiscSubtitle, const int iTimesPlayed, int iStartOffset, int iEndOffset, const CDateTime& dtLastPlayed, float rating, int userrating, int votes, const ReplayGain& replayGain); @@ -174,6 +176,7 @@ class CMusicDatabase : public CDatabase \param iTrack [in] the track number and disc number of the song \param iDuration [in] the duration of the song \param iYear [in] the year of the song + \param strDiscSubtitle [in] subtitle of a disc if it belongs to a box-set \param iTimesPlayed [in] the number of times the song has been played \param iStartOffset [in] the start offset of the song (when using a single audio file with a .cue) \param iEndOffset [in] the end offset of the song (when using a single audio file with .cue) @@ -191,6 +194,7 @@ class CMusicDatabase : public CDatabase const std::string &artistDisp, const std::string &artistSort, const std::vector& genres, int iTrack, int iDuration, int iYear, + const std::string& strDiscSubtitle, int iTimesPlayed, int iStartOffset, int iEndOffset, const CDateTime& dtLastPlayed, float rating, int userrating, int votes, const ReplayGain& replayGain); @@ -202,6 +206,7 @@ class CMusicDatabase : public CDatabase bool SetSongUserrating(const std::string &filePath, int userrating); bool SetSongUserrating(int idSong, int userrating); bool SetSongVotes(const std::string &filePath, int votes); + bool GetSongByDiscSubtitleAndAlbum(const std::string& strDiscSubtitle, int idAlbum); int GetSongByArtistAndAlbumAndTitle(const std::string& strArtist, const std::string& strAlbum, const std::string& strTitle); ///////////////////////////////////////////////// @@ -227,18 +232,20 @@ class CMusicDatabase : public CDatabase \param strArtistSort the album artist name(s) sort string \param strGenre the album genre(s) \param year the year + \param set the set (if any) that the album belongs to \param strRecordLabel the recording label \param strType album type (Musicbrainz release type e.g. "Broadcast, Soundtrack, live"), \param bCompilation if the album is a compilation \param releaseType "album" or "single" + \param strDiscSubtitle the titles of discs (if any) in the set \return the id of the album */ int AddAlbum(const std::string& strAlbum, const std::string& strMusicBrainzAlbumID, const std::string& strReleaseGroupMBID, const std::string& strArtist, const std::string& strArtistSort, - const std::string& strGenre, int year, + const std::string& strGenre, int year, bool idSet, const std::string& strRecordLabel, const std::string& strType, - bool bCompilation, CAlbum::ReleaseType releaseType); + bool bCompilation, CAlbum::ReleaseType releaseType ); /*! \brief retrieve an album, optionally with all songs. \param idAlbum the database id of the album. @@ -256,7 +263,8 @@ class CMusicDatabase : public CDatabase const std::string& strThemes, const std::string& strReview, const std::string& strImage, const std::string& strLabel, const std::string& strType, - float fRating, int iUserrating, int iVotes, int iYear, bool bCompilation, + float fRating, int iUserrating, int iVotes, int iYear, bool bBoxedSet, + bool bCompilation, CAlbum::ReleaseType releaseType, bool bScrapedMBID); bool ClearAlbumLastScrapedTime(int idAlbum); @@ -283,6 +291,7 @@ class CMusicDatabase : public CDatabase std::string GetAlbumById(int id); bool SetAlbumUserrating(const int idAlbum, int userrating); + ///////////////////////////////////////////////// // Artist CRUD ///////////////////////////////////////////////// @@ -418,9 +427,18 @@ class CMusicDatabase : public CDatabase bool GetCompilationAlbums(const std::string& strBaseDir, CFileItemList& items); bool GetCompilationSongs(const std::string& strBaseDir, CFileItemList& items); int GetCompilationAlbumsCount(); - int GetSinglesCount(); + //////////////////////////////////////////////// + // Boxsets + //////////////////////////////////////////////// + bool GetBoxsetsAlbums(const std::string& strBaseDir, CFileItemList& items); + bool GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList& items, int idAlbum = -1, const SortDescription &sortDescription = SortDescription()); + bool GetBoxsetDiscSongs(const std::string& strBaseDir, CFileItemList& items); + int GetBoxsetsCount(); + bool RemoveBoxset(int albumId); +//////////////////////////////////////////////// + int GetArtistCountForRole(int role); int GetArtistCountForRole(const std::string& strRole); @@ -437,6 +455,7 @@ class CMusicDatabase : public CDatabase bool GetGenresNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter(), bool countOnly = false); bool GetSourcesNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter(), bool countOnly = false); bool GetYearsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); + bool GetBoxsetsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); bool GetRolesNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); bool GetArtistsNav(const std::string& strBaseDir, CFileItemList& items, bool albumArtistsOnly = false, int idGenre = -1, int idAlbum = -1, int idSong = -1, const Filter &filter = Filter(), const SortDescription &sortDescription = SortDescription(), bool countOnly = false); bool GetCommonNav(const std::string &strBaseDir, const std::string &table, const std::string &labelField, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */); @@ -464,7 +483,7 @@ class CMusicDatabase : public CDatabase unsigned int GetRandomSongIDs(const Filter &filter, std::vector > &songIDs); ///////////////////////////////////////////////// - // JSON-RPC + // JSON-RPC ///////////////////////////////////////////////// bool GetGenresJSON(CFileItemList& items, bool bSources = false); bool GetArtistsByWhereJSON(const std::set& fields, const std::string& baseDir, @@ -664,7 +683,7 @@ void SetLibraryLastUpdated(); void GetFileItemFromDataset(CFileItem* item, const CMusicDbUrl &baseUrl); void GetFileItemFromDataset(const dbiplus::sql_record* const record, CFileItem* item, const CMusicDbUrl &baseUrl); void GetFileItemFromArtistCredits(VECARTISTCREDITS& artistCredits, CFileItem* item); - + bool CleanupSongs(CGUIDialogProgress* progressDialog = nullptr); bool CleanupSongsByIds(const std::string &strSongIds); bool CleanupPaths(); @@ -690,26 +709,26 @@ void SetLibraryLastUpdated(); \param sortAttributes the sort attributes e.g. SortAttributeIgnoreArticle \param strField original name or title field that articles could be removed from \param strSortField sort name or title field to be used instead of original (when data not null) - \return SQL string e.g. - CASE WHEN strArtistSort IS NOT NULL THEN strArtistSort + \return SQL string e.g. + CASE WHEN strArtistSort IS NOT NULL THEN strArtistSort WHEN strField LIKE 'the ' OR strField LIKE 'the_' ESCAPE '_' THEN SUBSTR(strArtist, 5) ELSE strField END AS strAlias */ - std::string SortnameBuildSQL(const std::string& strAlias, const SortAttribute& sortAttributes, + std::string SortnameBuildSQL(const std::string& strAlias, const SortAttribute& sortAttributes, const std::string& strField, const std::string& strSortField); /*! \brief Build SQL for sorting field naturally and case insensitvely (in SQLite). \param strField field name \param sortOrder the sort order - \return SQL string e.g. - CASE WHEN CAST(strTitle AS INTEGER) = 0 THEN 100000000 + \return SQL string e.g. + CASE WHEN CAST(strTitle AS INTEGER) = 0 THEN 100000000 ELSE CAST(strTitle AS INTEGER) END DESC, strTitle COLLATE NOCASE DESC */ std::string AlphanumericSortSQL(const std::string& strField, const SortOrder& sortOrder); /*! \brief Checks that source table matches sources.xml - returns true when they do + returns true when they do */ bool CheckSources(VECSOURCES& sources); @@ -733,6 +752,7 @@ void SetLibraryLastUpdated(); song_iTrack, song_iDuration, song_iYear, + song_strDiscSubtitle, song_strFileName, song_strMusicBrainzTrackID, song_iTimesPlayed, @@ -747,6 +767,7 @@ void SetLibraryLastUpdated(); song_strAlbum, song_strPath, song_bCompilation, + song_bBoxedSet, song_strAlbumArtists, song_strAlbumArtistSort, song_strAlbumReleaseType, @@ -768,6 +789,7 @@ void SetLibraryLastUpdated(); album_strArtistSort, album_strGenres, album_iYear, + album_bBoxedSet, album_strMoods, album_strStyles, album_strThemes, @@ -785,6 +807,7 @@ void SetLibraryLastUpdated(); album_strReleaseType, album_dtDateAdded, album_dtLastPlayed, + album_iDiscNo, // filled on the fly album_enumCount // end of the enum, do not add past here } AlbumFields; @@ -869,7 +892,7 @@ void SetLibraryLastUpdated(); // Fields fetched by GetSongsByWhereJSON, order same as in JSONtoDBSong static enum _JoinToSongFields { - // Used by GetSongsByWhereJSON + // Used by GetSongsByWhereJSON joinToSongs_idAlbumArtist = 0, joinToSongs_strAlbumArtist, joinToSongs_strAlbumArtistMBID, diff --git a/xbmc/music/MusicDbUrl.cpp b/xbmc/music/MusicDbUrl.cpp index 181bccd3a0146..6d3f3505eaa06 100644 --- a/xbmc/music/MusicDbUrl.cpp +++ b/xbmc/music/MusicDbUrl.cpp @@ -44,6 +44,8 @@ bool CMusicDbUrl::parse() case NODE_TYPE_ALBUM_TOP100: case NODE_TYPE_ALBUM_COMPILATIONS: case NODE_TYPE_YEAR_ALBUM: + case NODE_TYPE_BOXSETS: + case NODE_TYPE_BOXSET_DISCS: m_type = "albums"; break; @@ -51,6 +53,7 @@ bool CMusicDbUrl::parse() case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS: case NODE_TYPE_ALBUM_TOP100_SONGS: case NODE_TYPE_ALBUM_COMPILATIONS_SONGS: + case NODE_TYPE_BOXSET_DISC_SONGS: case NODE_TYPE_SONG: case NODE_TYPE_SONG_TOP100: case NODE_TYPE_YEAR_SONG: @@ -73,14 +76,19 @@ bool CMusicDbUrl::parse() case NODE_TYPE_ALBUM_RECENTLY_PLAYED: case NODE_TYPE_ALBUM_TOP100: case NODE_TYPE_YEAR_ALBUM: + case NODE_TYPE_BOXSETS: + case NODE_TYPE_BOXSET_DISCS: m_type = "albums"; break; + + case NODE_TYPE_SONG: case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS: case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS: case NODE_TYPE_ALBUM_TOP100_SONGS: case NODE_TYPE_ALBUM_COMPILATIONS_SONGS: + case NODE_TYPE_BOXSET_DISC_SONGS: case NODE_TYPE_SONG_TOP100: case NODE_TYPE_YEAR_SONG: case NODE_TYPE_SINGLES: diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp index c2d6146f8616d..89ac9c570238a 100644 --- a/xbmc/music/Song.cpp +++ b/xbmc/music/Song.cpp @@ -57,6 +57,7 @@ CSong::CSong(CFileItem& item) userrating = tag.GetUserrating(); votes = tag.GetVotes(); iYear = stTime.wYear; + strDiscSubtitle = tag.GetDiscSubtitle(); iTrack = tag.GetTrackAndDiscNumber(); iDuration = tag.GetDuration(); strRecordLabel = tag.GetRecordLabel(); @@ -221,6 +222,7 @@ void CSong::Serialize(CVariant& value) const value["duration"] = iDuration; value["track"] = iTrack; value["year"] = iYear; + value['discsubtitle'] = strDiscSubtitle; value["musicbrainztrackid"] = strMusicBrainzTrackID; value["comment"] = strComment; value["mood"] = strMood; @@ -254,6 +256,7 @@ void CSong::Clear() iTrack = 0; iDuration = 0; iYear = 0; + strDiscSubtitle.clear(); iStartOffset = 0; iEndOffset = 0; idSong = -1; @@ -349,3 +352,9 @@ bool CSong::ArtMatches(const CSong &right) const return (right.strThumb == strThumb && embeddedArt.Matches(right.embeddedArt)); } +const std::string CSong::GetDiscSubtitle() const +{ + if (!strDiscSubtitle.empty()) + return strDiscSubtitle; + return ""; +} diff --git a/xbmc/music/Song.h b/xbmc/music/Song.h index 8dde36f1420fd..ea52b7ccc8536 100644 --- a/xbmc/music/Song.h +++ b/xbmc/music/Song.h @@ -142,6 +142,8 @@ class CSong final : public ISerializable */ bool ArtMatches(const CSong &right) const; + const std::string GetDiscSubtitle() const; + /*! \brief Set artist credits using the arrays of tag values. If strArtistSort (as from ARTISTSORT tag) is already set then individual artist sort names are also processed. @@ -173,6 +175,7 @@ class CSong final : public ISerializable int iTrack; int iDuration; int iYear; + std::string strDiscSubtitle; int iTimesPlayed; CDateTime lastPlayed; CDateTime dateAdded; diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp index 862b0e4e1dce5..ef67fd1ee15fe 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.cpp +++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp @@ -148,6 +148,7 @@ void CMusicInfoScanner::Process() { // Set local art for added album disc sets and primary album artists RetrieveLocalArt(); +// CreateBoxSets(); if (m_flags & SCAN_ONLINE) // Download additional album and artist information for the recently added albums. @@ -300,7 +301,7 @@ void CMusicInfoScanner::Start(const std::string& strDirectory, int flags) } else { - m_pathsToScan.insert(strDirectory); + m_pathsToScan.insert(strDirectory); m_idSourcePath = m_musicDatabase.GetSourceFromPath(strDirectory); } m_musicDatabase.Close(); @@ -617,8 +618,6 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album { CMusicInfoTag& tag = *items[i]->GetMusicInfoTag(); CSong song(*items[i]); - - // keep the db-only fields intact on rescan... if (songsMap != NULL) { MAPSONGS::iterator it = songsMap->find(items[i]->GetPath()); @@ -631,7 +630,6 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album if (song.strThumb.empty()) song.strThumb = it->second.strThumb; } } - if (!tag.GetMusicBrainzAlbumID().empty()) { VECALBUMS::iterator it; @@ -657,6 +655,8 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album In the case where the album artist is unknown, we use the primary artist (i.e. first artist from each song). */ + + for (auto& songsByAlbumName : songsByAlbumNames) { VECSONGS& songs = songsByAlbumName.second; @@ -667,7 +667,7 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album bool tracksOverlap = false; bool hasAlbumArtist = false; bool isCompilation = true; - + std::string old_DiscSubtitle; std::map > artists; for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song) { @@ -677,6 +677,8 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album if (!song->bCompilation) isCompilation = false; + if (song->strDiscSubtitle != old_DiscSubtitle) + old_DiscSubtitle = song->strDiscSubtitle; // get primary artist std::string primary; @@ -823,6 +825,7 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album album.strType = k->strAlbumType; album.songs.push_back(*k); } +// album.strDiscTitles = t_discTitles; albums.push_back(album); } } @@ -886,6 +889,7 @@ int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileI */ FindArtForAlbums(albums, items.GetPath()); + CheckBoxSets(albums); /* Strategy: Having scanned tags and made a list of albums, add them to the library. Only then try to scrape additional album and artist information. Music is often tagged to a mixed standard - some albums have mbid tags, some don't. Once all the music files have been added to the library, @@ -919,6 +923,60 @@ int CMusicInfoScanner::RetrieveMusicInfo(const std::string& strDirectory, CFileI return numAdded; } +void CMusicInfoScanner::CheckBoxSets(VECALBUMS &albums) +{ + /* Strategy: Having scanned the tags of the albums to add to the library, + * check for disc subtitles. If an album only has one subtitled disc, we don't + * consider it a boxset as its most likely a 'live'version of the album or a 'bonus disc'. + * Single disc subtitles are still added to the database so they can be displayed by + * skins at appropriate times, If 'boxset' was added to the albumreleasetype then add the + * album regardless of any other rules. */ + + std::string m_oldDiscSubtitle; + int count; + int discno; + int old_discno = 0; + for(auto& album : albums) + { + if (album.bCompilation && !album.bBoxedSet) + { + album.bBoxedSet = false; + continue; // skip compilations that haven't had the boxset tag added to them + } + // + count = 0; + for (auto& song : album.songs) + { + if (!song.strDiscSubtitle.empty()) + { + if (song.strDiscSubtitle != "dummy") + { + if (song.strDiscSubtitle != m_oldDiscSubtitle) + { + m_oldDiscSubtitle = song.strDiscSubtitle; +// album.strDiscTitles.append(song.strDiscSubtitle).append(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + count ++; + if (count > 2) + album.bBoxedSet = true; + } + } + else + { + discno = song.iTrack >> 16; + song.strDiscSubtitle = StringUtils::Format("Disc %i", discno); // create dummy titles + if (discno != old_discno) + { +// album.strDiscTitles.append(song.strDiscSubtitle).append(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + old_discno = discno; + album.bBoxedSet = true; + } + } + } + if (count < 3 && !album.bBoxedSet) // must have more than 2 titled discs to be considered a boxset unless tag was set + album.bBoxedSet = false; } + } +} + void MUSIC_INFO::CMusicInfoScanner::ScrapeInfoAddedAlbums() { /* Strategy: Having scanned tags, make a list of albums and add them to the library, only then try @@ -1454,7 +1512,7 @@ CMusicInfoScanner::DownloadAlbumInfo(const CAlbum& album, CInfoScanner::INFO_TYPE result = CInfoScanner::NO_NFO; CNfoFile nfoReader; existsNFO = XFILE::CFile::Exists(strNfo); - // When on GUI ask user if they want to ignore nfo and refresh from Internet + // When on GUI ask user if they want to ignore nfo and refresh from Internet if (existsNFO && pDialog && CGUIDialogYesNo::ShowAndGetInput(10523, 20446)) { existsNFO = false; @@ -1741,7 +1799,7 @@ CMusicInfoScanner::DownloadArtistInfo(const CArtist& artist, CLog::Log(LOGDEBUG, "%s not have path, nfo file not possible", artist.strArtist.c_str()); } - // When on GUI ask user if they want to ignore nfo and refresh from Internet + // When on GUI ask user if they want to ignore nfo and refresh from Internet if (existsNFO && pDialog && CGUIDialogYesNo::ShowAndGetInput(21891, 20446)) { existsNFO = false; diff --git a/xbmc/music/infoscanner/MusicInfoScanner.h b/xbmc/music/infoscanner/MusicInfoScanner.h index ec6dff3f850b0..b84f0026c4432 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.h +++ b/xbmc/music/infoscanner/MusicInfoScanner.h @@ -95,6 +95,8 @@ class CMusicInfoScanner : public IRunnable, public CInfoScanner */ static void FindArtForAlbums(VECALBUMS &albums, const std::string &path); + static void CheckBoxSets(VECALBUMS &albums); + /*! \brief Scrape additional album information and update the database. Search for the given album using the given scraper. If info is found, update the database and artwork with the new diff --git a/xbmc/music/tags/MusicInfoTag.cpp b/xbmc/music/tags/MusicInfoTag.cpp index e03c1fe13e0ab..81e0fda498efa 100644 --- a/xbmc/music/tags/MusicInfoTag.cpp +++ b/xbmc/music/tags/MusicInfoTag.cpp @@ -16,6 +16,7 @@ #include "utils/Archive.h" #include "utils/StringUtils.h" #include "utils/Variant.h" +#include "utils/log.h" #include @@ -36,6 +37,7 @@ bool CMusicInfoTag::operator !=(const CMusicInfoTag& tag) const if (m_albumArtist != tag.m_albumArtist) return true; if (m_strAlbum != tag.m_strAlbum) return true; if (m_iDuration != tag.m_iDuration) return true; + if (m_strDiscSubtitle != tag.m_strDiscSubtitle) return true; if (m_iTrack != tag.m_iTrack) return true; if (m_albumReleaseType != tag.m_albumReleaseType) return true; return false; @@ -101,6 +103,11 @@ const std::string& CMusicInfoTag::GetAlbum() const return m_strAlbum; } +const std::string& CMusicInfoTag::GetDiscSubtitle() const +{ + return m_strDiscSubtitle; +} + int CMusicInfoTag::GetAlbumId() const { return m_iAlbumId; @@ -221,6 +228,11 @@ bool CMusicInfoTag::GetCompilation() const return m_bCompilation; } +bool CMusicInfoTag::GetBoxset() const +{ + return m_bBoxset; +} + const EmbeddedArtInfo &CMusicInfoTag::GetCoverArtInfo() const { return m_coverArt; @@ -368,6 +380,11 @@ void CMusicInfoTag::SetDiscNumber(int iDiscNumber) m_iTrack = (m_iTrack & 0xffff) | (iDiscNumber << 16); } +void CMusicInfoTag::SetDiscSubtitle(const std::string& strDiscSubtitle) +{ + m_strDiscSubtitle = strDiscSubtitle; +} + void CMusicInfoTag::SetTrackAndDiscNumber(int iTrackAndDisc) { m_iTrack = iTrackAndDisc; @@ -461,6 +478,11 @@ void CMusicInfoTag::SetCompilation(bool compilation) m_bCompilation = compilation; } +void CMusicInfoTag::SetBoxset(bool boxset) +{ + m_bBoxset = boxset; +} + void CMusicInfoTag::SetLoaded(bool bOnOff) { m_bLoaded = bOnOff; @@ -614,6 +636,7 @@ void CMusicInfoTag::SetAlbum(const CAlbum& album) SetCompilation(album.bCompilation); SYSTEMTIME stTime; stTime.wYear = album.iYear; + SetBoxset(album.bBoxedSet); SetReleaseDate(stTime); SetAlbumReleaseType(album.releaseType); SetDateAdded(album.dateAdded); diff --git a/xbmc/music/tags/MusicInfoTag.h b/xbmc/music/tags/MusicInfoTag.h index 8bd5053c7cd74..d40cde9e59831 100644 --- a/xbmc/music/tags/MusicInfoTag.h +++ b/xbmc/music/tags/MusicInfoTag.h @@ -50,6 +50,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo int GetYear() const; int GetDatabaseId() const; const std::string &GetType() const; + const std::string& GetDiscSubtitle() const; void GetReleaseDate(SYSTEMTIME& dateTime) const; std::string GetYearString() const; @@ -69,6 +70,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo const CDateTime& GetLastPlayed() const; const CDateTime& GetDateAdded() const; bool GetCompilation() const; + bool GetBoxset() const; float GetRating() const; int GetUserrating() const; int GetVotes() const; @@ -127,10 +129,12 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo void SetDateAdded(const std::string& strDateAdded); void SetDateAdded(const CDateTime& strDateAdded); void SetCompilation(bool compilation); + void SetBoxset(bool boxset); void SetCoverArtInfo(size_t size, const std::string &mimeType); void SetReplayGain(const ReplayGain& aGain); void SetAlbumReleaseType(CAlbum::ReleaseType releaseType); void SetType(const MediaType mediaType); + void SetDiscSubtitle(const std::string& strDiscSubtitle); /*! \brief Append a unique artist to the artist list Checks if we have this artist already added, and if not adds it to the songs artist list. @@ -198,6 +202,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo std::string m_strRecordLabel; std::string m_strLyrics; std::string m_cuesheet; + std::string m_strDiscSubtitle; CDateTime m_lastPlayed; CDateTime m_dateAdded; bool m_bCompilation; @@ -212,6 +217,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo int m_listeners; int m_iTimesPlayed; int m_iAlbumId; + bool m_bBoxset; SYSTEMTIME m_dwReleaseDate; CAlbum::ReleaseType m_albumReleaseType; diff --git a/xbmc/music/tags/TagLoaderTagLib.cpp b/xbmc/music/tags/TagLoaderTagLib.cpp index 51e4be4417330..d6b421c6a9ed4 100644 --- a/xbmc/music/tags/TagLoaderTagLib.cpp +++ b/xbmc/music/tags/TagLoaderTagLib.cpp @@ -183,6 +183,8 @@ bool CTagLoaderTagLib::ParseTag(ASF::Tag *asf, EmbeddedArt *art, CMusicInfoTag& {} // Known unsupported, suppress warnings else if (it->first == "WM/Year") tag.SetYear(atoi(it->second.front().toString().toCString(true))); + else if (it->first == "WM/SetSubTitle") + tag.SetDiscSubtitle(it->second.front().toString().to8Bit(true)); else if (it->first == "MusicBrainz/Artist Id") tag.SetMusicBrainzArtistID(SplitMBID(GetASFStringList(it->second))); else if (it->first == "MusicBrainz/Album Id") @@ -308,6 +310,7 @@ bool CTagLoaderTagLib::ParseTag(ID3v2::Tag *id3v2, EmbeddedArt *art, MUSIC_INFO: else if (it->first == "TDTG") {} // Tagging time else if (it->first == "TLAN") {} // Languages else if (it->first == "TMOO") tag.SetMood(it->second.front()->toString().to8Bit(true)); + else if (it->first == "TSST") tag.SetDiscSubtitle(it->second.front()->toString().to8Bit(true)); else if (it->first == "USLT") // Loop through any lyrics frames. Could there be multiple frames, how to choose? for (ID3v2::FrameList::ConstIterator lt = it->second.begin(); lt != it->second.end(); ++lt) @@ -500,6 +503,8 @@ bool CTagLoaderTagLib::ParseTag(APE::Tag *ape, EmbeddedArt *art, CMusicInfoTag& tag.SetDiscNumber(it->second.toString().toInt()); else if (it->first == "YEAR") tag.SetYear(it->second.toString().toInt()); + else if (it->first == "DISCSUBTITLE") + tag.SetDiscSubtitle(it->second.toString().to8Bit(true)); else if (it->first == "GENRE") SetGenre(tag, StringListToVectorString(it->second.toStringList())); else if (it->first == "MOOD") @@ -644,6 +649,8 @@ bool CTagLoaderTagLib::ParseTag(Ogg::XiphComment *xiph, EmbeddedArt *art, CMusic tag.SetComment(it->second.front().to8Bit(true)); else if (it->first == "CUESHEET") tag.SetCueSheet(it->second.front().to8Bit(true)); + else if (it->first == "DISCSUBTITLE") + tag.SetDiscSubtitle(it->second.front().to8Bit(true)); else if (it->first == "ENCODEDBY") {} // Known but unsupported, suppress warnings else if (it->first == "COMPOSER") @@ -844,6 +851,8 @@ bool CTagLoaderTagLib::ParseTag(MP4::Tag *mp4, EmbeddedArt *art, CMusicInfoTag& //No MP4 standard tag for musician credits else if (it->first == "----:com.apple.iTunes:LABEL") tag.SetRecordLabel(it->second.toStringList().front().to8Bit(true)); + else if (it->first == "----:com.apple.iTunes:DISCSUBTITLE") + tag.SetDiscSubtitle(it->second.toStringList().front().to8Bit(true)); else if (it->first == "cpil") tag.SetCompilation(it->second.toBool()); else if (it->first == "trkn") @@ -1048,10 +1057,27 @@ void CTagLoaderTagLib::SetGenre(CMusicInfoTag &tag, const std::vector &values) { + tag.SetBoxset(false); if (values.size() == 1) + { + if (values[0].find("boxset") != std::string::npos) + { + tag.SetBoxset(true); + if (tag.GetDiscSubtitle().empty()) // no disc title for the tracks on this boxset disc + tag.SetDiscSubtitle("dummy"); + } tag.SetMusicBrainzReleaseType(values[0]); + } else + { tag.SetMusicBrainzReleaseType(StringUtils::Join(values, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator)); + if (tag.GetMusicBrainzReleaseType().find("boxset") != std::string::npos) + { + tag.SetBoxset(true); + if (tag.GetDiscSubtitle().empty()) + tag.SetDiscSubtitle("dummy"); + } + } } void CTagLoaderTagLib::AddArtistRole(CMusicInfoTag &tag, const std::string& strRole, const std::vector &values) @@ -1062,6 +1088,14 @@ void CTagLoaderTagLib::AddArtistRole(CMusicInfoTag &tag, const std::string& strR tag.AddArtistRole(strRole, values); } +void CTagLoaderTagLib::SetDiscSubtitle(CMusicInfoTag & tag, const::std::vector &values) +{ + if (values.size() == 1) + tag.SetDiscSubtitle(values[0]); + else + tag.SetDiscSubtitle(std::string()); +} + void CTagLoaderTagLib::AddArtistRole(CMusicInfoTag &tag, const std::vector &values) { // Values contains role, name pairs (as in ID3 standard for TIPL or TMCL tags) diff --git a/xbmc/music/tags/TagLoaderTagLib.h b/xbmc/music/tags/TagLoaderTagLib.h index febe8850e3e4c..c9adcb9a700ee 100644 --- a/xbmc/music/tags/TagLoaderTagLib.h +++ b/xbmc/music/tags/TagLoaderTagLib.h @@ -44,9 +44,9 @@ class CTagLoaderTagLib : public MUSIC_INFO::IMusicInfoTagLoader static void AddArtistRole(MUSIC_INFO::CMusicInfoTag &tag, const std::string& strRole, const std::vector &values); static void AddArtistRole(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); static void AddArtistInstrument(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); + static void SetDiscSubtitle(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); static int POPMtoXBMC(int popm); template static bool ParseTag(T *tag, EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& infoTag); }; - diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index 2a7e6385c1d16..0ccd47f9f125c 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -1128,16 +1128,16 @@ void CGUIWindowMusicBase::DoScan(const std::string &strPath, bool bRescan /*= fa void CGUIWindowMusicBase::OnRemoveSource(int iItem) { - + //Remove music source from library, even when leaving songs CMusicDatabase database; database.Open(); database.RemoveSource(m_vecItems->Get(iItem)->GetLabel()); - + bool bCanceled; if (CGUIDialogYesNo::ShowAndGetInput(CVariant{522}, CVariant{20340}, bCanceled, CVariant{""}, CVariant{""}, CGUIDialogYesNo::NO_TIMEOUT)) { - MAPSONGS songs; + MAPSONGS songs; database.RemoveSongsFromPath(m_vecItems->Get(iItem)->GetPath(), songs, false); database.CleanupOrphanedItems(); CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider().ResetLibraryBools(); @@ -1180,4 +1180,3 @@ void CGUIWindowMusicBase::OnAssignContent(const std::string& oldName, const CMed g_application.StartMusicScan(source.strPath, true); } - diff --git a/xbmc/music/windows/GUIWindowMusicBase.h b/xbmc/music/windows/GUIWindowMusicBase.h index 9d47f52a8357a..cb6d339d81a59 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.h +++ b/xbmc/music/windows/GUIWindowMusicBase.h @@ -49,7 +49,7 @@ class CGUIWindowMusicBase : public CGUIMediaWindow, public IBackgroundLoaderObse /*! \brief Once a music source is added, store source in library, and prompt the user to scan this folder into the library - \param oldName the original music source name + \param oldName the original music source name \param source details of the music source (just added or edited) */ static void OnAssignContent(const std::string& oldName, const CMediaSource& source); diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index 8809b8a3801ab..fe255b9175985 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -241,6 +241,8 @@ std::string CGUIWindowMusicNav::GetQuickpathName(const std::string& strPath) con return "Singles"; else if (path == "special://musicplaylists/") return "Playlists"; + else if (path == "musicdb://boxsets/") + return "Boxsets"; else { CLog::Log(LOGERROR, " CGUIWindowMusicNav::GetQuickpathName: Unknown parameter (%s)", strPath.c_str()); @@ -450,7 +452,9 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem node == NODE_TYPE_ALBUM_RECENTLY_PLAYED || node == NODE_TYPE_ALBUM_TOP100 || node == NODE_TYPE_ALBUM_COMPILATIONS || - node == NODE_TYPE_YEAR_ALBUM) + node == NODE_TYPE_YEAR_ALBUM || + node == NODE_TYPE_BOXSETS || + node == NODE_TYPE_BOXSET_DISCS) items.SetContent("albums"); else if (node == NODE_TYPE_ARTIST) items.SetContent("artists"); @@ -461,7 +465,8 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem node == NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS || node == NODE_TYPE_ALBUM_COMPILATIONS_SONGS || node == NODE_TYPE_ALBUM_TOP100_SONGS || - node == NODE_TYPE_YEAR_SONG) + node == NODE_TYPE_YEAR_SONG || + node == NODE_TYPE_BOXSET_DISC_SONGS) items.SetContent("songs"); else if (node == NODE_TYPE_GENRE) items.SetContent("genres"); @@ -622,8 +627,10 @@ void CGUIWindowMusicNav::GetContextButtons(int itemNumber, CContextButtons &butt !item->IsPlugin() && !StringUtils::StartsWithNoCase(item->GetPath(), "musicsearch://")) { if (item->IsAlbum()) + { // enable query all albums button only in album view buttons.Add(CONTEXT_BUTTON_INFO_ALL, 20059); + } else if (dir.IsArtistDir(item->GetPath())) // enable query all artist button only in artist view buttons.Add(CONTEXT_BUTTON_INFO_ALL, 21884); @@ -952,6 +959,8 @@ std::string CGUIWindowMusicNav::GetStartFolder(const std::string &dir) return "musicdb://compilations/"; else if (lower == "years") return "musicdb://years/"; + else if (lower == "boxsets") + return "musicdb://boxsets/"; else if (lower == "files") return "sources://music/"; From bdca0e74795398f21a412eef13cd63ffb9c6f54c Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Sat, 31 Aug 2019 15:03:58 +0100 Subject: [PATCH 2/7] Add listitems (DiscName and IsBoxset) Remove unused code, switch to using pItem->SetArt Add support for reading disc subtitles Bump db version to 73. Update album & song tables for box sets Remove strDiscSubtitle from artist table --- .../skin.estuary/xml/MusicVisualisation.xml | 191 ++-- addons/skin.estuary/xml/Variables.xml | 946 +++++++++--------- addons/skin.estuary/xml/View_50_List.xml | 539 +++++----- addons/skin.estuary/xml/View_54_InfoWall.xml | 938 +++++++++-------- xbmc/guilib/guiinfo/GUIInfoLabels.h | 3 + xbmc/music/MusicDatabase.cpp | 111 +- xbmc/music/MusicDatabase.h | 7 +- xbmc/music/infoscanner/MusicInfoScanner.cpp | 2 - xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp | 2 + 9 files changed, 1315 insertions(+), 1424 deletions(-) diff --git a/addons/skin.estuary/xml/MusicVisualisation.xml b/addons/skin.estuary/xml/MusicVisualisation.xml index afb682b5ec819..3bbab7da638ef 100644 --- a/addons/skin.estuary/xml/MusicVisualisation.xml +++ b/addons/skin.estuary/xml/MusicVisualisation.xml @@ -1,103 +1,94 @@ - - background - RunScript(script.artistslideshow) - - - FullScreenDimensions - Player.HasAudio - - - DepthBackground - FullScreenDimensions - !Skin.HasSetting(hide_background_fanart) - Conditional - Conditional - - scale - 400 - WindowOpen - WindowClose - $INFO[Player.Art(fanart)] - - - scale - 10000 - true - 600 - yes - $INFO[Window(Visualisation).Property(ArtistSlideshow)] - System.HasAddon(script.artistslideshow) - - - - Conditional - ColoredBackgroundImages - - - 0 - 1080 - OpenClose_Left - - 33 - 200 - Visible_Left - [Player.ShowInfo | Window.IsActive(musicosd)] + !MusicPlayer.Content(livetv) - 500 - 500 - 400 - keep - $INFO[MusicPlayer.Cover] - colors/black.png - 4 - - - -30 - [Player.ShowInfo | Window.IsActive(musicosd)] + ![Window.IsActive(playerprocessinfo) | MusicPlayer.Content(livetv)] - Visible_Left - - 30 - 740 - - 0 - 1600 - 40 - - font60 - black - true - - - 80 - 1600 - 40 - - font37 - black - true - - - 120 - 1600 - 40 - - font40 - black - true - - - 167 - 1600 - 40 - - font45 - black - button_focus - true - - - - - + + background + RunScript(script.artistslideshow) + + + FullScreenDimensions + Player.HasAudio + + + DepthBackground + FullScreenDimensions + !Skin.HasSetting(hide_background_fanart) + Conditional + Conditional + + scale + 400 + WindowOpen + WindowClose + $INFO[Player.Art(fanart)] + + + scale + 10000 + true + 600 + yes + $INFO[Window(Visualisation).Property(ArtistSlideshow)] + System.HasAddon(script.artistslideshow) + + + + Conditional + ColoredBackgroundImages + + + 0 + 1080 + OpenClose_Left + + 33 + 200 + Visible_Left + [Player.ShowInfo | Window.IsActive(musicosd)] + !MusicPlayer.Content(livetv) + 500 + 500 + 400 + keep + $INFO[MusicPlayer.Cover] + colors/black.png + 4 + + + -30 + [Player.ShowInfo | Window.IsActive(musicosd)] + ![Window.IsActive(playerprocessinfo) | MusicPlayer.Content(livetv)] + Visible_Left + + 30 + 740 + + 0 + 1600 + 40 + + font60 + black + true + + + 80 + 1600 + 40 + + font37 + black + true + + + 127 + 1600 + 40 + + font45 + black + button_focus + true + + + + + diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 7c5417e2b2ef1..ab9bae788e96e 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -1,476 +1,476 @@ - - windows/pvr/record.png - windows/pvr/timer.png - windows/pvr/archive.png - - - plugin://plugin.program.autocompletion?info=autocomplete&&id=$INFO[Control.GetLabel(312).index(1)]&&limit=9 - - - $INFO[ListItem.TVShowtitle,,: ]$INFO[ListItem.Season,,x]$INFO[ListItem.Episode,,. ]$INFO[ListItem.Title] - $INFO[ListItem.Artist,, - ]$INFO[ListItem.Title] - $INFO[ListItem.Label] - - - $INFO[ListItem.Duration] - $INFO[ListItem.Duration,, $LOCALIZE[12391]] - - - $INFO[ListItem.FolderPath] - - - - >9 - 9 - 8 - 7 - 6 - 5 - 4 - 3 - 2 - 1 - [COLOR grey]0[/COLOR] - - - $INFO[ListItem.Comment,[B]$LOCALIZE[569][/B][CR][COLOR=white],[/COLOR]] - $INFO[ListItem.Property(Album_Description),[COLOR=white],[/COLOR]] - $INFO[ListItem.Property(Artist_Description),[COLOR=white],[/COLOR]] - - - $INFO[VideoPlayer.TvShowTitle] - $INFO[VideoPlayer.Year]$INFO[VideoPlayer.Genre, - ] - $INFO[VideoPlayer.ChannelName] - $INFO[MusicPlayer.Artist] - - - icons/now-playing/pause.png - icons/now-playing/play.png - - - $INFO[Player.Art(poster)] - $INFO[Player.Art(tvshow.poster)] - DefaultTVShows.png - $INFO[Player.Icon] - - - $INFO[Listitem.Art(poster)] - DefaultFolderBackSquare.png - DefaultAudio.png - DefaultFolderSquare.png - $INFO[ListItem.Thumb] - - - $INFO[Listitem.Art(thumb)] - DefaultArtist.png - DefaultAlbumCover.png - DefaultAudio.png - - - $INFO[Listitem.Art(poster)] - $INFO[ListItem.Icon] - - - $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] - $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] - $INFO[Listitem.Art(poster)] - $INFO[ListItem.Thumb] - $INFO[ListItem.Icon] - - - $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] - $INFO[ListItem.Year] - $LOCALIZE[38026]: $INFO[ListItem.Appearances] - $INFO[ListItem.Label2] - - - $INFO[ListItem.Property(description),,[CR]]$INFO[ListItem.PictureDatetime,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]]$INFO[ListItem.PictureResolution,[COLOR button_focus]$LOCALIZE[169]: [/COLOR],[CR]]$INFO[ListItem.PictureCamMake,[COLOR button_focus]$LOCALIZE[31041]: [/COLOR],[CR]]$INFO[ListItem.PictureCamModel,[COLOR button_focus]$LOCALIZE[21823]: [/COLOR],[CR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_YearsActive),[COLOR button_focus]$LOCALIZE[21898]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_Style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] - $INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(album_label),[COLOR button_focus]$LOCALIZE[21899]: [/COLOR],[CR]]$INFO[ListItem.Property(album_style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Director,[COLOR button_focus]$LOCALIZE[20339]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR button_focus]$LOCALIZE[20417]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] - - - $INFO[ListItem.Size,[COLOR button_focus]$LOCALIZE[289]: [/COLOR],[CR]]$INFO[ListItem.PictureAperture,[COLOR button_focus]$LOCALIZE[21826]: [/COLOR],[CR]]$INFO[ListItem.PictureFocalLen,[COLOR button_focus]$LOCALIZE[21827]: [/COLOR],[CR]]$INFO[ListItem.PictureExpTime,[COLOR button_focus]$LOCALIZE[21830]: [/COLOR],[CR]]$INFO[ListItem.Date,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]] - $INFO[ListItem.Property(artist_description)] - $INFO[ListItem.Property(album_description)] - $INFO[ListItem.Plot] - - - [COLOR=button_focus]$INFO[Container(3).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(3).CurrentPage]/$INFO[Container(3).NumPages][/COLOR] - [COLOR=button_focus]$INFO[Container(6).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(6).CurrentPage]/$INFO[Container(6).NumPages][/COLOR] - - - [COLOR=button_focus]$INFO[Container(450).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(450).CurrentPage]/$INFO[Container(450).NumPages][/COLOR] - [COLOR=button_focus]$INFO[Container(451).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(451).CurrentPage]/$INFO[Container(451).NumPages][/COLOR] - - - DefaultBackBanner.png - $INFO[ListItem.Art(banner)] - $INFO[ListItem.Art(fanart)] - dialogs/dialog-bg-nobo.png - - - special://skin/extras/home-images/movie.jpg - $INFO[ListItem.Art(fanart)] - - - $INFO[ListItem.Addonnews] - $INFO[ListItem.AddonDescription] - $INFO[ListItem.Property(album_description)] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Plot] - $INFO[ListItem.Property(artist_description)] - $INFO[ListItem.Plot] - $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] - $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]] - - - $INFO[listitem.TrackNumber, ] - $INFO[listitem.TrackNumber, ,.]$INFO[listitem.Title, ] - - - $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] - DefaultGenre.png - - - $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] - DefaultStudio.png - - - $INFO[ListItem.Property(addon.status)] - $INFO[ListItem.Label2] - $INFO[ListItem.AddonCreator,, - ]$INFO[ListItem.AddonVersion] - - - $LOCALIZE[563] - $LOCALIZE[38018] - $LOCALIZE[16018] - - - $LOCALIZE[31165] - $LOCALIZE[31166] - $LOCALIZE[16018] - - - icons/addonstatus/disable.png - icons/addonstatus/orphan.png - icons/addonstatus/install.png - icons/addonstatus/disable.png - icons/addonstatus/update.png - OverlayWatched.png - OverlayUnwatched.png - - - flags/videoresolution/3D.png - $INFO[ListItem.VideoResolution,flags/videoresolution/,.png] - - - flags/videoresolution/3D.png - $INFO[Container.ListItem.VideoResolution,flags/videoresolution/,.png] - - - $INFO[ListItem.Art(banner)] - $INFO[ListItem.Art(tvshow.banner)] - - - DefaultFolderBackPoster.png - $INFO[Container.Art(season.poster)] - $INFO[Container.Art(tvshow.poster)] - $INFO[Container.Art(tvshow.poster)] - $INFO[Container.ListItem.Art(thumb)] - $INFO[ListItem.Art(tvshow.poster)] - $INFO[ListItem.Art(poster)] - - - $INFO[ListItem.Art(poster)] - $INFO[ListItem.Art(season.poster)] - $INFO[ListItem.Art(tvshow.poster)] - - - $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] - - - - 2x - 4x - 8x - 16x - 32x - - - $LOCALIZE[773][COLOR=grey] $INFO[Player.SeekStepSize][/COLOR] - $LOCALIZE[112] - $LOCALIZE[31039] $VAR[VideoPlayerForwardRewindVar] - $LOCALIZE[31038] $VAR[VideoPlayerForwardRewindVar] - $LOCALIZE[31142]: $INFO[Player.PlaySpeed] - - - [B]$INFO[Player.SeekNumeric(hh:mm:ss)][/B] - $INFO[PVR.EpgEventSeekTime]$INFO[PVR.EpgEventDuration, / ] - $INFO[Player.SeekTime]$INFO[Player.Duration, / ] - $INFO[PVR.EpgEventElapsedTime]$INFO[PVR.EpgEventDuration, / ] - $INFO[Player.Time]$INFO[Player.Duration, / ] - - - $LOCALIZE[31050] - $LOCALIZE[298] - $LOCALIZE[31106] - $LOCALIZE[24012] - $LOCALIZE[31092] - $LOCALIZE[19033] - $LOCALIZE[19019] - $LOCALIZE[19069] - $LOCALIZE[5] - $LOCALIZE[36501] - $LOCALIZE[19059] - $LOCALIZE[264] - $LOCALIZE[31054] - - - $LOCALIZE[31033]$INFO[MusicPlayer.UserRating, : ] - $LOCALIZE[19033] - $LOCALIZE[19019] - $LOCALIZE[19069] - $LOCALIZE[29900] - $LOCALIZE[486]$INFO[Playlist.Repeat, : ] - $LOCALIZE[590]: $LOCALIZE[16041] - $LOCALIZE[590]: $LOCALIZE[16039] - $LOCALIZE[24013] - $LOCALIZE[10004] - - - $LOCALIZE[31129] - $LOCALIZE[31130] - - - dialogs/volume/mute.png - dialogs/volume/volume.png - dialogs/volume/volume2.png - dialogs/volume/volume.png - dialogs/volume/volume1.png - - - - [COLOR grey]$INFO[ListItem.Year, (,)][/COLOR] - - - $INFO[ListItem.Title] - $INFO[ListItem.TVShowTitle]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] - $INFO[ListItem.Title]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] - $INFO[ListItem.Label]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] - - - $LOCALIZE[1024] - $LOCALIZE[208] - - - $INFO[ListItem.Season]$INFO[ListItem.Episode,[COLOR grey]x[/COLOR],: ]$INFO[ListItem.Title] - $INFO[ListItem.Tagline,[I],[/I]] - $INFO[ListItem.Genre] - - - $INFO[ListItem.LastPlayed,$LOCALIZE[568]: ] - $INFO[ListItem.FileNameAndPath] - - - $INFO[SlideShow.Filename] - $INFO[ListItem.Label] - - - $INFO[SlideShow.EXIFtime] - $INFO[ListItem.PictureDateTime] - - - $INFO[VideoPlayer.Title] - $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] - $INFO[VideoPlayer.TVShowTitle]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] - $INFO[VideoPlayer.Title]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] - $LOCALIZE[589] - $LOCALIZE[31000]... - - - $LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length] - $INFO[VideoPlayer.Artist]$INFO[VideoPlayer.Album, - ] - $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] - $INFO[VideoPlayer.ChannelNumberLabel,([COLOR button_focus],[/COLOR]) ]$INFO[VideoPlayer.ChannelName] $INFO[VideoPlayer.EpisodeName, - ] - $INFO[VideoPlayer.Genre] - - - $INFO[Player.Art(tvshow.clearlogo)] - $INFO[Player.Art(clearlogo)] - - - - $INFO[Window(home).Property(infobackground)] - $INFO[Container.ListItem.Art(fanart)] - $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] - - - - $INFO[Window(home).Property(infobackground)] - $INFO[Container.ListItem.Art(fanart)] - $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]power$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]settings$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]favorites$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]search$INFO[Skin.String(HomeFanart.ext)] - $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] - - - - $INFO[Window(home).Property(infobackground)] - $INFO[Skin.String(MovieGenreFanart.path)]$INFO[ListItem.Label]$INFO[Skin.String(MovieGenreFanart.ext)] - $INFO[ListItem.FolderPath] - $INFO[ListItem.Art(fanart)] - $INFO[Container.Art(tvshow.fanart)] - $INFO[Container.Art(artist.fanart)] - $INFO[Container.Art(fanart)] - - - $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] - $INFO[Skin.String(weatherfanart.path)]$INFO[Window(Weather).Property(Current.FanartCode)]$INFO[Skin.String(weatherfanart.ext)] - $INFO[Skin.String(HomeFanart.path)]weather$INFO[Skin.String(HomeFanart.ext)] - - - windows/pvr/record.png - overlays/watched/OverlayPlaying-List.png - overlays/watched/resume.png - overlays/set.png - overlays/folder.png - $INFO[ListItem.Overlay] - OverlayUnwatched.png - - - windows/pvr/record.png - overlays/set.png - overlays/watched/OverlayPlaying-List.png - overlays/watched/resume.png - windows/pvr/archive.png - $INFO[ListItem.Overlay] - - - - $LOCALIZE[20342] - $LOCALIZE[20389] - $LOCALIZE[20343] - $LOCALIZE[20343] - $LOCALIZE[20343] - $LOCALIZE[3] - - - $LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] - $LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] - - - $LOCALIZE[19020] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] - $LOCALIZE[19021] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] - - - $LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - $LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - - - $LOCALIZE[19020] / $LOCALIZE[19040] - $LOCALIZE[19021] / $LOCALIZE[19040] - $LOCALIZE[19020] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] - $LOCALIZE[19021] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] - - - $LOCALIZE[19020] / $LOCALIZE[137] - $LOCALIZE[19021] / $LOCALIZE[137] - - - $LOCALIZE[15016] - - - button_focus - FFFFFFFF - - - $LOCALIZE[19199] - $LOCALIZE[19024] - $LOCALIZE[19199] - $LOCALIZE[19023] - - - $INFO[Player.Title] - $INFO[MusicPlayer.ChannelName]$INFO[Player.Title, - ] - - - $LOCALIZE[19048] - $LOCALIZE[19174] - $LOCALIZE[19048] - $LOCALIZE[19173] - - - $LOCALIZE[19019] - $LOCALIZE[19069] - $LOCALIZE[19017] - $LOCALIZE[19040] - $LOCALIZE[19138] - $LOCALIZE[137] - - - osd/fullscreen/buttons/repeat-one.png - osd/fullscreen/buttons/repeat-all.png - osd/fullscreen/buttons/repeat-off.png - - - $INFO[VideoPlayer.Title] - $INFO[MusicPlayer.Artist] - - - $LOCALIZE[20373]$INFO[VideoPlayer.Season,: , / ]$LOCALIZE[20359]$INFO[VideoPlayer.Episode,: ] - $INFO[VideoPlayer.Year] - $INFO[VideoPlayer.ChannelName] - [COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ] - - - $INFO[VideoPlayer.TvShowTitle] - $INFO[VideoPlayer.Genre] - $INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR] - - - icons/pvr/PVR-IsRecording.png - icons/pvr/timers/bell.png - icons/pvr/PVR-HasTimerScheduleError.png - icons/pvr/PVR-HasTimerError.png - icons/pvr/PVR-HasTimerScheduleConflict.png - icons/pvr/PVR-HasTimerConflict.png - icons/pvr/PVR-HasRecording.png - icons/pvr/PVR-HasTimerScheduleDisabled.png - icons/pvr/PVR-HasTimerDisabled.png - icons/pvr/PVR-HasTimerSchedule.png - icons/pvr/PVR-HasTimer.png - icons/pvr/PVR-HasArchive.png - - - $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E] - $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ] - - - [COLOR grey]$LOCALIZE[19299]:[/COLOR] $INFO[ListItem.ExpirationDate] $INFO[ListItem.ExpirationTime][CR] - - - HW - SW - - - $LOCALIZE[31136] - - - $INFO[ListItem.Timertype] - $INFO[ListItem.EpisodeName] - - - $LOCALIZE[231] - $INFO[Skin.String(background_overlay),$LOCALIZE[467] ] - - - $LOCALIZE[20045] - $LOCALIZE[20046] - - - $INFO[ListItem.EpgEventIcon] - $INFO[ListItem.Icon] - + + windows/pvr/record.png + windows/pvr/timer.png + windows/pvr/archive.png + + + plugin://plugin.program.autocompletion?info=autocomplete&&id=$INFO[Control.GetLabel(312).index(1)]&&limit=9 + + + $INFO[ListItem.TVShowtitle,,: ]$INFO[ListItem.Season,,x]$INFO[ListItem.Episode,,. ]$INFO[ListItem.Title] + $INFO[ListItem.Artist,, - ]$INFO[ListItem.Title] + $INFO[ListItem.Label] + + + $INFO[ListItem.Duration] + $INFO[ListItem.Duration,, $LOCALIZE[12391]] + + + $INFO[ListItem.FolderPath] + + + + >9 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + [COLOR grey]0[/COLOR] + + + $INFO[ListItem.Comment,[B]$LOCALIZE[569][/B][CR][COLOR=white],[/COLOR]] + $INFO[ListItem.Property(Album_Description),[COLOR=white],[/COLOR]] + $INFO[ListItem.Property(Artist_Description),[COLOR=white],[/COLOR]] + + + $INFO[VideoPlayer.TvShowTitle] + $INFO[VideoPlayer.Year]$INFO[VideoPlayer.Genre, - ] + $INFO[VideoPlayer.ChannelName] + $INFO[MusicPlayer.Artist] + + + icons/now-playing/pause.png + icons/now-playing/play.png + + + $INFO[Player.Art(poster)] + $INFO[Player.Art(tvshow.poster)] + DefaultTVShows.png + $INFO[Player.Icon] + + + $INFO[Listitem.Art(poster)] + DefaultFolderBackSquare.png + DefaultAudio.png + DefaultFolderSquare.png + $INFO[ListItem.Thumb] + + + $INFO[Listitem.Art(thumb)] + DefaultArtist.png + DefaultAlbumCover.png + DefaultAudio.png + + + $INFO[Listitem.Art(poster)] + $INFO[ListItem.Icon] + + + $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] + $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] + $INFO[Listitem.Art(poster)] + $INFO[ListItem.Thumb] + $INFO[ListItem.Icon] + + + $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] + $INFO[ListItem.Year] + $LOCALIZE[38026]: $INFO[ListItem.Appearances] + $INFO[ListItem.Label2] + + + $INFO[ListItem.Property(description),,[CR]]$INFO[ListItem.PictureDatetime,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]]$INFO[ListItem.PictureResolution,[COLOR button_focus]$LOCALIZE[169]: [/COLOR],[CR]]$INFO[ListItem.PictureCamMake,[COLOR button_focus]$LOCALIZE[31041]: [/COLOR],[CR]]$INFO[ListItem.PictureCamModel,[COLOR button_focus]$LOCALIZE[21823]: [/COLOR],[CR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_YearsActive),[COLOR button_focus]$LOCALIZE[21898]: [/COLOR],[CR]]$INFO[ListItem.Property(Artist_Style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] + $INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Property(album_label),[COLOR button_focus]$LOCALIZE[21899]: [/COLOR],[CR]]$INFO[ListItem.Property(album_style),[COLOR button_focus]$LOCALIZE[736]: [/COLOR],[CR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Director,[COLOR button_focus]$LOCALIZE[20339]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR button_focus]$LOCALIZE[20417]: [/COLOR],[CR]]$INFO[ListItem.Premiered,[COLOR button_focus]$LOCALIZE[20416]: [/COLOR]] + + + $INFO[ListItem.Size,[COLOR button_focus]$LOCALIZE[289]: [/COLOR],[CR]]$INFO[ListItem.PictureAperture,[COLOR button_focus]$LOCALIZE[21826]: [/COLOR],[CR]]$INFO[ListItem.PictureFocalLen,[COLOR button_focus]$LOCALIZE[21827]: [/COLOR],[CR]]$INFO[ListItem.PictureExpTime,[COLOR button_focus]$LOCALIZE[21830]: [/COLOR],[CR]]$INFO[ListItem.Date,[COLOR button_focus]$LOCALIZE[552]: [/COLOR],[CR]] + $INFO[ListItem.Property(artist_description)] + $INFO[ListItem.Property(album_description)] + $INFO[ListItem.Plot] + + + [COLOR=button_focus]$INFO[Container(3).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(3).CurrentPage]/$INFO[Container(3).NumPages][/COLOR] + [COLOR=button_focus]$INFO[Container(6).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(6).CurrentPage]/$INFO[Container(6).NumPages][/COLOR] + + + [COLOR=button_focus]$INFO[Container(450).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(450).CurrentPage]/$INFO[Container(450).NumPages][/COLOR] + [COLOR=button_focus]$INFO[Container(451).NumItems][/COLOR] $LOCALIZE[31036] - [COLOR=button_focus]$INFO[Container(451).CurrentPage]/$INFO[Container(451).NumPages][/COLOR] + + + DefaultBackBanner.png + $INFO[ListItem.Art(banner)] + $INFO[ListItem.Art(fanart)] + dialogs/dialog-bg-nobo.png + + + special://skin/extras/home-images/movie.jpg + $INFO[ListItem.Art(fanart)] + + + $INFO[ListItem.Addonnews] + $INFO[ListItem.AddonDescription] + $INFO[ListItem.Property(album_description)] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Plot] + $INFO[ListItem.Property(artist_description)] + $INFO[ListItem.Plot] + $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] + $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]] + + + $INFO[listitem.TrackNumber, ] + $INFO[listitem.TrackNumber, ,.]$INFO[listitem.Title, ] + + + $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] + DefaultGenre.png + + + $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] + DefaultStudio.png + + + $INFO[ListItem.Property(addon.status)] + $INFO[ListItem.Label2] + $INFO[ListItem.AddonCreator,, - ]$INFO[ListItem.AddonVersion] + + + $LOCALIZE[563] + $LOCALIZE[38018] + $LOCALIZE[16018] + + + $LOCALIZE[31165] + $LOCALIZE[31166] + $LOCALIZE[16018] + + + icons/addonstatus/disable.png + icons/addonstatus/orphan.png + icons/addonstatus/install.png + icons/addonstatus/disable.png + icons/addonstatus/update.png + OverlayWatched.png + OverlayUnwatched.png + + + flags/videoresolution/3D.png + $INFO[ListItem.VideoResolution,flags/videoresolution/,.png] + + + flags/videoresolution/3D.png + $INFO[Container.ListItem.VideoResolution,flags/videoresolution/,.png] + + + $INFO[ListItem.Art(banner)] + $INFO[ListItem.Art(tvshow.banner)] + + + DefaultFolderBackPoster.png + $INFO[Container.Art(season.poster)] + $INFO[Container.Art(tvshow.poster)] + $INFO[Container.Art(tvshow.poster)] + $INFO[Container.ListItem.Art(thumb)] + $INFO[ListItem.Art(tvshow.poster)] + $INFO[ListItem.Art(poster)] + + + $INFO[ListItem.Art(poster)] + $INFO[ListItem.Art(season.poster)] + $INFO[ListItem.Art(tvshow.poster)] + + + $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] + + + + 2x + 4x + 8x + 16x + 32x + + + $LOCALIZE[773][COLOR=grey] $INFO[Player.SeekStepSize][/COLOR] + $LOCALIZE[112] + $LOCALIZE[31039] $VAR[VideoPlayerForwardRewindVar] + $LOCALIZE[31038] $VAR[VideoPlayerForwardRewindVar] + $LOCALIZE[31142]: $INFO[Player.PlaySpeed] + + + [B]$INFO[Player.SeekNumeric(hh:mm:ss)][/B] + $INFO[PVR.EpgEventSeekTime]$INFO[PVR.EpgEventDuration, / ] + $INFO[Player.SeekTime]$INFO[Player.Duration, / ] + $INFO[PVR.EpgEventElapsedTime]$INFO[PVR.EpgEventDuration, / ] + $INFO[Player.Time]$INFO[Player.Duration, / ] + + + $LOCALIZE[31050] + $LOCALIZE[298] + $LOCALIZE[31106] + $LOCALIZE[24012] + $LOCALIZE[31092] + $LOCALIZE[19033] + $LOCALIZE[19019] + $LOCALIZE[19069] + $LOCALIZE[5] + $LOCALIZE[36501] + $LOCALIZE[19059] + $LOCALIZE[264] + $LOCALIZE[31054] + + + $LOCALIZE[31033]$INFO[MusicPlayer.UserRating, : ] + $LOCALIZE[19033] + $LOCALIZE[19019] + $LOCALIZE[19069] + $LOCALIZE[29900] + $LOCALIZE[486]$INFO[Playlist.Repeat, : ] + $LOCALIZE[590]: $LOCALIZE[16041] + $LOCALIZE[590]: $LOCALIZE[16039] + $LOCALIZE[24013] + $LOCALIZE[10004] + + + $LOCALIZE[31129] + $LOCALIZE[31130] + + + dialogs/volume/mute.png + dialogs/volume/volume.png + dialogs/volume/volume2.png + dialogs/volume/volume.png + dialogs/volume/volume1.png + + + + [COLOR grey]$INFO[ListItem.Year, (,)][/COLOR] + + + $INFO[ListItem.Title] + $INFO[ListItem.TVShowTitle]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] + $INFO[ListItem.Title]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] + $INFO[ListItem.Label]$INFO[ListItem.Year, ([COLOR grey],[/COLOR])] + + + $LOCALIZE[1024] + $LOCALIZE[208] + + + $INFO[ListItem.Season]$INFO[ListItem.Episode,[COLOR grey]x[/COLOR],: ]$INFO[ListItem.Title] + $INFO[ListItem.Tagline,[I],[/I]] + $INFO[ListItem.Genre] + + + $INFO[ListItem.LastPlayed,$LOCALIZE[568]: ] + $INFO[ListItem.FileNameAndPath] + + + $INFO[SlideShow.Filename] + $INFO[ListItem.Label] + + + $INFO[SlideShow.EXIFtime] + $INFO[ListItem.PictureDateTime] + + + $INFO[VideoPlayer.Title] + $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] + $INFO[VideoPlayer.TVShowTitle]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] + $INFO[VideoPlayer.Title]$INFO[VideoPlayer.Year, ([COLOR button_focus],[/COLOR])] + $LOCALIZE[589] + $LOCALIZE[31000]... + + + $LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length] + $INFO[VideoPlayer.Artist]$INFO[VideoPlayer.Album, - ] + $INFO[VideoPlayer.Season,[COLOR button_focus]S,[/COLOR]]$INFO[VideoPlayer.Episode,[COLOR button_focus]E,: [/COLOR]]$INFO[VideoPlayer.Title] + $INFO[VideoPlayer.ChannelNumberLabel,([COLOR button_focus],[/COLOR]) ]$INFO[VideoPlayer.ChannelName] $INFO[VideoPlayer.EpisodeName, - ] + $INFO[VideoPlayer.Genre] + + + $INFO[Player.Art(tvshow.clearlogo)] + $INFO[Player.Art(clearlogo)] + + + + $INFO[Window(home).Property(infobackground)] + $INFO[Container.ListItem.Art(fanart)] + $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] + + + + $INFO[Window(home).Property(infobackground)] + $INFO[Container.ListItem.Art(fanart)] + $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]power$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]settings$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]favorites$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]search$INFO[Skin.String(HomeFanart.ext)] + $INFO[Skin.String(HomeFanart.path)]$INFO[Container(9000).ListItem.Property(id)]$INFO[Skin.String(HomeFanart.ext)] + + + + $INFO[Window(home).Property(infobackground)] + $INFO[Skin.String(MovieGenreFanart.path)]$INFO[ListItem.Label]$INFO[Skin.String(MovieGenreFanart.ext)] + $INFO[ListItem.FolderPath] + $INFO[ListItem.Art(fanart)] + $INFO[Container.Art(tvshow.fanart)] + $INFO[Container.Art(artist.fanart)] + $INFO[Container.Art(fanart)] + + + $INFO[Skin.String(WeatherFanart.path)]$INFO[Container.ListItem.Property(FanartCode)]$INFO[Skin.String(WeatherFanart.ext)] + $INFO[Skin.String(weatherfanart.path)]$INFO[Window(Weather).Property(Current.FanartCode)]$INFO[Skin.String(weatherfanart.ext)] + $INFO[Skin.String(HomeFanart.path)]weather$INFO[Skin.String(HomeFanart.ext)] + + + windows/pvr/record.png + overlays/watched/OverlayPlaying-List.png + overlays/watched/resume.png + overlays/set.png + overlays/folder.png + $INFO[ListItem.Overlay] + OverlayUnwatched.png + + + windows/pvr/record.png + overlays/set.png + overlays/watched/OverlayPlaying-List.png + overlays/watched/resume.png + windows/pvr/archive.png + $INFO[ListItem.Overlay] + + + + $LOCALIZE[20342] + $LOCALIZE[20389] + $LOCALIZE[20343] + $LOCALIZE[20343] + $LOCALIZE[20343] + $LOCALIZE[3] + + + $LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] + $LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)] + + + $LOCALIZE[19020] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] + $LOCALIZE[19021] / $INFO[Control.GetLabel(29)] - $INFO[Control.GetLabel(30)] + + + $LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] + $LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] + + + $LOCALIZE[19020] / $LOCALIZE[19040] + $LOCALIZE[19021] / $LOCALIZE[19040] + $LOCALIZE[19020] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] + $LOCALIZE[19021] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ] + + + $LOCALIZE[19020] / $LOCALIZE[137] + $LOCALIZE[19021] / $LOCALIZE[137] + + + $LOCALIZE[15016] + + + button_focus + FFFFFFFF + + + $LOCALIZE[19199] - $LOCALIZE[19024] + $LOCALIZE[19199] - $LOCALIZE[19023] + + + $INFO[Player.Title] + $INFO[MusicPlayer.ChannelName]$INFO[Player.Title, - ] + + + $LOCALIZE[19048] - $LOCALIZE[19174] + $LOCALIZE[19048] - $LOCALIZE[19173] + + + $LOCALIZE[19019] + $LOCALIZE[19069] + $LOCALIZE[19017] + $LOCALIZE[19040] + $LOCALIZE[19138] + $LOCALIZE[137] + + + osd/fullscreen/buttons/repeat-one.png + osd/fullscreen/buttons/repeat-all.png + osd/fullscreen/buttons/repeat-off.png + + + $INFO[VideoPlayer.Title] + $INFO[MusicPlayer.Artist] + + + $LOCALIZE[20373]$INFO[VideoPlayer.Season,: , / ]$LOCALIZE[20359]$INFO[VideoPlayer.Episode,: ] + $INFO[VideoPlayer.Year] + $INFO[VideoPlayer.ChannelName] + [COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ] + + + $INFO[VideoPlayer.TvShowTitle] + $INFO[VideoPlayer.Genre] + $INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR] + + + icons/pvr/PVR-IsRecording.png + icons/pvr/timers/bell.png + icons/pvr/PVR-HasTimerScheduleError.png + icons/pvr/PVR-HasTimerError.png + icons/pvr/PVR-HasTimerScheduleConflict.png + icons/pvr/PVR-HasTimerConflict.png + icons/pvr/PVR-HasRecording.png + icons/pvr/PVR-HasTimerScheduleDisabled.png + icons/pvr/PVR-HasTimerDisabled.png + icons/pvr/PVR-HasTimerSchedule.png + icons/pvr/PVR-HasTimer.png + icons/pvr/PVR-HasArchive.png + + + $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E] + $INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ] + + + [COLOR grey]$LOCALIZE[19299]:[/COLOR] $INFO[ListItem.ExpirationDate] $INFO[ListItem.ExpirationTime][CR] + + + HW + SW + + + $LOCALIZE[31136] + + + $INFO[ListItem.Timertype] + $INFO[ListItem.EpisodeName] + + + $LOCALIZE[231] + $INFO[Skin.String(background_overlay),$LOCALIZE[467] ] + + + $LOCALIZE[20045] + $LOCALIZE[20046] + + + $INFO[ListItem.EpgEventIcon] + $INFO[ListItem.Icon] + diff --git a/addons/skin.estuary/xml/View_50_List.xml b/addons/skin.estuary/xml/View_50_List.xml index c6bbde8f84311..3591a9bfde50a 100644 --- a/addons/skin.estuary/xml/View_50_List.xml +++ b/addons/skin.estuary/xml/View_50_List.xml @@ -1,273 +1,272 @@ - - - OpenClose_Right - 596 - Control.IsVisible(50) - Visible_Right - - - - - - - DepthContentPanel - - -20 - 634 - - - - - - - - 20 - list_y_offset - 12 - list_y_offset - 50 - 50 - vertical - conditional - - - DepthContentPopout - 38 - 36 - 120 - 124 - 200 - keep - $VAR[InfoWallThumbVar] - - - 310 - 936 - - - - - - - - - - 0 - 0 - - - $PARAM[left] - $PARAM[right] - list_y_offset - list_y_offset - 5 - 6 - 500 - vertical - $PARAM[list_id]600 - 9000 - $PARAM[list_id]600 - $PARAM[list_id] - $PARAM[list_id] - list - Container.Content(movies) | Container.Content(sets) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(games) | Window.IsActive(MyPlaylist.xml) - - - 0 - 0 - 0 - 0 - lists/focus.png - Control.HasFocus($PARAM[list_id]) - - - 70 - 70 - 0 - 0 - center - true - font27 - - text_shadow - - - 0 - 0 - 100 - 20 - right - center - font12 - - Focus - text_shadow - - - 21 - 50% - 32 - 32 - $VAR[ListWatchedIconVar] - Focus - - - 21 - 50% - 32 - 32 - $VAR[ListWatchedIconVar] - Focus - VisibleChange - !Control.HasFocus($PARAM[list_id]) - - - - - 70 - 70 - 0 - 0 - center - font27 - - text_shadow - - - 20 - 20 - 0 - 0 - right - center - font12 - - grey - text_shadow - - - 21 - 50% - 32 - 32 - $VAR[ListWatchedIconVar] - - - - - - - - false - - - DepthContentPanel - - - - - - 30 - 140 - 530 - 470 - keep - 300 - $VAR[IconWallThumbVar] - !String.IsEqual(ListItem.DbType,episode) - - - 30 - 140 - 530 - 330 - keep - 300 - $INFO[ListItem.Icon] - String.IsEqual(ListItem.DbType,episode) - - - 273 - 590 - RatingCircle - conditional - - - !Container.Content() | !String.isempty(ListItem.Plot) - 30 - - 640 - 525 - 117 - !System.HasModalDialog + Skin.HasSetting(AutoScroll) - - !String.IsEqual(ListItem.DbType,episode) - - - 485 - 525 - 96 - !System.HasModalDialog + Skin.HasSetting(AutoScroll) - - String.IsEqual(ListItem.DbType,episode) - - - - 20 - 640 - ListItem.IsCollection + String.IsEmpty(ListItem.Plot) - - - - - - - - - - !ListItem.IsCollection + String.IsEmpty(Control.GetLabel(15500)) + Control.IsVisible(15500) - - 30 - 460 - 530 - 413 - center - center - font27 - 80FFFFFF - - !ListItem.IsParentFolder - !Integer.IsGreater(Container(42000).NumItems,0) + !Integer.IsGreater(Container(43000).NumItems,0) - !Container.Content() | !String.isempty(ListItem.Plot) - - - 20 - 640 - !String.IsEmpty(ListItem.DBID) - - - - - - - - - - - - - - - - - - - - - - - - + + + OpenClose_Right + 596 + Control.IsVisible(50) + Visible_Right + + + + + + + DepthContentPanel + + -20 + 634 + + + + + + + + 20 + list_y_offset + 12 + list_y_offset + 50 + 50 + vertical + conditional + + + DepthContentPopout + 38 + 36 + 120 + 124 + 200 + keep + $VAR[InfoWallThumbVar] + + + 310 + 936 + + + + + + + + + + 0 + 0 + + + $PARAM[left] + $PARAM[right] + list_y_offset + list_y_offset + 5 + 6 + 500 + vertical + $PARAM[list_id]600 + 9000 + $PARAM[list_id]600 + $PARAM[list_id] + $PARAM[list_id] + list + Container.Content(movies) | Container.Content(sets) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(games) | Window.IsActive(MyPlaylist.xml) + + + 0 + 0 + 0 + 0 + lists/focus.png + Control.HasFocus($PARAM[list_id]) + + + 70 + 70 + 0 + 0 + center + true + font27 + + text_shadow + + + 0 + 0 + 100 + 20 + right + center + font12 + + Focus + text_shadow + + + 21 + 50% + 32 + 32 + $VAR[ListWatchedIconVar] + Focus + + + 21 + 50% + 32 + 32 + $VAR[ListWatchedIconVar] + Focus + VisibleChange + !Control.HasFocus($PARAM[list_id]) + + + + + 70 + 70 + 0 + 0 + center + font27 + + text_shadow + + + 20 + 20 + 0 + 0 + right + center + font12 + + grey + text_shadow + + + 21 + 50% + 32 + 32 + $VAR[ListWatchedIconVar] + + + + + + + + false + + + DepthContentPanel + + + + + + 30 + 140 + 530 + 470 + keep + 300 + $VAR[IconWallThumbVar] + !String.IsEqual(ListItem.DbType,episode) + + + 30 + 140 + 530 + 330 + keep + 300 + $INFO[ListItem.Icon] + String.IsEqual(ListItem.DbType,episode) + + + 273 + 590 + RatingCircle + conditional + + + !Container.Content() | !String.isempty(ListItem.Plot) + 30 + + 640 + 525 + 117 + !System.HasModalDialog + Skin.HasSetting(AutoScroll) + + !String.IsEqual(ListItem.DbType,episode) + + + 485 + 525 + 96 + !System.HasModalDialog + Skin.HasSetting(AutoScroll) + + String.IsEqual(ListItem.DbType,episode) + + + + 20 + 640 + ListItem.IsCollection + String.IsEmpty(ListItem.Plot) + + + + + + + + + + !ListItem.IsCollection + String.IsEmpty(Control.GetLabel(15500)) + Control.IsVisible(15500) + + 30 + 460 + 530 + 413 + center + center + font27 + 80FFFFFF + + !ListItem.IsParentFolder + !Integer.IsGreater(Container(42000).NumItems,0) + !Integer.IsGreater(Container(43000).NumItems,0) + !Container.Content() | !String.isempty(ListItem.Plot) + + + 20 + 640 + !String.IsEmpty(ListItem.DBID) + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/skin.estuary/xml/View_54_InfoWall.xml b/addons/skin.estuary/xml/View_54_InfoWall.xml index c1300199d2d56..7aa4047fef4d8 100644 --- a/addons/skin.estuary/xml/View_54_InfoWall.xml +++ b/addons/skin.estuary/xml/View_54_InfoWall.xml @@ -1,477 +1,467 @@ - - false - - - -10 - 376 - 380 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - -14 - -4 - 384 - 388 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 20 - 10 - 336 - 300 - $VAR[InfoWallThumbVar] - keep - - - 20 - 318 - 338 - 20 - 20 - center - center - font12 - - - - - - DefaultFolder.png - $INFO[ListItem.Artist] - $INFO[ListItem.Title] - $INFO[ListItem.Label] - false - - - 0 - 316 - 386 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - -4 - -4 - 324 - 394 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 0 - 0 - 316 - 316 - $VAR[InfoWallThumbVar] - keep - 20 - - - 10 - 10 - 40 - 40 - overlays/set.png - String.IsEqual(Listitem.IsBoxset,1) - - - 28 - 289 - 262 - 80 - font12 - center - center - - !String.IsEqual(ListItem.DBType,album) - - - String.IsEqual(ListItem.DBType,album) - - 29 - 300 - 260 - - - font10 - text_shadow - $PARAM[focused] - center - - - 29 - 328 - 260 - - font12 - text_shadow - $PARAM[focused] - center - - - - - 133 - 2 - RatingCircle - - - - - $INFO[ListItem.Title] - $INFO[ListItem.TVShowTitle] - $INFO[ListItem.Season,,x]$INFO[ListItem.Episode] - DefaultTVShows.png - false - - - 10 - 316 - 288 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - 6 - -4 - 324 - 296 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 0 - 10 - 316 - 218 - $VAR[InfoWallThumbVar] - scale - 20 - - - 20 - 138 - 276 - 70 - overlays/overlayfade.png - !ListItem.IsParentFolder - - - 31 - 178 - 260 - - font20_title - text_shadow - right - - - 20 - 175 - 32 - 32 - $VAR[WallWatchedIconVar] - - - 28 - 202 - 262 - 80 - font12 - center - center - - Window.IsActive(videos) - - - !Window.IsActive(videos) - - 29 - 210 - 260 - - font12 - text_shadow - $PARAM[focused] - center - - - 29 - 240 - 260 - - font10 - text_shadow - $PARAM[focused] - center - - - - 20 - 258 - 276 - 1 - - progress/texturebg_alt_white.png - ListItem.PercentPlayed - !Integer.IsEqual(ListItem.PercentPlayed,0) - - - 134 - 8 - RatingCircle - - - - - false - - - String.IsEmpty(ListItem.Art(poster)) - - 15 - -10 - 290 - 400 - dialogs/dialog-bg-nobo.png - overlays/shadow.png - 20 - - - 15 - -10 - 290 - 400 - colors/grey.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 40 - 244 - 242 - 120 - font27 - center - center - - $PARAM[focused] - !ListItem.IsParentFolder - - - 24 - -1 - 272 - 270 - $INFO[ListItem.Icon] - scale - 20 - - - - !String.IsEmpty(ListItem.Art(poster)) - - 11 - -14 - 298 - 408 - colors/grey.png - overlays/shadow.png - 20 - $PARAM[focused] - Animation_FocusTextureFade - - - 15 - -10 - 290 - 400 - $INFO[ListItem.Art(poster)] - scale - overlays/shadow.png - 20 - - - 35 - 290 - 80 - 80 - overlays/overlay-bg.png - Listitem.IsCollection | ListItem.IsPlaying | Integer.IsGreater(ListItem.Playcount,0) - - - - String.IsEqual(ListItem.DBtype,tvshow) - 320 - - 35 - 0 - 250 - 50 - overlays/overlayfade.png - !String.IsEmpty(ListItem.Art(poster)) - - - 0 - 20 - 244 - - font20_title - text_shadow - right - - - 254 - 23 - 24 - 24 - lists/played-total.png - - - - 35 - 338 - 32 - 32 - $VAR[WallWatchedIconVar] - - - 135 - -8 - RatingCircle - - - 35 - 350 - 250 - 1 - - progress/texturebg_alt_white.png - ListItem.PercentPlayed - !Integer.IsEqual(ListItem.PercentPlayed,0) - - - - - - OpenClose_Right - 100 - 0 - Control.IsVisible(54) - Visible_Right - - 490 - 0 - 15 - 100% - 9000 - 531 - 54 - 9000 - 54 - icon - 2 - 531 - 500 - Container.Content(artists) | Container.Content(albums) | Container.Content(sets) | Container.Content(movies) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes) | Container.Content(musicvideos) | Container.Content(images) | Container.Content(videos) | Container.Content(games) - - - 30 - 120 - InfoWallMovieLayout - - - - - DepthContentPopout - 30 - Focus - UnFocus - 120 - - - - - - - - 64 - 110 - - - - - - - - - DepthContentPopout - Focus - UnFocus - 64 - 110 - - - - - - - - - 150 - 40 - InfoWallMusicLayout - - - - - DepthContentPopout - Focus - UnFocus - 150 - 40 - - - - - - - - 0 - 150 - InfoWallPictureLayout - - - - - DepthContentPopout - 0 - 150 - Focus - UnFocus - - - - - - - - + + false + + + -10 + 376 + 380 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + -14 + -4 + 384 + 388 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 20 + 10 + 336 + 300 + $VAR[InfoWallThumbVar] + keep + + + 20 + 318 + 338 + 20 + 20 + center + center + font12 + + + + + + DefaultFolder.png + $INFO[ListItem.Artist] + $INFO[ListItem.Title] + $INFO[ListItem.Label] + false + + + 0 + 316 + 386 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + -4 + -4 + 324 + 394 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 0 + 0 + 316 + 316 + $VAR[InfoWallThumbVar] + keep + 20 + + + 28 + 289 + 262 + 80 + font12 + center + center + + !String.IsEqual(ListItem.DBType,album) + + + String.IsEqual(ListItem.DBType,album) + + 29 + 300 + 260 + + font10 + text_shadow + $PARAM[focused] + center + + + 29 + 328 + 260 + + font12 + text_shadow + $PARAM[focused] + center + + + + 133 + 2 + RatingCircle + + + + + $INFO[ListItem.Title] + $INFO[ListItem.TVShowTitle] + $INFO[ListItem.Season,,x]$INFO[ListItem.Episode] + DefaultTVShows.png + false + + + 10 + 316 + 288 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + 6 + -4 + 324 + 296 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 0 + 10 + 316 + 218 + $VAR[InfoWallThumbVar] + scale + 20 + + + 20 + 138 + 276 + 70 + overlays/overlayfade.png + !ListItem.IsParentFolder + + + 31 + 178 + 260 + + font20_title + text_shadow + right + + + 20 + 175 + 32 + 32 + $VAR[WallWatchedIconVar] + + + 28 + 202 + 262 + 80 + font12 + center + center + + Window.IsActive(videos) + + + !Window.IsActive(videos) + + 29 + 210 + 260 + + font12 + text_shadow + $PARAM[focused] + center + + + 29 + 240 + 260 + + font10 + text_shadow + $PARAM[focused] + center + + + + 20 + 258 + 276 + 1 + + progress/texturebg_alt_white.png + ListItem.PercentPlayed + !Integer.IsEqual(ListItem.PercentPlayed,0) + + + 134 + 8 + RatingCircle + + + + + false + + + String.IsEmpty(ListItem.Art(poster)) + + 15 + -10 + 290 + 400 + dialogs/dialog-bg-nobo.png + overlays/shadow.png + 20 + + + 15 + -10 + 290 + 400 + colors/grey.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 40 + 244 + 242 + 120 + font27 + center + center + + $PARAM[focused] + !ListItem.IsParentFolder + + + 24 + -1 + 272 + 270 + $INFO[ListItem.Icon] + scale + 20 + + + + !String.IsEmpty(ListItem.Art(poster)) + + 11 + -14 + 298 + 408 + colors/grey.png + overlays/shadow.png + 20 + $PARAM[focused] + Animation_FocusTextureFade + + + 15 + -10 + 290 + 400 + $INFO[ListItem.Art(poster)] + scale + overlays/shadow.png + 20 + + + 35 + 290 + 80 + 80 + overlays/overlay-bg.png + Listitem.IsCollection | ListItem.IsPlaying | Integer.IsGreater(ListItem.Playcount,0) + + + + String.IsEqual(ListItem.DBtype,tvshow) + 320 + + 35 + 0 + 250 + 50 + overlays/overlayfade.png + !String.IsEmpty(ListItem.Art(poster)) + + + 0 + 20 + 244 + + font20_title + text_shadow + right + + + 254 + 23 + 24 + 24 + lists/played-total.png + + + + 35 + 338 + 32 + 32 + $VAR[WallWatchedIconVar] + + + 135 + -8 + RatingCircle + + + 35 + 350 + 250 + 1 + + progress/texturebg_alt_white.png + ListItem.PercentPlayed + !Integer.IsEqual(ListItem.PercentPlayed,0) + + + + + + OpenClose_Right + 100 + 0 + Control.IsVisible(54) + Visible_Right + + 490 + 0 + 15 + 100% + 9000 + 531 + 54 + 9000 + 54 + icon + 2 + 531 + 500 + Container.Content(artists) | Container.Content(albums) | Container.Content(sets) | Container.Content(movies) | Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes) | Container.Content(musicvideos) | Container.Content(images) | Container.Content(videos) | Container.Content(games) + + + 30 + 120 + InfoWallMovieLayout + + + + + DepthContentPopout + 30 + Focus + UnFocus + 120 + + + + + + + + 64 + 110 + + + + + + + + + DepthContentPopout + Focus + UnFocus + 64 + 110 + + + + + + + + + 150 + 40 + InfoWallMusicLayout + + + + + DepthContentPopout + Focus + UnFocus + 150 + 40 + + + + + + + + 0 + 150 + InfoWallPictureLayout + + + + + DepthContentPopout + 0 + 150 + Focus + UnFocus + + + + + + + + diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 6d85ad3efc47b..e2e7bd7b230c4 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -878,7 +878,10 @@ #define LISTITEM_ISPLAYABLE (LISTITEM_START + 186) #define LISTITEM_FILENAME_NO_EXTENSION (LISTITEM_START + 187) #define LISTITEM_CURRENTITEM (LISTITEM_START + 188) +#define LISTITEM_DISC_NAME (LISTITEM_START + 188) +#define LISTITEM_IS_BOXSET (LISTITEM_START + 189) #define LISTITEM_END (LISTITEM_START + 2500) + #define CONDITIONAL_LABEL_START (LISTITEM_END + 1) // 37501 #define CONDITIONAL_LABEL_END 39999 diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index bf68f452aeb18..5c18303ca5d66 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -4002,100 +4002,6 @@ bool CMusicDatabase::GetSourcesNav(const std::string& strBaseDir, CFileItemList& return false; } -bool CMusicDatabase::GetBoxsetsNav(const std::string& strBaseDir, CFileItemList& items, const Filter& filter) -{ - CLog::Log(LOGNOTICE, "GetBoxsetsNav - StrBaseDir = [%s]", strBaseDir.c_str()); -// GetBoxsetsAlbums(strBaseDir, items); -//return true; - - try - { - unsigned int querytime = 0; - unsigned int time = XbmcThreads::SystemClockMillis(); - int total = -1; - - if (NULL == m_pDB.get()) return false; - if (NULL == m_pDS.get()) return false; - - Filter extFilter = filter; - CMusicDbUrl musicUrl; - SortDescription sorting; - if (!musicUrl.FromString(strBaseDir) ) - return false; - - // get boxsets from albumlist - std::string strSQL = "SELECT albumview.* FROM albumview"; - extFilter.AppendWhere("albumview.bBoxedSet = 1"); - - if (!BuildSQL(strSQL, extFilter, strSQL)) - return false; - - CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); - querytime = XbmcThreads::SystemClockMillis(); - if (!m_pDS->query(strSQL)) - return false; - int iRowsFound = m_pDS->num_rows(); - if (iRowsFound == 0) - { - m_pDS->close(); - return true; - } - querytime = XbmcThreads::SystemClockMillis() - querytime; - - // store the total value of items as a property - if (total < iRowsFound) - total = iRowsFound; - items.SetProperty("total", total); - - - DatabaseResults results; - results.reserve(iRowsFound); - - // Random order with limits already applied in SQL, just fetch results from dataset -// sorting = sortDescription; -// if (limitedInSQL && sortDescription.sortBy == SortByRandom) -// sorting.sortBy = SortByNone; -// if (!SortUtils::SortFromDataset(sorting, MediaTypeAlbum, m_pDS, results)) -// return false; - - // get data from returned rows - items.Reserve(results.size()); - const dbiplus::query_data &data = m_pDS->get_result_set().records; - for (const auto &i : results) - { - unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger(); - const dbiplus::sql_record* const record = data.at(targetRow); - - try - { - CMusicDbUrl itemUrl = musicUrl; - std::string path = StringUtils::Format("%i/", record->at(album_idAlbum).get_asInt()); - itemUrl.AppendPath(path); - - CFileItemPtr pItem(new CFileItem(itemUrl.ToString(), GetAlbumFromDataset(record))); - pItem->SetIconImage("DefaultAlbumCover.png"); - items.Add(pItem); - } - catch (...) - { - m_pDS->close(); - CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size()); - } - } - - // cleanup - m_pDS->close(); - CLog::Log(LOGDEBUG, "{0}: Time to fill list with albums {1}ms query took {2}ms", - __FUNCTION__, XbmcThreads::SystemClockMillis() - time, querytime); - return true; - } - catch (...) - { - m_pDS->close(); - CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filter.where.c_str()); - } - return false; -} bool CMusicDatabase::GetYearsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */) { @@ -7571,7 +7477,12 @@ void CMusicDatabase::UpdateTables(int version) // and filled as part of scanning anyway so simply force full rescan. MigrateSources(); } - + if (version < 73) + { + // add bBoxedSet to album table, add strDiscSubtitles to song table + m_pDS->exec("ALTER TABLE album ADD bBoxedSet INTEGER \n"); + m_pDS->exec("ALTER TABLE song ADD strDiscSubtitle TEXT \n"); + } // Set the verion of tag scanning required. // Not every schema change requires the tags to be rescanned, set to the highest schema version // that needs this. Forced rescanning (of music files that have not changed since they were @@ -7580,9 +7491,9 @@ void CMusicDatabase::UpdateTables(int version) // The original db version when the tags were scanned, and the minimal db version needed are // later used to determine if a forced rescan should be prompted - // The last schema change needing forced rescanning was 60. - // Mostly because of the new tags processed by v17 rather than a schema change. - SetMusicNeedsTagScan(60); + // The last schema change needing forced rescanning was 73. + // This is because Kodi can now read and process extra tags involved in the creation of box sets. + SetMusicNeedsTagScan(73); // After all updates, store the original db version. // This indicates the version of tag processing that was used to populate db @@ -7591,7 +7502,7 @@ void CMusicDatabase::UpdateTables(int version) int CMusicDatabase::GetSchemaVersion() const { - return 72; + return 73; } int CMusicDatabase::GetMusicNeedsTagScan() @@ -9127,7 +9038,7 @@ bool CMusicDatabase::GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList path += StringUtils::Format("%s/", disctitle.c_str()); pItem->SetPath(path); pItem->SetLabel(disctitle.c_str()); - pItem->SetIconImage("DefaultAlbumCover.png"); + pItem->SetArt("icon", "DefaultAlbumCover.png"); items.Add(pItem); } } diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index 1d76c00415e9f..a3fbbf91b00ac 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -232,18 +232,17 @@ class CMusicDatabase : public CDatabase \param strArtistSort the album artist name(s) sort string \param strGenre the album genre(s) \param year the year - \param set the set (if any) that the album belongs to + \param bBoxedSet if the album is a boxset \param strRecordLabel the recording label \param strType album type (Musicbrainz release type e.g. "Broadcast, Soundtrack, live"), \param bCompilation if the album is a compilation \param releaseType "album" or "single" - \param strDiscSubtitle the titles of discs (if any) in the set \return the id of the album */ int AddAlbum(const std::string& strAlbum, const std::string& strMusicBrainzAlbumID, const std::string& strReleaseGroupMBID, const std::string& strArtist, const std::string& strArtistSort, - const std::string& strGenre, int year, bool idSet, + const std::string& strGenre, int year, bool bBoxedSet, const std::string& strRecordLabel, const std::string& strType, bool bCompilation, CAlbum::ReleaseType releaseType ); @@ -455,7 +454,6 @@ class CMusicDatabase : public CDatabase bool GetGenresNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter(), bool countOnly = false); bool GetSourcesNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter(), bool countOnly = false); bool GetYearsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); - bool GetBoxsetsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); bool GetRolesNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); bool GetArtistsNav(const std::string& strBaseDir, CFileItemList& items, bool albumArtistsOnly = false, int idGenre = -1, int idAlbum = -1, int idSong = -1, const Filter &filter = Filter(), const SortDescription &sortDescription = SortDescription(), bool countOnly = false); bool GetCommonNav(const std::string &strBaseDir, const std::string &table, const std::string &labelField, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */); @@ -807,7 +805,6 @@ void SetLibraryLastUpdated(); album_strReleaseType, album_dtDateAdded, album_dtLastPlayed, - album_iDiscNo, // filled on the fly album_enumCount // end of the enum, do not add past here } AlbumFields; diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp index ef67fd1ee15fe..b469f1ed37dc0 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.cpp +++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp @@ -954,7 +954,6 @@ void CMusicInfoScanner::CheckBoxSets(VECALBUMS &albums) if (song.strDiscSubtitle != m_oldDiscSubtitle) { m_oldDiscSubtitle = song.strDiscSubtitle; -// album.strDiscTitles.append(song.strDiscSubtitle).append(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); count ++; if (count > 2) album.bBoxedSet = true; @@ -966,7 +965,6 @@ void CMusicInfoScanner::CheckBoxSets(VECALBUMS &albums) song.strDiscSubtitle = StringUtils::Format("Disc %i", discno); // create dummy titles if (discno != old_discno) { -// album.strDiscTitles.append(song.strDiscSubtitle).append(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); old_discno = discno; album.bBoxedSet = true; } diff --git a/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp b/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp index c61338df2957d..1175918157a20 100644 --- a/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp +++ b/xbmc/music/tags/MusicInfoTagLoaderFFmpeg.cpp @@ -135,6 +135,8 @@ bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName, CMusicInfoT strcasecmp(avtag->key, "TSOP") == 0) {} else if (strcasecmp(avtag->key, "TSO2") == 0) {} // Album artist sort else if (strcasecmp(avtag->key, "TSOC") == 0) {} // composer sort + else if (strcasecmp(avtag->key, "TSST") == 0) + tag.SetDiscSubtitle(avtag->value); }; AVDictionaryEntry* avtag=nullptr; From 695590b7b922cf32d8bce2da17bef3061b594623 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Sun, 1 Sep 2019 12:52:10 +0100 Subject: [PATCH 3/7] Show the disc subtitle in songview and MusicVisualisation --- addons/skin.estuary/xml/MusicVisualisation.xml | 9 +++++++++ addons/skin.estuary/xml/Variables.xml | 2 +- .../MusicDatabaseDirectory/DirectoryNode.cpp | 2 -- .../filesystem/MusicDatabaseDirectory/DirectoryNode.h | 2 +- .../MusicDatabaseDirectory/DirectoryNodeSong.cpp | 1 - xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h | 1 - xbmc/music/GUIViewStateMusic.cpp | 11 ----------- xbmc/music/MusicDatabase.cpp | 2 -- xbmc/music/infoscanner/MusicInfoScanner.cpp | 10 +++------- xbmc/music/tags/MusicInfoTag.cpp | 2 +- 10 files changed, 15 insertions(+), 27 deletions(-) diff --git a/addons/skin.estuary/xml/MusicVisualisation.xml b/addons/skin.estuary/xml/MusicVisualisation.xml index 3bbab7da638ef..f2e6d533a0dbd 100644 --- a/addons/skin.estuary/xml/MusicVisualisation.xml +++ b/addons/skin.estuary/xml/MusicVisualisation.xml @@ -77,6 +77,15 @@ black true + + 120 + 1600 + 40 + + 40 + black + true + 127 1600 diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index ab9bae788e96e..883c410785d3f 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -123,7 +123,7 @@ $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Plot] $INFO[ListItem.Property(artist_description)] $INFO[ListItem.Plot] - $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] + $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.DiscName,[COLOR button_focus]$LOCALIZE[471]:[/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]] diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp index 6eb18b40c8add..30504a4cc184e 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp @@ -35,7 +35,6 @@ #include "URL.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" -#include "utils/log.h" using namespace XFILE::MUSICDATABASEDIRECTORY; @@ -95,7 +94,6 @@ void CDirectoryNode::GetDatabaseInfo(const std::string& strPath, CQueryParams& p // Create a node object CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent) { -// CLog::Log(LOGNOTICE, "Creating a node of value %i", Type); switch (Type) { case NODE_TYPE_ROOT: diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h index a4aad738fd1a2..deb46c7a51d98 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h @@ -43,7 +43,7 @@ namespace XFILE NODE_TYPE_YEAR_ALBUM, NODE_TYPE_YEAR_SONG, NODE_TYPE_SINGLES, - NODE_TYPE_BOXSETS, //23 + NODE_TYPE_BOXSETS, NODE_TYPE_BOXSET_DISCS, NODE_TYPE_BOXSET_DISC_SONGS } NODE_TYPE; diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp index 8de9bc4878fa7..4c43f907721ba 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp +++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp @@ -10,7 +10,6 @@ #include "QueryParams.h" #include "music/MusicDatabase.h" -#include "utils/log.h" using namespace XFILE::MUSICDATABASEDIRECTORY; diff --git a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h index 0fb0187c6b159..5d9953a177b68 100644 --- a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h +++ b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h @@ -35,7 +35,6 @@ namespace XFILE long m_idSong; long m_year; long m_disc; -// long m_discNo <- temp index generated by MusicDatabase::GetBoxsetDiscs() }; } } diff --git a/xbmc/music/GUIViewStateMusic.cpp b/xbmc/music/GUIViewStateMusic.cpp index 32f00443e57d5..b76df3142d887 100644 --- a/xbmc/music/GUIViewStateMusic.cpp +++ b/xbmc/music/GUIViewStateMusic.cpp @@ -138,16 +138,6 @@ CGUIViewStateMusicDatabase::CGUIViewStateMusicDatabase(const CFileItemList& item SetSortOrder(SortOrderNone); } break; - //case NODE_TYPE_BOXSETS: - //{ - //AddSortMethod(SortByNone, 38074, LABEL_MASKS("%F", "", "%G", "")); // Filename, empty | Genre, empty - //SetSortMethod(SortByPlaycount); - - //SetViewAsControl(DEFAULT_VIEW_LIST); - - //SetSortOrder(SortOrderNone); - //} - //break; case NODE_TYPE_YEAR: { AddSortMethod(SortByLabel, 562, LABEL_MASKS("%F", "", "%Y", "")); // Filename, empty | Year, empty @@ -173,7 +163,6 @@ CGUIViewStateMusicDatabase::CGUIViewStateMusicDatabase(const CFileItemList& item case NODE_TYPE_ALBUM: case NODE_TYPE_YEAR_ALBUM: case NODE_TYPE_BOXSETS: -// case NODE_TYPE_BOXSET_DISCS: { // album AddSortMethod(SortByAlbum, sortAttribute, 558, LABEL_MASKS("%F", "", strAlbum, "%A")); // Filename, empty | Userdefined (default=%B), Artist diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 5c18303ca5d66..db2d60107ad43 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -8,7 +8,6 @@ #include "MusicDatabase.h" -#include #include "Album.h" #include "Application.h" #include "Artist.h" @@ -9066,7 +9065,6 @@ bool CMusicDatabase::GetBoxsetDiscSongs(const std::string& strBaseDir, CFileItem start = end + 1; // set start to end of albumID field + '/' end = strBaseDir.find("/", start + 1); std::string strDiscName = strBaseDir.substr(start, (strBaseDir.length() -1) - start); - CLog::Log(LOGNOTICE, "%s - Album ID [%i] strDiscName [%s]", __FUNCTION__, idAlbum, strDiscName.c_str()); if (idAlbum == -1) return false; if (m_pDB.get() == NULL || m_pDS.get() == NULL) diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp index b469f1ed37dc0..1bbd859d16540 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.cpp +++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp @@ -148,7 +148,6 @@ void CMusicInfoScanner::Process() { // Set local art for added album disc sets and primary album artists RetrieveLocalArt(); -// CreateBoxSets(); if (m_flags & SCAN_ONLINE) // Download additional album and artist information for the recently added albums. @@ -825,7 +824,6 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album album.strType = k->strAlbumType; album.songs.push_back(*k); } -// album.strDiscTitles = t_discTitles; albums.push_back(album); } } @@ -938,11 +936,8 @@ void CMusicInfoScanner::CheckBoxSets(VECALBUMS &albums) int old_discno = 0; for(auto& album : albums) { - if (album.bCompilation && !album.bBoxedSet) - { - album.bBoxedSet = false; + if (album.bCompilation && !album.bBoxedSet) continue; // skip compilations that haven't had the boxset tag added to them - } // count = 0; for (auto& song : album.songs) @@ -971,7 +966,8 @@ void CMusicInfoScanner::CheckBoxSets(VECALBUMS &albums) } } if (count < 3 && !album.bBoxedSet) // must have more than 2 titled discs to be considered a boxset unless tag was set - album.bBoxedSet = false; } + album.bBoxedSet = false; + } } } diff --git a/xbmc/music/tags/MusicInfoTag.cpp b/xbmc/music/tags/MusicInfoTag.cpp index 81e0fda498efa..aa28c48cefdb7 100644 --- a/xbmc/music/tags/MusicInfoTag.cpp +++ b/xbmc/music/tags/MusicInfoTag.cpp @@ -16,7 +16,6 @@ #include "utils/Archive.h" #include "utils/StringUtils.h" #include "utils/Variant.h" -#include "utils/log.h" #include @@ -933,6 +932,7 @@ void CMusicInfoTag::Clear() m_lastPlayed.Reset(); m_dateAdded.Reset(); m_bCompilation = false; + m_bBoxedSet = false; m_strComment.clear(); m_strMood.clear(); m_strRecordLabel.clear(); From 4a5357dd834d741ab46efc0bed0ef417949cb7a9 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Wed, 4 Sep 2019 07:22:13 +0100 Subject: [PATCH 4/7] Start to add JSON support --- .../resources/strings.po | 19 +- .../skin.estuary/xml/MusicVisualisation.xml | 6 +- addons/skin.estuary/xml/Variables.xml | 2 +- xbmc/GUIInfoManager.cpp | 272 +++++++-------- xbmc/guilib/guiinfo/GUIInfoLabels.h | 4 +- xbmc/guilib/guiinfo/MusicGUIInfo.cpp | 4 +- xbmc/interfaces/json-rpc/AudioLibrary.cpp | 9 +- xbmc/interfaces/json-rpc/schema/methods.json | 3 +- xbmc/interfaces/json-rpc/schema/types.json | 9 +- xbmc/music/MusicDatabase.cpp | 325 ++++++++---------- xbmc/music/tags/MusicInfoTag.cpp | 2 +- 11 files changed, 324 insertions(+), 331 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 4f59f0a89fe69..f3110c0b33237 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -2004,7 +2004,7 @@ msgstr "" #: addons/skin.estuary/xml/Variables.xml msgctxt "#427" -msgid "Disc Number" +msgid "Disc" msgstr "" msgctxt "#428" @@ -2192,11 +2192,8 @@ msgctxt "#470" msgid "Credits" msgstr "" -msgctxt "#471" -msgid "Disc Title" -msgstr "" -#empty strings from id 472 to 473 +#empty strings from id 471 to 473 msgctxt "#474" msgid "Off" @@ -8526,7 +8523,17 @@ msgctxt "#15311" msgid "Path:" msgstr "" -#empty strings from id 15312 to 15999 +#: addons/skin.estuary/xml/Variables.xml +msgctxt "#15312" +msgid "Disc number" +msgstr "" + +#: addons/skin.estuary/xml/Variables.xml +msgctxt "#15313" +msgid "Disc title" +msgstr "" + +#empty strings from id 15314 to 15999 #: system/settings/settings.xml msgctxt "#16000" diff --git a/addons/skin.estuary/xml/MusicVisualisation.xml b/addons/skin.estuary/xml/MusicVisualisation.xml index f2e6d533a0dbd..86e69a84025ed 100644 --- a/addons/skin.estuary/xml/MusicVisualisation.xml +++ b/addons/skin.estuary/xml/MusicVisualisation.xml @@ -78,16 +78,16 @@ true - 120 + 122 1600 40 - + 40 black true - 127 + 151 1600 40 diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 883c410785d3f..727136ad26ec4 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -123,7 +123,7 @@ $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Plot] $INFO[ListItem.Property(artist_description)] $INFO[ListItem.Plot] - $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[427]: [/COLOR],[CR]]$INFO[ListItem.DiscName,[COLOR button_focus]$LOCALIZE[471]:[/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] + $VAR[MusicTrackInfo,[COLOR button_focus]$LOCALIZE[554]:[/COLOR],[CR]]$INFO[ListItem.Artist,[COLOR button_focus]$LOCALIZE[557]: [/COLOR],[CR]]$INFO[listitem.Album,[COLOR button_focus]$LOCALIZE[558]: [/COLOR],[CR]]$INFO[ListItem.DiscNumber,[COLOR button_focus]$LOCALIZE[15312]: [/COLOR],[CR]]$INFO[ListItem.DiscTitle,[COLOR button_focus]$LOCALIZE[15313]:[/COLOR],[CR]]$INFO[ListItem.Year,[COLOR button_focus]$LOCALIZE[345]: [/COLOR],[CR]]$INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]]$INFO[ListItem.Duration,[COLOR button_focus]$LOCALIZE[180]: [/COLOR],[CR]]$INFO[ListItem.Playcount,[COLOR button_focus]$LOCALIZE[567]: [/COLOR],[CR]] $INFO[ListItem.Genre,[COLOR button_focus]$LOCALIZE[515]: [/COLOR],[CR]] diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index dcad902b23e28..81a12f342e288 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -86,14 +86,14 @@ typedef struct /// /// Skins can use infolabels with $INFO[infolabel] or the \ tag. Scripts /// can read infolabels with xbmc.getInfoLabel('infolabel'). -/// +/// /// @todo [docs] Improve the description and create links for functions /// @todo [docs] Separate boolean conditions from infolabels /// @todo [docs] Order items alphabetically within subsections for a better search experience /// @todo [docs] Order subsections alphabetically /// @todo [docs] Use links instead of bold values for infolabels/bools /// so we can use a link to point users when providing help -/// +/// /// \page modules__infolabels_boolean_conditions @@ -139,7 +139,7 @@ typedef struct /// _boolean_, /// @return **True** if the info is empty. /// @param info - infolabel -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -153,7 +153,7 @@ typedef struct /// @return **True** if the info is equal to the given string. /// @param info - infolabel /// @param string - comparison string -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -167,7 +167,7 @@ typedef struct /// @return **True** if the info starts with the given substring. /// @param info - infolabel /// @param substring - substring to check -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -181,7 +181,7 @@ typedef struct /// @return **True** if the info ends with the given substring. /// @param info - infolabel /// @param substring - substring to check -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -195,7 +195,7 @@ typedef struct /// @return **True** if the info contains the given substring. /// @param info - infolabel /// @param substring - substring to check -/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, +/// @note **Example of info:** \link ListItem_Title `ListItem.Title` \endlink \, /// \link ListItem_Genre `ListItem.Genre` \endlink. /// Please note that string can also be a `$LOCALIZE[]`. /// Also note that in a panelview or similar this only works on the focused item @@ -550,7 +550,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.FilenameAndPath`, /// \anchor Player_FilenameAndPath /// _string_, -/// @return The full path with filename of the currently +/// @return The full path with filename of the currently /// playing song or movie ///

/// } @@ -583,14 +583,14 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.ChannelPreviewActive`, /// \anchor Player_ChannelPreviewActive /// _boolean_, -/// @return **True** if PVR channel preview is active (used +/// @return **True** if PVR channel preview is active (used /// channel tag different from played tag) ///

/// } /// \table_row3{ `Player.TempoEnabled`, /// \anchor Player_TempoEnabled /// _boolean_, -/// @return **True** if player supports tempo (i.e. speed up/down normal +/// @return **True** if player supports tempo (i.e. speed up/down normal /// playback speed) ///


/// @skinning_v17 **[New Boolean Condition]** \link Player_TempoEnabled `Player.TempoEnabled`\endlink @@ -608,9 +608,9 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.PlaySpeed`, /// \anchor Player_PlaySpeed /// _string_, -/// @return The player playback speed with the format `%1.2f` (1.00 means normal +/// @return The player playback speed with the format `%1.2f` (1.00 means normal /// playback speed). -/// @note For Tempo\, the default range is 0.80 - 1.50 (it can be changed +/// @note For Tempo\, the default range is 0.80 - 1.50 (it can be changed /// in advanced settings). If \ref Player_PlaySpeed "Player.PlaySpeed" returns a value different from 1.00 /// and \ref Player_IsTempo "Player.IsTempo" is false it means the player is in ff/rw mode. ///

@@ -618,7 +618,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.HasResolutions`, /// \anchor Player_HasResolutions /// _boolean_, -/// @return **True** if the player is allowed to switch resolution and refresh rate +/// @return **True** if the player is allowed to switch resolution and refresh rate /// (i.e. if whitelist modes are configured in Kodi's System/Display settings) ///


/// @skinning_v18 **[New Boolean Condition]** \link Player_HasResolutions `Player.HasResolutions`\endlink @@ -627,7 +627,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// \table_row3{ `Player.HasPrograms`, /// \anchor Player_HasPrograms /// _boolean_, -/// @return **True** if the media file being played has programs\, i.e. groups of streams. +/// @return **True** if the media file being played has programs\, i.e. groups of streams. /// @note Ex: if a media file has multiple streams (quality\, channels\, etc) a program represents /// a particular stream combo. ///

@@ -648,7 +648,7 @@ const infomap integer_bools[] = {{ "isequal", INTEGER_IS_EQUAL }, /// the icon will be returned\, if available. ///


/// @skinning_v18 **[New Infolabel]** \link Player_Icon `Player.Icon`\endlink -///

+///

/// } /// \table_row3{ `Player.Cutlist`, /// \anchor Player_Cutlist @@ -1115,7 +1115,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// _boolean_, /// @return **True** if PVR is supported from Kodi. /// @note normally always true -/// +/// /// } /// \table_row3{ `System.HasPVRAddon`, /// \anchor System_HasPVRAddon @@ -1403,7 +1403,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// \table_row3{ `System.FriendlyName`, /// \anchor System_FriendlyName /// _string_, -/// @return The Kodi instance name. +/// @return The Kodi instance name. /// @note It will auto append (%hostname%) in case /// the device name was not changed. eg. "Kodi (htpc)" ///

@@ -1575,7 +1575,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// \table_row3{ `System.GetBool(boolean)`, /// \anchor System_GetBool /// _string_, -/// @return The value of any standard system boolean setting. +/// @return The value of any standard system boolean setting. /// @note Will not work with settings in advancedsettings.xml ///

/// } @@ -2010,9 +2010,9 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// _string_, /// @return The name of the dj who remixed the selected song. /// @todo So maybe rather than a row each have one entry for Role.XXXXX with composer\, arranger etc. as listed values -/// @note MusicPlayer.Property(Role.any_custom_role) also works\, +/// @note MusicPlayer.Property(Role.any_custom_role) also works\, /// where any_custom_role could be an instrument violin or some other production activity e.g. sound engineer. -/// The roles listed (composer\, arranger etc.) are standard ones but there are many possible. +/// The roles listed (composer\, arranger etc.) are standard ones but there are many possible. /// Music file tagging allows for the musicians and all other people involved in the recording to be added\, Kodi /// will gathers and stores that data\, and it is availlable to GUI. ///


@@ -2076,7 +2076,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// _string_, /// @return Artist(s) of the song which has an offset `number` with respect /// to the start of the playlist. -/// @param number - the offset of the song with respect to +/// @param number - the offset of the song with respect to /// the start of the playlist ///

/// } @@ -2098,7 +2098,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// @return The sortname of the currently playing Artist. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Sortname `MusicPlayer.Property(Artist_Sortname)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Type)`, /// \anchor MusicPlayer_Property_Artist_Type @@ -2107,7 +2107,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// group\, orchestra\, choir etc. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Type `MusicPlayer.Property(Artist_Type)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Gender)`, /// \anchor MusicPlayer_Property_Artist_Gender @@ -2116,7 +2116,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// female\, other. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Gender `MusicPlayer.Property(Artist_Gender)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Disambiguation)`, /// \anchor MusicPlayer_Property_Artist_Disambiguation @@ -2125,7 +2125,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// from others with the same name. ///


/// @skinning_v18 **[New Infolabel]** \link MusicPlayer_Property_Artist_Disambiguation `MusicPlayer.Property(Artist_Disambiguation)`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Property(Artist_Born)`, /// \anchor MusicPlayer_Property_Artist_Born @@ -2275,7 +2275,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// @return The scraped rating of the currently playing song (1-10). ///


/// @skinning_v17 **[New Infolabel]** \link MusicPlayer_UserRating `MusicPlayer.UserRating`\endlink -///

+///

/// } /// \table_row3{ `MusicPlayer.Votes`, /// \anchor MusicPlayer_Votes @@ -2414,7 +2414,7 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// _string_, /// @return The track number of the song with an offset `number` /// with respect to start of the playlist. -/// @param number - The offset number of the song with respect +/// @param number - The offset number of the song with respect /// to start of the playlist ///

/// } @@ -2540,7 +2540,7 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, { "samplerate", MUSICPLAYER_SAMPLERATE }, { "codec", MUSICPLAYER_CODEC }, { "discnumber", MUSICPLAYER_DISC_NUMBER }, - { "discname", MUSICPLAYER_DISC_NAME }, + { "disctitle", MUSICPLAYER_DISC_TITLE }, { "isboxset", MUSICPLAYER_IS_BOXSET }, { "rating", MUSICPLAYER_RATING }, { "ratingandvotes", MUSICPLAYER_RATING_AND_VOTES }, @@ -3128,7 +3128,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, /// - original (Shrink to the original resolution) ///


/// @skinning_v18 **[New Infolabel]** \link RetroPlayer_StretchMode `RetroPlayer.StretchMode`\endlink -///

+///

/// } /// \table_row3{ `RetroPlayer.VideoRotation`, /// \anchor RetroPlayer_VideoRotation @@ -3142,7 +3142,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, /// - 270 (Shown in the GUI as 90 degrees) ///


/// @skinning_v18 **[New Infolabel]** \link RetroPlayer_VideoRotation `RetroPlayer.VideoRotation`\endlink -///

+///

/// } /// \table_end /// @@ -3341,14 +3341,14 @@ const infomap mediacontainer[] = {{ "hasfiles", CONTAINER_HASFILES }, /// \table_row3{ `Container(id).NumItems`, /// \anchor Container_NumItems /// _integer_, -/// @return The number of items in the container or grouplist with given id excluding parent folder item. +/// @return The number of items in the container or grouplist with given id excluding parent folder item. /// @note If no id is specified it grabs the current container. ///

/// } /// \table_row3{ `Container(id).NumAllItems`, /// \anchor Container_NumAllItems /// _integer_, -/// @return The number of all items in the container or grouplist with given id including parent folder item. +/// @return The number of all items in the container or grouplist with given id including parent folder item. /// @note If no id is specified it grabs the current container. ///


/// @skinning_v18 **[New Infolabel]** \link Container_NumAllItems `Container(id).NumAllItems`\endlink @@ -3358,7 +3358,7 @@ const infomap mediacontainer[] = {{ "hasfiles", CONTAINER_HASFILES }, /// \anchor Container_NumNonFolderItems /// _integer_, /// @return The Number of items in the container or grouplist with given id excluding all folder items. -/// @note **Example:** pvr recordings folders\, parent ".." folder). +/// @note **Example:** pvr recordings folders\, parent ".." folder). /// If no id is specified it grabs the current container. ///


/// @skinning_v18 **[New Infolabel]** \link Container_NumNonFolderItems `Container(id).NumNonFolderItems`\endlink @@ -3377,7 +3377,7 @@ const infomap mediacontainer[] = {{ "hasfiles", CONTAINER_HASFILES }, /// @return **True** if the user is currently scrolling through the container /// with id (or current container if id is omitted). /// @note This is slightly delayed from the actual scroll start. Use -/// \ref Container_OnScrollNext "Container(id).OnScrollNext" or +/// \ref Container_OnScrollNext "Container(id).OnScrollNext" or /// \ref Container_OnScrollPrevious "Container(id).OnScrollPrevious" to trigger animations /// immediately on scroll. ///

@@ -3473,7 +3473,7 @@ const infomap container_bools[] ={{ "onnext", CONTAINER_MOVE_NEXT }, /// \table_row3{ `Container(id).CurrentItem`, /// \anchor Container_CurrentItem /// _integer_, -/// @return The current item in the container or grouplist with given id. +/// @return The current item in the container or grouplist with given id. /// @note If no id is specified it grabs the current container. ///


/// @skinning_v15 **[New Infolabel]** \link Container_CurrentItem `Container(id).CurrentItem`\endlink @@ -3537,8 +3537,8 @@ const infomap container_ints[] = {{ "row", CONTAINER_ROW }, /// @return the same as \link Container_ListItem_property `Container(id).ListItem(offset).Property` \endlink /// but it won't wrap. /// @param offset - The offset for the listitem. -/// @note That means if the last item of a list is focused\, `ListItemNoWrap(1)` -/// will be empty while `ListItem(1)` will return the first item of the list. +/// @note That means if the last item of a list is focused\, `ListItemNoWrap(1)` +/// will be empty while `ListItem(1)` will return the first item of the list. /// `Property` has to be replaced with `Label`\, `Label2`\, `Icon` etc. /// @note **Example:** `Container(50).ListitemNoWrap(1).Plot` ///

@@ -3565,15 +3565,15 @@ const infomap container_ints[] = {{ "row", CONTAINER_ROW }, /// \anchor Container_Content_parameter /// _string_, /// @return **True** if the current container you are in contains the following: -/// - files -/// - songs +/// - files +/// - songs /// - artists -/// - albums +/// - albums /// - movies /// - tvshows /// - seasons -/// - episodes -/// - musicvideos +/// - episodes +/// - musicvideos /// - genres /// - years /// - actors @@ -3636,7 +3636,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.Icon`, /// \anchor ListItem_Icon /// _string_, -/// @return The thumbnail (if it exists) of the currently selected item in a list or thumb control. +/// @return The thumbnail (if it exists) of the currently selected item in a list or thumb control. /// @note If no thumbnail image exists\, it will show the icon. ///

/// } @@ -4314,9 +4314,9 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.PictureExpTime`, /// \anchor ListItem_PictureExpTime /// _string_, -/// @return The exposure time of the selected picture\, in seconds. +/// @return The exposure time of the selected picture\, in seconds. /// @note This is the value of the EXIF ExposureTime tag (hex code 0x829A). -/// If the ExposureTime tag is not found\, the ShutterSpeedValue tag (hex code 0x9201) +/// If the ExposureTime tag is not found\, the ShutterSpeedValue tag (hex code 0x9201) /// might be used. ///

/// } @@ -4339,7 +4339,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.PictureFocusDist`, /// \anchor ListItem_PictureFocusDist /// _string_, -/// @return The focal length of the lens\, in mm. +/// @return The focal length of the lens\, in mm. /// @note This is the value of the EXIF FocalLength tag (hex code 0x920A). /// } /// \table_row3{ `ListItem.PictureGPSLat`, @@ -4354,7 +4354,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureGPSLon /// _string_, /// @return The longitude where the selected picture was taken (degrees\, -/// minutes\, seconds East or West). +/// minutes\, seconds East or West). /// @note This is the value of the EXIF GPSInfo.GPSLongitude and GPSInfo.GPSLongitudeRef tags. ///

/// } @@ -4435,7 +4435,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureLongDate /// _string_, /// @return Only the localized date of the selected picture. The long form of -/// the date is used. +/// the date is used. /// @note The value of the EXIF DateTimeOriginal tag (hex code /// 0x9003) is preferred. If the DateTimeOriginal tag is not found\, the /// value of DateTimeDigitized (hex code 0x9004) or of DateTime (hex code @@ -4448,7 +4448,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureLongDatetime /// _string_, /// @return The date/timestamp of the selected picture. The localized long -/// form of the date and time is used. +/// form of the date and time is used. /// @note The value of the EXIF DateTimeOriginal /// tag (hex code 0x9003) is preferred. if the DateTimeOriginal tag is not /// found\, the value of DateTimeDigitized (hex code 0x9004) or of DateTime @@ -4462,7 +4462,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// possible values are: /// - "Center weight" /// - "Spot" -/// - "Matrix" +/// - "Matrix" /// @note This is the value of the EXIF MeteringMode tag (hex code 0x9207). ///


/// @skinning_v13 **[New Infolabel]** \link ListItem_PictureMeteringMode `ListItem.PictureMeteringMode`\endlink @@ -4481,11 +4481,11 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PictureOrientation /// _string_, /// @return The orientation of the selected picture. Possible values are: -/// - "Top Left" +/// - "Top Left" /// - "Top Right" /// - "Left Top" /// - "Right Bottom" -/// - etc +/// - etc /// @note This is the value of the EXIF Orientation tag (hex code 0x0112). ///


/// @skinning_v13 **[New Infolabel]** \link ListItem_PictureOrientation `ListItem.PictureOrientation`\endlink @@ -4686,8 +4686,8 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.Rating([name])`, /// \anchor ListItem_Rating /// _string_, -/// @return The scraped rating of the currently selected item in a container (1-10). -/// @param name - [opt] you can specify the name of the scraper to retrieve a specific rating\, +/// @return The scraped rating of the currently selected item in a container (1-10). +/// @param name - [opt] you can specify the name of the scraper to retrieve a specific rating\, /// for use in dialogvideoinfo.xml. ///


/// @skinning_v18 **[Infolabel Updated]** \link ListItem_Rating `ListItem.Rating([name])`\endlink replaces @@ -4703,7 +4703,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the set the movie is part of. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Set `ListItem.Set`\endlink -///

+///

/// } /// \table_row3{ `ListItem.SetId`, /// \anchor ListItem_SetId @@ -4711,7 +4711,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The id of the set the movie is part of. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_SetId `ListItem.SetId`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Status`, /// \anchor ListItem_Status @@ -4726,7 +4726,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @note For use with tv shows. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Status `ListItem.Status`\endlink -///

+///

/// } /// \table_row3{ `ListItem.EndTimeResume`, /// \anchor ListItem_EndTimeResume @@ -4734,7 +4734,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return Returns the time a video will end if you resume it\, instead of playing it from the beginning. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_EndTimeResume `ListItem.EndTimeResume`\endlink -///

+///

/// } /// \table_row3{ `ListItem.UserRating`, /// \anchor ListItem_UserRating @@ -4756,7 +4756,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v17 **[Infolabel Updated]** \link ListItem_Votes `ListItem.Votes([name])`\endlink /// add optional param name to specify the scrapper. /// @skinning_v13 **[New Infolabel]** \link ListItem_Votes `ListItem.Votes`\endlink -///

+///

/// } /// \table_row3{ `ListItem.RatingAndVotes([name])`, /// \anchor ListItem_RatingAndVotes @@ -4769,7 +4769,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v17 **[New Infolabel]** \link ListItem_RatingAndVotes `ListItem.RatingAndVotes([name])`\endlink /// @skinning_v17 **[Infolabel Updated]** \link ListItem_RatingAndVotes `ListItem.RatingAndVotes`\endlink /// now available for albums/songs. -///

+///

/// } /// \table_row3{ `ListItem.Mood`, /// \anchor ListItem_Mood @@ -4777,7 +4777,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The mood of the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Mood `ListItem.Mood`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Mpaa`, /// \anchor ListItem_Mpaa @@ -4817,7 +4817,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// _string_, /// @return The database type of the \ref ListItem_DBID "ListItem.DBID" for videos (movie\, set\, /// genre\, actor\, tvshow\, season\, episode). It does not return any value -/// for the music library. +/// for the music library. /// @note Beware with season\, the "*all seasons" entry does /// give a DBTYPE "season" and a DBID\, but you can't get the details of that /// entry since it's a virtual entry in the Video Library. @@ -4892,7 +4892,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The summary of current Video in a container. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Tag `ListItem.Tag`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Tagline`, /// \anchor ListItem_Tagline @@ -4989,7 +4989,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_VideoResolution /// _string_, /// @return The resolution of the currently selected video. Possible values: -/// - 480 +/// - 480 /// - 576 /// - 540 /// - 720 @@ -5105,7 +5105,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_row3{ `ListItem.Property(SubtitleLanguage.[n])`, /// \anchor ListItem_Property_SubtitleLanguage /// _string_, -/// @return The subtitle language of the currently selected video +/// @return The subtitle language of the currently selected video /// @param n - the number of the subtitle (values: see \ref ListItem_SubtitleLanguage "ListItem.SubtitleLanguage") ///


/// @skinning_v16 **[New Infolabel]** \link ListItem_Property_SubtitleLanguage `ListItem.Property(SubtitleLanguage.[n])`\endlink @@ -5526,7 +5526,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// - artist.fanart - the artist fanart of an album or song item. /// - album.thumb - the album thumb (cover) of a song item. /// - artist[n].* - in case a song has multiple artists\, a digit is added to the art type for the 2nd artist onwards -/// e.g `Listitem.Art(artist1.thumb)` gives the thumb of the 2nd artist of a song. +/// e.g `Listitem.Art(artist1.thumb)` gives the thumb of the 2nd artist of a song. /// - albumartist[n].* - n case a song has multiple album artists\, a digit is added to the art type for the 2nd artist /// onwards e.g `Listitem.Art(artist1.thumb)` gives the thumb of the 2nd artist of a song. ///

@@ -5534,7 +5534,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, ///


/// @skinning_v18 **[Infolabel Updated]** \link ListItem_Art_Type `ListItem.Art(type)`\endlink add artist[n].* and /// albumartist[n].* as possible targets for type -///

+///

/// } /// \table_row3{ `ListItem.Platform`, /// \anchor ListItem_Platform @@ -5542,7 +5542,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game platform (e.g. "Atari 2600") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Platform `ListItem.Platform`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Genres`, /// \anchor ListItem_Genres @@ -5550,7 +5550,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game genres (e.g. "["Action"\,"Strategy"]") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Genres `ListItem.Genres`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Publisher`, /// \anchor ListItem_Publisher @@ -5558,7 +5558,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game publisher (e.g. "Nintendo") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Publisher `ListItem.Publisher`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Developer`, /// \anchor ListItem_Developer @@ -5566,7 +5566,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game developer (e.g. "Square") (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Developer `ListItem.Developer`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Overview`, /// \anchor ListItem_Overview @@ -5574,7 +5574,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The game overview/summary (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Overview `ListItem.Overview`\endlink -///

+///

/// } /// \table_row3{ `ListItem.GameClient`, /// \anchor ListItem_GameClient @@ -5583,7 +5583,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// (e.g. game.libretro.fceumm) (RETROPLAYER). ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_GameClient `ListItem.GameClient`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(propname)`, /// \anchor ListItem_Property_Propname @@ -5598,7 +5598,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who composed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Composer `ListItem.Property(Role.Composer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Conductor)`, /// \anchor ListItem_Property_Role_Conductor @@ -5606,7 +5606,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who conducted the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Conductor `ListItem.Property(Role.Conductor)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Orchestra)`, /// \anchor ListItem_Property_Role_Orchestra @@ -5614,7 +5614,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the orchestra performing the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Orchestra `ListItem.Property(Role.Orchestra)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Lyricist)`, /// \anchor ListItem_Property_Role_Lyricist @@ -5622,7 +5622,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who wrote the lyrics of the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Lyricist `ListItem.Property(Role.Lyricist)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Remixer)`, /// \anchor ListItem_Property_Role_Remixer @@ -5630,7 +5630,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who remixed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Remixer `ListItem.Property(Role.Remixer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Arranger)`, /// \anchor ListItem_Property_Role_Arranger @@ -5638,7 +5638,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who arranged the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Arranger `ListItem.Property(Role.Arranger)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Engineer)`, /// \anchor ListItem_Property_Role_Engineer @@ -5646,7 +5646,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who was the engineer of the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Engineer `ListItem.Property(Role.Engineer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Producer)`, /// \anchor ListItem_Property_Role_Producer @@ -5654,7 +5654,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who produced the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_Producer `ListItem.Property(Role.Producer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.DJMixer)`, /// \anchor ListItem_Property_Role_DJMixer @@ -5662,7 +5662,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the dj who remixed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_DJMixer `ListItem.Property(Role.DJMixer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Role.Mixer)`, /// \anchor ListItem_Property_Role_Mixer @@ -5670,7 +5670,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The name of the person who mixed the selected song. ///


/// @skinning_v17 **[New Infolabel]** \link ListItem_Property_Role_DJMixer `ListItem.Property(Role.DJMixer)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Game.VideoFilter)`, /// \anchor ListItem_Property_Game_VideoFilter @@ -5681,7 +5681,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// for the possible values. ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Property_Game_VideoFilter `ListItem.Property(Game.VideoFilter)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Game.StretchMode)`, /// \anchor ListItem_Property_Game_StretchMode @@ -5692,7 +5692,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// for the possible values. ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Property_Game_StretchMode `ListItem.Property(Game.StretchMode)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.Property(Game.VideoRotation)`, /// \anchor ListItem_Property_Game_VideoRotation @@ -5703,7 +5703,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// for the possible values. ///


/// @skinning_v18 **[New Infolabel]** \link ListItem_Property_Game_VideoRotation `ListItem.Property(Game.VideoRotation)`\endlink -///

+///

/// } /// \table_row3{ `ListItem.ParentalRating`, /// \anchor ListItem_ParentalRating @@ -5737,9 +5737,9 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "genre", LISTITEM_GENRE }, { "contributors", LISTITEM_CONTRIBUTORS }, { "contributorandrole", LISTITEM_CONTRIBUTOR_AND_ROLE }, - { "discname", LISTITEM_DISC_NAME }, - { "isboxset", LISTITEM_IS_BOXSET }, { "director", LISTITEM_DIRECTOR }, + { "disctitle", LISTITEM_DISC_TITLE }, + { "isboxset", LISTITEM_IS_BOXSET }, { "filename", LISTITEM_FILENAME }, { "filenameandpath", LISTITEM_FILENAME_AND_PATH }, { "fileextension", LISTITEM_FILE_EXTENSION }, @@ -6099,7 +6099,7 @@ const infomap skin_labels[] = {{ "currenttheme", SKIN_THEME }, /// \anchor Window_IsModalDialogTopmost /// _boolean_, /// @return **True** if the dialog with id or title _dialog_ is on top of the -/// modal dialog stack +/// modal dialog stack /// @note Excludes fade out time on dialogs ///

/// } @@ -6166,7 +6166,7 @@ const infomap skin_labels[] = {{ "currenttheme", SKIN_THEME }, /// - 36Hour.%i.OutlookIcon /// - Weekend.%i.OutlookIcon /// - Hourly.%i.OutlookIcon -/// +/// /// previously the openweathermap addon would provide the full\, hardcoded path to the icon /// ie. `resource://resource.images.weathericons.default/28.png` /// to make it easier for skins to work with custom icon sets\, it now will return the filename only @@ -6773,7 +6773,7 @@ const infomap playlist[] = {{ "length", PLAYLIST_LENGTH }, /// @return The currently entered channel number while in numeric channel input mode\, an empty string otherwise. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_ChannelNumberInput `PVR.ChannelNumberInput`\endlink -///

+///

/// } /// \table_row3{ `PVR.CanRecordPlayingChannel`, /// \anchor PVR_CanRecordPlayingChannel @@ -6807,7 +6807,7 @@ const infomap playlist[] = {{ "length", PLAYLIST_LENGTH }, /// @return The percentage of the current play position within the PVR timeshift progress. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressPlayPos `PVR.TimeshiftProgressPlayPos`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressEpgStart`, /// \anchor PVR_TimeshiftProgressEpgStart @@ -6976,7 +6976,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_EpgEventRemainingTime `PVR.EpgEventRemainingTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.EpgEventRemainingTime(format)`, /// \anchor PVR_EpgEventRemainingTime_format @@ -6994,7 +6994,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_EpgEventSeekTime `PVR.EpgEventSeekTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.EpgEventSeekTime(format)`, /// \anchor PVR_EpgEventSeekTime_format @@ -7012,7 +7012,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_EpgEventFinishTime `PVR.EpgEventFinishTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.EpgEventFinishTime(format)`, /// \anchor PVR_EpgEventFinishTime_format @@ -7094,7 +7094,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressDuration `PVR.TimeshiftProgressDuration`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressDuration(format)`, /// \anchor PVR_TimeshiftProgressDuration_format @@ -7112,7 +7112,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressStartTime `PVR.TimeshiftProgressStartTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressStartTime(format)`, /// \anchor PVR_TimeshiftProgressStartTime_format @@ -7130,7 +7130,7 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING /// @note hh: will be omitted if hours value is zero. ///


/// @skinning_v18 **[New Infolabel]** \link PVR_TimeshiftProgressEndTime `PVR.TimeshiftProgressEndTime`\endlink -///

+///

/// } /// \table_row3{ `PVR.TimeshiftProgressEndTime(format)`, /// \anchor PVR_TimeshiftProgressEndTime_format @@ -7159,7 +7159,7 @@ const infomap pvr_times[] = {{ "epgeventduration", PVR_EPG_EVENT_DURA /// \page modules__infolabels_boolean_conditions /// \subsection modules__infolabels_boolean_conditions_RDS RDS /// @note Only supported if both the PVR backend and the Kodi client support RDS. -/// +/// /// \table_start /// \table_h3{ Labels, Type, Description } /// \table_row3{ `RDS.HasRds`, @@ -7700,7 +7700,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Author /// _string_, /// @return The name of the person involved in writing about the current -/// picture. +/// picture. /// @note This is the value of the IPTC Writer tag (hex code 0x7A). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Author `Slideshow.Author`\endlink @@ -7709,7 +7709,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.Byline`, /// \anchor Slideshow_Byline /// _string_, -/// @return The name of the person who created the current picture. +/// @return The name of the person who created the current picture. /// @note This is the value of the IPTC Byline tag (hex code 0x50). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Byline `Slideshow.Byline`\endlink @@ -7718,7 +7718,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.BylineTitle`, /// \anchor Slideshow_BylineTitle /// _string_, -/// @return The title of the person who created the current picture. +/// @return The title of the person who created the current picture. /// @note This is the value of the IPTC BylineTitle tag (hex code 0x55). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_BylineTitle `Slideshow.BylineTitle`\endlink @@ -7735,14 +7735,14 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_CameraModel /// _string_, /// @return The manufacturer's model name or number of the camera used to take -/// the current picture. +/// the current picture. /// @note This is the value of the EXIF Model tag (hex code 0x0110). ///

/// } /// \table_row3{ `Slideshow.Caption`, /// \anchor Slideshow_Caption /// _string_, -/// @return A description of the current picture. +/// @return A description of the current picture. /// @note This is the value of the IPTC Caption tag (hex code 0x78). ///

/// } @@ -7759,7 +7759,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_CCDWidth /// _string_, /// @return The width of the CCD in the camera used to take the current -/// picture. +/// picture. /// @note This is calculated from three EXIF tags (0xA002 * 0xA210 / 0xA20e). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_CCDWidth `Slideshow.CCDWidth`\endlink @@ -7787,7 +7787,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.CopyrightNotice`, /// \anchor Slideshow_CopyrightNotice /// _string_, -/// @return The copyright notice of the current picture. +/// @return The copyright notice of the current picture. /// @note This is the value of the IPTC Copyright tag (hex code 0x74). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_CopyrightNotice `Slideshow.CopyrightNotice`\endlink @@ -7806,7 +7806,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_CountryCode /// _string_, /// @return The country code of the country where the current picture was -/// taken. +/// taken. /// @note This is the value of the IPTC CountryCode tag (hex code 0x64). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_CountryCode `Slideshow.CountryCode`\endlink @@ -7815,7 +7815,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.Credit`, /// \anchor Slideshow_Credit /// _string_, -/// @return Who provided the current picture. +/// @return Who provided the current picture. /// @note This is the value of the IPTC Credit tag (hex code 0x6E). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Credit `Slideshow.Credit`\endlink @@ -7833,8 +7833,8 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.EXIFComment`, /// \anchor Slideshow_EXIFComment /// _string_, -/// @return A description of the current picture. -/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). +/// @return A description of the current picture. +/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). /// This is the same value as \ref Slideshow_SlideComment "Slideshow.SlideComment". ///

/// } @@ -7855,7 +7855,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_EXIFDescription /// _string_, /// @return A short description of the current picture. The SlideComment\, -/// EXIFComment or Caption values might contain a longer description. +/// EXIFComment or Caption values might contain a longer description. /// @note This is the value of the EXIF ImageDescription tag (hex code 0x010E). ///

/// } @@ -7863,7 +7863,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_EXIFSoftware /// _string_, /// @return The name and version of the firmware used by the camera that took -/// the current picture. +/// the current picture. /// @note This is the value of the EXIF Software tag (hex code 0x0131). ///

/// } @@ -7871,10 +7871,10 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_EXIFTime /// _string_, /// @return The date/timestamp of the current picture. The localized short -/// form of the date and time is used. -/// @note The value of the EXIF DateTimeOriginal tag (hex code 0x9003) is -/// preferred. If the DateTimeOriginal tag is not found\, the value of -/// DateTimeDigitized (hex code 0x9004) or of DateTime (hex code 0x0132) +/// form of the date and time is used. +/// @note The value of the EXIF DateTimeOriginal tag (hex code 0x9003) is +/// preferred. If the DateTimeOriginal tag is not found\, the value of +/// DateTimeDigitized (hex code 0x9004) or of DateTime (hex code 0x0132) /// might be used. ///

/// } @@ -7887,7 +7887,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// - "Program (Auto)" /// - "Aperture priority (Semi-Auto)" /// - "Shutter priority (semi-auto)" -/// - etc... +/// - etc... /// @note This is the value of the EXIF ExposureProgram tag /// (hex code 0x8822). ///


@@ -7954,7 +7954,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.FocalLength`, /// \anchor Slideshow_FocalLength /// _string_, -/// @return The focal length of the lens\, in mm. +/// @return The focal length of the lens\, in mm. /// @note This is the value of the EXIF FocalLength tag (hex code 0x920A). ///

/// } @@ -8009,8 +8009,8 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Latitude /// _string_, /// @return The latitude where the current picture was taken (degrees\, -/// minutes\, seconds North or South). -/// @note This is the value of the EXIF GPSInfo.GPSLatitude and +/// minutes\, seconds North or South). +/// @note This is the value of the EXIF GPSInfo.GPSLatitude and /// GPSInfo.GPSLatitudeRef tags. ///

/// } @@ -8032,7 +8032,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_LongEXIFDate /// _string_, /// @return Only the localized date of the current picture. The long form of -/// the date is used. +/// the date is used. /// @note The value of the EXIF DateTimeOriginal tag (hex code /// 0x9003) is preferred. If the DateTimeOriginal tag is not found\, the /// value of DateTimeDigitized (hex code 0x9004) or of DateTime (hex code @@ -8059,7 +8059,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// _string_, /// @return The longitude where the current picture was taken (degrees\, /// minutes\, seconds East or West). -/// @note This is the value of the EXIF GPSInfo.GPSLongitude and +/// @note This is the value of the EXIF GPSInfo.GPSLongitude and /// GPSInfo.GPSLongitudeRef tags. ///

/// } @@ -8132,8 +8132,8 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_row3{ `Slideshow.SlideComment`, /// \anchor Slideshow_SlideComment /// _string_, -/// @return A description of the current picture. -/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). +/// @return A description of the current picture. +/// @note This is the value of the EXIF User Comment tag (hex code 0x9286). /// This is the same value as \ref Slideshow_EXIFComment "Slideshow.EXIFComment". ///

/// } @@ -8175,7 +8175,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Sublocation /// _string_, /// @return The location within a city where the current picture was taken - -/// might indicate the nearest landmark. +/// might indicate the nearest landmark. /// @note This is the value of the IPTC SubLocation tag (hex code 0x5C). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_Sublocation `Slideshow.Sublocation`\endlink @@ -8185,7 +8185,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_SupplementalCategories /// _string_, /// @return The supplemental category codes to further refine the subject of the -/// current picture. +/// current picture. /// @note This is the value of the IPTC SuppCategory tag (hex /// code 0x14). ///


@@ -8196,7 +8196,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_TimeCreated /// _string_, /// @return The time when the intellectual content of the current picture was -/// created\, rather than when the picture was created. +/// created\, rather than when the picture was created. /// @note This is the value of the IPTC TimeCreated tag (hex code 0x3C). ///


/// @skinning_v13 **[New Infolabel]** \link Slideshow_TimeCreated `Slideshow.TimeCreated`\endlink @@ -8206,7 +8206,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_TransmissionReference /// _string_, /// @return A code representing the location of original transmission of the -/// current picture. +/// current picture. /// @note This is the value of the IPTC TransmissionReference tag /// (hex code 0x67). ///


@@ -8217,7 +8217,7 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \anchor Slideshow_Urgency /// _string_, /// @return The urgency of the current picture. Values are 1-9. The 1 is most -/// urgent. +/// urgent. /// @note Some image management programs use urgency to indicate picture /// rating\, where urgency 1 is 5 stars and urgency 5 is 1 star. Urgencies /// 6-9 are not used for rating. This is the value of the IPTC Urgency tag @@ -8470,7 +8470,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// \subsection modules_rm_infolabels_booleans_v18 Kodi v18 (Leia) /// /// @skinning_v18 **[Removed Infolabels]** The following infolabels have been removed: -/// - `Listitem.Property(artistthumbs)`, `Listitem.Property(artistthumb)` - use +/// - `Listitem.Property(artistthumbs)`, `Listitem.Property(artistthumb)` - use /// \link ListItem_Art_Type `ListItem.Art(type)`\endlink with albumartist[n].* or artist[n].* as type /// - `ADSP.ActiveStreamType` /// - `ADSP.DetectedStreamType` @@ -8478,10 +8478,10 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ADSP.MasterInfo` /// - `ADSP.MasterOwnIcon` /// - `ADSP.MasterOverrideIcon` -/// - `ListItem.ChannelNumber`, `ListItem.SubChannelNumber`, `MusicPlayer.ChannelNumber`, +/// - `ListItem.ChannelNumber`, `ListItem.SubChannelNumber`, `MusicPlayer.ChannelNumber`, /// `MusicPlayer.SubChannelNumber`, `VideoPlayer.ChannelNumber`, /// `VideoPlayer.SubChannelNumber`. Please use the following alternatives -/// \link ListItem_ChannelNumberLabel `ListItem.ChannelNumberLabel` \endlink, +/// \link ListItem_ChannelNumberLabel `ListItem.ChannelNumberLabel` \endlink, /// \link MusicPlayer_ChannelNumberLabel `MusicPlayer.ChannelNumberLabel` \endlink /// \link VideoPlayer_ChannelNumberLabel `VideoPlayer.ChannelNumberLabel` \endlink from now on. /// @@ -8522,7 +8522,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ADSP.HasOutputResample` /// - `ADSP.MasterActive` /// - `System.HasModalDialog` -/// +/// /// @skinning_v16 **[New Infolabels]** The following infolabels were added: /// - `ADSP.ActiveStreamType` /// - `ADSP.DetectedStreamType` @@ -8530,7 +8530,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ADSP.MasterInfo` /// - `ADSP.MasterOwnIcon` /// - `ADSP.MasterOverrideIcon` -/// +/// /// @skinning_v16 **[Removed Boolean Conditions]** The following infobols were removed: /// - `System.Platform.ATV2` @@ -8542,12 +8542,12 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// - `ListItem.SubChannelNumber` /// - `MusicPlayer.SubChannelNumber` /// - `VideoPlayer.SubChannelNumber` -/// +/// ///
/// \subsection modules_rm_infolabels_booleans_v13 XBMC v13 (Gotham) /// @skinning_v13 **[Removed Infolabels]** The following infolabels were removed: /// - `Network.SubnetAddress` -/// +/// ///
// Crazy part, to use tableofcontents must it be on end /// \page modules__infolabels_boolean_conditions diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index e2e7bd7b230c4..d671a7a4ec6aa 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -194,7 +194,7 @@ #define MUSICPLAYER_CONTRIBUTOR_AND_ROLE 240 #define MUSICPLAYER_DBID 241 #define MUSICPLAYER_PROPERTY 242 -#define MUSICPLAYER_DISC_NAME 243 +#define MUSICPLAYER_DISC_TITLE 243 #define MUSICPLAYER_IS_BOXSET 244 #define VIDEOPLAYER_AUDIO_BITRATE 248 #define VIDEOPLAYER_VIDEO_BITRATE 249 @@ -878,7 +878,7 @@ #define LISTITEM_ISPLAYABLE (LISTITEM_START + 186) #define LISTITEM_FILENAME_NO_EXTENSION (LISTITEM_START + 187) #define LISTITEM_CURRENTITEM (LISTITEM_START + 188) -#define LISTITEM_DISC_NAME (LISTITEM_START + 188) +#define LISTITEM_DISC_TITLE (LISTITEM_START + 188) #define LISTITEM_IS_BOXSET (LISTITEM_START + 189) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp index a2675c88a5a4b..8ad343c27bea4 100644 --- a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp @@ -142,8 +142,8 @@ bool CMusicGUIInfo::GetLabel(std::string& value, const CFileItem *item, int cont return true; } break; - case MUSICPLAYER_DISC_NAME: - case LISTITEM_DISC_NAME: + case MUSICPLAYER_DISC_TITLE: + case LISTITEM_DISC_TITLE: value = tag->GetDiscSubtitle(); return true; case MUSICPLAYER_ARTIST: diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index bcad21ee60570..b7a926f7db3a7 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -180,6 +180,9 @@ JSONRPC_STATUS CAudioLibrary::GetAlbums(const std::string &method, ITransportLay if (parameterObject["includesingles"].asBoolean()) musicUrl.AddOption("show_singles", true); + + if(parameterObject["boxset"].asBoolean()) + musicUrl.AddOption("boxset", true); bool allroles = false; if (parameterObject["allroles"].isBoolean()) @@ -220,9 +223,9 @@ JSONRPC_STATUS CAudioLibrary::GetAlbums(const std::string &method, ITransportLay std::set fields; if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) { - for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); - field != parameterObject["properties"].end_array(); field++) - fields.insert(field->asString()); + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); field++) + fields.insert(field->asString()); } if (!musicdatabase.GetAlbumsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json index 833c3b026a30f..0c6a02ebc83f9 100644 --- a/xbmc/interfaces/json-rpc/schema/methods.json +++ b/xbmc/interfaces/json-rpc/schema/methods.json @@ -785,7 +785,8 @@ ] }, { "name": "includesingles", "type": "boolean", "default": false }, - { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than the default of excluding other contributions. When true it overrides any role filter value." } + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than the default of excluding other contributions. When true it overrides any role filter value." }, + { "name": "boxset", "type": "boolean", "default": false, "description": "When true only returns albums that are also boxsets"} ], "returns": { "type": "object", diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json index 17282f61c8594..0d3e579089344 100644 --- a/xbmc/interfaces/json-rpc/schema/types.json +++ b/xbmc/interfaces/json-rpc/schema/types.json @@ -505,7 +505,7 @@ "description": "Requesting the songgenres, artistid and/or sourceid fields will result in increased response times", "enum": [ "title", "description", "artist", "genre", "theme", "mood", "style", "type", "albumlabel", - "rating", "votes", "userrating","year", "musicbrainzalbumid", + "rating", "votes", "userrating","year", "boxset", "musicbrainzalbumid", "musicbrainzalbumartistid", "fanart", "thumbnail", "playcount", "artistid", "displayartist", "compilation", "releasetype", "dateadded", @@ -581,6 +581,7 @@ "title": { "type": "string" }, "artist": { "$ref": "Array.String" }, "year": { "type": "integer" }, + "boxset" : {"type": "integer" }, "rating": { "type": "number" }, "musicbrainzalbumartistid": { "$ref": "Array.String" }, "artistid": { "$ref": "Array.Integer" }, @@ -628,6 +629,7 @@ "albumlabel": { "type": "string" }, "playcount": { "type": "integer" }, "compilation": { "type": "boolean" }, + "boxset": {"type": "boolean" }, "releasetype": { "$ref": "Audio.Album.ReleaseType" }, "musicbrainzreleasegroupid": { "type": "string" }, "musicbrainzalbumid": { "type": "string" }, @@ -1476,6 +1478,7 @@ "specialsortseason": { "type": "integer" }, "specialsortepisode": { "type": "integer" }, "compilation": { "type": "boolean" }, + "boxset": { "type": "boolean" }, "releasetype": { "$ref": "Audio.Album.ReleaseType" }, "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, "contributors": { "$ref": "Audio.Contributors" }, @@ -1503,7 +1506,7 @@ "description", "theme", "mood", "style", "albumlabel", "sorttitle", "episodeguide", "uniqueid", "dateadded", "channel", "channeltype", "hidden", "locked", "channelnumber", "starttime", "endtime", "specialsortseason", - "specialsortepisode", "compilation", "releasetype", "albumreleasetype", + "specialsortepisode", "compilation", "boxset", "releasetype", "albumreleasetype", "contributors", "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist", "userrating", "votes", "sortartist", "musicbrainzreleasegroupid", "mediapath", "dynpath"] } @@ -1523,7 +1526,7 @@ "List.Fields.Files": { "extends": "Item.Fields.Base", "items": { "type": "string", - "enum": [ "title", "artist", "albumartist", "genre", "year", "rating", + "enum": [ "title", "artist", "albumartist", "genre", "year", "boxset", "rating", "album", "track", "duration", "comment", "lyrics", "musicbrainztrackid", "musicbrainzartistid", "musicbrainzalbumid", "musicbrainzalbumartistid", "playcount", "fanart", "director", "trailer", "tagline", "plot", diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index db2d60107ad43..10a9a82b7a4a9 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -211,7 +211,6 @@ void CMusicDatabase::CreateTables() CLog::Log(LOGINFO, "create versiontagscan table"); m_pDS->exec("CREATE TABLE versiontagscan (idVersion INTEGER, iNeedsScan INTEGER, lastscanned VARCHAR(20))"); m_pDS->exec(PrepareSQL("INSERT INTO versiontagscan (idVersion, iNeedsScan) values(%i, 0)", GetSchemaVersion())); - } void CMusicDatabase::CreateAnalytics() @@ -285,7 +284,7 @@ void CMusicDatabase::CreateAnalytics() " DELETE FROM source_path WHERE source_path.idSource = old.idSource;" " DELETE FROM album_source WHERE album_source.idSource = old.idSource;" " END"); - + // we create views last to ensure all indexes are rolled in CreateViews(); @@ -645,6 +644,7 @@ int CMusicDatabase::AddSong(const int idAlbum, artistDisp, artistSort, genres, iTrack, iDuration, iYear, strDiscSubtitle, iTimesPlayed, iStartOffset, iEndOffset, dtLastPlayed, rating, userrating, votes, replayGain); } + if (!strThumb.empty()) SetArtForItem(idSong, MediaTypeSong, "thumb", strThumb); @@ -784,12 +784,13 @@ int CMusicDatabase::UpdateSong(int idSong, int idPath = AddPath(strPath); strSQL = PrepareSQL("UPDATE song SET idPath = %i, strArtistDisp = '%s', strGenres = '%s', " - " strTitle = '%s', iTrack = %i, iDuration = %i, iYear = %i, strFileName = '%s'", + " strTitle = '%s', iTrack = %i, iDuration = %i, iYear = %i, strDiscSubtitle = '%s', strFileName = '%s'", idPath, artistDisp.c_str(), StringUtils::Join(genres, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator).c_str(), strTitle.c_str(), iTrack, iDuration, iYear, + strDiscSubtitle.c_str(), strFileName.c_str()); if (strMusicBrainzTrackID.empty()) strSQL += PrepareSQL(", strMusicBrainzTrackID = NULL"); @@ -823,7 +824,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str const std::string& strArtist, const std::string& strArtistSort, const std::string& strGenre, int year, bool bBoxedSet, const std::string& strRecordLabel, const std::string& strType, - bool bCompilation, CAlbum::ReleaseType releaseType ) + bool bCompilation, CAlbum::ReleaseType releaseType) { std::string strSQL; try @@ -871,6 +872,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str strSQL += PrepareSQL(", '%s'", strArtistSort.c_str()); strSQL += ")"; m_pDS->exec(strSQL); + return (int)m_pDS->lastinsertid(); } else @@ -882,13 +884,9 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str stored for it. Most values here should be the same across all songs anyway, but it does mean that if there's any inconsistencies then only the last folders information will be taken. - NOTE - This means for boxset albums that are multi-folder albums, only the last disc name will be - stored and Kodi will not recognize it as a set UNLESS the appropriate tag is set. - We make sure we clear out the link tables (album artists, album sources) and we reset the last scraped time to make sure that online metadata is re-fetched. */ int idAlbum = m_pDS->fv("idAlbum").get_asInt(); -// bool isBoxset = m_pDS->fv("bBoxedSet").get_asInt() == 1; m_pDS->close(); strSQL = "UPDATE album SET "; @@ -903,7 +901,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str else strSQL += PrepareSQL(" strArtistSort = '%s'", strArtistSort.c_str()); - strSQL += PrepareSQL(", strGenres = '%s', iYear=%i, bBoxedSet=%i, strLabel = '%s', strType = '%s', " + strSQL += PrepareSQL(", strGenres = '%s', iYear=%i, bBoxedSet=%i strLabel = '%s', strType = '%s', " "bCompilation=%i, strReleaseType = '%s', lastScraped = NULL " "WHERE idAlbum=%i", strGenre.c_str(), @@ -975,6 +973,7 @@ int CMusicDatabase::UpdateAlbum(int idAlbum, strSQL += PrepareSQL(", strArtistSort = '%s'", strArtistSort.c_str()); strSQL += PrepareSQL(" WHERE idAlbum = %i", idAlbum); + bool status = ExecuteQuery(strSQL); if (status) AnnounceUpdate(MediaTypeAlbum, idAlbum); @@ -1088,26 +1087,6 @@ bool CMusicDatabase::HasAlbumBeenScraped(int idAlbum) return GetSingleValue(strSQL).empty(); } -bool CMusicDatabase::RemoveBoxset(int albumId) -{ - std::string strSQL; - bool off = false; - try - { - strSQL=PrepareSQL("UPDATE album SET bBoxedSet = %i WHERE idAlbum = %i", off, albumId); - CLog::Log(LOGDEBUG, "%s - query (%s)", __FUNCTION__, strSQL.c_str()); - if (!ExecuteQuery(strSQL)) - return false; - return true; - } - catch (...) - { - CLog::Log(LOGERROR, "musicdatabase:unable to remove boxset (%s)", strSQL.c_str()); - } - return false; -} - - int CMusicDatabase::AddGenre(std::string& strGenre) { std::string strSQL; @@ -1416,7 +1395,7 @@ bool CMusicDatabase::GetArtist(int idArtist, CArtist &artist, bool fetchAll /* = if (fetchAll) strSQL = PrepareSQL("SELECT * FROM artistview LEFT JOIN discography ON artistview.idArtist = discography.idArtist WHERE artistview.idArtist = %i", idArtist); else - // Same fields as artistview, but don't fetch dateadded when value not + // Same fields as artistview, but don't fetch dateadded when value not // needed. MySQL very slow for view with subquery column with aggregate //! @todo replace with artistview once dateadded is column of artist table strSQL = PrepareSQL("SELECT " @@ -1929,12 +1908,13 @@ bool CMusicDatabase::GetSongsByArtist(int idArtist, std::vector &songs) return false; }; -bool CMusicDatabase::GetSongByDiscSubtitleAndAlbum(const std::string& strDiscSubtitle, int idAlbum) +bool CMusicDatabase::GetArtistsBySong(int idSong, std::vector &artists) { try { std::string strSQL; - strSQL = PrepareSQL("SELECT idSong FROM song WHERE strDiscSubtitle = '%s' AND idAlbum = %i", strDiscSubtitle, idAlbum); + //Restrict to Artists only, no other roles + strSQL = PrepareSQL("SELECT idArtist FROM song_artist WHERE idSong = %i AND idRole = 1", idSong); if (!m_pDS->query(strSQL)) return false; if (m_pDS->num_rows() == 0) @@ -1945,7 +1925,7 @@ bool CMusicDatabase::GetSongByDiscSubtitleAndAlbum(const std::string& strDiscSub while (!m_pDS->eof()) { -// songs.push_back(m_pDS->fv("idSong").get_asInt()); + artists.push_back(m_pDS->fv("idArtist").get_asInt()); m_pDS->next(); } m_pDS->close(); @@ -1953,18 +1933,17 @@ bool CMusicDatabase::GetSongByDiscSubtitleAndAlbum(const std::string& strDiscSub } catch (...) { - CLog::Log(LOGERROR, "%s(%i, %s) failed", __FUNCTION__, idAlbum, strDiscSubtitle); + CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong); } return false; -}; +} -bool CMusicDatabase::GetArtistsBySong(int idSong, std::vector &artists) +bool CMusicDatabase::GetSongByDiscSubtitleAndAlbum(const std::string& strDiscSubtitle, int idAlbum) { try { std::string strSQL; - //Restrict to Artists only, no other roles - strSQL = PrepareSQL("SELECT idArtist FROM song_artist WHERE idSong = %i AND idRole = 1", idSong); + strSQL = PrepareSQL("SELECT idSong FROM song WHERE strDiscSubtitle = '%s' AND idAlbum = %i", strDiscSubtitle, idAlbum); if (!m_pDS->query(strSQL)) return false; if (m_pDS->num_rows() == 0) @@ -1975,7 +1954,7 @@ bool CMusicDatabase::GetArtistsBySong(int idSong, std::vector &artists) while (!m_pDS->eof()) { - artists.push_back(m_pDS->fv("idArtist").get_asInt()); +// songs.push_back(m_pDS->fv("idSong").get_asInt()); m_pDS->next(); } m_pDS->close(); @@ -1983,7 +1962,7 @@ bool CMusicDatabase::GetArtistsBySong(int idSong, std::vector &artists) } catch (...) { - CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong); + CLog::Log(LOGERROR, "%s(%i, %s) failed", __FUNCTION__, idAlbum, strDiscSubtitle); } return false; } @@ -2197,6 +2176,7 @@ CSong CMusicDatabase::GetSongFromDataset(const dbiplus::sql_record* const record song.iTrack = record->at(offset + song_iTrack).get_asInt() ; song.iDuration = record->at(offset + song_iDuration).get_asInt() ; song.iYear = record->at(offset + song_iYear).get_asInt() ; + song.strDiscSubtitle = record->at(offset + song_strDiscSubtitle).get_asString(); song.strTitle = record->at(offset + song_strTitle).get_asString(); song.iTimesPlayed = record->at(offset + song_iTimesPlayed).get_asInt(); song.lastPlayed.SetFromDBDateTime(record->at(offset + song_lastplayed).get_asString()); @@ -3806,14 +3786,14 @@ bool CMusicDatabase::GetGenresNav(const std::string& strBaseDir, CFileItemList& { if (extFilter.where.find("artistview") != std::string::npos) { - extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); - extFilter.AppendJoin("JOIN songview ON songview.idSong = song_genre.idSong"); - extFilter.AppendJoin("JOIN song_artist ON song_artist.idSong = songview.idSong"); + extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); + extFilter.AppendJoin("JOIN songview ON songview.idSong = song_genre.idSong"); + extFilter.AppendJoin("JOIN song_artist ON song_artist.idSong = songview.idSong"); extFilter.AppendJoin("JOIN artistview ON artistview.idArtist = song_artist.idArtist"); } else if (extFilter.where.find("songview") != std::string::npos) { - extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); + extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre"); extFilter.AppendJoin("JOIN songview ON songview.idSong = song_genre.idSong"); } else if (extFilter.where.find("albumview") != std::string::npos) @@ -4001,7 +3981,6 @@ bool CMusicDatabase::GetSourcesNav(const std::string& strBaseDir, CFileItemList& return false; } - bool CMusicDatabase::GetYearsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */) { try @@ -4334,7 +4313,7 @@ bool CMusicDatabase::GetArtistsByWhere(const std::string& strBaseDir, const Filt // run query CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); querytime = XbmcThreads::SystemClockMillis(); - if (!m_pDS->query(strSQL)) + if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); if (iRowsFound == 0) @@ -4368,7 +4347,7 @@ bool CMusicDatabase::GetArtistsByWhere(const std::string& strBaseDir, const Filt sorting.sortBy = SortByNone; if (!SortUtils::SortFromDataset(sorting, MediaTypeArtist, m_pDS, results)) return false; - + // get data from returned rows items.Reserve(results.size()); const dbiplus::query_data &data = m_pDS->get_result_set().records; @@ -4508,7 +4487,7 @@ bool CMusicDatabase::GetAlbumsByWhere(const std::string &baseDir, const Filter & // run query CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); querytime = XbmcThreads::SystemClockMillis(); - if (!m_pDS->query(strSQL)) + if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); if (iRowsFound == 0) @@ -4958,7 +4937,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - // Count number of artists that satisfy selection criteria + // Count number of artists that satisfy selection criteria //(includes xsp limits from filter, but not sort limits) total = static_cast(strtol(GetSingleValue("SELECT COUNT(1) FROM artist " + strSQLExtra, m_pDS).c_str(), NULL, 10)); @@ -5022,7 +5001,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, std::string strSQL; // Setup fields to query, and album field number mapping - // Find first join field (isSong) in JSONtoDBArtist for offset + // Find first join field (isSong) in JSONtoDBArtist for offset int index_firstjoin = -1; for (unsigned int i = 0; i < NUM_ARTIST_FIELDS; i++) { @@ -5054,7 +5033,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, { if (JSONtoDBArtist[i].bSimple) { - // Store indexes of requested artist table and scalar subquery fields + // Store indexes of requested artist table and scalar subquery fields // to be output, and -1 when not output to JSON if (!foundJSON) dbfieldindex.emplace_back(-1); @@ -5104,7 +5083,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } else joinFilter.AppendOrder("a1.idArtist"); - joinFilter.AppendGroup("a1.idArtist"); + joinFilter.AppendGroup("a1.idArtist"); // Album artists and song artists if (joinLayout.GetFetch(joinToArtist_isalbumartist) || joinLayout.GetFetch(joinToArtist_idSourceAlbum) || @@ -5128,7 +5107,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, // Sources if (joinLayout.GetFetch(joinToArtist_idSourceAlbum)) - { // Left join as source may have been removed but leaving lib entries + { // Left join as source may have been removed but leaving lib entries albumArtistFilter.AppendJoin("LEFT JOIN album_source ON album_source.idAlbum = album_artist.idAlbum"); albumArtistFilter.AppendGroup("album_source.idSource"); albumArtistFilter.AppendField(JSONtoDBArtist[index_firstjoin + joinToArtist_idSourceAlbum].SQL); @@ -5189,8 +5168,8 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, // Roles if (roleidfilter == 1 && !joinLayout.GetFetch(joinToArtist_strRole)) - // Only looking at album and song artists not other roles (default), - // so filter dataset rows likewise. + // Only looking at album and song artists not other roles (default), + // so filter dataset rows likewise. songArtistFilter.AppendWhere("song_artist.idRole = 1"); else if (joinLayout.GetFetch(joinToArtist_strRole) || // "roles" field (bJoinSongArtist && @@ -5245,7 +5224,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, if (bJoinArt) { // Left join as artist may not have any art joinFilter.AppendJoin("LEFT JOIN art ON art.media_id = a1.idArtist AND art.media_type = 'artist'"); - joinLayout.SetField(joinToArtist_idArt, JSONtoDBArtist[index_firstjoin + joinToArtist_idArt].SQL, + joinLayout.SetField(joinToArtist_idArt, JSONtoDBArtist[index_firstjoin + joinToArtist_idArt].SQL, joinLayout.GetOutput(joinToArtist_idArt)); joinLayout.SetField(joinToArtist_artType, JSONtoDBArtist[index_firstjoin + joinToArtist_artType].SQL); joinLayout.SetField(joinToArtist_artURL, JSONtoDBArtist[index_firstjoin + joinToArtist_artURL].SQL); @@ -5270,15 +5249,15 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, // Adjust where in the results record the join fields are allowing for the // inline view fields (Quicker than finding field by name every time) - // idArtist + other artist fields + // idArtist + other artist fields joinLayout.AdjustRecordNumbers(1 + dbfieldindex.size()); // Build full query // When have multiple value joins e.g. song genres, use inline view - // SELECT a1.*, FROM - // (SELECT FROM artist + + ) AS a1 + // SELECT a1.*, FROM + // (SELECT FROM artist + + ) AS a1 // + - // Don't use prepareSQL - confuses arttype = 'thumb' filter + // Don't use prepareSQL - confuses arttype = 'thumb' filter strSQL = "SELECT " + extFilter.fields + " FROM artist " + strSQLExtra; if (joinLayout.HasFilterFields()) @@ -5407,7 +5386,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } } - // Sources - gathered via both album_artist and song_artist (with role = 1) + // Sources - gathered via both album_artist and song_artist (with role = 1) if (joinLayout.GetFetch(joinToArtist_idSourceAlbum)) { if ((bAlbumArtistRow && joinLayout.GetRecNo(joinToArtist_idSourceAlbum) > -1 && @@ -5431,7 +5410,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, { sourceId = record->at(joinLayout.GetRecNo(joinToArtist_idSourceSong)).get_asInt(); // Song artist row may repeat sources found via album artist - // Already have that source? + // Already have that source? for (const auto& i : sourceidlist) if (i == sourceId) { @@ -5481,7 +5460,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, bool found(false); if (newgenre) { - // Already have that genre? + // Already have that genre? for (const auto& i : genreidlist) if (i == genreId) { @@ -5499,7 +5478,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } } } - // Roles - gathered via song_artist roleid rows + // Roles - gathered via song_artist roleid rows if (joinLayout.GetFetch(joinToArtist_idRole)) { if (!bAlbumArtistRow && roleId != idRoleRow) @@ -5508,7 +5487,7 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, roleId = idRoleRow; if (joinLayout.GetOutput(joinToArtist_strRole)) { - // Already have that role? + // Already have that role? bool found(false); for (const auto& i : roleidlist) if (i == roleId) @@ -5529,9 +5508,9 @@ bool CMusicDatabase::GetArtistsByWhereJSON(const std::set& fields, } } // Art - if (bJoinArt && !bArtDone && + if (bJoinArt && !bArtDone && !record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_isNull() && - record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt() > 0 && + record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt() > 0 && artId != record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt()) { artId = record->at(joinLayout.GetRecNo(joinToArtist_idArt)).get_asInt(); @@ -5584,6 +5563,7 @@ static const translateJSONField JSONtoDBAlbum[] = { { "votes", "integer", true, "iVotes", "" }, { "userrating", "unsigned", true, "iUserrating", "" }, { "year", "integer", true, "iYear", "" }, + { "boxset", "boolean", true, "bBoxedSet", "" }, { "musicbrainzalbumid", "string", true, "strMusicBrainzAlbumID", "" }, { "displayartist", "string", true, "strArtists", "" }, //strArtistDisp in album table { "compilation", "boolean", true, "bCompilation", "" }, @@ -5611,7 +5591,7 @@ static const translateJSONField JSONtoDBAlbum[] = { Using albmview, rather than album table, as view has scalar subqueries for playcount, dateadded and lastplayed already defined. Needed as MySQL does not support use of scalar subquery field alias names in where clauses (they - have to be repeated) and these fields can be used by filter rules. + have to be repeated) and these fields can be used by filter rules. Using this view is no slower than the album table as these scalar fields are only calculated (slowing query) when field is in field list. */ @@ -5619,10 +5599,10 @@ static const translateJSONField JSONtoDBAlbum[] = { static const size_t NUM_ALBUM_FIELDS = sizeof(JSONtoDBAlbum) / sizeof(translateJSONField); -bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, const std::string &baseDir, +bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, const std::string &baseDir, CVariant& result, int& total, const SortDescription &sortDescription /* = SortDescription() */) { - + if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; @@ -5643,7 +5623,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - // Count number of albums that satisfy selection criteria + // Count number of albums that satisfy selection criteria // (includes xsp limits from filter, but not sort limits) // Use albumview as filter rules in where clause may use scalar query fields total = static_cast(strtol(GetSingleValue("SELECT COUNT(1) FROM albumview " + strSQLExtra, m_pDS).c_str(), nullptr, 10)); @@ -5742,7 +5722,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c // Natural number case insensitve sort extFilter.AppendOrder(AlphanumericSortSQL(name, sortDescription.sortOrder)); } - else if (name.compare("strAlbum") == 0 || + else if (name.compare("strAlbum") == 0 || name.compare("strType") == 0 || name.compare("strGenres") == 0) // Natural number case insensitve sort @@ -5750,11 +5730,11 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c else extFilter.AppendOrder(name + DESC); } - + std::string strSQL; // Setup fields to query, and album field number mapping - // Find idArtist in JSONtoDBAlbum, offset of first join field + // Find idArtist in JSONtoDBAlbum, offset of first join field int index_idArtist = -1; for (unsigned int i = 0; i < NUM_ALBUM_FIELDS; i++) { @@ -5763,7 +5743,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c index_idArtist = i; break; } - } + } Filter joinFilter; DatasetLayout joinLayout(static_cast(joinToAlbum_enumCount)); extFilter.AppendField("albumview.idAlbum"); // ID "albumid" in JSON @@ -5787,7 +5767,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c { if (JSONtoDBAlbum[i].bSimple) { - // Store indexes of requested album table and scalar subquery fields + // Store indexes of requested album table and scalar subquery fields // to be output, and -1 when not output to JSON if (!foundJSON) dbfieldindex.emplace_back(-1); @@ -5795,7 +5775,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c dbfieldindex.emplace_back(i); // Field from scaler subquery if (!JSONtoDBAlbum[i].SQL.empty()) - { + { if (JSONtoDBAlbum[i].fieldDB == "artistsortname") extFilter.AppendField(artistsortSQL); else @@ -5852,12 +5832,12 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c joinFilter.AppendJoin("JOIN album_artist ON album_artist.idAlbum = a1.idAlbum"); joinFilter.AppendGroup("album_artist.idArtist"); joinFilter.AppendOrder("album_artist.iOrder"); - // Ensure idArtist is queried + // Ensure idArtist is queried if (!joinLayout.GetFetch(joinToAlbum_idArtist)) joinLayout.SetField(joinToAlbum_idArtist, JSONtoDBAlbum[index_idArtist + joinToAlbum_idArtist].SQL); } - // artist table needed for strArtist or MBID - // (album_artist.strArtist can be an alias or spelling variation) + // artist table needed for strArtist or MBID + // (album_artist.strArtist can be an alias or spelling variation) if (joinLayout.GetFetch(joinToAlbum_strArtist) || joinLayout.GetFetch(joinToAlbum_strArtistMBID)) joinFilter.AppendJoin("JOIN artist ON artist.idArtist = album_artist.idArtist"); @@ -5877,16 +5857,16 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c if (joinLayout.HasFilterFields()) if (!BuildSQL(strSQLJoin, joinFilter, strSQLJoin)) return false; - + // Adjust where in the results record the join fields are allowing for the // inline view fields (Quicker than finding field by name every time) - // idAlbum + other album fields + // idAlbum + other album fields joinLayout.AdjustRecordNumbers(1 + dbfieldindex.size()); - + // Build full query // When have multiple value joins (artists or song genres) use inline view - // SELECT a1.*, FROM - // (SELECT FROM albumview + + ) AS a1 + // SELECT a1.*, FROM + // (SELECT FROM albumview + + ) AS a1 // // Don't use prepareSQL - confuses releasetype = 'album' filter and group_concat separator @@ -5896,7 +5876,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c strSQL = "(" + strSQL + ") AS a1 "; strSQL = "SELECT a1.*, " + joinLayout.GetFields() + " FROM " + strSQL + strSQLJoin; } - + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); // run query unsigned int time = XbmcThreads::SystemClockMillis(); @@ -5925,7 +5905,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c const dbiplus::sql_record* const record = m_pDS->get_sql_record(); if (m_pDS->eof() || albumId != record->at(0).get_asInt()) - { + { // Store previous or last album if (!albumObj.empty()) { @@ -5944,7 +5924,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c result["albums"].append(albumObj); - albumObj.clear(); + albumObj.clear(); artistId = -1; bSongGenreDone = false; } @@ -6002,7 +5982,7 @@ bool CMusicDatabase::GetAlbumsByWhereJSON(const std::set& fields, c if (joinLayout.GetOutput(joinToAlbum_strArtistMBID) && joinLayout.GetRecNo(joinToAlbum_strArtistMBID) > -1) albumObj["musicbrainzalbumartistid"].append(record->at(joinLayout.GetRecNo(joinToAlbum_strArtistMBID)).get_asString()); } - } + } } if (!bSongGenreDone && joinLayout.GetRecNo(joinToAlbum_idSongGenre) > -1 && joinLayout.GetRecNo(joinToAlbum_strSongGenre) > -1 && @@ -6051,13 +6031,13 @@ static const translateJSONField JSONtoDBSong[] = { { "userrating", "unsigned", true, "song.userrating", "" }, { "mood", "array", true, "mood", "" }, { "dateadded", "string", true, "dateAdded", "" }, - { "file", "string", true, "strPathFile", "CONCAT(path.strPath, strFilename) AS strPathFile" }, + { "file", "string", true, "strPathFile", "CONCAT(path.strPath, strFilename) AS strPathFile" }, { "", "string", true, "strPath", "path.strPath AS strPath" }, { "album", "string", true, "strAlbum", "album.strAlbum AS strAlbum" }, { "albumreleasetype", "string", true, "strAlbumReleaseType", "album.strReleaseType AS strAlbumReleaseType" }, { "musicbrainzalbumid", "string", true, "strMusicBrainzAlbumID", "album.strMusicBrainzAlbumID AS strMusicBrainzAlbumID" }, - // JOIN fields (multivalue), same order as _JoinToSongFields + // JOIN fields (multivalue), same order as _JoinToSongFields { "albumartistid", "array", false, "idAlbumArtist", "album_artist.idArtist AS idAlbumArtist" }, { "albumartist", "array", false, "strAlbumArtist", "albumartist.strArtist AS strAlbumArtist" }, { "musicbrainzalbumartistid", "array", false, "strAlbumArtistMBID", "albumartist.strMusicBrainzArtistID AS strAlbumArtistMBID" }, @@ -6077,19 +6057,19 @@ static const translateJSONField JSONtoDBSong[] = { { "displayconductor", "string", false, "Role_Conductor", "song_artist.idRole AS Role_Conductor" }, { "displayorchestra", "string", false, "Role_Orchestra", "song_artist.idRole AS Role_Orchestra" }, { "displaylyricist", "string", false, "Role_Lyricist", "song_artist.idRole AS Role_Lyricist" }, - + // Scalar subquery fields { "track", "integer", true, "track", "(iTrack & 0xffff) AS track" }, { "disc", "integer", true, "disc", "(iTrack >> 16) AS disc" }, { "sourceid", "string", true, "sourceid", "(SELECT GROUP_CONCAT(album_source.idSource SEPARATOR '; ') FROM album_source WHERE album_source.idAlbum = song.idAlbum) AS sources" }, { "", "", true, "artistsortname", "CASE WHEN song.strArtistSort IS NOT NULL THEN song.strArtistSort ELSE song.strArtistDisp END AS artistsortname"} - /* + /* Song "thumbnail", "fanart" and "art" fields of JSON schema are fetched using thumbloader and separate queries to allow for fallback strategy "lyrics"?? Can be set for an item (by addons) but not held in db so AudioLibrary.GetSongs() never fills this field despite being in schema - FROM ( SELECT * FROM song + FROM ( SELECT * FROM song JOIN album ON album.idAlbum = song.idAlbum JOIN path ON path.idPath = song.idPath) AS sv JOIN album_artist ON album_artist.idAlbum = song.idAlbum @@ -6125,7 +6105,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - // Count number of songs that satisfy selection criteria + // Count number of songs that satisfy selection criteria // (includes xsp limits from filter, but not sort limits) // Use songview as filter rules in where clause may use album and path JOIN fields total = static_cast(strtol(GetSingleValue("SELECT COUNT(1) FROM songview " + strSQLExtra, m_pDS).c_str(), nullptr, 10)); @@ -6170,7 +6150,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co orderfields.emplace_back("strAlbum"); orderfields.emplace_back("song.strArtistDisp"); orderfields.emplace_back("song.iTrack"); - } + } else if (sortDescription.sortBy == SortByArtist) { orderfields.emplace_back("song.strArtistDisp"); @@ -6235,7 +6215,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co orderfields.emplace_back("iDuration"); // Always sort by id to define order when other fields same - if (sortDescription.sortBy != SortByRandom) + if (sortDescription.sortBy != SortByRandom) orderfields.emplace_back("song.idSong"); // Fill inline view filter order fields, and build sort scalar subquery SQL @@ -6245,15 +6225,15 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co //Add field for adjusted name sorting using sort name and ignoring articles if (name.compare("song.strArtistDisp") == 0) { - artistsortSQL = SortnameBuildSQL("artistsortname", sortDescription.sortAttributes, + artistsortSQL = SortnameBuildSQL("artistsortname", sortDescription.sortAttributes, "song.strArtistDisp", "song.strArtistSort"); if (!artistsortSQL.empty()) name = "artistsortname"; // Natural number case insensitve sort extFilter.AppendOrder(AlphanumericSortSQL(name, sortDescription.sortOrder)); } - else if (name.compare("strTitle") == 0 || - name.compare("strAlbum") == 0 || + else if (name.compare("strTitle") == 0 || + name.compare("strAlbum") == 0 || name.compare("song.strGenres") == 0) // Natural number case insensitve sort extFilter.AppendOrder(AlphanumericSortSQL(name, sortDescription.sortOrder)); @@ -6265,7 +6245,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co std::string strSQL; // Setup fields to query, and song field number mapping - // Find idAlbumArtist in JSONtoDBSong, offset of first join field + // Find idAlbumArtist in JSONtoDBSong, offset of first join field int index_idAlbumArtist = -1; for (unsigned int i = 0; i < NUM_SONG_FIELDS; i++) { @@ -6274,7 +6254,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co index_idAlbumArtist = i; break; } - } + } Filter joinFilter; DatasetLayout joinLayout(static_cast(joinToSongs_enumCount)); extFilter.AppendField("song.idSong"); // ID "songid" in JSON @@ -6299,7 +6279,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { if (JSONtoDBSong[i].bSimple) { - // Store indexes of requested album table and scalar subquery fields + // Store indexes of requested album table and scalar subquery fields // to be output, and -1 when not output to JSON if (!foundJSON) dbfieldindex.emplace_back(-1); @@ -6307,7 +6287,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co dbfieldindex.emplace_back(i); // Field from scaler subquery if (!JSONtoDBSong[i].SQL.empty()) - { + { if (JSONtoDBSong[i].fieldDB == "artistsortname") extFilter.AppendField(artistsortSQL); else @@ -6330,7 +6310,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co } } } - // Build matching list of role id for "displaycomposer", "displayconductor", + // Build matching list of role id for "displaycomposer", "displayconductor", // "displayorchestra", "displaylyricist" for (const auto& name : rolefieldlist) { @@ -6353,7 +6333,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { // All songs have one path so inner join sufficient extFilter.AppendJoin("JOIN path ON path.idPath = song.idPath"); } - + // Build JOIN, WHERE, ORDER BY and LIMIT for inline view strSQLExtra = ""; if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) @@ -6365,7 +6345,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { strSQLExtra += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart); } - + // Setup multivalue JOINs, GROUP BY and ORDER BY bool bJoinSongArtist(false); bool bJoinAlbumArtist(false); @@ -6382,7 +6362,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co else joinFilter.AppendOrder("sv.idSong"); joinFilter.AppendGroup("sv.idSong"); - + // Album artists if (joinLayout.GetFetch(joinToSongs_idAlbumArtist) || joinLayout.GetFetch(joinToSongs_strAlbumArtist) || @@ -6395,17 +6375,17 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co // Ensure idAlbumArtist is queried for processing repeats if (!joinLayout.GetFetch(joinToSongs_idAlbumArtist)) { - joinLayout.SetField(joinToSongs_idAlbumArtist, + joinLayout.SetField(joinToSongs_idAlbumArtist, JSONtoDBSong[index_idAlbumArtist + joinToSongs_idAlbumArtist].SQL); - } + } // Ensure song.IdAlbum is field of the inline view for join if (fields.find("albumid") == fields.end()) { extFilter.AppendField("song.idAlbum"); //Prefer lookup JSONtoDBSong[XXX].dbField); dbfieldindex.emplace_back(-1); } - // artist table needed for strArtist or MBID - // (album_artist.strArtist can be an alias or spelling variation) + // artist table needed for strArtist or MBID + // (album_artist.strArtist can be an alias or spelling variation) if (joinLayout.GetFetch(joinToSongs_strAlbumArtistMBID) || joinLayout.GetFetch(joinToSongs_strAlbumArtist)) joinFilter.AppendJoin("JOIN artist AS albumartist ON albumartist.idArtist = album_artist.idArtist"); } @@ -6427,7 +6407,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co joinFilter.AppendGroup("song_artist.idArtist"); joinFilter.AppendOrder("song_artist.iOrder"); } - else + else { // Ensure idRole is queried if (!joinLayout.GetFetch(joinToSongs_idRole)) @@ -6455,7 +6435,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co { // Get just roles for "displaycomposer", "displayconductor" etc. std::string where; for (size_t i = 0; i < roleidlist.size(); i++) - { + { int idRole = roleidlist[i]; if (idRole <= 1) continue; @@ -6477,16 +6457,16 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co joinLayout.SetField(joinToSongs_idArtist, JSONtoDBSong[index_idAlbumArtist + joinToSongs_idArtist].SQL); } - // artist table needed for strArtist or MBID - // (song_artist.strArtist can be an alias or spelling variation) + // artist table needed for strArtist or MBID + // (song_artist.strArtist can be an alias or spelling variation) if (joinLayout.GetFetch(joinToSongs_strArtistMBID) || joinLayout.GetFetch(joinToSongs_strArtist)) joinFilter.AppendJoin("JOIN artist AS songartist ON songartist.idArtist = song_artist.idArtist"); - } + } // Genre ids if (joinLayout.GetFetch(joinToSongs_idGenre)) { // song genre ids (strGenre demormalised in song table) - // Left join as songs may not have genre + // Left join as songs may not have genre joinFilter.AppendJoin("LEFT JOIN song_genre ON song_genre.idSong = sv.idSong"); joinFilter.AppendGroup("song_genre.idGenre"); joinFilter.AppendOrder("song_genre.iOrder"); @@ -6505,8 +6485,8 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co // Build full query // When have multiple value joins use inline view - // SELECT sv.*, FROM - // (SELECT FROM song + + ) AS sv + // SELECT sv.*, FROM + // (SELECT FROM song + + ) AS sv // // + // Don't use prepareSQL - confuses releasetype = 'album' filter and group_concat separator @@ -6516,7 +6496,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co strSQL = "("+ strSQL + ") AS sv "; strSQL = "SELECT sv.*, " + joinLayout.GetFields() + " FROM " + strSQL + strSQLJoin; } - + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); // Run query @@ -6699,7 +6679,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co contributor["role"] = record->at(joinLayout.GetRecNo(joinToSongs_strRole)).get_asString(); contributor["roleid"] = roleId; contributor["artistid"] = record->at(joinLayout.GetRecNo(joinToSongs_idArtist)).get_asInt(); - songObj["contributors"].append(contributor); + songObj["contributors"].append(contributor); } // "displaycomposer", "displayconductor" etc. for (size_t i = 0; i < roleidlist.size(); i++) @@ -6738,7 +6718,7 @@ bool CMusicDatabase::GetSongsByWhereJSON(const std::set& fields, co std::string CMusicDatabase::GetIgnoreArticleSQL(const std::string& strField) { - /* + /* Make SQL clause from ignore article list. Group tokens the same length together, for example : WHEN strArtist LIKE 'the ' OR strArtist LIKE 'the.' strArtist LIKE 'the_' ESCAPE '_' @@ -6791,7 +6771,7 @@ std::string CMusicDatabase::SortnameBuildSQL(const std::string& strAlias, const /* Build SQL for sort name scalar subquery from sort attributes and ignore article list. For example : - CASE WHEN strArtistSort IS NOT NULL THEN strArtistSort + CASE WHEN strArtistSort IS NOT NULL THEN strArtistSort WHEN strField LIKE 'the ' OR strField LIKE 'the_' ESCAPE '_' THEN SUBSTR(strArtist, 5) WHEN strField LIKE 'LIKE 'an.' strField LIKE 'an_' ESCAPE '_' THEN SUBSTR(strArtist, 4) ELSE strField @@ -7995,10 +7975,10 @@ int CMusicDatabase::UpdateSource(const std::string& strOldName, const std::strin if (NULL == m_pDB.get()) return -1; if (NULL == m_pDS.get()) return -1; - // Get details of named old source + // Get details of named old source if (!strOldName.empty()) { - strSQL = PrepareSQL("SELECT idSource, strMultipath FROM source WHERE strName LIKE '%s'", + strSQL = PrepareSQL("SELECT idSource, strMultipath FROM source WHERE strName LIKE '%s'", strOldName.c_str()); if (!m_pDS->query(strSQL)) return -1; @@ -8115,7 +8095,7 @@ bool CMusicDatabase::AddAlbumSources(int idAlbum, const std::string& strPath) if (!strPath.empty()) { - // Find sources related to album using album path + // Find sources related to album using album path strSQL = PrepareSQL("SELECT DISTINCT idSource FROM source_path " "WHERE SUBSTR('%s', 1, LENGTH(strPath)) = strPath", strPath.c_str()); if (!m_pDS->query(strSQL)) @@ -8128,7 +8108,7 @@ bool CMusicDatabase::AddAlbumSources(int idAlbum, const std::string& strPath) m_pDS->close(); } else - { + { // Find sources using song paths, check each source path individually if (NULL == m_pDS2.get()) return false; strSQL = "SELECT idSource, strPath FROM source_path"; @@ -8156,7 +8136,7 @@ bool CMusicDatabase::AddAlbumSources(int idAlbum, const std::string& strPath) { AddAlbumSource(idAlbum, idSource); } - + return true; } catch (...) @@ -8235,7 +8215,7 @@ bool CMusicDatabase::MigrateSources() std::string strSQL; try - { + { // Fill source and source paths tables for (const auto& source : sources) { @@ -8254,7 +8234,7 @@ bool CMusicDatabase::MigrateSources() idSource, idPath, path.c_str()); m_pDS->exec(strSQL); ++idPath; - } + } } return true; @@ -8272,12 +8252,12 @@ bool CMusicDatabase::MigrateSources() VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); if (CheckSources(sources)) return true; - + try - { + { // Empty sources table (related link tables removed by trigger); ExecuteQuery("DELETE FROM source"); - + // Fill source table, and album sources for (const auto& source : sources) AddSource(source.strName, source.strPath, source.vecPaths); @@ -8395,12 +8375,11 @@ bool CMusicDatabase::GetSourcesByArtist(int idArtist, CFileItem* item) m_pDS->close(); return true; } - } CVariant artistSources(CVariant::VariantTypeArray); while (!m_pDS->eof()) - { + { artistSources.push_back(m_pDS->fv("idSource").get_asInt()); m_pDS->next(); } @@ -8439,14 +8418,14 @@ bool CMusicDatabase::GetSourcesByAlbum(int idAlbum, CFileItem* item) } m_pDS->close(); } - else + else { //! @todo: handle singles, or don't waste time checking songs // Album does have any sources, may be a single?? // Check via song paths, check each source path individually // usually fewer source paths than songs m_pDS->close(); - + if (NULL == m_pDS2.get()) return false; strSQL = "SELECT idSource, strPath FROM source_path"; if (!m_pDS->query(strSQL)) @@ -8468,7 +8447,7 @@ bool CMusicDatabase::GetSourcesByAlbum(int idAlbum, CFileItem* item) m_pDS->close(); } - + item->SetProperty("sourceid", albumSources); return true; } @@ -8888,13 +8867,13 @@ bool CMusicDatabase::GetGenresJSON(CFileItemList& items, bool bSources) extFilter.AppendJoin("LEFT JOIN album_source on album_source.idAlbum = album.idAlbum"); extFilter.AppendOrder("genre.strGenre"); extFilter.AppendOrder("album_source.idSource"); - } + } extFilter.AppendWhere("genre.strGenre != ''"); std::string strSQLExtra; if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; - + strSQL = PrepareSQL(strSQL.c_str(), extFilter.fields.c_str()) + strSQLExtra; // run query @@ -8926,7 +8905,7 @@ bool CMusicDatabase::GetGenresJSON(CFileItemList& items, bool bSources) items[items.Size() - 1].get()->SetProperty("sourceid", genreSources); genreSources.clear(); } - idGenre = m_pDS->fv("genre.idGenre").get_asInt(); + idGenre = m_pDS->fv("genre.idGenre").get_asInt(); std::string strGenre = m_pDS->fv("genre.strGenre").get_asString(); CFileItemPtr pItem(new CFileItem(strGenre)); pItem->GetMusicInfoTag()->SetTitle(strGenre); @@ -9136,6 +9115,11 @@ bool CMusicDatabase::GetBoxsetDiscSongs(const std::string& strBaseDir, CFileItem return true; } +int CMusicDatabase::GetBoxsetsCount() +{ + return strtol(GetSingleValue("album", "count(idAlbum)", "bBoxedSet = 1").c_str(), NULL, 10); +} + bool CMusicDatabase::GetCompilationAlbums(const std::string& strBaseDir, CFileItemList& items) { CMusicDbUrl musicUrl; @@ -9160,11 +9144,6 @@ bool CMusicDatabase::GetCompilationSongs(const std::string& strBaseDir, CFileIte return GetSongsFullByWhere(musicUrl.ToString(), filter, items, SortDescription(), true); } -int CMusicDatabase::GetBoxsetsCount() -{ - return strtol(GetSingleValue("album", "count(idAlbum)", "bBoxedSet = 1").c_str(), NULL, 10); -} - int CMusicDatabase::GetCompilationAlbumsCount() { return strtol(GetSingleValue("album", "count(idAlbum)", "bCompilation = 1").c_str(), NULL, 10); @@ -9741,7 +9720,7 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog if (!settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS) && !settings.IsItemExported(ELIBEXPORT_SONGARTISTS) && !settings.IsItemExported(ELIBEXPORT_OTHERARTISTS) && - !settings.IsItemExported(ELIBEXPORT_ALBUMS) && + !settings.IsItemExported(ELIBEXPORT_ALBUMS) && !settings.IsItemExported(ELIBEXPORT_SONGS)) return; @@ -9768,7 +9747,7 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog else if (settings.IsArtistFoldersOnly() || (settings.IsToLibFolders() && settings.IsArtists())) { // Exporting artist folders only, or artist NFO or art to library folders - // need Artist Information Folder defined. + // need Artist Information Folder defined. // (Album NFO and art goes to music folders) strFolder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); if (strFolder.empty()) @@ -9776,9 +9755,9 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog } // - bool artistfoldersonly; + bool artistfoldersonly; artistfoldersonly = settings.IsArtistFoldersOnly() || - ((settings.IsToLibFolders() || settings.IsSeparateFiles()) && + ((settings.IsToLibFolders() || settings.IsSeparateFiles()) && settings.m_skipnfo && !settings.m_artwork); int iFailCount = 0; @@ -10164,7 +10143,7 @@ bool CMusicDatabase::ExportSongHistory(TiXmlNode* pNode, CGUIDialogProgress* pro } void CMusicDatabase::ImportFromXML(const std::string& xmlFile, CGUIDialogProgress* progressDialog) -{ +{ try { if (NULL == m_pDB.get()) return; @@ -10273,7 +10252,7 @@ void CMusicDatabase::ImportFromXML(const std::string& xmlFile, CGUIDialogProgres } bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int total, CGUIDialogProgress* progressDialog) -{ +{ bool bHistSongExists = false; try { @@ -10282,21 +10261,21 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot return false; TiXmlElement* root = xmlDoc.RootElement(); - if (!root) + if (!root) return false; TiXmlElement* entry = root->FirstChildElement(); int current = 0; - + if (progressDialog) { progressDialog->SetLine(1, CVariant{38350}); //"Importing song playback history" progressDialog->SetLine(2, CVariant{ "" }); } - + // As can be many songs do in db, not song at a time which would be slow // Convert xml entries into a SQL bulk insert statement - std::string strSQL; + std::string strSQL; entry = root->FirstChildElement(); while (entry) { @@ -10315,7 +10294,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot int iVotes; std::string strSQLSong; if (strnicmp(entry->Value(), "song", 4) == 0) - { + { XMLUtils::GetString(entry, "artistdesc", strArtistDisp); XMLUtils::GetString(entry, "title", strTitle); XMLUtils::GetInt(entry, "track", iTrack); @@ -10337,7 +10316,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot if (rating > 10.f) rating = 10.f; fRating = rating; - } + } XMLUtils::GetInt(entry, "votes", iVotes); const TiXmlElement* userrating = entry->FirstChildElement("userrating"); if (userrating) @@ -10414,7 +10393,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot if (progressDialog) { - progressDialog->SetLine(2, CVariant{38351}); //"Matching data" + progressDialog->SetLine(2, CVariant{38351}); //"Matching data" progressDialog->SetLine(3, CVariant{ "" }); progressDialog->Progress(); if (progressDialog->IsCanceled()) @@ -10425,7 +10404,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot } BeginTransaction(); - // Match albums first on mbid then artist string and album title, setting idAlbum + // Match albums first on mbid then artist string and album title, setting idAlbum strSQL = "UPDATE HistSong " "SET idAlbum = (SELECT album.idAlbum FROM album " "WHERE album.strMusicBrainzAlbumID = HistSong.strMusicBrainzAlbumID) " @@ -10500,9 +10479,9 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot } } - /* Update song table using the song ids we have matched. + /* Update song table using the song ids we have matched. Use correlated subqueries as SQLite does not support updatable joins. - MySQL requires HistSong table not to be defined temporary for this. + MySQL requires HistSong table not to be defined temporary for this. */ BeginTransaction(); // Times played and last played date(when count is greater) @@ -10513,14 +10492,14 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot "WHERE EXISTS(SELECT 1 FROM HistSong WHERE " "HistSong.idSong = song.idSong AND HistSong.iTimesPlayed > song.iTimesPlayed)"; m_pDS->exec(strSQL); - + // User rating strSQL = "UPDATE song SET userrating = " "(SELECT userrating FROM HistSong WHERE HistSong.idSong = song.idSong) " "WHERE EXISTS(SELECT 1 FROM HistSong WHERE " "HistSong.idSong = song.idSong AND HistSong.userrating > 0)"; m_pDS->exec(strSQL); - + // Rating and votes strSQL = "UPDATE song SET rating = " "(SELECT rating FROM HistSong WHERE HistSong.idSong = song.idSong), " @@ -10550,7 +10529,7 @@ bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int tot progressDialog->SetLine(2, CVariant{ 331 }); progressDialog->Progress(); } - Compress(false); + Compress(false); // Write event log entry // "Importing song history {1} of {2} songs matched", total - unmatched, total) @@ -10619,7 +10598,7 @@ void CMusicDatabase::SetPropertiesFromAlbum(CFileItem& item, const CAlbum& album item.SetProperty("album_userrating", album.iUserrating); if (album.iVotes > 0) item.SetProperty("album_votes", album.iVotes); - if (album.bBoxedSet ) +if (album.bBoxedSet ) item.SetProperty("album_bBoxedSet", true); item.SetProperty("album_releasetype", CAlbum::ReleaseTypeToString(album.releaseType)); } @@ -11072,7 +11051,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription For artists these rules are combined because they apply via album and song and so we need to ensure all criteria are met via the same album or song. - 1) Some artists may be only album artists, so for all artists (with linked + 1) Some artists may be only album artists, so for all artists (with linked albums or songs) we need to check both album_artist and song_artist tables. 2) Role is determined from song_artist table, so even if looking for album artists only we find those that also have a specific role e.g. which album artist is a @@ -11081,8 +11060,8 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription b) When not album artists only and a specific role wanted then only the song_artist table is checked. c) When album artists only and role = 1 (an "artist") then only the album_artist - table is checked. - */ + table is checked. + */ std::string albumArtistSQL, songArtistSQL; ExistsSubQuery albumArtistSub("album_artist", "album_artist.idArtist = artistview.idArtist"); // Prepare album artist subquery SQL @@ -11098,7 +11077,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription albumArtistSub.AppendWhere(PrepareSQL("EXISTS(SELECT 1 FROM album_source " "WHERE album_source.idSource = %i " "AND album_source.idAlbum = album_artist.idAlbum)", idSource)); - } + } } if (idRole <= 1 && idGenre > 0) { // Check genre of songs of album using nested subquery @@ -11122,7 +11101,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription "WHERE album_source.idSource = %i " "AND album_source.idAlbum = song.idAlbum))", idGenre, idSource)); } - else + else { if (idGenre > 0) { @@ -11141,7 +11120,7 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription songArtistSub.AppendJoin("JOIN song ON song.idSong = song_artist.idSong"); songArtistSub.param = "song_artist.idArtist = album_artist.idArtist"; songArtistSub.AppendWhere("song.idAlbum = album_artist.idAlbum"); - } + } } // Build filter clause from subqueries diff --git a/xbmc/music/tags/MusicInfoTag.cpp b/xbmc/music/tags/MusicInfoTag.cpp index aa28c48cefdb7..ac878a63a37d8 100644 --- a/xbmc/music/tags/MusicInfoTag.cpp +++ b/xbmc/music/tags/MusicInfoTag.cpp @@ -932,7 +932,7 @@ void CMusicInfoTag::Clear() m_lastPlayed.Reset(); m_dateAdded.Reset(); m_bCompilation = false; - m_bBoxedSet = false; + m_bBoxset = false; m_strComment.clear(); m_strMood.clear(); m_strRecordLabel.clear(); From e9dd60ab7d196a84cb7ee4cf9266e785f338ffc5 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Fri, 6 Sep 2019 08:03:00 +0100 Subject: [PATCH 5/7] Ensure strDiscSubtitle is not empty --- xbmc/music/MusicDatabase.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 10a9a82b7a4a9..56d2fb5a0c6fd 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -901,7 +901,7 @@ int CMusicDatabase::AddAlbum(const std::string& strAlbum, const std::string& str else strSQL += PrepareSQL(" strArtistSort = '%s'", strArtistSort.c_str()); - strSQL += PrepareSQL(", strGenres = '%s', iYear=%i, bBoxedSet=%i strLabel = '%s', strType = '%s', " + strSQL += PrepareSQL(", strGenres = '%s', iYear=%i, bBoxedSet=%i, strLabel = '%s', strType = '%s', " "bCompilation=%i, strReleaseType = '%s', lastScraped = NULL " "WHERE idAlbum=%i", strGenre.c_str(), @@ -8967,7 +8967,7 @@ bool CMusicDatabase::GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string strSQL; - strSQL=PrepareSQL("SELECT DISTINCT strDiscSubtitle FROM song where song.idAlbum = %i ORDER BY iTrack", idAlbum); + strSQL=PrepareSQL("SELECT DISTINCT strDiscSubtitle FROM song where song.idAlbum = %i AND strDiscSubtitle != '' ORDER BY iTrack", idAlbum); // run query if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); @@ -9052,7 +9052,7 @@ bool CMusicDatabase::GetBoxsetDiscSongs(const std::string& strBaseDir, CFileItem try { int total = -1; - std::string strSQL = PrepareSQL("SELECT songview.* from songview WHERE songview.strDiscSubtitle LIKE '%s' AND songview.idAlbum = %i ", strDiscName.c_str(), idAlbum) ; + std::string strSQL = PrepareSQL("SELECT songview.* from songview WHERE songview.strDiscSubtitle = '%s' AND songview.idAlbum = %i ", strDiscName.c_str(), idAlbum) ; SortDescription sorting; CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str()); // run query From bfe3e88adb474070b29285e43dc763f59df7a84d Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Fri, 6 Sep 2019 08:37:42 +0100 Subject: [PATCH 6/7] Only show boxset node if we have sets. Add docs --- system/library/music/boxsets.xml | 2 +- xbmc/GUIInfoManager.cpp | 26 ++++++++++++++++++++++++++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 1 + xbmc/guilib/guiinfo/LibraryGUIInfo.cpp | 18 ++++++++++++++++++ xbmc/guilib/guiinfo/LibraryGUIInfo.h | 1 + 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/system/library/music/boxsets.xml b/system/library/music/boxsets.xml index 34eb84d826df9..3a7b8c98f748f 100644 --- a/system/library/music/boxsets.xml +++ b/system/library/music/boxsets.xml @@ -1,5 +1,5 @@ - + DefaultSets.png musicdb://boxsets/ diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 81a12f342e288..669b13f7d2c04 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -2520,6 +2520,14 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// @skinning_v17 **[New Infolabel]** \link MusicPlayer_DBID `MusicPlayer.DBID`\endlink ///

/// } +/// \table_row3{ `MusicPlayer.DiscTitle`, +/// \anchor MusicPlayer_DiscTitle +/// _string_, +/// @return The title of the disc currently playing. +///


+/// @skinning_v19 **[New Infolabel]** \link MusicPlayer_DiscTitle `MusicPlayer.DiscTitle`\endlink +///

+/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -5719,6 +5727,14 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v19 **[New Infolabel]** \link ListItem_CurrentItem `ListItem.CurrentItem`\endlink ///

/// } +/// \table_row3{ `ListItem.DiscTitle`, +/// \anchor ListItem_DiscTitle +/// _string_, +/// @return The disc title of the currently selected album or song. +///


+/// @skinning_v19 **[New Infolabel]** \link ListItem_DiscTitle `ListItem.DiscTitle`\endlink +///

+/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -8459,6 +8475,14 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// @skinning_v17 **[New Boolean Condition]** \link Library_HasContent_Role_Mixer `Library.HasContent(Role.Mixer)`\endlink ///

/// } +/// \table_row3{ `Library.HasContent(boxsets)`, +/// \anchor Library_HasContent_Boxsets +/// _boolean_, +/// @return **True** if there are albums in the library which are boxsets. +///


+/// @skinning_v19 **[New Boolean Condition]** \link Library_HasContent_Boxsets `Library.HasContent(boxsets)`\endlink +///

+/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -8873,6 +8897,8 @@ int CGUIInfoManager::TranslateSingleString(const std::string &strCondition, bool return LIBRARY_HAS_SINGLES; else if (cat == "compilations") return LIBRARY_HAS_COMPILATIONS; + else if (cat == "boxsets") + return LIBRARY_HAS_BOXSETS; else if (cat == "role" && prop.num_params() > 1) return AddMultiInfo(CGUIInfo(LIBRARY_HAS_ROLE, prop.param(1), 0)); } diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index d671a7a4ec6aa..8f9f31421c6b0 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -412,6 +412,7 @@ #define LIBRARY_IS_SCANNING_VIDEO 729 #define LIBRARY_IS_SCANNING_MUSIC 730 #define LIBRARY_HAS_ROLE 735 +#define LIBRARY_HAS_BOXSETS 736 #define SYSTEM_PLATFORM_LINUX 741 #define SYSTEM_PLATFORM_WINDOWS 742 diff --git a/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp b/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp index 510c05b21562b..167fcdb08dfd5 100644 --- a/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp @@ -54,6 +54,9 @@ void CLibraryGUIInfo::SetLibraryBool(int condition, bool value) case LIBRARY_HAS_COMPILATIONS: m_libraryHasCompilations = value ? 1 : 0; break; + case LIBRARY_HAS_BOXSETS: + m_libraryHasBoxsets = value ? 1 : 0; + break; default: break; } @@ -68,6 +71,7 @@ void CLibraryGUIInfo::ResetLibraryBools() m_libraryHasMovieSets = -1; m_libraryHasSingles = -1; m_libraryHasCompilations = -1; + m_libraryHasBoxsets = -1; m_libraryRoleCounts.clear(); } @@ -191,6 +195,20 @@ bool CLibraryGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contex value = m_libraryHasCompilations > 0; return true; } + case LIBRARY_HAS_BOXSETS: + { + if (m_libraryHasBoxsets < 0) + { + CMusicDatabase db; + if (db.Open()) + { + m_libraryHasBoxsets = (db.GetBoxsetsCount() > 0) ? 1 : 0; + db.Close(); + } + } + value = m_libraryHasBoxsets > 0; + return true; + } case LIBRARY_HAS_VIDEO: { return (GetBool(value, gitem, contextWindow, CGUIInfo(LIBRARY_HAS_MOVIES)) || diff --git a/xbmc/guilib/guiinfo/LibraryGUIInfo.h b/xbmc/guilib/guiinfo/LibraryGUIInfo.h index d068d37ea7eec..a9d47fc13a66a 100644 --- a/xbmc/guilib/guiinfo/LibraryGUIInfo.h +++ b/xbmc/guilib/guiinfo/LibraryGUIInfo.h @@ -47,6 +47,7 @@ class CLibraryGUIInfo : public CGUIInfoProvider mutable int m_libraryHasMovieSets; mutable int m_libraryHasSingles; mutable int m_libraryHasCompilations; + mutable int m_libraryHasBoxsets; //Count of artists in music library contributing to song by role e.g. composers, conductors etc. //For checking visibility of custom nodes for a role. From f1ab740644bec59d87e1e4aad8cc9b889395f122 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Sun, 8 Sep 2019 09:22:16 +0100 Subject: [PATCH 7/7] Use filter and GetsongsByWhere in GetBoxsetDiscSongs --- xbmc/music/MusicDatabase.cpp | 104 +++++++++++------------------------ 1 file changed, 31 insertions(+), 73 deletions(-) diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 56d2fb5a0c6fd..2c67b2704e9df 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -8991,7 +8991,6 @@ bool CMusicDatabase::GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList musicUrl.FromString(strBaseDir); musicUrl.RemoveOption("boxset"); temp_path += StringUtils::Format("%i/", idAlbum); - strSQL=PrepareSQL("SELECT albumview.* FROM albumview WHERE albumview.idAlbum = %i", idAlbum); if (!m_pDS->query(strSQL)) return false; int iAlbumsFound = m_pDS->num_rows(); @@ -9014,6 +9013,7 @@ bool CMusicDatabase::GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList std::string path; path = temp_path; path += StringUtils::Format("%s/", disctitle.c_str()); + musicUrl.FromString(path); pItem->SetPath(path); pItem->SetLabel(disctitle.c_str()); pItem->SetArt("icon", "DefaultAlbumCover.png"); @@ -9032,86 +9032,40 @@ bool CMusicDatabase::GetBoxsetDiscs(const std::string& strBaseDir, CFileItemList bool CMusicDatabase::GetBoxsetDiscSongs(const std::string& strBaseDir, CFileItemList& items) { CMusicDbUrl musicUrl; - int idAlbum; Filter filter; - // Parse out the album ID from the base directory - unsigned int start = strBaseDir.find("boxsets/"); + SortDescription sorting; + std::string buildUrl; + std::string tmp; + std::string strDiscName; + unsigned int start; + unsigned int end; + int idAlbum; + + if (!musicUrl.FromString(strBaseDir)) + { + CLog::Log(LOGERROR, "%s - error getting musicUrl from [%s]", __FUNCTION__, strBaseDir.c_str()); + return false; + } + start = strBaseDir.find("boxsets/"); if (start == std::string::npos) - idAlbum = -1; - unsigned int end = strBaseDir.find("/", start + 9); // 9 is 'boxsets/' + 1 - std::string tmp = strBaseDir.substr(start + 8, end - 18); // 8 = 'boxsets/', 18 = 'musicdb://boxsets/' + return false; + + end = strBaseDir.find("/", start + 9); // 9 is 'boxsets/' + 1 + tmp = strBaseDir.substr(start + 8, end - 18); // 8 = 'boxsets/', 18 = 'musicdb://boxsets/' idAlbum = atoi(tmp.c_str()); start = end + 1; // set start to end of albumID field + '/' end = strBaseDir.find("/", start + 1); - std::string strDiscName = strBaseDir.substr(start, (strBaseDir.length() -1) - start); + strDiscName = strBaseDir.substr(start, (strBaseDir.length() -1) - start); if (idAlbum == -1) return false; - if (m_pDB.get() == NULL || m_pDS.get() == NULL) - return false; - - try - { - int total = -1; - std::string strSQL = PrepareSQL("SELECT songview.* from songview WHERE songview.strDiscSubtitle = '%s' AND songview.idAlbum = %i ", strDiscName.c_str(), idAlbum) ; - SortDescription sorting; - CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str()); - // run query - if (!m_pDS->query(strSQL)) - return false; - - int iRowsFound = m_pDS->num_rows(); - if (iRowsFound == 0) - { - m_pDS->close(); - return true; - } - - // store the total value of items as a property - if (total < iRowsFound) - total = iRowsFound; - items.SetProperty("total", total); - - DatabaseResults results; - results.reserve(iRowsFound); - if (!SortUtils::SortFromDataset(sorting, MediaTypeSong, m_pDS, results)) - return false; - // get data from returned rows - items.Reserve(results.size()); - const dbiplus::query_data &data = m_pDS->get_result_set().records; - int count = 0; - for (const auto &i : results) - { - unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger(); - const dbiplus::sql_record* const record = data.at(targetRow); - - try - { - CFileItemPtr item(new CFileItem); - GetFileItemFromDataset(record, item.get(), musicUrl); - // HACK for sorting by database returned order - item->m_iprogramCount = ++count; - items.Add(item); - } - catch (...) - { - m_pDS->close(); - CLog::Log(LOGERROR, "%s: out of memory loading query: %s", __FUNCTION__, filter.where.c_str()); - return (items.Size() > 0); - } - } - - // cleanup - m_pDS->close(); - return true; - } - catch (...) - { - // cleanup - m_pDS->close(); - CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, filter.where.c_str()); - return false; - } + buildUrl = "musicdb://boxsets/"; + buildUrl += StringUtils::Format("%i/", idAlbum); + buildUrl += StringUtils::Format("%s/", strDiscName); + musicUrl.FromString(buildUrl); + musicUrl.AddOption("disctitle", strDiscName.c_str()); + GetFilter(musicUrl, filter, sorting); + GetSongsByWhere(musicUrl.ToString(), filter, items); return true; } @@ -11273,6 +11227,10 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription if (option != options.end()) filter.AppendWhere(PrepareSQL("songview.bCompilation = %i", option->second.asBoolean() ? 1 : 0)); + option = options.find("disctitle"); + if (option != options.end()) + filter.AppendWhere(PrepareSQL("songview.strDiscSubtitle = '%s'", option->second.asString().c_str())); + if (idSong > 0) filter.AppendWhere(PrepareSQL("songview.idSong = %i", idSong));