Skip to content
Snippets Groups Projects
gui.py 12.72 KiB
import requests
import random
import xbmc
import xbmcaddon
import xbmcgui
import json

# Plugin data and configuration
ADDON_ID = 'script.spotify.screensaver'
SETTINGS = xbmcaddon.Addon(id=ADDON_ID)
ADDON_NAME = SETTINGS.getAddonInfo('name')
ADDON_VERSION = SETTINGS.getAddonInfo('version')
KODI_MONITOR = xbmc.Monitor()

# Spotify setting
SPOTIFY_API_BASE_URL = 'https://api.spotify.com/v1/'
SPOTIFY_AUTH_URL = 'https://accounts.spotify.com/api/token'
LIBRESPOT_EVENT_FILE = '/tmp/spotify'


class GUI(xbmcgui.WindowXMLDialog):
    def __init__(self, *args, **kwargs):
        self.isExiting = False

    def log(self, msg, level=xbmc.LOGDEBUG):
        if self.force_debug:
            level = xbmc.LOGERROR

        xbmc.log(ADDON_ID + ' - ' + ADDON_VERSION + ' - ' + msg, level)

    def onInit(self):
        # load vars
        self._get_vars()
        # get addon settings
        self._get_settings()
        # set animation time on skin
        xbmcgui.Window(10000).setProperty('picture_animation', self.animation)
        # get spotify access token
        self._init_spotify_access_token()
        # get the images
        self._init_images()
        # start slideshow
        self._start_show()

    def _get_vars(self):
        # get the screensaver window id
        self.winid = xbmcgui.Window(xbmcgui.getCurrentWindowDialogId())
        # init the monitor class to catch onscreensaverdeactivated calls
        self.Monitor = MyMonitor(action=self._exit)
        self.stop = False
        self.last_spotify_event = ''
        self.current_track_id = ''

    def _get_settings(self):
        # read addon settings
        SETTINGS = xbmcaddon.Addon(id=ADDON_ID)

        self.addon_path = (SETTINGS.getAddonInfo('path').decode('utf-8'))
        self.setting_location = SETTINGS.getAddonInfo(
            'profile').decode('utf-8')

        self.slideshow_time = [30, 60, 120, 240][int(
            SETTINGS.getSetting("RotateTime"))]
        self.random = SETTINGS.getSetting("Randomize") == 'true'
        self.animation = 'okay' if SETTINGS.getSetting(
            "Animate") == 'true' else 'nope'

        self.spotify_client_id = SETTINGS.getSetting(
            'SpotifyClientId').decode('utf-8')
        self.spotify_client_secret = SETTINGS.getSetting(
            'SpotifyClientSecret').decode('utf-8')

        self.force_debug = SETTINGS.getSetting('ForceDebug') == 'true'

    def _init_spotify_access_token(self):
        auth_response = requests.post(SPOTIFY_AUTH_URL, {
            'grant_type': 'client_credentials',
            'client_id': self.spotify_client_id,
            'client_secret': self.spotify_client_secret,
        })
        # convert the response to JSON
        auth_response_data = auth_response.json()
        # save the access token
        self.spotify_access_token = auth_response_data['access_token']
        self.log('spotify access_token: ' + str(self.spotify_access_token))

    def _init_images(self):
        self.PanelItems = self.getControl(101)
        self.PanelItems.reset()
        self._parse_spotify_event(self._get_last_spotify_event())

    def _start_show(self):
        # loop until onScreensaverDeactivated is called
        while (not self.Monitor.abortRequested()) and (not self.stop):
            xbmc.executebuiltin('SetFocus(101)')
            self.next = False
            countdown = self.slideshow_time
            # display the image for the specified amount of time
            while (
                (not self.Monitor.abortRequested())
                and (not self.stop)
                and (not self.next)
                and countdown > 0
            ):
                countdown -= 1
                xbmc.sleep(1000)
                new_last_spotify_event = self._get_last_spotify_event()
                if (self.last_spotify_event != new_last_spotify_event):
                    self._parse_spotify_event(new_last_spotify_event)
                    self.last_spotify_event = new_last_spotify_event

            # break out of the for loop if onScreensaverDeactivated is called
            if self.stop or self.Monitor.abortRequested():
                break

            # next (random) image
            seek = str(random.randint(1, 7)) if self.random else '1'
            xbmc.executebuiltin("Control.Move(101,%s)" % seek)

    def _parse_spotify_event(self, spotify_event):
        self.log('spotify event: ' + spotify_event)
        event_split = spotify_event.split(':')

        event_type = event_split[1]
        event_value = event_split[2] if len(event_split) > 2 else ''

        if event_type == 'changed' or event_type == 'playing':
            if (self.current_track_id != event_value):
                self._reload_images(event_value)
                self.next = True
        elif event_type == 'preloading':
            self._preload_images(event_value)
        elif (
            event_type == 'paused'
            or event_type == 'stopped'
            or event_type == 'sink'
        ):
            self._remove_images()
            self.next = True
        elif event_type == 'volume_set':
            self._unimplemented_event_type(event_type, event_value)
        else:
            self.log('unknown event type: ' + event_type, xbmc.LOGERROR)

    def _unimplemented_event_type(self, event_type, event_value):
        self.log(
            'unimplemented event type: ' + str(event_type)
            + ' (value: ' + str(event_value) + ')'
        )

    def _preload_images(self, track_id):
        self.log('preload images for track: ' + track_id)

        track_data = self._get_track_data(track_id)

        if track_data is None:
            self.log('failed to get next track data', xbmc.LOGERROR)
        else:
            track_name = track_data.get('track_name')
            track_artists = track_data.get('track_artists')
            track_image_url = track_data.get('track_image_url')

            if ((track_name is None)
                or (track_artists is None)
                    or (track_image_url is None)):
                self.log('failed to parse next track data', xbmc.LOGERROR)
            else:
                artists_string = ', '.join(track_artists).replace('"', '\"')

                notification = (
                    'Notification("Next:", "'
                    + str(artists_string) + ' - ' + str(track_name)
                    + '", 29000, ' + str(track_image_url) + ')'
                )

                xbmc.executebuiltin(notification)

    def _reload_images(self, track_id):
        self.log('load images for track: ' + track_id)
        self.PanelItems = self.getControl(101)
        self.PanelItems.reset()
        self.PanelItems.addItems(self._build_items_list(track_id))
        self.current_track_id = track_id

    def _remove_images(self):
        self.log('no played track. remove images')
        self.PanelItems = self.getControl(101)
        self.PanelItems.reset()
        self.current_track_id = ''

    def _fix_encoding(self, string):
        return str(string.encode('utf-8'))

    def _get_track_data(self, track_id, try_count=1):
        self.log('track_id: [' + str(track_id) + ']')
        self.log('(try: ' + str(try_count) + ')')

        track_data = {}

        headers = {
            'Authorization': 'Bearer {token}'.format(
                token=self.spotify_access_token
            )
        }

        try:
            response = requests.get(
                SPOTIFY_API_BASE_URL + 'tracks/' + track_id,
                headers=headers
            )
            data = response.json()

            track_data['track_name'] = self._fix_encoding(data.get('name', ''))
            track_data['track_artists'] = []

            # album/track image
            album = data.get('album')
            if album is None:
                self.log(
                    'failed to get album from API: '
                    + json.dumps(data), xbmc.LOGERROR)

                error_string = ('{"error": {"status": 401, '
                                + '"message": "The access token expired"}}')
                if json.dumps(data) == error_string:
                    if try_count < 3:
                        self.log('trying to refresh access token')
                        self._init_spotify_access_token()
                        self.log('retry get image')
                        return self._get_track_data(track_id, try_count + 1)
                    else:
                        self.log('giving up', xbmc.LOGERROR)
                else:
                    self.log('unknown error getting image', xbmc.LOGERROR)
            else:
                self.log('ok got album from API: ' + json.dumps(album))
                # get artists
                artists = album.get('artists')
                if artists is None:
                    self.log(
                        'failed to get artists from API: '
                        + json.dumps(album), xbmc.LOGERROR)
                else:
                    self.log('ok got artists from API: ' + json.dumps(artists))
                    for artist in artists:
                        artist_name = artist.get('name')
                        if artist_name is None:
                            self.log(
                                'failed to get artist_name from API: '
                                + json.dumps(artist), xbmc.LOGERROR)
                        else:
                            self.log(
                                'ok got artist_name from API: '
                                + json.dumps(artist_name))
                            track_data['track_artists'].append(
                                self._fix_encoding(artist_name))

                # get images
                images = album.get('images')
                if images is None:
                    self.log(
                        'failed to get images from API: '
                        + json.dumps(album), xbmc.LOGERROR)
                else:
                    self.log('ok got images from API: ' + json.dumps(images))
                    image = images[0]
                    if image is None:
                        self.log(
                            'failed to get image from API: '
                            + json.dumps(images), xbmc.LOGERROR)
                    else:
                        self.log('ok got image from API: ' + json.dumps(image))
                        track_image_url = image.get('url')
                        if track_image_url is None:
                            self.log(
                                'failed to get track_image_url from API: '
                                + json.dumps(image), xbmc.LOGERROR)
                        else:
                            self.log(
                                'ok got track_image_url from API: '
                                + track_image_url)
                            track_data['track_image_url'] = track_image_url
        except Exception as e:
            self.log('failed to get track data: ' + str(e), xbmc.LOGERROR)

        self.log('track_name: ' + track_data.get('track_name', ''))
        self.log('track_artists: ' + str(track_data.get('track_artists', [])))
        self.log('track_image_url: ' + track_data.get('track_image_url', ''))

        return track_data

    def _build_items_list(self, track_id):
        imageLST = []

        track_data = self._get_track_data(track_id)
        if track_data is None:
            self.log('failed to get next track data', xbmc.LOGERROR)
        else:
            track_name = track_data.get('track_name')
            track_artists = track_data.get('track_artists')
            track_image_url = track_data.get('track_image_url')

            if ((track_name is None)
                or (track_artists is None)
                    or (track_image_url is None)):
                self.log('failed to parse next track data', xbmc.LOGERROR)
            else:
                artists_string = ', '.join(track_artists)
                imageLST.append(
                    xbmcgui.ListItem(
                        artists_string + ' - ' + track_name,
                        thumbnailImage=track_image_url
                    )
                )

        return imageLST

    def onFocus(self, controlId):
        pass

    def onClick(self, controlId):
        pass

    def onAction(self, action):
        self.isExiting = True
        self.close()

    def _get_last_spotify_event(self):
        last_event = ''
        try:
            f = open(LIBRESPOT_EVENT_FILE, 'r')
            last_event = f.readlines()[-1]
        finally:
            f.close()

        last_event = ''.join(last_event.splitlines())
        return last_event

    def _exit(self):
        self.stop = True
        self.close()


class MyMonitor(xbmc.Monitor):
    def __init__(self, *args, **kwargs):
        self.action = kwargs['action']

    def onScreensaverDeactivated(self):
        self.action()

    def onDPMSActivated(self):
        self.action()