import cgi, cgitb
from datetime import datetime
import hashlib
+import html
+import itertools
import numbers
import os
import random
+import re
import subprocess
-from xbmcjson import XBMC
import urllib
+import urllib.parse
+from kodijson import Kodi
from yattag import Doc
+import youtube_dl
-xbmc = None
+kodi = None
-def connect (_xbmc):
- global xbmc
- xbmc = _xbmc
- return xbmc
+def connect (_kodi):
+ global kodi
+ kodi = _kodi
+ return kodi
-SONG_PROPERTIES = ['album', 'artist', 'albumartist', 'dateadded', 'userrating', 'displayartist']
+SONG_PROPERTIES = ['album', 'artist', 'albumartist', 'title', 'dateadded', 'userrating', 'displayartist']
PAGE_SELF = os.environ['SCRIPT_NAME'] if 'SCRIPT_NAME' in os.environ else ''
class Song:
if 'id' in song:
# item from playlist
- self.key = hashlib.sha256(str(song['id'])).hexdigest()
+ self.key = hashlib.sha256(str(song['id']).encode('utf-8')).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 'userrating' in song:
- libsong = xbmc.AudioLibrary.GetSongDetails (songid = song['id'], properties = ['userrating'])
+ libsong = kodi.AudioLibrary.GetSongDetails (songid = song['id'], properties = ['userrating'])
#print (libsong)
if 'result' in libsong and 'songdetails' in libsong['result']:
song['userrating'] = libsong['result']['songdetails']['userrating']
elif 'songid' in song:
# search results
- self.key = hashlib.sha256(str(song['songid'])).hexdigest()
+ self.key = hashlib.sha256(str(song['songid']).encode('utf-8')).hexdigest()
self.kodi_id = song['songid']
else:
- self.key = hashlib.sha256(song['label'] + self.artist).hexdigest()
+ self.key = hashlib.sha256((song['label'] + self.artist).encode('utf-8')).hexdigest()
self.kodi_id = 0
# videos can still be labeled as songs, but the rating will be a
else:
self.rating = -1 # might be better to use None here
- self.label = song['label']
+ if 'title' in song and len(song['title']) > 0:
+ self.label = song['title']
+ else:
+ 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'])
+ return songs (kodi.Playlist.GetItems (playlistid=playlistid, properties=SONG_PROPERTIES)['result']['items'])
class SongControls:
aactions = {'songup': 'up', 'songdown': 'down', 'songtop': 'next!', 'songbottom': 'banish!',
return doc.getvalue ()
class Search:
+ ANY_SEARCH_PROPERTIES = [ 'title', 'album', 'artist' ]
+
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 = []
+ self.results = self.get_search_results ()
+
+ def get_search_results (self):
+ if (self.term == ''):
+ return {}
+
+ if (self.prop != 'any'):
+ res = kodi.AudioLibrary.GetSongs (filter={'operator': "contains", 'field': self.prop, 'value': self.term}, properties=SONG_PROPERTIES, sort={'order': 'ascending', 'method': 'artist'})['result']
+ if 'songs' in res:
+ return songs(res['songs'])
+ else:
+ return []
+ else:
+ all_songs = [kodi.AudioLibrary.GetSongs (filter={'operator': "contains", 'field': p, 'value': self.term}, properties=SONG_PROPERTIES, sort={'order': 'ascending', 'method': 'artist'})['result']
+ for p
+ in self.ANY_SEARCH_PROPERTIES]
+ # does not remove duplicates...
+ return list (itertools.chain.from_iterable ([res['songs'] for res in all_songs if 'songs' in res]))
def show_quick_search (self, thereal=False):
doc, tag, text = Doc(defaults = {'searchfield': self.prop}).tagtext()
else:
doc.stag ('input', type = 'text', name = 'searchterm', value = self.term)
with doc.select (name = 'searchfield'):
- for prop in ['title', 'artist', 'album']:
+ for prop in ['title', 'artist', 'album', 'any']:
with doc.option (value = prop):
text (prop)
with tag ('button', type = 'submit', name = 'searchgo', value = '1'):
text ('Search')
- print doc.getvalue ()
+ print (doc.getvalue ())
def show_search_results (self):
doc, tag, text = Doc().tagtext()
return doc.getvalue ()
def get_playlist (self, playlistid=0):
- return songs (xbmc.Playlist.GetItems (playlistid=playlistid, properties=SONG_PROPERTIES)['result']['items'])
+ return songs (kodi.Playlist.GetItems (playlistid=playlistid, properties=SONG_PROPERTIES)['result']['items'])
class Upload:
upload_dir = '/srv/archive/incoming/stolen-moosic'
# works instead of dealing with MIME. For now.
def attempt_rpgain (self):
subprocess.call (["/usr/bin/vorbisgain", "-q", self.filename])
- subprocess.call (["/usr/bin/mp3gain", "-q", "-s", "i", self.filename])
+ subprocess.call (["/usr/local/bin/mp3gain", "-q", "-s", "i", self.filename])
subprocess.call (["/usr/bin/aacgain", "-q", "-s", "i", self.filename])
subprocess.call (["/usr/bin/metaflac", "--add-replay-gain", self.filename])
def save (self):
- fout = file (os.path.join(self.upload_dir, self.fileitem.filename), 'wb')
+ fout = open (os.path.join(self.upload_dir, self.fileitem.filename), 'wb')
fout.write (self.fileitem.value)
fout.close()
self.attempt_rpgain ()
- return self.filename
+ return { 'file': self.filename }
+
+class Youtube:
+ upload_dir = '/srv/archive/incoming/youtube-moosic'
+ ydl_opts = {
+ 'format': 'bestaudio/best',
+ 'outtmpl': upload_dir + '/%(title)s-%(id)s.%(ext)s',
+ 'quiet': True,
+ 'postprocessors': [
+ {
+ 'key': 'FFmpegMetadata',
+ },
+ {
+ 'key': 'FFmpegExtractAudio',
+ 'preferredcodec': 'vorbis',
+ }],
+
+ }
+
+ def __init__ (self, form, field):
+ self.ydl = youtube_dl.YoutubeDL(self.ydl_opts)
+ self.url = form.getvalue (field)
+
+ def save (self):
+ info = self.ydl.extract_info (self.url, download=True)
+ filename = re.sub ('\..{3,4}$', '.ogg', self.ydl.prepare_filename (info))
+ subprocess.call (["/usr/bin/vorbisgain", "-q", filename])
+ return { 'file': filename }
def css ():
return doc.getvalue ()
def print_escaped (item):
- print (u"<p>{}</p>".format (cgi.escape (u"{}".format (item))))
+ print (u"<p>{}</p>".format (html.escape (u"{}".format (item))))
# This is awful
class PartyManager:
# divisor) songs
if divisor is None:
divisor = self.DEFAULT_QUEUE_DIVISOR
- totalitems = xbmc.Playlist.GetItems (playlistid=0)['result']['limits']['total']
- playpos = random.randint (1, totalitems / divisor + 1)
- print_escaped (xbmc.Playlist.Insert (playlistid=0, item=item, 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)
+ totalitems = kodi.Playlist.GetItems (playlistid=0)['result']['limits']['total']
+ playpos = random.randint (1, int(totalitems / divisor + 1))
+ print_escaped (kodi.Playlist.Insert (playlistid=0, item=item, 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))
return (playpos, totalitems+1)
def process (self):
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))
+ print (u'<p>Deleted {}</p>'.format(html.escape (song.label)))
+ print_escaped (kodi.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))
+ print (u"<p>Promoted {}</p>".format(html.escape(song.label)))
+ print_escaped (kodi.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))
+ print (u"<p>Demoted {}</p>".format(html.escape(song.label)))
+ print_escaped (kodi.Playlist.Swap (playlistid=0, position1=pos, position2=pos+1))
elif 'songtop' in form:
songid = form['songtop'].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>Bumped Up {}</p>".format(cgi.escape(song.label)))
+ print (u"<p>Bumped Up {}</p>".format(html.escape(song.label)))
for i in range (pos, 1, -1):
- print_escaped (xbmc.Playlist.Swap (playlistid=0, position1=i, position2=i-1))
+ print_escaped (kodi.Playlist.Swap (playlistid=0, position1=i, position2=i-1))
elif 'songbottom' in form:
songid = form['songbottom'].value
print (u"<p>{}</p>".format (songid))
playlist = get_playlist ()
(pos,song) = next ((i,s) for i,s in enumerate(playlist) if s.key == songid)
- print (u"<p>Banished {}</p>".format(cgi.escape(song.label)))
+ print (u"<p>Banished {}</p>".format(html.escape(song.label)))
for i in range (pos, len (playlist), 1):
- print_escaped (xbmc.Playlist.Swap (playlistid=0, position1=i, position2=i+1))
+ print_escaped (kodi.Playlist.Swap (playlistid=0, position1=i, position2=i+1))
elif 'volchange' in form:
- curvolume = xbmc.Application.GetProperties (properties=['volume'])['result']['volume']
+ curvolume = kodi.Application.GetProperties (properties=['volume'])['result']['volume']
newvolume = max (0, min (int (form['volchange'].value) + curvolume, 100))
- print_escaped (xbmc.Application.SetVolume (volume=newvolume))
+ print_escaped (kodi.Application.SetVolume (volume=newvolume))
elif 'volmute' in form:
- print_escaped (xbmc.Application.SetMute (mute="toggle"))
+ print_escaped (kodi.Application.SetMute (mute="toggle"))
elif 'navigate' in form:
action = form['navigate'].value
if action == 'prev':
- print_escaped (xbmc.Player.GoTo (to="previous", playerid=0))
+ print_escaped (kodi.Player.GoTo (to="previous", playerid=0))
elif action == 'next':
- print_escaped (xbmc.Player.GoTo (to="next", playerid=0))
+ print_escaped (kodi.Player.GoTo (to="next", playerid=0))
elif action == 'playpause':
- print_escaped (xbmc.Player.PlayPause (play="toggle", playerid=0))
+ print_escaped (kodi.Player.PlayPause (play="toggle", playerid=0))
elif 'searchgo' in form:
term = form['searchterm'].value
field = form['searchfield'].value
newrating = int(form['songrating'].value)
print (songid)
print (newrating)
- print_escaped (xbmc.AudioLibrary.SetSongDetails (songid = songid, userrating = newrating))
+ print_escaped (kodi.AudioLibrary.SetSongDetails (songid = songid, userrating = newrating))
print_escaped (u'Rating Changed')
elif 'browseartists' in form:
- artists = xbmc.AudioLibrary.GetArtists (sort={'order': 'ascending', 'method': 'artist'})['result']['artists']
+ artists = kodi.AudioLibrary.GetArtists (sort={'order': 'ascending', 'method': 'artist'})['result']['artists']
doc, tag, text = Doc().tagtext()
with tag ('ol', klass='flex_list'):
for artist in artists:
print (doc.getvalue ())
elif 'uploadgo' in form:
upload = Upload (form, 'song')
- filename = upload.save ()
- self.randomqueue ({"file": filename}, 1 if 'asap' not in form else 3)
+ item = upload.save ()
+ self.randomqueue (item, 1 if 'asap' not in form else 3)
+ elif 'youtubego' in form:
+ youtube = Youtube (form, 'youtubeurl')
+ item = youtube.save ()
+ self.randomqueue (item, 1 if 'asap' not in form else 3)
elif 'partyon' in form:
- if 'error' in xbmc.Player.SetPartymode (partymode=True, playerid=0):
- xbmc.Player.Open (item={"partymode": "music"})
+ if 'error' in kodi.Player.SetPartymode (partymode=True, playerid=0):
+ kodi.Player.Open (item={"partymode": "music"})
elif 'lockon' in form:
subprocess.call (['/usr/bin/xscreensaver-command', 'lock'])
elif 'lights' in form: