1653 lines
68 KiB
Python
1653 lines
68 KiB
Python
import math
|
|
import os
|
|
import sys
|
|
import time
|
|
import urllib.parse
|
|
from typing import Any, Dict, List, Tuple, Union
|
|
|
|
import simplecache
|
|
import xbmc
|
|
import xbmcaddon
|
|
import xbmcgui
|
|
import xbmcplugin
|
|
import xbmcvfs
|
|
|
|
import spotipy
|
|
import utils
|
|
from string_ids import *
|
|
from utils import ADDON_ID, PROXY_PORT, log_exception, log_msg, get_chunks
|
|
|
|
MUSIC_ARTISTS_ICON = "icon_music_artists.png"
|
|
MUSIC_TOP_ARTISTS_ICON = "icon_music_top_artists.png"
|
|
MUSIC_SONGS_ICON = "icon_music_songs.png"
|
|
MUSIC_TOP_TRACKS_ICON = "icon_music_top_tracks.png"
|
|
MUSIC_ALBUMS_ICON = "icon_music_albums.png"
|
|
MUSIC_PLAYLISTS_ICON = "icon_music_playlists.png"
|
|
MUSIC_LIBRARY_ICON = "icon_music_library.png"
|
|
MUSIC_SEARCH_ICON = "icon_music_search.png"
|
|
MUSIC_EXPLORE_ICON = "icon_music_explore.png"
|
|
CLEAR_CACHE_ICON = "icon_clear_cache.png"
|
|
|
|
Playlist = Dict[str, Union[str, Dict[str, List[Any]]]]
|
|
|
|
|
|
class PluginContent:
|
|
__addon: xbmcaddon.Addon = xbmcaddon.Addon(id=ADDON_ID)
|
|
__win: xbmcgui.Window = xbmcgui.Window(utils.ADDON_WINDOW_ID)
|
|
__addon_icon_path = os.path.join(__addon.getAddonInfo("path"), "resources")
|
|
__action = ""
|
|
__spotipy = None
|
|
__userid = ""
|
|
__user_country = ""
|
|
__offset = 0
|
|
__playlist_id = ""
|
|
__album_id = ""
|
|
__track_id = ""
|
|
__artist_id = ""
|
|
__artist_name = ""
|
|
__owner_id = ""
|
|
__filter = ""
|
|
__token = ""
|
|
__limit = 50
|
|
__params = {}
|
|
__base_url = sys.argv[0]
|
|
__addon_handle = int(sys.argv[1])
|
|
__cached_checksum = ""
|
|
__last_playlist_position = 0
|
|
|
|
def __init__(self):
|
|
try:
|
|
self.cache: simplecache.SimpleCache = simplecache.SimpleCache(ADDON_ID)
|
|
|
|
self.append_artist_to_title: bool = (
|
|
self.__addon.getSetting("appendArtistToTitle") == "true"
|
|
)
|
|
self.default_view_songs: str = self.__addon.getSetting("songDefaultView")
|
|
self.default_view_artists: str = self.__addon.getSetting("artistDefaultView")
|
|
self.default_view_playlists: str = self.__addon.getSetting("playlistDefaultView")
|
|
self.default_view_albums: str = self.__addon.getSetting("albumDefaultView")
|
|
self.default_view_category: str = self.__addon.getSetting("categoryDefaultView")
|
|
|
|
auth_token: str = self.__get_authkey()
|
|
if not auth_token:
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
return
|
|
|
|
self.__spotipy: spotipy.Spotify = spotipy.Spotify(auth=auth_token)
|
|
self.__userid: str = self.__spotipy.me()["id"]
|
|
self.__user_country = self.__spotipy.me()["country"]
|
|
|
|
self.parse_params()
|
|
|
|
if self.__action:
|
|
log_msg(f"Evaluating action '{self.__action}'.")
|
|
action = f"self.{self.__action}"
|
|
eval(action)()
|
|
else:
|
|
log_msg("Browsing main and setting up precache library.")
|
|
self.__browse_main()
|
|
self.__precache_library()
|
|
|
|
except Exception as exc:
|
|
log_exception(exc, "PluginContent init error")
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
def parse_params(self):
|
|
"""parse parameters from the plugin entry path"""
|
|
log_msg(f"sys.argv = {str(sys.argv)}")
|
|
self.__params: Dict[str, Any] = urllib.parse.parse_qs(sys.argv[2][1:])
|
|
|
|
action = self.__params.get("action", None)
|
|
if action:
|
|
self.__action = action[0].lower()
|
|
log_msg(f"Set action to '{self.__action}'.")
|
|
|
|
playlist_id = self.__params.get("playlistid", None)
|
|
if playlist_id:
|
|
self.__playlist_id = playlist_id[0]
|
|
owner_id = self.__params.get("ownerid", None)
|
|
if owner_id:
|
|
self.__owner_id = owner_id[0]
|
|
track_id = self.__params.get("trackid", None)
|
|
if track_id:
|
|
self.__track_id = track_id[0]
|
|
album_id = self.__params.get("albumid", None)
|
|
if album_id:
|
|
self.__album_id = album_id[0]
|
|
artist_id = self.__params.get("artistid", None)
|
|
if artist_id:
|
|
self.__artist_id = artist_id[0]
|
|
artist_name = self.__params.get("artistname", None)
|
|
if artist_name:
|
|
self.__artist_name = artist_name[0]
|
|
offset = self.__params.get("offset", None)
|
|
if offset:
|
|
self.__offset = int(offset[0])
|
|
filt = self.__params.get("applyfilter", None)
|
|
if filt:
|
|
self.__filter = filt[0]
|
|
|
|
def __get_authkey(self) -> str:
|
|
"""get authentication key"""
|
|
auth_token = utils.get_cached_auth_token()
|
|
|
|
if not auth_token:
|
|
msg = self.__addon.getLocalizedString(NO_CREDENTIALS_MSG_STR_ID)
|
|
dialog = xbmcgui.Dialog()
|
|
header = self.__addon.getAddonInfo("name")
|
|
dialog.ok(header, msg)
|
|
|
|
return auth_token
|
|
|
|
def __cache_checksum(self, opt_value: Any = None) -> str:
|
|
"""simple cache checksum based on a few most important values"""
|
|
result = self.__cached_checksum
|
|
if not result:
|
|
# log_msg("__cached_checksum not found. Getting a new one.")
|
|
saved_tracks = self.__get_saved_track_ids()
|
|
saved_albums = self.__get_saved_album_ids()
|
|
followed_artists = self.__get_followed_artists()
|
|
generic_checksum = self.__addon.getSetting("cache_checksum")
|
|
result = (
|
|
f"{len(saved_tracks)}-{len(saved_albums)}-{len(followed_artists)}"
|
|
f"-{generic_checksum}"
|
|
)
|
|
self.__cached_checksum = result
|
|
# log_msg(f"New __cached_checksum = '{self.__cached_checksum}'.")
|
|
|
|
if opt_value:
|
|
result += f"-{opt_value}"
|
|
|
|
return result
|
|
|
|
def __build_url(self, query: Dict[str, str]) -> str:
|
|
query_encoded = {}
|
|
for key, value in list(query.items()):
|
|
if isinstance(key, str):
|
|
key = key.encode("utf-8")
|
|
if isinstance(value, str):
|
|
value = value.encode("utf-8")
|
|
query_encoded[key] = value
|
|
|
|
return self.__base_url + "?" + urllib.parse.urlencode(query_encoded)
|
|
|
|
def delete_cache_db(self) -> None:
|
|
log_msg("Deleting plugin cache...")
|
|
simple_db_cache_addon = xbmcaddon.Addon(ADDON_ID)
|
|
db_path = simple_db_cache_addon.getAddonInfo("profile")
|
|
db_file = xbmcvfs.translatePath(f"{db_path}/simplecache.db")
|
|
os.remove(db_file)
|
|
log_msg(f"Deleted simplecache database file {db_file}.")
|
|
|
|
dialog = xbmcgui.Dialog()
|
|
header = self.__addon.getAddonInfo("name")
|
|
msg = self.__addon.getLocalizedString(CACHED_CLEARED_STR_ID)
|
|
dialog.ok(header, msg)
|
|
|
|
def refresh_listing(self) -> None:
|
|
self.__addon.setSetting("cache_checksum", time.strftime("%Y%m%d%H%M%S", time.gmtime()))
|
|
log_msg(f"New cache_checksum = \'{self.__addon.getSetting('cache_checksum')}\'")
|
|
xbmc.executebuiltin("Container.Refresh")
|
|
|
|
def __add_track_listitems(self, tracks, append_artist_to_label: bool = False) -> None:
|
|
list_items = self.__get_track_list(tracks, append_artist_to_label)
|
|
xbmcplugin.addDirectoryItems(self.__addon_handle, list_items, totalItems=len(list_items))
|
|
|
|
@staticmethod
|
|
def __get_track_name(track, append_artist_to_label: bool) -> str:
|
|
if not append_artist_to_label:
|
|
return track["name"]
|
|
return f"{track['artist']} - {track['name']}"
|
|
|
|
@staticmethod
|
|
def __get_track_rating(popularity: int) -> int:
|
|
if not popularity:
|
|
return 0
|
|
|
|
return int(math.ceil(popularity * 6 / 100.0)) - 1
|
|
|
|
def __get_track_list(
|
|
self, tracks, append_artist_to_label: bool = False
|
|
) -> List[Tuple[str, xbmcgui.ListItem, bool]]:
|
|
list_items = []
|
|
for count, track in enumerate(tracks):
|
|
list_items.append(self.__get_track_item(track, append_artist_to_label) + (False,))
|
|
|
|
return list_items
|
|
|
|
def __get_track_item(
|
|
self, track: Dict[str, Any], append_artist_to_label: bool = False
|
|
) -> Tuple[str, xbmcgui.ListItem]:
|
|
label = self.__get_track_name(track, append_artist_to_label)
|
|
title = label if self.append_artist_to_title else track["name"]
|
|
|
|
# Local playback by using proxy on this machine.
|
|
url = f"http://localhost:{PROXY_PORT}/track/{track['id']}"
|
|
|
|
# Testing Proxy - mitmproxy ftw
|
|
# url = f"http://localhost:8080/track/{track['id']}"
|
|
|
|
li = xbmcgui.ListItem(label, offscreen=True)
|
|
li.setProperty("isPlayable", "true")
|
|
# Kodi 19 legacy code
|
|
# Unsure how to detect Kodi version so I'll leave this here for those stuck in the past
|
|
# duration = track["duration_ms"] / 1000
|
|
# li.setInfo(
|
|
# "music",
|
|
# {
|
|
# "title": title,
|
|
# "genre": track["genre"],
|
|
# "year": track["year"],
|
|
# "tracknumber": track["track_number"],
|
|
# "album": track["album"]["name"],
|
|
# "artist": track["artist"],
|
|
# "rating": track["rating"],
|
|
# "duration": duration,
|
|
# },
|
|
# )
|
|
|
|
# Kodi 20+ Equivalent
|
|
duration = int(math.ceil(track["duration_ms"] / 1000))
|
|
limus: xbmc.InfoTagMusic = li.getMusicInfoTag()
|
|
limus.setTitle(title)
|
|
limus.setAlbum(track["album"]["name"])
|
|
limus.setGenres([track["genre"]])
|
|
limus.setYear(track["year"])
|
|
limus.setTrack(track["track_number"])
|
|
limus.setArtist(track["artist"])
|
|
limus.setRating(float(track["rating"]))
|
|
limus.setDuration(duration)
|
|
|
|
li.setArt({"thumb": track["thumb"]})
|
|
li.setProperty("spotifytrackid", track["id"])
|
|
li.setContentLookup(False)
|
|
li.addContextMenuItems(track["contextitems"], True)
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setMimeType("audio/ogg")
|
|
li.setProperty('mimetype', 'audio/ogg')
|
|
li.setProperty('inputstream', 'inputstream.ffmpegdirect')
|
|
li.setProperty('inputstream.ffmpegdirect.open_mode', 'curl')
|
|
li.setProperty('inputstream.ffmpegdirect.default_url', url)
|
|
|
|
return url, li
|
|
|
|
def __browse_main(self) -> None:
|
|
# Main listing.
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
|
|
items = [
|
|
(
|
|
self.__addon.getLocalizedString(MY_MUSIC_FOLDER_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_main_library.__name__}",
|
|
MUSIC_LIBRARY_ICON,
|
|
True,
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(EXPLORE_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_main_explore.__name__}",
|
|
MUSIC_EXPLORE_ICON,
|
|
True,
|
|
),
|
|
(
|
|
xbmc.getLocalizedString(KODI_SEARCH_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.search.__name__}",
|
|
MUSIC_SEARCH_ICON,
|
|
True,
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(CLEAR_CACHE_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.delete_cache_db.__name__}",
|
|
CLEAR_CACHE_ICON,
|
|
False,
|
|
),
|
|
]
|
|
|
|
for item in items:
|
|
li = xbmcgui.ListItem(item[0], path=item[1])
|
|
li.setProperty("IsPlayable", "false")
|
|
li.setArt({"icon": os.path.join(self.__addon_icon_path, item[2])})
|
|
li.addContextMenuItems([], True)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=item[1], listitem=li, isFolder=item[3]
|
|
)
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
def browse_main_library(self) -> None:
|
|
# Library nodes.
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle,
|
|
"FolderName",
|
|
self.__addon.getLocalizedString(MY_MUSIC_FOLDER_STR_ID),
|
|
)
|
|
|
|
items = [
|
|
(
|
|
xbmc.getLocalizedString(KODI_PLAYLISTS_STR_ID),
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.browse_playlists.__name__}&ownerid={self.__userid}",
|
|
MUSIC_PLAYLISTS_ICON,
|
|
),
|
|
(
|
|
xbmc.getLocalizedString(KODI_ALBUMS_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_saved_albums.__name__}",
|
|
MUSIC_ALBUMS_ICON,
|
|
),
|
|
(
|
|
xbmc.getLocalizedString(KODI_SONGS_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_saved_tracks.__name__}",
|
|
MUSIC_SONGS_ICON,
|
|
),
|
|
(
|
|
xbmc.getLocalizedString(KODI_ARTISTS_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_saved_artists.__name__}",
|
|
MUSIC_ARTISTS_ICON,
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(FOLLOWED_ARTISTS_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_followed_artists.__name__}",
|
|
MUSIC_ARTISTS_ICON,
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(MOST_PLAYED_ARTISTS_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_top_artists.__name__}",
|
|
MUSIC_TOP_ARTISTS_ICON,
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(MOST_PLAYED_TRACKS_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_top_tracks.__name__}",
|
|
MUSIC_TOP_TRACKS_ICON,
|
|
),
|
|
]
|
|
|
|
for item in items:
|
|
li = xbmcgui.ListItem(item[0], path=item[1])
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
li.setArt({"icon": os.path.join(self.__addon_icon_path, item[2])})
|
|
li.addContextMenuItems([], True)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=item[1], listitem=li, isFolder=True
|
|
)
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
def browse_top_artists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "artists")
|
|
result = self.__spotipy.current_user_top_artists(limit=20, offset=0)
|
|
|
|
cache_str = f"spotify.topartists.{self.__userid}"
|
|
checksum = self.__cache_checksum(result["total"])
|
|
items = self.cache.get(cache_str, checksum=checksum)
|
|
if not items:
|
|
count = len(result["items"])
|
|
while result["total"] > count:
|
|
result["items"] += self.__spotipy.current_user_top_artists(limit=20, offset=count)[
|
|
"items"
|
|
]
|
|
count += 50
|
|
items = self.__prepare_artist_listitems(result["items"])
|
|
self.cache.set(cache_str, items, checksum=checksum)
|
|
self.__add_artist_listitems(items)
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_artists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_artists})")
|
|
|
|
def browse_top_tracks(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "songs")
|
|
results = self.__spotipy.current_user_top_tracks(limit=20, offset=0)
|
|
|
|
cache_str = f"spotify.toptracks.{self.__userid}"
|
|
checksum = self.__cache_checksum(results["total"])
|
|
tracks = self.cache.get(cache_str, checksum=checksum)
|
|
if not tracks:
|
|
tracks = results["items"]
|
|
while results["next"]:
|
|
results = self.__spotipy.next(results)
|
|
tracks.extend(results["items"])
|
|
tracks = self.__prepare_track_listitems(tracks=tracks)
|
|
self.cache.set(cache_str, tracks, checksum=checksum)
|
|
self.__add_track_listitems(tracks, True)
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_songs:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_songs})")
|
|
|
|
def __get_explore_categories(self) -> List[Tuple[Any, str, Union[str, Any]]]:
|
|
items = []
|
|
|
|
categories = self.__spotipy.categories(
|
|
country=self.__user_country, limit=50, locale=self.__user_country
|
|
)
|
|
count = len(categories["categories"]["items"])
|
|
while categories["categories"]["total"] > count:
|
|
categories["categories"]["items"] += self.__spotipy.categories(
|
|
country=self.__user_country, limit=50, offset=count, locale=self.__user_country
|
|
)["categories"]["items"]
|
|
count += 50
|
|
|
|
for item in categories["categories"]["items"]:
|
|
thumb = "DefaultMusicGenre.png"
|
|
for icon in item["icons"]:
|
|
thumb = icon["url"]
|
|
break
|
|
items.append(
|
|
(
|
|
item["name"],
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.browse_category.__name__}&applyfilter={item['id']}",
|
|
thumb,
|
|
)
|
|
)
|
|
|
|
return items
|
|
|
|
def browse_main_explore(self) -> None:
|
|
# Explore nodes.
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", self.__addon.getLocalizedString(EXPLORE_STR_ID)
|
|
)
|
|
items = [
|
|
(
|
|
self.__addon.getLocalizedString(FEATURED_PLAYLISTS_STR_ID),
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.browse_playlists.__name__}&applyfilter=featured",
|
|
MUSIC_PLAYLISTS_ICON,
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(BROWSE_NEW_RELEASES_STR_ID),
|
|
f"plugin://{ADDON_ID}/?action={self.browse_new_releases.__name__}",
|
|
MUSIC_ALBUMS_ICON,
|
|
),
|
|
]
|
|
|
|
# Add categories.
|
|
items += self.__get_explore_categories()
|
|
for item in items:
|
|
li = xbmcgui.ListItem(item[0], path=item[1])
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
li.setArt({"icon": os.path.join(self.__addon_icon_path, item[2])})
|
|
li.addContextMenuItems([], True)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=item[1], listitem=li, isFolder=True
|
|
)
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
def __get_album_tracks(self, album: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
cache_str = f"spotify.albumtracks{album['id']}"
|
|
checksum = self.__cache_checksum()
|
|
|
|
album_tracks = self.cache.get(cache_str, checksum=checksum)
|
|
if not album_tracks:
|
|
track_ids = []
|
|
count = 0
|
|
while album["tracks"]["total"] > count:
|
|
tracks = self.__spotipy.album_tracks(
|
|
album["id"], market=self.__user_country, limit=50, offset=count
|
|
)["items"]
|
|
for track in tracks:
|
|
track_ids.append(track["id"])
|
|
count += 50
|
|
album_tracks = self.__prepare_track_listitems(track_ids, album_details=album)
|
|
self.cache.set(cache_str, album_tracks, checksum=checksum)
|
|
|
|
return album_tracks
|
|
|
|
def browse_album(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "songs")
|
|
album = self.__spotipy.album(self.__album_id, market=self.__user_country)
|
|
xbmcplugin.setProperty(self.__addon_handle, "FolderName", album["name"])
|
|
tracks = self.__get_album_tracks(album)
|
|
if album.get("album_type") == "compilation":
|
|
self.__add_track_listitems(tracks, True)
|
|
else:
|
|
self.__add_track_listitems(tracks)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_TRACKNUM)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_TITLE)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_SONG_RATING)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_ARTIST)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_songs:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_songs})")
|
|
|
|
def artist_top_tracks(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "songs")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle,
|
|
"FolderName",
|
|
self.__addon.getLocalizedString(ARTIST_TOP_TRACKS_STR_ID),
|
|
)
|
|
tracks = self.__spotipy.artist_top_tracks(self.__artist_id, country=self.__user_country)
|
|
tracks = self.__prepare_track_listitems(tracks=tracks["tracks"])
|
|
self.__add_track_listitems(tracks)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_TRACKNUM)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_TITLE)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_SONG_RATING)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_songs:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_songs})")
|
|
|
|
def related_artists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "artists")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle,
|
|
"FolderName",
|
|
self.__addon.getLocalizedString(RELATED_ARTISTS_STR_ID),
|
|
)
|
|
cache_str = f"spotify.relatedartists.{self.__artist_id}"
|
|
checksum = self.__cache_checksum()
|
|
artists = self.cache.get(cache_str, checksum=checksum)
|
|
if not artists:
|
|
artists = self.__spotipy.artist_related_artists(self.__artist_id)
|
|
artists = self.__prepare_artist_listitems(artists["artists"])
|
|
self.cache.set(cache_str, artists, checksum=checksum)
|
|
self.__add_artist_listitems(artists)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_artists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_artists})")
|
|
|
|
def __get_playlist_details(self, playlist_id: str) -> Playlist:
|
|
playlist = self.__spotipy.playlist(
|
|
playlist_id, fields="tracks(total),name,owner(id),id", market=self.__user_country
|
|
)
|
|
# Get from cache first.
|
|
cache_str = f"spotify.playlistdetails.{playlist['id']}"
|
|
checksum = self.__cache_checksum(playlist["tracks"]["total"])
|
|
# log_msg(f"Playlist cache_str = '{cache_str}', checksum = '{checksum}'.")
|
|
playlist_details = self.cache.get(cache_str, checksum=checksum)
|
|
if not playlist_details:
|
|
# Get listing from api.
|
|
count = 0
|
|
playlist_details = playlist
|
|
playlist_details["tracks"]["items"] = []
|
|
while playlist["tracks"]["total"] > count:
|
|
playlist_details["tracks"]["items"] += self.__spotipy.user_playlist_tracks(
|
|
playlist["owner"]["id"],
|
|
playlist["id"],
|
|
market=self.__user_country,
|
|
fields="",
|
|
limit=50,
|
|
offset=count,
|
|
)["items"]
|
|
count += 50
|
|
playlist_details["tracks"]["items"] = self.__prepare_track_listitems(
|
|
tracks=playlist_details["tracks"]["items"], playlist_details=playlist
|
|
)
|
|
# log_msg(f"playlist_details = {playlist_details}")
|
|
checksum = self.__cache_checksum(playlist["tracks"]["total"])
|
|
self.cache.set(cache_str, playlist_details, checksum=checksum)
|
|
# log_msg(f"Got new playlist - checksum = '{checksum}'")
|
|
|
|
return playlist_details
|
|
|
|
def browse_playlist(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "songs")
|
|
playlist_details = self.__get_playlist_details(self.__playlist_id)
|
|
xbmcplugin.setProperty(self.__addon_handle, "FolderName", playlist_details["name"])
|
|
self.__add_track_listitems(playlist_details["tracks"]["items"], True)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_songs:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_songs})")
|
|
|
|
def play_playlist(self) -> None:
|
|
"""play entire playlist"""
|
|
playlist_details = self.__get_playlist_details(self.__playlist_id)
|
|
log_msg(f"Start playing playlist '{playlist_details['name']}'.")
|
|
|
|
kodi_playlist = xbmc.PlayList(0)
|
|
kodi_playlist.clear()
|
|
|
|
def add_to_playlist(trk) -> None:
|
|
url, li = self.__get_track_item(trk, True)
|
|
kodi_playlist.add(url, li)
|
|
|
|
# Add first track and start playing.
|
|
add_to_playlist(playlist_details["tracks"]["items"][0])
|
|
kodi_player = xbmc.Player()
|
|
kodi_player.play(kodi_playlist)
|
|
|
|
# Add remaining tracks to the playlist while already playing.
|
|
for track in playlist_details["tracks"]["items"][1:]:
|
|
add_to_playlist(track)
|
|
|
|
def __get_category(self, categoryid: str) -> Playlist:
|
|
category = self.__spotipy.category(
|
|
categoryid, country=self.__user_country, locale=self.__user_country
|
|
)
|
|
playlists = self.__spotipy.category_playlists(
|
|
categoryid, country=self.__user_country, limit=50, offset=0
|
|
)
|
|
playlists["category"] = category["name"]
|
|
count = len(playlists["playlists"]["items"])
|
|
while playlists["playlists"]["total"] > count:
|
|
playlists["playlists"]["items"] += self.__spotipy.category_playlists(
|
|
categoryid, country=self.__user_country, limit=50, offset=count
|
|
)["playlists"]["items"]
|
|
count += 50
|
|
playlists["playlists"]["items"] = self.__prepare_playlist_listitems(
|
|
playlists["playlists"]["items"]
|
|
)
|
|
|
|
return playlists
|
|
|
|
def browse_category(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
playlists = self.__get_category(self.__filter)
|
|
self.__add_playlist_listitems(playlists["playlists"]["items"])
|
|
xbmcplugin.setProperty(self.__addon_handle, "FolderName", playlists["category"])
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_category:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_category})")
|
|
|
|
def follow_playlist(self) -> None:
|
|
self.__spotipy.current_user_follow_playlist(self.__playlist_id)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def add_track_to_playlist(self) -> None:
|
|
xbmc.executebuiltin("ActivateWindow(busydialog)")
|
|
|
|
if not self.__track_id and xbmc.getInfoLabel("MusicPlayer.(1).Property(spotifytrackid)"):
|
|
self.__track_id = xbmc.getInfoLabel("MusicPlayer.(1).Property(spotifytrackid)")
|
|
|
|
own_playlists, own_playlist_names = utils.get_user_playlists(self.__spotipy, 50)
|
|
own_playlist_names.append(xbmc.getLocalizedString(KODI_NEW_PLAYLIST_STR_ID))
|
|
|
|
xbmc.executebuiltin("Dialog.Close(busydialog)")
|
|
select = xbmcgui.Dialog().select(
|
|
xbmc.getLocalizedString(KODI_SELECT_PLAYLIST_STR_ID), own_playlist_names
|
|
)
|
|
if select != -1 and own_playlist_names[select] == xbmc.getLocalizedString(
|
|
KODI_NEW_PLAYLIST_STR_ID
|
|
):
|
|
# create new playlist...
|
|
kb = xbmc.Keyboard("", xbmc.getLocalizedString(KODI_ENTER_NEW_PLAYLIST_STR_ID))
|
|
kb.setHiddenInput(False)
|
|
kb.doModal()
|
|
if kb.isConfirmed():
|
|
name = kb.getText()
|
|
playlist = self.__spotipy.user_playlist_create(self.__userid, name, False)
|
|
self.__spotipy.playlist_add_items(playlist["id"], [self.__track_id])
|
|
elif select != -1:
|
|
playlist = own_playlists[select]
|
|
self.__spotipy.playlist_add_items(playlist["id"], [self.__track_id])
|
|
|
|
def remove_track_from_playlist(self) -> None:
|
|
self.__spotipy.playlist_remove_all_occurrences_of_items(
|
|
self.__playlist_id, [self.__track_id]
|
|
)
|
|
self.refresh_listing()
|
|
|
|
def unfollow_playlist(self) -> None:
|
|
self.__spotipy.current_user_unfollow_playlist(self.__playlist_id)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def follow_artist(self) -> None:
|
|
self.__spotipy.user_follow_artists([self.__artist_id])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def unfollow_artist(self) -> None:
|
|
self.__spotipy.user_unfollow_artists([self.__artist_id])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def save_album(self) -> None:
|
|
self.__spotipy.current_user_saved_albums_add([self.__album_id])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def remove_album(self) -> None:
|
|
self.__spotipy.current_user_saved_albums_delete([self.__album_id])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def save_track(self) -> None:
|
|
self.__spotipy.current_user_saved_tracks_add([self.__track_id])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def remove_track(self) -> None:
|
|
self.__spotipy.current_user_saved_tracks_delete([self.__track_id])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
self.refresh_listing()
|
|
|
|
def __get_featured_playlists(self) -> Playlist:
|
|
playlists = self.__spotipy.featured_playlists(
|
|
country=self.__user_country, limit=50, offset=0
|
|
)
|
|
count = len(playlists["playlists"]["items"])
|
|
total = playlists["playlists"]["total"]
|
|
while total > count:
|
|
playlists["playlists"]["items"] += self.__spotipy.featured_playlists(
|
|
country=self.__user_country, limit=50, offset=count
|
|
)["playlists"]["items"]
|
|
count += 50
|
|
playlists["playlists"]["items"] = self.__prepare_playlist_listitems(
|
|
playlists["playlists"]["items"]
|
|
)
|
|
|
|
return playlists
|
|
|
|
def __get_user_playlists(self, userid):
|
|
playlists = self.__spotipy.user_playlists(userid, limit=1, offset=0)
|
|
count = len(playlists["items"])
|
|
total = playlists["total"]
|
|
cache_str = f"spotify.userplaylists.{userid}"
|
|
checksum = self.__cache_checksum(total)
|
|
|
|
cache = self.cache.get(cache_str, checksum=checksum)
|
|
if cache:
|
|
playlists = cache
|
|
else:
|
|
while total > count:
|
|
playlists["items"] += self.__spotipy.user_playlists(userid, limit=50, offset=count)[
|
|
"items"
|
|
]
|
|
count += 50
|
|
playlists = self.__prepare_playlist_listitems(playlists["items"])
|
|
self.cache.set(cache_str, playlists, checksum=checksum)
|
|
|
|
return playlists
|
|
|
|
def __get_curuser_playlistids(self) -> List[str]:
|
|
playlists = self.__spotipy.current_user_playlists(limit=1, offset=0)
|
|
count = len(playlists["items"])
|
|
total = playlists["total"]
|
|
cache_str = f"spotify.userplaylistids.{self.__userid}"
|
|
playlist_ids = self.cache.get(cache_str, checksum=total)
|
|
if not playlist_ids:
|
|
playlist_ids = []
|
|
while total > count:
|
|
playlists["items"] += self.__spotipy.current_user_playlists(limit=50, offset=count)[
|
|
"items"
|
|
]
|
|
count += 50
|
|
for playlist in playlists["items"]:
|
|
playlist_ids.append(playlist["id"])
|
|
self.cache.set(cache_str, playlist_ids, checksum=total)
|
|
return playlist_ids
|
|
|
|
def browse_playlists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
if self.__filter == "featured":
|
|
playlists = self.__get_featured_playlists()
|
|
xbmcplugin.setProperty(self.__addon_handle, "FolderName", playlists["message"])
|
|
playlists = playlists["playlists"]["items"]
|
|
else:
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_PLAYLISTS_STR_ID)
|
|
)
|
|
playlists = self.__get_user_playlists(self.__owner_id)
|
|
|
|
self.__add_playlist_listitems(playlists)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_playlists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_playlists})")
|
|
|
|
def __get_new_releases(self):
|
|
albums = self.__spotipy.new_releases(country=self.__user_country, limit=50, offset=0)
|
|
count = len(albums["albums"]["items"])
|
|
while albums["albums"]["total"] > count:
|
|
albums["albums"]["items"] += self.__spotipy.new_releases(
|
|
country=self.__user_country, limit=50, offset=count
|
|
)["albums"]["items"]
|
|
count += 50
|
|
|
|
album_ids = []
|
|
for album in albums["albums"]["items"]:
|
|
album_ids.append(album["id"])
|
|
albums = self.__prepare_album_listitems(album_ids)
|
|
|
|
return albums
|
|
|
|
def browse_new_releases(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "albums")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", self.__addon.getLocalizedString(NEW_RELEASES_STR_ID)
|
|
)
|
|
albums = self.__get_new_releases()
|
|
self.__add_album_listitems(albums)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_albums:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_albums})")
|
|
|
|
def __prepare_track_listitems(
|
|
self, track_ids=None, tracks=None, playlist_details=None, album_details=None
|
|
) -> List[Dict[str, Any]]:
|
|
if tracks is None:
|
|
tracks = []
|
|
if track_ids is None:
|
|
track_ids = []
|
|
|
|
new_tracks: List[Dict[str, Any]] = []
|
|
|
|
# For tracks, we always get the full details unless full tracks already supplied.
|
|
if track_ids and not tracks:
|
|
for chunk in get_chunks(track_ids, 20):
|
|
tracks += self.__spotipy.tracks(chunk, market=self.__user_country)["tracks"]
|
|
|
|
saved_track_ids = self.__get_saved_track_ids()
|
|
|
|
followed_artists = []
|
|
for artist in self.__get_followed_artists():
|
|
followed_artists.append(artist["id"])
|
|
|
|
for track in tracks:
|
|
if track.get("track"):
|
|
track = track["track"]
|
|
if album_details:
|
|
track["album"] = album_details
|
|
if track.get("images"):
|
|
thumb = track["images"][0]["url"]
|
|
elif track.get("album", {}).get("images"):
|
|
thumb = track["album"]["images"][0]["url"]
|
|
else:
|
|
thumb = "DefaultMusicSongs.png"
|
|
track["thumb"] = thumb
|
|
|
|
# skip local tracks in playlists
|
|
if not track.get("id"):
|
|
continue
|
|
|
|
artists = []
|
|
try:
|
|
for artist in track["artists"]:
|
|
artists.append(artist["name"])
|
|
track["artist"] = " / ".join(artists)
|
|
except:
|
|
track['artist'] = track["artists"][0]
|
|
track["artistid"] = track["artists"][0]["id"]
|
|
|
|
track["genre"] = " / ".join(track["album"].get("genres", []))
|
|
|
|
# Allow for 'release_date' being empty.
|
|
release_date = "0" if "album" not in track else track["album"].get("release_date", "0")
|
|
track["year"] = (
|
|
1900
|
|
if not release_date
|
|
else int(track["album"].get("release_date", "0").split("-")[0])
|
|
)
|
|
|
|
track["rating"] = str(self.__get_track_rating(track["popularity"]))
|
|
if playlist_details:
|
|
track["playlistid"] = playlist_details["id"]
|
|
|
|
# Use original track id for actions when the track was relinked.
|
|
if track.get("linked_from"):
|
|
real_track_id = track["linked_from"]["id"]
|
|
real_track_uri = track["linked_from"]["uri"]
|
|
else:
|
|
real_track_id = track["id"]
|
|
real_track_uri = track["uri"]
|
|
|
|
contextitems = []
|
|
if track["id"] in saved_track_ids:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(REMOVE_TRACKS_FROM_MY_MUSIC_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.remove_track.__name__}&trackid={real_track_id})",
|
|
)
|
|
)
|
|
else:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(SAVE_TRACKS_TO_MY_MUSIC_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.save_track.__name__}&trackid={real_track_id})",
|
|
)
|
|
)
|
|
|
|
if playlist_details and playlist_details["owner"]["id"] == self.__userid:
|
|
contextitems.append(
|
|
(
|
|
f"{self.__addon.getLocalizedString(REMOVE_FROM_PLAYLIST_STR_ID)}"
|
|
f" {playlist_details['name']}",
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.remove_track_from_playlist.__name__}&trackid="
|
|
f"{real_track_uri}&playlistid={playlist_details['id']})",
|
|
)
|
|
)
|
|
|
|
contextitems.append(
|
|
(
|
|
xbmc.getLocalizedString(KODI_ADD_TO_PLAYLIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.add_track_to_playlist.__name__}&trackid={real_track_uri})",
|
|
)
|
|
)
|
|
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(ARTIST_TOP_TRACKS_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.artist_top_tracks.__name__}&artistid={track['artistid']})",
|
|
)
|
|
)
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(RELATED_ARTISTS_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.related_artists.__name__}&artistid={track['artistid']})",
|
|
)
|
|
)
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(ALL_ALBUMS_FOR_ARTIST_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.browse_artist_albums.__name__}&artistid={track['artistid']})",
|
|
)
|
|
)
|
|
|
|
if track["artistid"] in followed_artists:
|
|
# unfollow artist
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(UNFOLLOW_ARTIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.unfollow_artist.__name__}&artistid={track['artistid']})",
|
|
)
|
|
)
|
|
else:
|
|
# follow artist
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(FOLLOW_ARTIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.follow_artist.__name__}&artistid={track['artistid']})",
|
|
)
|
|
)
|
|
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(REFRESH_LISTING_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/" f"?action={self.refresh_listing.__name__})",
|
|
)
|
|
)
|
|
track["contextitems"] = contextitems
|
|
new_tracks.append(track)
|
|
|
|
return new_tracks
|
|
|
|
def __prepare_album_listitems(
|
|
self, album_ids: List[str] = None, albums: List[Dict[str, Any]] = None
|
|
) -> List[Dict[str, Any]]:
|
|
if albums is None:
|
|
albums: List[Dict[str, Any]] = []
|
|
if album_ids is None:
|
|
album_ids = []
|
|
if not albums and album_ids:
|
|
# Get full info in chunks of 20.
|
|
for chunk in get_chunks(album_ids, 20):
|
|
albums += self.__spotipy.albums(chunk, market=self.__user_country)["albums"]
|
|
|
|
saved_albums = self.__get_saved_album_ids()
|
|
|
|
# process listing
|
|
for track in albums:
|
|
if track.get("images"):
|
|
track["thumb"] = track["images"][0]["url"]
|
|
else:
|
|
track["thumb"] = "DefaultMusicAlbums.png"
|
|
|
|
track["url"] = self.__build_url(
|
|
{"action": self.browse_album.__name__, "albumid": track["id"]}
|
|
)
|
|
|
|
artists = []
|
|
for artist in track["artists"]:
|
|
artists.append(artist["name"])
|
|
track["artist"] = " / ".join(artists)
|
|
track["genre"] = " / ".join(track["genres"])
|
|
track["year"] = int(track["release_date"].split("-")[0])
|
|
track["rating"] = str(self.__get_track_rating(track["popularity"]))
|
|
track["artistid"] = track["artists"][0]["id"]
|
|
|
|
contextitems = [
|
|
(xbmc.getLocalizedString(KODI_BROWSE_STR_ID), f"RunPlugin({track['url']})"),
|
|
(
|
|
self.__addon.getLocalizedString(ARTIST_TOP_TRACKS_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.artist_top_tracks.__name__}&artistid={track['artistid']})",
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(RELATED_ARTISTS_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.related_artists.__name__}&artistid={track['artistid']})",
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(ALL_ALBUMS_FOR_ARTIST_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.browse_artist_albums.__name__}&artistid={track['artistid']})",
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(REFRESH_LISTING_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/" f"?action={self.refresh_listing.__name__})",
|
|
),
|
|
]
|
|
|
|
if track["id"] in saved_albums:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(REMOVE_TRACKS_FROM_MY_MUSIC_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.remove_album.__name__}&albumid={track['id']})",
|
|
)
|
|
)
|
|
else:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(SAVE_TRACKS_TO_MY_MUSIC_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.save_album.__name__}&albumid={track['id']})",
|
|
)
|
|
)
|
|
|
|
track["contextitems"] = contextitems
|
|
|
|
return albums
|
|
|
|
def __add_album_listitems(
|
|
self, albums: List[Dict[str, Any]], append_artist_to_label: bool = False
|
|
) -> None:
|
|
# Process listing.
|
|
for track in albums:
|
|
label = self.__get_track_name(track, append_artist_to_label)
|
|
|
|
li = xbmcgui.ListItem(label, path=track["url"], offscreen=True)
|
|
info_labels = {
|
|
"title": track["name"],
|
|
"genre": track["genre"],
|
|
"year": track["year"],
|
|
"album": track["name"],
|
|
"artist": track["artist"],
|
|
"rating": track["rating"],
|
|
}
|
|
li.setInfo(type="Music", infoLabels=info_labels)
|
|
li.setArt({"thumb": track["thumb"]})
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
li.addContextMenuItems(track["contextitems"], True)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=track["url"], listitem=li, isFolder=True
|
|
)
|
|
|
|
def __prepare_artist_listitems(
|
|
self, artists: List[Dict[str, Any]], is_followed: bool = False
|
|
) -> List[Dict[str, Any]]:
|
|
followed_artists = []
|
|
if not is_followed:
|
|
for artist in self.__get_followed_artists():
|
|
followed_artists.append(artist["id"])
|
|
|
|
for item in artists:
|
|
if not item:
|
|
return []
|
|
if item.get("artist"):
|
|
item = item["artist"]
|
|
if item.get("images"):
|
|
item["thumb"] = item["images"][0]["url"]
|
|
else:
|
|
item["thumb"] = "DefaultMusicArtists.png"
|
|
|
|
item["url"] = self.__build_url(
|
|
{"action": self.browse_artist_albums.__name__, "artistid": item["id"]}
|
|
)
|
|
|
|
item["genre"] = " / ".join(item["genres"])
|
|
item["rating"] = str(self.__get_track_rating(item["popularity"]))
|
|
item["followerslabel"] = f"{item['followers']['total']} followers"
|
|
|
|
contextitems = [
|
|
(xbmc.getLocalizedString(KODI_ALBUMS_STR_ID), f"Container.Update({item['url']})"),
|
|
(
|
|
self.__addon.getLocalizedString(ARTIST_TOP_TRACKS_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.artist_top_tracks.__name__}&artistid={item['id']})",
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(RELATED_ARTISTS_STR_ID),
|
|
f"Container.Update(plugin://{ADDON_ID}/"
|
|
f"?action={self.related_artists.__name__}&artistid={item['id']})",
|
|
),
|
|
]
|
|
|
|
if is_followed or item["id"] in followed_artists:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(UNFOLLOW_ARTIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.unfollow_artist.__name__}&artistid={item['id']})",
|
|
)
|
|
)
|
|
else:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(FOLLOW_ARTIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.follow_artist.__name__}&artistid={item['id']})",
|
|
)
|
|
)
|
|
|
|
item["contextitems"] = contextitems
|
|
|
|
return artists
|
|
|
|
def __add_artist_listitems(self, artists: List[Dict[str, Any]]) -> None:
|
|
for item in artists:
|
|
li = xbmcgui.ListItem(item["name"], path=item["url"], offscreen=True)
|
|
info_labels = {
|
|
"title": item["name"],
|
|
"genre": item["genre"],
|
|
"artist": item["name"],
|
|
"rating": item["rating"],
|
|
}
|
|
li.setInfo(type="Music", infoLabels=info_labels)
|
|
li.setArt({"thumb": item["thumb"]})
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
li.setLabel2(item["followerslabel"])
|
|
li.addContextMenuItems(item["contextitems"], True)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle,
|
|
url=item["url"],
|
|
listitem=li,
|
|
isFolder=True,
|
|
totalItems=len(artists),
|
|
)
|
|
|
|
def __prepare_playlist_listitems(self, playlists: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
playlists2 = []
|
|
followed_playlists = self.__get_curuser_playlistids()
|
|
|
|
for item in playlists:
|
|
if not item:
|
|
continue
|
|
|
|
if item.get("images"):
|
|
item["thumb"] = item["images"][0]["url"]
|
|
else:
|
|
item["thumb"] = "DefaultMusicAlbums.png"
|
|
|
|
item["url"] = self.__build_url(
|
|
{
|
|
"action": self.browse_playlist.__name__,
|
|
"playlistid": item["id"],
|
|
"ownerid": item["owner"]["id"],
|
|
}
|
|
)
|
|
|
|
contextitems = [
|
|
(
|
|
xbmc.getLocalizedString(KODI_PLAY_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.play_playlist.__name__}&playlistid={item['id']}"
|
|
f"&ownerid={item['owner']['id']})",
|
|
),
|
|
(
|
|
self.__addon.getLocalizedString(REFRESH_LISTING_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/" f"?action={self.refresh_listing.__name__})",
|
|
),
|
|
]
|
|
|
|
if item["owner"]["id"] != self.__userid and item["id"] in followed_playlists:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(UNFOLLOW_PLAYLIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.unfollow_playlist.__name__}&playlistid={item['id']}"
|
|
f"&ownerid={item['owner']['id']})",
|
|
)
|
|
)
|
|
elif item["owner"]["id"] != self.__userid:
|
|
contextitems.append(
|
|
(
|
|
self.__addon.getLocalizedString(FOLLOW_PLAYLIST_STR_ID),
|
|
f"RunPlugin(plugin://{ADDON_ID}/"
|
|
f"?action={self.follow_playlist.__name__}&playlistid={item['id']}"
|
|
f"&ownerid={item['owner']['id']})",
|
|
)
|
|
)
|
|
|
|
item["contextitems"] = contextitems
|
|
playlists2.append(item)
|
|
|
|
return playlists2
|
|
|
|
def __add_playlist_listitems(self, playlists: List[Dict[str, Any]]) -> None:
|
|
for item in playlists:
|
|
li = xbmcgui.ListItem(item["name"], path=item["url"], offscreen=True)
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
|
|
li.addContextMenuItems(item["contextitems"], True)
|
|
li.setArt(
|
|
{
|
|
"fanart": os.path.join(self.__addon_icon_path, "fanart.jpg"),
|
|
"thumb": item["thumb"],
|
|
}
|
|
)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=item["url"], listitem=li, isFolder=True
|
|
)
|
|
|
|
def browse_artist_albums(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "albums")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_ALBUMS_STR_ID)
|
|
)
|
|
artist_albums = self.__spotipy.artist_albums(
|
|
self.__artist_id,
|
|
album_type="album,single,compilation",
|
|
country=self.__user_country,
|
|
limit=50,
|
|
offset=0,
|
|
)
|
|
count = len(artist_albums["items"])
|
|
albumids = []
|
|
while artist_albums["total"] > count:
|
|
artist_albums["items"] += self.__spotipy.artist_albums(
|
|
self.__artist_id,
|
|
album_type="album,single,compilation",
|
|
country=self.__user_country,
|
|
limit=50,
|
|
offset=count,
|
|
)["items"]
|
|
count += 50
|
|
for album in artist_albums["items"]:
|
|
albumids.append(album["id"])
|
|
albums = self.__prepare_album_listitems(albumids)
|
|
self.__add_album_listitems(albums)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_ALBUM_IGNORE_THE)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_SONG_RATING)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_albums:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_albums})")
|
|
|
|
def __get_saved_album_ids(self) -> List[str]:
|
|
albums = self.__spotipy.current_user_saved_albums(limit=1, offset=0)
|
|
cache_str = f"spotify-savedalbumids.{self.__userid}"
|
|
checksum = albums["total"]
|
|
cache = self.cache.get(cache_str, checksum=checksum)
|
|
if cache:
|
|
return cache
|
|
|
|
album_ids = []
|
|
if albums and albums.get("items"):
|
|
count = len(albums["items"])
|
|
album_ids = []
|
|
while albums["total"] > count:
|
|
albums["items"] += self.__spotipy.current_user_saved_albums(limit=50, offset=count)[
|
|
"items"
|
|
]
|
|
count += 50
|
|
for album in albums["items"]:
|
|
album_ids.append(album["album"]["id"])
|
|
self.cache.set(cache_str, album_ids, checksum=checksum)
|
|
|
|
return album_ids
|
|
|
|
def __get_saved_albums(self) -> List[Dict[str, Any]]:
|
|
album_ids = self.__get_saved_album_ids()
|
|
cache_str = f"spotify.savedalbums.{self.__userid}"
|
|
checksum = self.__cache_checksum(len(album_ids))
|
|
albums = self.cache.get(cache_str, checksum=checksum)
|
|
if not albums:
|
|
albums = self.__prepare_album_listitems(album_ids)
|
|
self.cache.set(cache_str, albums, checksum=checksum)
|
|
return albums
|
|
|
|
def browse_saved_albums(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "albums")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_ALBUMS_STR_ID)
|
|
)
|
|
albums = self.__get_saved_albums()
|
|
self.__add_album_listitems(albums, True)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_ALBUM_IGNORE_THE)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_SONG_RATING)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
xbmcplugin.setContent(self.__addon_handle, "albums")
|
|
if self.default_view_albums:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_albums})")
|
|
|
|
def __get_saved_track_ids(self) -> List[str]:
|
|
saved_tracks = self.__spotipy.current_user_saved_tracks(
|
|
limit=1, offset=self.__offset, market=self.__user_country
|
|
)
|
|
total = saved_tracks["total"]
|
|
cache_str = f"spotify.savedtracksids.{self.__userid}"
|
|
cache = self.cache.get(cache_str, checksum=total)
|
|
if cache:
|
|
return cache
|
|
|
|
# Get from api.
|
|
track_ids = []
|
|
count = len(saved_tracks["items"])
|
|
while total > count:
|
|
saved_tracks["items"] += self.__spotipy.current_user_saved_tracks(
|
|
limit=50, offset=count, market=self.__user_country
|
|
)["items"]
|
|
count += 50
|
|
for track in saved_tracks["items"]:
|
|
track_ids.append(track["track"]["id"])
|
|
self.cache.set(cache_str, track_ids, checksum=total)
|
|
|
|
return track_ids
|
|
|
|
def __get_saved_tracks(self):
|
|
# Get from cache first.
|
|
track_ids = self.__get_saved_track_ids()
|
|
cache_str = f"spotify.savedtracks.{self.__userid}"
|
|
|
|
tracks = self.cache.get(cache_str, checksum=len(track_ids))
|
|
if not tracks:
|
|
# Get from api.
|
|
tracks = self.__prepare_track_listitems(track_ids)
|
|
self.cache.set(cache_str, tracks, checksum=len(track_ids))
|
|
|
|
return tracks
|
|
|
|
def browse_saved_tracks(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "songs")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_SONGS_STR_ID)
|
|
)
|
|
tracks = self.__get_saved_tracks()
|
|
self.__add_track_listitems(tracks, True)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_songs:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_songs})")
|
|
|
|
def __get_saved_artists(self) -> List[Dict[str, Any]]:
|
|
saved_albums = self.__get_saved_albums()
|
|
followed_artists = self.__get_followed_artists()
|
|
cache_str = f"spotify.savedartists.{self.__userid}"
|
|
checksum = len(saved_albums) + len(followed_artists)
|
|
artists = self.cache.get(cache_str, checksum=checksum)
|
|
if not artists:
|
|
all_artist_ids = []
|
|
artists = []
|
|
# extract the artists from all saved albums
|
|
for item in saved_albums:
|
|
for artist in item["artists"]:
|
|
if artist["id"] not in all_artist_ids:
|
|
all_artist_ids.append(artist["id"])
|
|
for chunk in get_chunks(all_artist_ids, 50):
|
|
artists += self.__prepare_artist_listitems(self.__spotipy.artists(chunk)["artists"])
|
|
# append artists that are followed
|
|
for artist in followed_artists:
|
|
if not artist["id"] in all_artist_ids:
|
|
artists.append(artist)
|
|
self.cache.set(cache_str, artists, checksum=checksum)
|
|
|
|
return artists
|
|
|
|
def browse_saved_artists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "artists")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_ARTISTS_STR_ID)
|
|
)
|
|
artists = self.__get_saved_artists()
|
|
self.__add_artist_listitems(artists)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_TITLE)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_artists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_artists})")
|
|
|
|
def __get_followed_artists(self) -> List[Dict[str, Any]]:
|
|
artists = self.__spotipy.current_user_followed_artists(limit=50)
|
|
cache_str = f"spotify.followedartists.{self.__userid}"
|
|
checksum = artists["artists"]["total"]
|
|
|
|
cache = self.cache.get(cache_str, checksum=checksum)
|
|
if cache:
|
|
artists = cache
|
|
else:
|
|
count = len(artists["artists"]["items"])
|
|
after = artists["artists"]["cursors"]["after"]
|
|
while artists["artists"]["total"] > count:
|
|
result = self.__spotipy.current_user_followed_artists(limit=50, after=after)
|
|
artists["artists"]["items"] += result["artists"]["items"]
|
|
after = result["artists"]["cursors"]["after"]
|
|
count += 50
|
|
artists = self.__prepare_artist_listitems(artists["artists"]["items"], is_followed=True)
|
|
self.cache.set(cache_str, artists, checksum=checksum)
|
|
|
|
return artists
|
|
|
|
def browse_followed_artists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "artists")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_ARTISTS_STR_ID)
|
|
)
|
|
artists = self.__get_followed_artists()
|
|
self.__add_artist_listitems(artists)
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_TITLE)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
if self.default_view_artists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_artists})")
|
|
|
|
def search_artists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "artists")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_ARTISTS_STR_ID)
|
|
)
|
|
|
|
result = self.__spotipy.search(
|
|
q=f"artist:{self.__artist_id}",
|
|
type="artist",
|
|
limit=self.__limit,
|
|
offset=self.__offset,
|
|
market=self.__user_country,
|
|
)
|
|
|
|
artists = self.__prepare_artist_listitems(result["artists"]["items"])
|
|
self.__add_artist_listitems(artists)
|
|
self.__add_next_button(result["artists"]["total"])
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
if self.default_view_artists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_artists})")
|
|
|
|
def search_tracks(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "songs")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_SONGS_STR_ID)
|
|
)
|
|
|
|
result = self.__spotipy.search(
|
|
q=f"track:{self.__track_id}",
|
|
type="track",
|
|
limit=self.__limit,
|
|
offset=self.__offset,
|
|
market=self.__user_country,
|
|
)
|
|
|
|
tracks = self.__prepare_track_listitems(tracks=result["tracks"]["items"])
|
|
self.__add_track_listitems(tracks, True)
|
|
self.__add_next_button(result["tracks"]["total"])
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
if self.default_view_songs:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_songs})")
|
|
|
|
def search_albums(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "albums")
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_ALBUMS_STR_ID)
|
|
)
|
|
|
|
result = self.__spotipy.search(
|
|
q=f"album:{self.__album_id}",
|
|
type="album",
|
|
limit=self.__limit,
|
|
offset=self.__offset,
|
|
market=self.__user_country,
|
|
)
|
|
|
|
album_ids = []
|
|
for album in result["albums"]["items"]:
|
|
album_ids.append(album["id"])
|
|
albums = self.__prepare_album_listitems(album_ids)
|
|
self.__add_album_listitems(albums, True)
|
|
self.__add_next_button(result["albums"]["total"])
|
|
|
|
xbmcplugin.addSortMethod(self.__addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
if self.default_view_albums:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_albums})")
|
|
|
|
def search_playlists(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
|
|
result = self.__spotipy.search(
|
|
q=self.__playlist_id,
|
|
type="playlist",
|
|
limit=self.__limit,
|
|
offset=self.__offset,
|
|
market=self.__user_country,
|
|
)
|
|
|
|
log_msg(result)
|
|
xbmcplugin.setProperty(
|
|
self.__addon_handle, "FolderName", xbmc.getLocalizedString(KODI_PLAYLISTS_STR_ID)
|
|
)
|
|
playlists = self.__prepare_playlist_listitems(result["playlists"]["items"])
|
|
self.__add_playlist_listitems(playlists)
|
|
self.__add_next_button(result["playlists"]["total"])
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
if self.default_view_playlists:
|
|
xbmc.executebuiltin(f"Container.SetViewMode({self.default_view_playlists})")
|
|
|
|
def search(self) -> None:
|
|
xbmcplugin.setContent(self.__addon_handle, "files")
|
|
xbmcplugin.setPluginCategory(
|
|
self.__addon_handle, xbmc.getLocalizedString(KODI_SEARCH_RESULTS_STR_ID)
|
|
)
|
|
|
|
kb = xbmc.Keyboard("", xbmc.getLocalizedString(KODI_ENTER_SEARCH_STRING_STR_ID))
|
|
kb.doModal()
|
|
if kb.isConfirmed():
|
|
value = kb.getText()
|
|
items = []
|
|
result = self.__spotipy.search(
|
|
q=f"{value}",
|
|
type="artist,album,track,playlist",
|
|
limit=1,
|
|
market=self.__user_country,
|
|
)
|
|
items.append(
|
|
(
|
|
f"{xbmc.getLocalizedString(KODI_ARTISTS_STR_ID)}"
|
|
f" ({result['artists']['total']})",
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.search_artists.__name__}&artistid={value}",
|
|
)
|
|
)
|
|
items.append(
|
|
(
|
|
f"{xbmc.getLocalizedString(KODI_PLAYLISTS_STR_ID)}"
|
|
f" ({result['playlists']['total']})",
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.search_playlists.__name__}&playlistid={value}",
|
|
)
|
|
)
|
|
items.append(
|
|
(
|
|
f"{xbmc.getLocalizedString(KODI_ALBUMS_STR_ID)} ({result['albums']['total']})",
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.search_albums.__name__}&albumid={value}",
|
|
)
|
|
)
|
|
items.append(
|
|
(
|
|
f"{xbmc.getLocalizedString(KODI_SONGS_STR_ID)} ({result['tracks']['total']})",
|
|
f"plugin://{ADDON_ID}/"
|
|
f"?action={self.search_tracks.__name__}&trackid={value}",
|
|
)
|
|
)
|
|
for item in items:
|
|
li = xbmcgui.ListItem(item[0], path=item[1])
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
li.addContextMenuItems([], True)
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=item[1], listitem=li, isFolder=True
|
|
)
|
|
|
|
xbmcplugin.endOfDirectory(handle=self.__addon_handle)
|
|
|
|
def __add_next_button(self, list_total: int) -> None:
|
|
# Adds a next button if needed.
|
|
params = self.__params
|
|
if list_total > self.__offset + self.__limit:
|
|
params["offset"] = [str(self.__offset + self.__limit)]
|
|
url = f"plugin://{ADDON_ID}/"
|
|
|
|
for key, value in list(params.items()):
|
|
if key == "action":
|
|
url += f"?{key}={value[0]}"
|
|
elif key == "offset":
|
|
url += f"&{key}={value}"
|
|
else:
|
|
url += f"&{key}={value[0]}"
|
|
|
|
li = xbmcgui.ListItem(xbmc.getLocalizedString(KODI_NEXT_PAGE_STR_ID), path=url)
|
|
li.setProperty("do_not_analyze", "true")
|
|
li.setProperty("IsPlayable", "false")
|
|
|
|
xbmcplugin.addDirectoryItem(
|
|
handle=self.__addon_handle, url=url, listitem=li, isFolder=True
|
|
)
|
|
|
|
def __precache_library(self) -> None:
|
|
if not self.__win.getProperty("Spotify.PreCachedItems"):
|
|
monitor = xbmc.Monitor()
|
|
self.__win.setProperty("Spotify.PreCachedItems", "busy")
|
|
user_playlists = self.__get_user_playlists(self.__userid)
|
|
for playlist in user_playlists:
|
|
self.__get_playlist_details(playlist["id"])
|
|
if monitor.abortRequested():
|
|
return
|
|
self.__get_saved_albums()
|
|
if monitor.abortRequested():
|
|
return
|
|
self.__get_saved_artists()
|
|
if monitor.abortRequested():
|
|
return
|
|
self.__get_saved_tracks()
|
|
del monitor
|
|
self.__win.setProperty("Spotify.PreCachedItems", "done")
|