Begin unsucking kodi admin panel
[clinton/unknownlamer-kodi-addons.git] / party-upload / admin.cgi
CommitLineData
fffe2166
CE
1#!/usr/bin/python
2
3# ADD COMMAND TO RESTART PARTY MODE
4# (probably should require confirmation)
5# also add undelete link to post-del link just in case (reinsert at old pos)
6
7# Trivial xbmc admin script to view active playlist, control volume,
8# etc.
9
10# I would not recommend putting this online, no attempt is made at
11# being even trivially secure (e.g. form values are passed directly to
12# kodi with zero verification)
13
14# todo
15# any kind of error checking
16
17from __future__ import unicode_literals
18
19# Python is being obnoxious as hell and refusing to .format() utf-8
20# strings. I have no idea. Just hack around it and deal with the
21# actual problem later instead of scattering the code with .encode
22# calls.
23
24import sys
25reload(sys)
26sys.setdefaultencoding ('utf-8')
27
28import cgi, cgitb
a68240a2
CE
29import hashlib
30import numbers
fffe2166 31import os
fffe2166 32import random
a68240a2 33import subprocess
fffe2166 34from xbmcjson import XBMC
a68240a2 35from yattag import Doc
fffe2166
CE
36
37cgitb.enable()
38
39print ("content-type: text/html; charset=utf-8\n\n")
a68240a2 40print ("<!DOCTYPE html>\n<html><head><title>partyparty beb</title></head><body>")
fffe2166 41
a68240a2 42SONG_PROPERTIES = ['album', 'artist', 'albumartist', 'dateadded', 'rating']
fffe2166 43
a68240a2
CE
44class Song:
45 def __init__ (self, song):
46 self._song = song
47 if len(song['artist']) > 0:
48 self.artist = song['artist'][0]
49 elif 'albumartist' in song and len(song['albumartist']) > 0:
50 self.artist = song['albumartist'][0]
51 else:
52 self.artist = 'who fucking knows'
fffe2166 53
a68240a2
CE
54 if len(song['album']) > 0:
55 self.album = song['album']
56 else:
57 self.album = 'album is for losers'
58
59 if 'id' in song:
60 # item from playlist
61 self.key = hashlib.sha256(str(song['id'])).hexdigest()
62 self.kodi_id = song['id']
63 # the playlist will not update things like ratings if we
64 # update via RPC. Just grab it from the library instead.
65 if 'rating' in song:
66 libsong = xbmc.AudioLibrary.GetSongDetails (songid = song['id'], properties = ['rating'])
67 #print (libsong)
68 if 'result' in libsong and 'songdetails' in libsong['result']:
69 song['rating'] = libsong['result']['songdetails']['rating']
70 elif 'songid' in song:
71 # search results
72 self.key = hashlib.sha256(str(song['songid'])).hexdigest()
73 self.kodi_id = song['songid']
74 else:
75 self.key = hashlib.sha256(song['label'] + self.artist).hexdigest()
76 self.kodi_id = 0
77
78 # videos can still be labeled as songs, but the rating will be a
79 # float...
80 if 'rating' in song and isinstance (song['rating'], numbers.Integral):
81 self.rating = song['rating']
82 else:
83 self.rating = -1 # might be better to use None here
84
85 self.label = song['label']
86
87def songs(items):
88 '''Convert list of Kodi Items into Song instances'''
89 return [Song(item) for item in items]
fffe2166 90
a68240a2
CE
91def get_playlist (playlistid=0):
92 return songs (xbmc.Playlist.GetItems (playlistid=playlistid, properties=SONG_PROPERTIES)['result']['items'])
93
94class SongControls:
95 aactions = {'songup': 'up', 'songdown': 'down', 'songdel': 'del', 'randomqueue': 'yeh', 'songrate': 'rate'}
96
97 def __init__ (self, song, actions = ['songup', 'songdown', 'songdel']):
98 self.song = song
99 self.actions = actions
100
101 def controls (self):
102 doc, tag, text = Doc().tagtext()
103 with tag ('form', method = 'post', action = 'admin.cgi', klass = 'song_controls'): #, style = 'display: inline-block'):
104 for action in self.actions:
105 with tag ('button', name = action, value = self.song.key):
106 text (self.aactions[action])
107 doc.asis (self.extra_elements (action))
108 return doc.getvalue()
109
110 def extra_elements (self, action):
111 doc, tag, text = Doc().tagtext()
112 if action == 'randomqueue':
113 doc.stag ('input', type = 'hidden', name = 'songkodiid', value = self.song.kodi_id)
114 elif action == 'songrate':
115 if self.song.rating > -1:
116 doc.defaults = {'songrating': self.song.rating}
117 with doc.select (name = 'songrating'):
118 with doc.option (value = 0):
119 text ('na')
120 for i in range (1,6):
121 with doc.option (value = i):
122 text (str (i))
123 doc.stag ('input', type = 'hidden', name = 'songkodiid', value = self.song.kodi_id)
124
125 return doc.getvalue ()
126
127
128class Search:
129 def __init__ (self, term = '', prop = 'title'):
130 self.term = term
131 self.prop = prop
132 if (term != ''):
133 res = xbmc.AudioLibrary.GetSongs (filter={'operator': "contains", 'field': self.prop, 'value': self.term}, properties=SONG_PROPERTIES, sort={'order': 'ascending', 'method': 'artist'})['result']
134 if 'songs' in res:
135 self.results = songs(res['songs'])
136 else:
137 self.results = []
138
139 def show_quick_search (self, thereal=False):
140 print (u'''
fffe2166
CE
141<form method="get" action="admin.cgi" style="display: inline-block">
142<input type="text" name="searchterm" value="{}" {} />
143<select name="searchfield">
144<option value="title">name</option>
a68240a2 145<option value="artist">artist</option>
fffe2166
CE
146<option value="album">album</option>
147</select>
148<button type="submit" name="searchgo" value="1">Search</button>
149</form>
a68240a2
CE
150 ''').format(self.term, 'id="quicksearch"' if thereal else '')
151
152 def show_search_results (self):
153 doc, tag, text = Doc().tagtext()
154 with tag ('h1'):
155 text ('Results')
156 if len (self.results) > 0:
157 with tag ('ol'):
158 for song in self.results:
159 with tag ('li'):
160 text (u'{} ({}) {}'.format (song.artist, song.album, song.label))
161 doc.asis (SongControls (song, actions = ['randomqueue']).controls ())
162 else:
163 with tag ('p'):
164 text ('You are unworthy. No results.')
165
166 print (doc.getvalue ())
167
168
169def show_menu ():
170 doc, tag, text = Doc().tagtext()
171 with tag ('style'):
172 text ('''
173input, select, button { font-size: 200%; margin: 0.1em; }
174.horiz-menu li { display: inline; padding-right: 0.5em; }
175body { /* background-image: url("fire-under-construction-animation.gif"); */
176 color: white;
177 background-color: black;
178}
179a { color: #5dfc0a}
180button[name=songdel] { margin-left: 1em; margin-right: 1em; }
181div.flex_row {
182 display: flex;
183 flex-flow: row nowrap;
184 justify-content: space-between;
185}
186
187ol li:nth-child(even) { background-color: #202020 }
188''')
189 with tag ('ul', klass = 'horiz-menu'):
190 for target, description in [('admin.cgi', 'reload'), ('#playlist', 'playlist'),
191 ('#controls', 'controls'), ('javascript:document.getElementById("quicksearch").focus()', 'search')]:
192 with tag ('li'):
193 with tag ('a', href = target):
194 text (description)
195
196 print (doc.getvalue ())
197
198form = cgi.FieldStorage ()
199
200xbmc = XBMC ("http://localhost:8080/jsonrpc")
201
202def print_escaped (item):
203 print (u"<p>{}</p>".format (cgi.escape (u"{}".format (item))))
204
205show_menu ()
fffe2166
CE
206
207if 'songdel' in form:
a68240a2
CE
208 songid = form['songdel'].value
209 print (u"<p>{}</p>".format (songid))
210 (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid)
211 print (u'<p>Deleted {}</p>'.format(cgi.escape (song.label)))
212 print_escaped (xbmc.Playlist.Remove (playlistid=0, position=pos))
fffe2166 213elif 'songup' in form:
a68240a2
CE
214 songid = form['songup'].value
215 print (u"<p>{}</p>".format (songid))
216 (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid)
217 print (u"<p>Promoted {}</p>".format(cgi.escape(song.label)))
218 print_escaped (xbmc.Playlist.Swap (playlistid=0, position1=pos, position2=pos-1))
fffe2166 219elif 'songdown' in form:
a68240a2
CE
220 songid = form['songdown'].value
221 print (u"<p>{}</p>".format (songid))
222 (pos,song) = next ((i,s) for i,s in enumerate(get_playlist ()) if s.key == songid)
223 print (u"<p>Demoted {}</p>".format(cgi.escape(song.label)))
224 print_escaped (xbmc.Playlist.Swap (playlistid=0, position1=pos, position2=pos+1))
fffe2166
CE
225elif 'volchange' in form:
226 curvolume = xbmc.Application.GetProperties (properties=['volume'])['result']['volume']
227 newvolume = max (0, min (int (form['volchange'].value) + curvolume, 100))
a68240a2 228 print_escaped (xbmc.Application.SetVolume (volume=newvolume))
fffe2166 229elif 'volmute' in form:
a68240a2 230 print_escaped (xbmc.Application.SetMute (mute="toggle"))
fffe2166
CE
231elif 'navigate' in form:
232 action = form['navigate'].value
233 if action == 'prev':
a68240a2 234 print_escaped (xbmc.Player.GoTo (to="previous", playerid=0))
fffe2166 235 elif action == 'next':
a68240a2 236 print_escaped (xbmc.Player.GoTo (to="next", playerid=0))
fffe2166 237 elif action == 'playpause':
a68240a2 238 print_escaped (xbmc.Player.PlayPause (play="toggle", playerid=0))
fffe2166
CE
239elif 'searchgo' in form:
240 term = form['searchterm'].value
241 field = form['searchfield'].value
a68240a2
CE
242 search = Search (term, field)
243 search.show_quick_search ()
244 search.show_search_results ()
fffe2166 245elif 'randomqueue' in form:
a68240a2 246 songid = int(form['songkodiid'].value)
fffe2166
CE
247 totalitems = xbmc.Playlist.GetItems (playlistid=0)['result']['limits']['total']
248 playpos = random.randint (1, totalitems / 3 + 1)
a68240a2 249 print_escaped (xbmc.Playlist.Insert (playlistid=0, item={"songid": songid}, position=playpos))
fffe2166 250 print '<p style="font-size: x-large">Your song is number {0} in the queue ({1} songs in playlist).</p>'.format (playpos, totalitems+1)
a68240a2
CE
251elif 'songrate' in form:
252 songid = int(form['songkodiid'].value)
253 newrating = int(form['songrating'].value)
254 print (songid)
255 print (newrating)
256 print_escaped (xbmc.AudioLibrary.SetSongDetails (songid = songid, rating = newrating))
257 print_escaped (u'Rating Changed')
258elif 'partyon' in form:
259 if 'error' in xbmc.Player.SetPartymode (partymode=True, playerid=0):
260 xbmc.Player.Open (item={"partymode": "music"})
261elif 'lockon' in form:
262 subprocess.call (['/usr/bin/xscreensaver-command', 'lock'])
fffe2166
CE
263
264
265
266
267
a68240a2 268playlist = get_playlist ()
fffe2166
CE
269#playpos = random.randint (1, totalitems / (1 if 'asap' not in form else 3))
270
271print ('<a name="controls"></a>')
272print ('<p>Volume: {}%</p>'.format(xbmc.Application.GetProperties (properties=['volume'])['result']['volume']))
273print ('''
274<form method="post" action="admin.cgi" style="display: inline-block">
275<button name="volchange" value="5" type="submit">+5</button>
276<button name="volchange" value="-5" type="submit">-5</button>
277
278<button name="volchange" value="10" type="submit">+10</button>
279<button name="volchange" value="-10" type="submit">-10</button>
280
281<button name="volmute" value="1">Toggle Mute</button>
282
283</form>
284''')
285
286print ('''
287<form method="post" action="admin.cgi" style="display: inline-block">
288<button name="navigate" value="prev" type="submit">&#x23ee;</button>
289<button name="navigate" value="next" type="submit">&#x23ed;</button>
290<button name="navigate" value="playpause" type="submit">&#x23ef;</button>
291</form>
292''')
293
294
295print ('<a name="playlist"></a><h1>Playlist</h1>')
a68240a2 296print ('<ol class="flex_list">')
fffe2166 297for song in playlist:
a68240a2
CE
298# print (song._song)
299 print (u'<li><div class="flex_row"><p><a href="admin.cgi?searchgo=1&amp;searchterm={0};searchfield=artist">{0}</a> {1}</p>'.format(song.artist, song.label).encode('UTF-8'))
300 print (SongControls (song, ['songup', 'songdown', 'songdel', 'songrate']).controls ())
301 print ("</div></li>")
fffe2166
CE
302print ("</ol>")
303
304print ('<a name="search"></a>')
a68240a2
CE
305Search ().show_quick_search (thereal=True)
306show_menu ()
307
308print ('<form method="post" action="admin.cgi" style="display: inline-block">')
309print ('<button name="partyon" value="true">re-enable party</button>')
310print ('<button name="lockon" value="true">lock em out</button>')
311print ('</form>')
312print ('</body></html>')