party-upload: more python3 compat
[clinton/unknownlamer-kodi-addons.git] / party-upload / partyparty.py
index e3d222d..c8ce3f0 100644 (file)
 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:
@@ -55,21 +60,21 @@ 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
@@ -79,14 +84,17 @@ class Song:
       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!',
@@ -123,15 +131,30 @@ class SongControls:
       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()
@@ -141,12 +164,12 @@ class Search:
           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()
@@ -185,7 +208,7 @@ class Playlist:
       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'
@@ -198,16 +221,43 @@ class Upload:
     # 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 ():
@@ -237,7 +287,7 @@ ol li:nth-child(even) { background-color: #202020 }
     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:
@@ -251,10 +301,10 @@ 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):
@@ -263,49 +313,49 @@ class PartyManager:
             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
@@ -320,10 +370,10 @@ class PartyManager:
             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:
@@ -333,11 +383,15 @@ class PartyManager:
             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: