#!/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 ("\n
partyparty 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'{0} {1}
'.format(song.artist, song.label).encode('UTF-8'))
print (SongControls (song, ['songup', 'songdown', 'songdel', 'songrate']).controls ())
print ("
")
print ("
")
print ('')
Search ().show_quick_search (thereal=True)
show_menu ()
print ('')
print ('')