#!/usr/bin/python # ADD COMMAND TO RESTART PARTY MODE # (probably should require confirmation) # also add undelete link to post-del link just in case (reinsert at old pos) # Trivial xbmc admin script to view active playlist, control volume, # etc. # I would not recommend putting this online, no attempt is made at # being even trivially secure (e.g. form values are passed directly to # kodi with zero verification) # todo # any kind of error checking from __future__ import unicode_literals # Python is being obnoxious as hell and refusing to .format() utf-8 # strings. I have no idea. Just hack around it and deal with the # actual problem later instead of scattering the code with .encode # calls. import sys reload(sys) sys.setdefaultencoding ('utf-8') import cgi, cgitb import hashlib import numbers import os import random import subprocess from xbmcjson import XBMC from yattag import Doc cgitb.enable() print ("content-type: text/html; charset=utf-8\n\n") print ("\npartyparty beb") SONG_PROPERTIES = ['album', 'artist', 'albumartist', 'dateadded', 'rating'] class Song: def __init__ (self, song): self._song = song if len(song['artist']) > 0: self.artist = song['artist'][0] elif 'albumartist' in song and len(song['albumartist']) > 0: self.artist = song['albumartist'][0] else: self.artist = 'who fucking knows' if len(song['album']) > 0: self.album = song['album'] else: self.album = 'album is for losers' if 'id' in song: # item from playlist self.key = hashlib.sha256(str(song['id'])).hexdigest() self.kodi_id = song['id'] # the playlist will not update things like ratings if we # update via RPC. Just grab it from the library instead. if 'rating' in song: libsong = xbmc.AudioLibrary.GetSongDetails (songid = song['id'], properties = ['rating']) #print (libsong) if 'result' in libsong and 'songdetails' in libsong['result']: song['rating'] = libsong['result']['songdetails']['rating'] elif 'songid' in song: # search results self.key = hashlib.sha256(str(song['songid'])).hexdigest() self.kodi_id = song['songid'] else: self.key = hashlib.sha256(song['label'] + self.artist).hexdigest() self.kodi_id = 0 # videos can still be labeled as songs, but the rating will be a # float... if 'rating' in song and isinstance (song['rating'], numbers.Integral): self.rating = song['rating'] else: self.rating = -1 # might be better to use None here self.label = song['label'] def songs(items): '''Convert list of Kodi Items into Song instances''' return [Song(item) for item in items] def get_playlist (playlistid=0): return songs (xbmc.Playlist.GetItems (playlistid=playlistid, properties=SONG_PROPERTIES)['result']['items']) class SongControls: aactions = {'songup': 'up', 'songdown': 'down', 'songdel': 'del', 'randomqueue': 'yeh', 'songrate': 'rate'} def __init__ (self, song, actions = ['songup', 'songdown', 'songdel']): self.song = song self.actions = actions def controls (self): doc, tag, text = Doc().tagtext() with tag ('form', method = 'post', action = 'admin.cgi', klass = 'song_controls'): #, style = 'display: inline-block'): for action in self.actions: with tag ('button', name = action, value = self.song.key): text (self.aactions[action]) doc.asis (self.extra_elements (action)) return doc.getvalue() def extra_elements (self, action): doc, tag, text = Doc().tagtext() if action == 'randomqueue': doc.stag ('input', type = 'hidden', name = 'songkodiid', value = self.song.kodi_id) elif action == 'songrate': if self.song.rating > -1: doc.defaults = {'songrating': self.song.rating} with doc.select (name = 'songrating'): with doc.option (value = 0): text ('na') for i in range (1,6): with doc.option (value = i): text (str (i)) doc.stag ('input', type = 'hidden', name = 'songkodiid', value = self.song.kodi_id) return doc.getvalue () class Search: def __init__ (self, term = '', prop = 'title'): self.term = term self.prop = prop if (term != ''): res = xbmc.AudioLibrary.GetSongs (filter={'operator': "contains", 'field': self.prop, 'value': self.term}, properties=SONG_PROPERTIES, sort={'order': 'ascending', 'method': 'artist'})['result'] if 'songs' in res: self.results = songs(res['songs']) else: self.results = [] def show_quick_search (self, thereal=False): print (u'''
''').format(self.term, 'id="quicksearch"' if thereal else '') def show_search_results (self): doc, tag, text = Doc().tagtext() with tag ('h1'): text ('Results') if len (self.results) > 0: with tag ('ol'): for song in self.results: with tag ('li'): text (u'{} ({}) {}'.format (song.artist, song.album, song.label)) doc.asis (SongControls (song, actions = ['randomqueue']).controls ()) else: with tag ('p'): text ('You are unworthy. No results.') print (doc.getvalue ()) def show_menu (): doc, tag, text = Doc().tagtext() with tag ('style'): text (''' input, select, button { font-size: 200%; margin: 0.1em; } .horiz-menu li { display: inline; padding-right: 0.5em; } body { /* background-image: url("fire-under-construction-animation.gif"); */ color: white; background-color: black; } a { color: #5dfc0a} button[name=songdel] { margin-left: 1em; margin-right: 1em; } div.flex_row { display: flex; flex-flow: row nowrap; justify-content: space-between; } ol li:nth-child(even) { background-color: #202020 } ''') with tag ('ul', klass = 'horiz-menu'): for target, description in [('admin.cgi', 'reload'), ('#playlist', 'playlist'), ('#controls', 'controls'), ('javascript:document.getElementById("quicksearch").focus()', 'search')]: with tag ('li'): with tag ('a', href = target): text (description) print (doc.getvalue ()) form = cgi.FieldStorage () xbmc = XBMC ("http://localhost:8080/jsonrpc") def print_escaped (item): print (u"

{}

".format (cgi.escape (u"{}".format (item)))) show_menu () if 'songdel' in form: songid = form['songdel'].value print (u"

{}

".format (songid)) (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid) print (u'

Deleted {}

'.format(cgi.escape (song.label))) print_escaped (xbmc.Playlist.Remove (playlistid=0, position=pos)) elif 'songup' in form: songid = form['songup'].value print (u"

{}

".format (songid)) (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid) print (u"

Promoted {}

".format(cgi.escape(song.label))) print_escaped (xbmc.Playlist.Swap (playlistid=0, position1=pos, position2=pos-1)) elif 'songdown' in form: songid = form['songdown'].value print (u"

{}

".format (songid)) (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid) print (u"

Demoted {}

".format(cgi.escape(song.label))) print_escaped (xbmc.Playlist.Swap (playlistid=0, position1=pos, position2=pos+1)) elif 'volchange' in form: curvolume = xbmc.Application.GetProperties (properties=['volume'])['result']['volume'] newvolume = max (0, min (int (form['volchange'].value) + curvolume, 100)) print_escaped (xbmc.Application.SetVolume (volume=newvolume)) elif 'volmute' in form: print_escaped (xbmc.Application.SetMute (mute="toggle")) elif 'navigate' in form: action = form['navigate'].value if action == 'prev': print_escaped (xbmc.Player.GoTo (to="previous", playerid=0)) elif action == 'next': print_escaped (xbmc.Player.GoTo (to="next", playerid=0)) elif action == 'playpause': print_escaped (xbmc.Player.PlayPause (play="toggle", playerid=0)) elif 'searchgo' in form: term = form['searchterm'].value field = form['searchfield'].value search = Search (term, field) search.show_quick_search () search.show_search_results () elif 'randomqueue' in form: songid = int(form['songkodiid'].value) totalitems = xbmc.Playlist.GetItems (playlistid=0)['result']['limits']['total'] playpos = random.randint (1, totalitems / 3 + 1) print_escaped (xbmc.Playlist.Insert (playlistid=0, item={"songid": songid}, position=playpos)) print '

Your song is number {0} in the queue ({1} songs in playlist).

'.format (playpos, totalitems+1) elif 'songrate' in form: songid = int(form['songkodiid'].value) newrating = int(form['songrating'].value) print (songid) print (newrating) print_escaped (xbmc.AudioLibrary.SetSongDetails (songid = songid, rating = newrating)) print_escaped (u'Rating Changed') elif 'partyon' in form: if 'error' in xbmc.Player.SetPartymode (partymode=True, playerid=0): xbmc.Player.Open (item={"partymode": "music"}) elif 'lockon' in form: subprocess.call (['/usr/bin/xscreensaver-command', 'lock']) playlist = get_playlist () #playpos = random.randint (1, totalitems / (1 if 'asap' not in form else 3)) print ('') print ('

Volume: {}%

'.format(xbmc.Application.GetProperties (properties=['volume'])['result']['volume'])) print ('''
''') print ('''
''') print ('

Playlist

') print ('
    ') for song in playlist: # print (song._song) print (u'
  1. {0} {1}

    '.format(song.artist, song.label).encode('UTF-8')) print (SongControls (song, ['songup', 'songdown', 'songdel', 'songrate']).controls ()) print ("
  2. ") print ("
") print ('') Search ().show_quick_search (thereal=True) show_menu () print ('
') print ('') print ('') print ('
') print ('')