From: Clinton Ebadi Date: Sun, 29 Nov 2015 20:40:14 +0000 (-0500) Subject: Begin unsucking kodi admin panel X-Git-Url: https://git.hcoop.net/clinton/unknownlamer-kodi-addons.git/commitdiff_plain/a68240a2deb1c49293214168bfb39515bf9e7c66?hp=fffe2166e5b9d471823c3c8b96ece2175ba18062 Begin unsucking kodi admin panel After hitting 300 lines of drunken extensions to make up for defects discovered during parties, I decided to start cleaning up this code. This is the beginning -- enough changes to need to save my place before doing anything more radical. Goals (partially achieved, for some definition of achieve): - Use yattag for html formatting instead of gross blocks of `print' that don't correctly escape anything - Centralize hacks to work around Kodi JSON RPC deficiencies - Code split up so that it can be reused in normal party goer interface --- diff --git a/party-upload/README b/party-upload/README index 5ad758c..582662d 100644 --- a/party-upload/README +++ b/party-upload/README @@ -1,10 +1,14 @@ -Trivial Party Mode Upload Script +Trivial Party Mode Upload and Admin Scripts -Requires python-xbmc . +Requires python-xbmc and +yattag (http://yattag.org) Throw upload.cgi and upload.html into a directory together, generate a QR code URL for friends to scan, and now anyone can upload music from their phones to your party playlist. No configuration file, modify upload.cgi directly if your user/pass differ from the default. +admin.cgi allows for basic playlist management -- moving songs, +deleting, rating, search, and randomly queuing search results. + Patches to add a config file or anything really are welcome. \ No newline at end of file diff --git a/party-upload/admin.cgi b/party-upload/admin.cgi index 4c67bb9..88fea04 100755 --- a/party-upload/admin.cgi +++ b/party-upload/admin.cgi @@ -26,112 +26,246 @@ reload(sys) sys.setdefaultencoding ('utf-8') import cgi, cgitb +import hashlib +import numbers import os -import subprocess 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") -print (u''' - - - -''') +SONG_PROPERTIES = ['album', 'artist', 'albumartist', 'dateadded', 'rating'] -music_dir = "/srv/archive/incoming/stolen-moosic" -form = cgi.FieldStorage () +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' -xbmc = XBMC ("http://localhost:8080/jsonrpc") + 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 show_quick_search (term="",thereal=False): - print (u''' +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(term, 'id="quicksearch"' if thereal else '') - -def show_search_results (songs): - print (u"

Results

") - print (u"
    ") - for song in songs: - print (u'
  1. {} ({}) {}'.format (song['albumartist'][0], song['album'], song['label'])) - print ('
    ') - print (''.format(song['songid'])) - print ('
    ') - print ('
  2. ') - 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 = int(form['songdel'].value) - print (songid) - (pos,name) = next ((i,s) for i,s in enumerate(xbmc.Playlist.GetItems (playlistid=0)['result']['items']) if s['id'] == songid) - print (u"

Deleted {}

".format(name['label'])) - print (xbmc.Playlist.Remove (playlistid=0, position=pos)) + 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 = int(form['songup'].value) - print (songid) - (pos,name) = next ((i,s) for i,s in enumerate(xbmc.Playlist.GetItems (playlistid=0)['result']['items']) if s['id'] == songid) - print (u"

Promoted {}

".format(name['label'])) - print (xbmc.Playlist.Swap (playlistid=0, position1=pos, position2=pos-1)) + 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 = int(form['songdown'].value) - print (songid) - (pos,name) = next ((i,s) for i,s in enumerate(xbmc.Playlist.GetItems (playlistid=0)['result']['items']) if s['id'] == songid) - print (u"

Demoted {}

".format(name['label'])) - print (xbmc.Playlist.Swap (playlistid=0, position1=pos, position2=pos+1)) + 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 (xbmc.Application.SetVolume (volume=newvolume)) + print_escaped (xbmc.Application.SetVolume (volume=newvolume)) elif 'volmute' in form: - print (xbmc.Application.SetMute (mute="toggle")) + print_escaped (xbmc.Application.SetMute (mute="toggle")) elif 'navigate' in form: action = form['navigate'].value if action == 'prev': - print xbmc.Player.GoTo (to="previous", playerid=0) + print_escaped (xbmc.Player.GoTo (to="previous", playerid=0)) elif action == 'next': - print xbmc.Player.GoTo (to="next", playerid=0) + print_escaped (xbmc.Player.GoTo (to="next", playerid=0)) elif action == 'playpause': - print xbmc.Player.PlayPause (play="toggle", playerid=0) + print_escaped (xbmc.Player.PlayPause (play="toggle", playerid=0)) elif 'searchgo' in form: term = form['searchterm'].value field = form['searchfield'].value - results = xbmc.AudioLibrary.GetSongs (filter={'operator': "contains", 'field': field, 'value': term}, properties=['album', 'albumartist'], sort={'order': 'ascending', 'method': 'artist'})['result']['songs'] - show_quick_search (term) - show_search_results (results) + search = Search (term, field) + search.show_quick_search () + search.show_search_results () elif 'randomqueue' in form: - songid = int(form['randomqueue'].value) + songid = int(form['songkodiid'].value) totalitems = xbmc.Playlist.GetItems (playlistid=0)['result']['limits']['total'] playpos = random.randint (1, totalitems / 3 + 1) - print xbmc.Playlist.Insert (playlistid=0, item={"songid": songid}, position=playpos) + 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 = xbmc.Playlist.GetItems (playlistid=0, properties=['album', 'albumartist'])['result']['items'] +playlist = get_playlist () #playpos = random.randint (1, totalitems / (1 if 'asap' not in form else 3)) print ('') @@ -159,17 +293,20 @@ print (''' print ('

Playlist

') -print ("
    ") +print ('
      ') for song in playlist: - print (u'
    1. {} {}'.format(song['albumartist'][0], song['label']).encode('UTF-8')) - - print ('
      ') - print (''.format(song['id'])) - print (''.format(song['id'])) - print (''.format(song['id'])) - print ('
      ' ) - print ("
    2. ") +# print (song._song) + print (u'
    3. {0} {1}

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