# Copyright (C) 2016 Lunatixz # # # This file is part of LastFM Tube. # # LastFM Tube 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. # # LastFM Tube 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 LastFM Tube. If not, see . # -*- coding: utf-8 -*- import os, sys, time, datetime, pylast, re import urllib, socket, json, collections, random import xbmc, xbmcgui, xbmcplugin, xbmcvfs, xbmcaddon from simplecache import use_cache, SimpleCache # Plugin Info ADDON_ID = 'plugin.audio.lastfmtube' REAL_SETTINGS = xbmcaddon.Addon(id=ADDON_ID) ADDON_NAME = REAL_SETTINGS.getAddonInfo('name') SETTINGS_LOC = REAL_SETTINGS.getAddonInfo('profile').decode('utf-8') ADDON_PATH = REAL_SETTINGS.getAddonInfo('path').decode('utf-8') ADDON_VERSION = REAL_SETTINGS.getAddonInfo('version') ICON = REAL_SETTINGS.getAddonInfo('icon') FANART = REAL_SETTINGS.getAddonInfo('fanart') ## GLOBALS ## TIMEOUT = 15 USER1 = REAL_SETTINGS.getSetting('USER1') PASS1 = REAL_SETTINGS.getSetting('PASS1') USER2 = REAL_SETTINGS.getSetting('USER2').replace('Enter Username','') PASS2 = REAL_SETTINGS.getSetting('PASS2').replace('Enter Password','') MEDIA_LIMIT= [25,50,100,250][int(REAL_SETTINGS.getSetting('MEDIA_LIMIT'))] RANDOM_PLAY= REAL_SETTINGS.getSetting('RANDOM_PLAY') == "true" YTURL = 'plugin://plugin.video.youtube/play/?video_id=' YSSEARCH = 'plugin://plugin.video.youtube/kodion/search/query/?q=%s' DEBUG = REAL_SETTINGS.getSetting('Enable_Debugging') == 'true' API_KEY = REAL_SETTINGS.getSetting('LASTFM_APIKEY') API_SECRET = REAL_SETTINGS.getSetting('LASTFM_APISECRET') USERLST = [] if len(USER1) > 0: USERLST.append((USER1, '', 0, USER1,pylast.md5(PASS1))) if len(USER2) > 0: USERLST.append((USER2, '', 0, USER2,pylast.md5(PASS2))) MENULST = (('Top Tracks' , '', 3), ('Loved Tracks' , '', 4), ('Recently Played', '', 2)) def log(msg, level = xbmc.LOGDEBUG): if DEBUG == True: xbmc.log(ADDON_ID + '-' + ADDON_VERSION + '-' + stringify(msg), level) def convertString(string): try: string = unicode(string, "ascii") except UnicodeError: string = unicode(string, "utf-8") else: string = stringify(string) return string def uni(string, encoding='utf-8'): if isinstance(string, basestring): if not isinstance(string, unicode): string = unicode(string, encoding) return string def ascii(string): if isinstance(string, basestring): if isinstance(string, unicode): string = string.encode('ascii', 'ignore') return string def utf(string): if isinstance(string, basestring): if not isinstance(string, unicode): string = string.encode('utf-8', 'ignore') return string def encodeString(string): return ''.join(i for i in string.encode('utf8') if ord(i)<128) def stringify(string): if isinstance(string, list): string = stringify(string[0]) elif isinstance(string, (int, float, long, complex, bool)): string = str(string) if isinstance(string, basestring): if not isinstance(string, unicode): string = unicode(string, 'utf-8') elif isinstance(string, unicode): string = string.encode('ascii', 'ignore') else: string = string.encode('utf-8', 'ignore') return string def getParams(): param=[] if len(sys.argv[2])>=2: params=sys.argv[2] cleanedparams=params.replace('?','') if (params[len(params)-1]=='/'): params=params[0:len(params)-2] pairsofparams=cleanedparams.split('&') param={} for i in range(len(pairsofparams)): splitparams={} splitparams=pairsofparams[i].split('=') if (len(splitparams))==2: param[splitparams[0]]=splitparams[1] return param socket.setdefaulttimeout(TIMEOUT) class LastFMTube(): def __init__(self): self.cache = SimpleCache() def mainMenu(self): for item in USERLST: self.addDir(*item) def browseMenu(self, user, pwd): for item in MENULST: print item self.addDir(*item,**{'user':user,'pwd':pwd}) def sendJSON(self, command): data = '' try: data = xbmc.executeJSONRPC(uni(command)) except UnicodeEncodeError: data = xbmc.executeJSONRPC(ascii(command)) return data def loadJson(self, string): if len(string) == 0: return {} try: return json.loads(uni(string)) except Exception,e: return {} def escapeDirJSON(self, dir_name): mydir = uni(dir_name) if (mydir.find(":")): mydir = mydir.replace("\\", "\\\\") return mydir @use_cache(31) def getDirectory(self, path, media='video', ignore='false', method='random', order='ascending', end=0, start=0, filter={}): json_query = ('{"jsonrpc":"2.0","method":"Files.GetDirectory","params":{"directory":"%s","properties":["thumbnail","fanart","plot","duration","playcount"],"media":"%s","sort":{"ignorearticle":%s,"method":"%s","order":"%s"},"limits":{"end":%s,"start":%s}},"id":1}' % (self.escapeDirJSON(path), media, ignore, method, order, end, start)) json_response = self.sendJSON(json_query) return self.loadJson(json_response) def buildMenu(self, url, auto=False): log('buildMenu, url = ' + url) json_response = self.getDirectory(url) if 'result' in json_response and(json_response['result'] != None) and 'files' in json_response['result']: response = json_response['result']['files'] response = [response[random.randint(0,len(response)-1)]] if auto == True else response for item in response: label = encodeString(item.get('label','')) if (item.get('filetype','') or '') == 'file': url = item.get('file','') infoLabels ={"label":label ,"title":label ,"plot":item.get('plot',''), "duration":(item.get('duration','') or 0), "playcount":(item.get('playcount','') or 0)} infoArt ={"thumb":(item.get('thumbnail','') or ICON),"poster":(item.get('thumbnail','') or ICON),"fanart":(item.get('fanart','') or FANART)} self.addLink(label, url, 9, infoList=infoLabels, infoArt=infoArt) def getRecentTracks(self, user, pwd, auto=False, rand=False, limit=250): """ Get list of recently played tracks """ playList = [] playCount = 0 log('getRecentTracks, user = ' + user) network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=user, password_hash=pwd) if auto == False: self.addDir('[B]Create Playlist (%d)[/B]'%MEDIA_LIMIT, '5', 5, user, pwd) user = network.get_user(network.username) for track in user.get_recent_tracks(limit=limit): artist = track.track.get_artist().name title = track.track.get_title() artist = encodeString(artist) title = encodeString(title) name = ('{0!s} - {1!s}'.format(artist, title)) url = YSSEARCH%(name.replace(' ','%20')) if auto == True: if rand == True and random.choice([True,False]) == True: continue self.buildMenu(url, True) playCount += 1 if playCount >= MEDIA_LIMIT: break else: self.addDir(name, url, 1) def getLovedTracks(self, user, pwd, auto=False, rand=False, limit=250): """Returns this user's loved track""" playList = [] playCount= 0 log('getPlaylists, user = ' + user) network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=user, password_hash=pwd) if auto == False: self.addDir('[B]Create Playlist (%d)[/B]'%MEDIA_LIMIT, '7', 7, user, pwd) user = network.get_user(network.username) for loved in user.get_loved_tracks(limit=limit): artist = loved.track.get_artist().name title = loved.track.get_title() artist = encodeString(artist) title = encodeString(title) name = ('{0!s} - {1!s}'.format(artist, title)) url = YSSEARCH%(name.replace(' ','%20')) if auto == True: #lazy method, won't result in MEDIA_LIMIT if rand == True and random.choice([True,False]) == True: continue self.buildMenu(url, True) playCount += 1 if playCount >= MEDIA_LIMIT: break else: self.addDir(name, url, 1) def getTopTracks(self, user, pwd, auto=False, rand=False): """Returns the most played tracks as a sequence of TopItem objects.""" playList = [] playCount= 0 log('getTopTracks, user = ' + user) network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=user, password_hash=pwd) if auto == False: self.addDir('[B]Create Playlist (%d)[/B]'%MEDIA_LIMIT, '6', 6, user, pwd) user = network.get_user(network.username) for top in user.get_top_tracks() : name = encodeString(str(top.item)) url = YSSEARCH%(name.replace(' ','%20')) if auto == True: #lazy method, won't result in MEDIA_LIMIT if rand == True and random.choice([True,False]) == True: continue self.buildMenu(url, True) playCount += 1 if playCount >= MEDIA_LIMIT: break else: self.addDir(name, url, 1) def resolveURL(self, url): log('resolveURL, url = ' + url) if len(re.findall('http[s]?://www.youtube.com/watch', url)) > 0: return YTURL + url.split('/watch?v=')[1] elif len(re.findall('http[s]?://youtu.be/', url)) > 0: return YTURL + url.split('/youtu.be/')[1] return url def playVideo(self, name, url): log('playVideo') liz=xbmcgui.ListItem(name, path=url) liz.setProperty("IsPlayable","true") xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, liz) def addLink(self, name, u, mode, user='', pwd='', infoList=False, infoArt=False, total=0): log('addLink, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'true') if infoList == False: liz.setInfo( type="Video", infoLabels={"label":name,"title":name} ) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name)+"&user="+urllib.quote_plus(user)+"&pwd="+str(pwd) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,totalItems=total) def addDir(self, name, u, mode, user='', pwd='', infoList=False, infoArt=False): log('addDir, name = ' + name) liz=xbmcgui.ListItem(name) liz.setProperty('IsPlayable', 'false') if infoList == False: liz.setInfo(type="Video", infoLabels={"label":name,"title":name}) else: liz.setInfo(type="Video", infoLabels=infoList) if infoArt == False: liz.setArt({'thumb':ICON,'fanart':FANART}) else: liz.setArt(infoArt) u=sys.argv[0]+"?url="+urllib.quote_plus(u)+"&mode="+str(mode)+"&name="+urllib.quote_plus(name)+"&user="+urllib.quote_plus(user)+"&pwd="+str(pwd) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True) params=getParams() try: url=urllib.unquote_plus(params["url"]) except: url=None try: name=urllib.unquote_plus(params["name"]) except: name=None try: user=urllib.unquote_plus(params["user"]) except: user=None try: pwd=urllib.unquote_plus(params["pwd"]) except: pwd=None try: mode=int(params["mode"]) except: mode=None log("Mode: "+str(mode)) log("URL : "+str(url)) log("Name: "+str(name)) log("User: "+str(user)) log("PWD : "+str(pwd)) if mode==None: LastFMTube().mainMenu() elif mode == 0: LastFMTube().browseMenu(user, pwd) elif mode == 1: LastFMTube().buildMenu(url) elif mode == 2: LastFMTube().getRecentTracks(user, pwd) elif mode == 3: LastFMTube().getTopTracks(user, pwd) elif mode == 4: LastFMTube().getLovedTracks(user, pwd) elif mode == 5: LastFMTube().getRecentTracks(user, pwd, True, RANDOM_PLAY) elif mode == 6: LastFMTube().getTopTracks(user, pwd, True, RANDOM_PLAY) elif mode == 7: LastFMTube().getLovedTracks(user, pwd, True, RANDOM_PLAY) elif mode == 9: LastFMTube().playVideo(name, url) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_NONE ) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_LABEL ) xbmcplugin.endOfDirectory(int(sys.argv[1]),cacheToDisc=True)