-#!/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 ("<!DOCTYPE html>\n<html><head><title>partyparty beb</title></head><body>")
-
-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'''
-<form method="get" action="admin.cgi" style="display: inline-block">
-<input type="text" name="searchterm" value="{}" {} />
-<select name="searchfield">
-<option value="title">name</option>
-<option value="artist">artist</option>
-<option value="album">album</option>
-</select>
-<button type="submit" name="searchgo" value="1">Search</button>
-</form>
- ''').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"<p>{}</p>".format (cgi.escape (u"{}".format (item))))
-
-show_menu ()
-
-if 'songdel' in form:
- songid = form['songdel'].value
- print (u"<p>{}</p>".format (songid))
- (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid)
- print (u'<p>Deleted {}</p>'.format(cgi.escape (song.label)))
- print_escaped (xbmc.Playlist.Remove (playlistid=0, position=pos))
-elif 'songup' in form:
- songid = form['songup'].value
- print (u"<p>{}</p>".format (songid))
- (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid)
- print (u"<p>Promoted {}</p>".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"<p>{}</p>".format (songid))
- (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid)
- print (u"<p>Demoted {}</p>".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 '<p style="font-size: x-large">Your song is number {0} in the queue ({1} songs in playlist).</p>'.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 ('<a name="controls"></a>')
-print ('<p>Volume: {}%</p>'.format(xbmc.Application.GetProperties (properties=['volume'])['result']['volume']))
-print ('''
-<form method="post" action="admin.cgi" style="display: inline-block">
-<button name="volchange" value="5" type="submit">+5</button>
-<button name="volchange" value="-5" type="submit">-5</button>
-
-<button name="volchange" value="10" type="submit">+10</button>
-<button name="volchange" value="-10" type="submit">-10</button>
-
-<button name="volmute" value="1">Toggle Mute</button>
-
-</form>
-''')
-
-print ('''
-<form method="post" action="admin.cgi" style="display: inline-block">
-<button name="navigate" value="prev" type="submit">⏮</button>
-<button name="navigate" value="next" type="submit">⏭</button>
-<button name="navigate" value="playpause" type="submit">⏯</button>
-</form>
-''')
-
-
-print ('<a name="playlist"></a><h1>Playlist</h1>')
-print ('<ol class="flex_list">')
-for song in playlist:
-# print (song._song)
- print (u'<li><div class="flex_row"><p><a href="admin.cgi?searchgo=1&searchterm={0};searchfield=artist">{0}</a> {1}</p>'.format(song.artist, song.label).encode('UTF-8'))
- print (SongControls (song, ['songup', 'songdown', 'songdel', 'songrate']).controls ())
- print ("</div></li>")
-print ("</ol>")
-
-print ('<a name="search"></a>')
-Search ().show_quick_search (thereal=True)
-show_menu ()
-
-print ('<form method="post" action="admin.cgi" style="display: inline-block">')
-print ('<button name="partyon" value="true">re-enable party</button>')
-print ('<button name="lockon" value="true">lock em out</button>')
-print ('</form>')
-print ('</body></html>')
+#!/usr/bin/python
+# Simple PartyMode Web Console
+# Copyright (c) 2015,2016 Clinton Ebadi <clinton@unknownlamer.org>
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# 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)
+
+# 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)
+
+# 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
+from datetime import datetime
+import hashlib
+import numbers
+import os
+import random
+import subprocess
+from xbmcjson import XBMC
+from yattag import Doc
+
+import partyparty
+from partyparty import Song, SongControls, Search, Playlist, PartyManager
+
+cgitb.enable()
+PAGE_SELF = os.environ['SCRIPT_NAME'] if 'SCRIPT_NAME' in os.environ else ''
+
+print ("content-type: text/html; charset=utf-8\n\n")
+print ("<!DOCTYPE html>\n<html><head><title>partyparty beb</title></head><body>")
+print (partyparty.css ())
+
+#print (os.environ)
+#print (os.environ['SCRIPT_NAME'])
+
+def show_menu ():
+ doc, tag, text = Doc().tagtext()
+ with tag ('ul', klass = 'horiz-menu flex_row'):
+ for target, description in [('#playlist', 'playlist'),
+ ('#controls', 'controls'),
+ ('javascript:document.getElementById("quicksearch").focus()', 'search'),
+ (PAGE_SELF, 'reload')]:
+ with tag ('li'):
+ with tag ('a', href = target):
+ text (description)
+
+ print (doc.getvalue ())
+
+xbmc = partyparty.connect (XBMC ("http://localhost:8080/jsonrpc"))
+
+def print_escaped (item):
+ print (u"<p>{}</p>".format (cgi.escape (u"{}".format (item))))
+
+show_menu ()
+
+manager = PartyManager (cgi.FieldStorage ())
+manager.process ()
+
+playlist = Playlist()
+#playpos = random.randint (1, totalitems / (1 if 'asap' not in form else 3))
+
+class PlayerControls:
+ def __init__ (self, name='controls'):
+ self.name = name
+
+ def info (self):
+ doc, tag, text = Doc().tagtext()
+ _playtime = xbmc.Player.GetProperties (playerid=0, properties = ['position', 'percentage', 'time', 'totaltime'])
+ pt = _playtime['result'] if 'result' in _playtime else None
+ with tag ('ul', klass = 'horiz-menu'):
+ for infotext in ['Volume {}%'.format(xbmc.Application.GetProperties (properties=['volume'])['result']['volume']),
+ 'Time {:02d}:{:02d} / {:02d}:{:02d} ({:.2f}%) @ {:%H:%M:%S}'.format (pt['time']['hours'] * 60 + pt['time']['minutes'], pt['time']['seconds'], pt['totaltime']['hours'] * 60 + pt['totaltime']['minutes'], pt['totaltime']['seconds'], pt['percentage'], datetime.now())]:
+ with tag ('li'):
+ text (infotext)
+
+ return doc.getvalue ()
+
+
+
+controls = PlayerControls ()
+print (controls.info ())
+
+print ('<a name="controls"></a>')
+print ('''
+<form method="post" action="{}" style="display: inline-block">
+<button name="volchange" value="5" type="submit">+5</button>
+<button name="volchange" value="-5" type="submit">-5</button>
+
+<button name="volchange" value="10" type="submit">+10</button>
+<button name="volchange" value="-10" type="submit">-10</button>
+
+<button name="volmute" value="1">Toggle Mute</button>
+
+</form>
+'''.format (cgi.escape (PAGE_SELF)))
+
+print ('''
+<form method="post" action="{}" style="display: inline-block">
+<button name="navigate" value="prev" type="submit">⏮</button>
+<button name="navigate" value="next" type="submit">⏭</button>
+<button name="navigate" value="playpause" type="submit">⏯</button>
+</form>
+'''.format (cgi.escape (PAGE_SELF)))
+
+print ('<a name="playlist"></a><h1>Playlist</h1>')
+print (playlist.show ())
+
+
+
+
+print ('<a name="search"></a>')
+Search ().show_quick_search (thereal=True)
+show_menu ()
+
+print ('<form method="post" action="{}" style="display: inline-block">'.format (cgi.escape (PAGE_SELF)))
+print ('<button name="partyon" value="true">re-enable party</button>')
+#print ('<button name="lockon" value="true">lock em out</button>')
+print ('<button name="lights" value="on">lights on</button>')
+print ('<button name="lights" value="off">lights off</button>')
+print ('</form>')
+print ('</body></html>')