From: stephendenham Date: Sun, 21 Aug 2011 10:40:12 +0000 (+0000) Subject: Big update for v3 API. X-Git-Url: https://git.hcoop.net/clinton/xbmc-groove.git/commitdiff_plain/f95afae7836df7119990caf9bf2285a18eba8b18 Big update for v3 API. git-svn-id: svn://svn.code.sf.net/p/xbmc-groove/code@47 2dec19e3-eb1d-4749-8193-008c8bba0994 --- diff --git a/addon.xml b/addon.xml index d4ab73e..22f0fe0 100644 --- a/addon.xml +++ b/addon.xml @@ -1,6 +1,6 @@ + version="0.5.1" provider-name="Stephen Denham"> diff --git a/changelog.txt b/changelog.txt index 6d76714..b92bc62 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +0.5.1: + +Refactor getting song duration to improve performance. + +0.5.0: + +Use new version 3 API. + 0.4.0: Add optional 1.0 API for those with a session id. diff --git a/default.py b/default.py index d4c6dfa..ff6d2bd 100644 --- a/default.py +++ b/default.py @@ -1,4 +1,4 @@ -import urllib, sys, os, shutil, re, time, xbmcaddon, xbmcplugin, xbmcgui, xbmc, pickle +import urllib, sys, os, shutil, re, pickle, time, tempfile, xbmcaddon, xbmcplugin, xbmcgui, xbmc MODE_SEARCH_SONGS = 1 MODE_SEARCH_ALBUMS = 2 MODE_SEARCH_ARTISTS = 3 @@ -34,6 +34,13 @@ ACTION_PREVIOUS_MENU = 10 ARTIST_ALBUM_NAME_LABEL = 0 NAME_ALBUM_ARTIST_LABEL = 1 +# Stream marking time (seconds) +STREAM_MARKING_TIME = 30 + +songMarkTime = 0 +player = xbmc.Player() +playTimer = None + baseDir = os.getcwd() resDir = xbmc.translatePath(os.path.join(baseDir, 'resources')) libDir = xbmc.translatePath(os.path.join(resDir, 'lib')) @@ -54,7 +61,7 @@ listBackground = os.path.join(imgDir, 'listbackground.png') sys.path.append (libDir) from GroovesharkAPI import GrooveAPI -from GroovesharkAPI import GrooveAPIv1 +from threading import Event, Thread try: groovesharkApi = GrooveAPI() @@ -65,7 +72,24 @@ except: dialog.ok('Grooveshark XBMC', 'Unable to connect with Grooveshark.', 'Please try again later') sys.exit(-1) - +# Mark song as playing or played +def markSong(songid, duration): + global songMarkTime + global playTimer + global player + if player.isPlayingAudio(): + tNow = player.getTime() + if tNow >= STREAM_MARKING_TIME and songMarkTime == 0: + groovesharkApi.markStreamKeyOver30Secs() + songMarkTime = tNow + elif duration > tNow and duration - tNow < 2 and songMarkTime >= STREAM_MARKING_TIME: + playTimer.cancel() + songMarkTime = 0 + groovesharkApi.markSongComplete(songid) + else: + playTimer.cancel() + songMarkTime = 0 + class _Info: def __init__( self, *args, **kwargs ): self.__dict__.update( kwargs ) @@ -131,7 +155,44 @@ class GroovesharkPlaylistSelect(xbmcgui.WindowDialog): elif action == ACTION_MOVE_UP or action == ACTION_MOVE_DOWN or action == ACTION_PAGE_UP or action == ACTION_PAGE_DOWN == 6: self.setFocus(self.playlistControl) self.setHighlight() - + + +class PlayTimer(Thread): + # interval -- floating point number specifying the number of seconds to wait before executing function + # function -- the function (or callable object) to be executed + + # iterations -- integer specifying the number of iterations to perform + # args -- list of positional arguments passed to function + # kwargs -- dictionary of keyword arguments passed to function + + def __init__(self, interval, function, iterations=0, args=[], kwargs={}): + Thread.__init__(self) + self.interval = interval + self.function = function + self.iterations = iterations + self.args = args + self.kwargs = kwargs + self.finished = Event() + + def run(self): + count = 0 + while not self.finished.isSet() and (self.iterations <= 0 or count < self.iterations): + self.finished.wait(self.interval) + if not self.finished.isSet(): + self.function(*self.args, **self.kwargs) + count += 1 + + def cancel(self): + self.finished.set() + + def setIterations(self, iterations): + self.iterations = iterations + + + def getTime(self): + return self.iterations * self.interval + + class Groveshark: albumImg = xbmc.translatePath(os.path.join(imgDir, 'album.png')) @@ -153,7 +214,6 @@ class Groveshark: songspagelimit = int(settings.getSetting('songspagelimit')) username = settings.getSetting('username') password = settings.getSetting('password') - sessionidv1 = settings.getSetting('sessionidv1') userid = 0 def __init__( self ): @@ -166,8 +226,6 @@ class Groveshark: os.makedirs(artDir) xbmc.log("Made " + artDir) - self.groovesharkApiv1 = GrooveAPIv1(self.sessionidv1) - # Top-level menu def categories(self): @@ -180,16 +238,17 @@ class Groveshark: self._add_dir('Search for albums...', '', MODE_SEARCH_ALBUMS, self.albumImg, 0) self._add_dir('Search for artists...', '', MODE_SEARCH_ARTISTS, self.artistImg, 0) self._add_dir(searchArtistsAlbumsName, '', MODE_SEARCH_ARTISTS_ALBUMS, self.artistsAlbumsImg, 0) - self._add_dir("Search for user's playlists...", '', MODE_SEARCH_PLAYLISTS, self.usersplaylistsImg, 0) - self._add_dir('Popular songs for artist...', '', MODE_ARTIST_POPULAR, self.popularSongsArtistImg, 0) - self._add_dir('Popular songs', '', MODE_POPULAR_SONGS, self.popularSongsImg, 0) + # Not supported by key + #self._add_dir("Search for user's playlists...", '', MODE_SEARCH_PLAYLISTS, self.usersplaylistsImg, 0) + self._add_dir('Popular Grooveshark songs for artist...', '', MODE_ARTIST_POPULAR, self.popularSongsArtistImg, 0) + self._add_dir('Popular Grooveshark songs', '', MODE_POPULAR_SONGS, self.popularSongsImg, 0) if (self.userid != 0): - self._add_dir('My favorites', '', MODE_FAVORITES, self.favoritesImg, 0) - self._add_dir('My playlists', '', MODE_PLAYLISTS, self.playlistImg, 0) + self._add_dir('My Grooveshark favorites', '', MODE_FAVORITES, self.favoritesImg, 0) + self._add_dir('My Grooveshark playlists', '', MODE_PLAYLISTS, self.playlistImg, 0) # Search for songs def searchSongs(self): - query = self._get_keyboard(default="", heading="Search for songs") + query = self._get_keyboard(default="", heading='Search for songs powered by Grooveshark') if (query != ''): songs = groovesharkApi.getSongSearchResults(query, limit = self.songsearchlimit) if (len(songs) > 0): @@ -203,7 +262,7 @@ class Groveshark: # Search for albums def searchAlbums(self): - query = self._get_keyboard(default="", heading="Search for albums") + query = self._get_keyboard(default="", heading='Search for albums powered by Grooveshark') if (query != ''): albums = groovesharkApi.getAlbumSearchResults(query, limit = self.albumsearchlimit) if (len(albums) > 0): @@ -217,7 +276,7 @@ class Groveshark: # Search for artists def searchArtists(self): - query = self._get_keyboard(default="", heading="Search for artists") + query = self._get_keyboard(default="", heading='Search for artists powered by Grooveshark') if (query != ''): artists = groovesharkApi.getArtistSearchResults(query, limit = self.artistsearchlimit) if (len(artists) > 0): @@ -233,7 +292,7 @@ class Groveshark: def searchPlaylists(self): query = self._get_keyboard(default="", heading="Username") if (query != ''): - playlists = groovesharkApi.getUserPlaylistsEx(query) + playlists = groovesharkApi.getUserPlaylistsByUsername(query) if (len(playlists) > 0): self._add_playlists_directory(playlists) else: @@ -246,7 +305,7 @@ class Groveshark: # Search for artists albums def searchArtistsAlbums(self, artistName = None): if artistName == None or artistName == searchArtistsAlbumsName: - query = self._get_keyboard(default="", heading="Search for artist's albums") + query = self._get_keyboard(default="", heading="Search for artist's albums powered by Grooveshark") else: query = artistName if (query != ''): @@ -319,10 +378,10 @@ class Groveshark: # Remove song from favorites def unfavorite(self, songid, prevMode=0): - userid = self._get_login(version = 1) + userid = self._get_login() if (userid != 0): xbmc.log("Unfavorite song: " + str(songid) + ', previous mode was ' + str(prevMode)) - self.groovesharkApiv1.unfavorite(songID = songid) + groovesharkApi.removeUserFavoriteSongs(songIDs = songid) xbmc.executebuiltin('XBMC.Notification(Grooveshark XBMC, Removed from Grooveshark favorites, 1000, ' + thumbDef + ')') # Refresh to remove item from directory if (int(prevMode) == MODE_FAVORITES): @@ -377,15 +436,33 @@ class Groveshark: # Play a song def playSong(self, item): - songid = item.getProperty('songid') - song = groovesharkApi.getSongURLFromSongID(songid) - if song != '': - item.setPath(song) - xbmc.log("Playing: " + song) + global playTimer + global player + if item != None: + songid = item.getProperty('songid') + stream = groovesharkApi.getSubscriberStreamKey(songid) + url = stream['url'] + item.setPath(url) + xbmc.log("Grooveshark playing: " + url) xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=item) + # Wait for play then start time + seconds = 0 + while seconds < STREAM_MARKING_TIME: + try: + if player.isPlayingAudio() == True: + if playTimer != None: + playTimer.cancel() + songMarkTime = 0 + duration = int(item.getProperty('duration')) + playTimer = PlayTimer(1, markSong, duration, [songid, duration]) + playTimer.start() + break + except: pass + time.sleep(1) + seconds = seconds + 1 else: xbmc.executebuiltin('XBMC.Notification(Grooveshark XBMC, Unable to play song, 1000, ' + thumbDef + ')') - + # Make a song directory item def songItem(self, songid, name, album, artist, coverart, trackLabelFormat=ARTIST_ALBUM_NAME_LABEL): songImg = self._get_icon(coverart, 'song-' + str(songid) + "-image") @@ -393,8 +470,9 @@ class Groveshark: trackLabel = name + " - " + album + " - " + artist else: trackLabel = artist + " - " + album + " - " + name + duration = self._getSongDuration(songid) item = xbmcgui.ListItem(label = trackLabel, thumbnailImage=songImg, iconImage=songImg) - item.setInfo( type="music", infoLabels={ "title": name, "album": album, "artist": artist} ) + item.setInfo( type="music", infoLabels={ "title": name, "album": album, "artist": artist, "duration": duration} ) item.setProperty('mimetype', 'audio/mpeg') item.setProperty("IsPlayable", "true") item.setProperty('songid', str(songid)) @@ -402,6 +480,7 @@ class Groveshark: item.setProperty('title', name) item.setProperty('album', album) item.setProperty('artist', artist) + item.setProperty('duration', str(duration)) return item @@ -411,7 +490,7 @@ class Groveshark: # Make a playlist from an album def makePlaylist(self, albumid, name): - userid = self._get_login(version = 1) + userid = self._get_login() if (userid != 0): re.split(' - ',name,1) nameTokens = re.split(' - ',name,1) # suggested name @@ -421,7 +500,7 @@ class Groveshark: songids = [] for song in album: songids.append(song[1]) - if self.groovesharkApiv1.playlistCreateUnique(name, songids) == 0: + if groovesharkApi.createPlaylist(name, songids) == 0: dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Cannot create Grooveshark playlist ', name) else: @@ -432,12 +511,12 @@ class Groveshark: # Rename a playlist def renamePlaylist(self, playlistid, name): - userid = self._get_login(version = 1) + userid = self._get_login() if (userid != 0): newname = self._get_keyboard(default=name, heading="Grooveshark playlist name") if newname == '': return - elif self.groovesharkApiv1.playlistRename(playlistid, newname) == 0: + elif groovesharkApi.playlistRename(playlistid, newname) == 0: dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Cannot rename Grooveshark playlist ', name) else: @@ -451,9 +530,9 @@ class Groveshark: def removePlaylist(self, playlistid, name): dialog = xbmcgui.Dialog() if dialog.yesno('Grooveshark XBMC', name, 'Delete this Grooveshark playlist?') == True: - userid = self._get_login(version = 1) + userid = self._get_login() if (userid != 0): - if self.groovesharkApiv1.playlistDelete(playlistid) == 0: + if groovesharkApi.playlistDelete(playlistid) == 0: dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Cannot remove Grooveshark playlist ', name) else: @@ -465,7 +544,7 @@ class Groveshark: # Add song to playlist def addPlaylistSong(self, songid): - userid = self._get_login(version = 1) + userid = self._get_login() if (userid != 0): playlists = groovesharkApi.getUserPlaylists() if (len(playlists) > 0): @@ -483,7 +562,7 @@ class Groveshark: if name != '': songIds = [] songIds.append(songid) - if self.groovesharkApiv1.playlistCreateUnique(name, songIds) == 0: + if groovesharkApi.createPlaylist(name, songIds) == 0: dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Cannot create Grooveshark playlist ', name) else: @@ -493,8 +572,13 @@ class Groveshark: playlist = playlists[i] playlistid = playlist[1] xbmc.log("Add song " + str(songid) + " to playlist " + str(playlistid)) - ret = self.groovesharkApiv1.playlistAddSong(playlistid, songid, 0) - if ret == 0: + songIDs=[] + songs = groovesharkApi.getPlaylistSongs(playlistid) + for song in songs: + songIDs.append(song[1]) + songIDs.append(songid) + ret = groovesharkApi.setPlaylistSongs(playlistid, songIDs) + if ret == False: dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Cannot add to playlist ') else: @@ -508,12 +592,18 @@ class Groveshark: dialog.ok('Grooveshark XBMC', 'You must be logged in', 'to add a song to a Grooveshark playlist.') # Remove song from playlist - def removePlaylistSong(self, playlistid, playlistname, songpos): + def removePlaylistSong(self, playlistid, playlistname, songid): dialog = xbmcgui.Dialog() if dialog.yesno('Grooveshark XBMC', 'Delete this song from', 'the Grooveshark playlist?') == True: - userid = self._get_login(version = 1) + userid = self._get_login() if (userid != 0): - if self.groovesharkApiv1.playlistDeleteSong(playlistid, songpos) == 0: + songs = groovesharkApi.getPlaylistSongs(playlistID) + songIDs=[] + for song in songs: + if (song[1] != songid): + songIDs.append(song[1]) + ret = groovesharkApi.setPlaylistSongs(playlistID, songIDs) + if ret == False: dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Failed to remove', 'song from Grooveshark playlist.') else: @@ -526,7 +616,7 @@ class Groveshark: # Find similar artists to searched artist def similarArtists(self, artistId): - similar = self.groovesharkApiv1.artistGetSimilar(artistId, limit = self.artistsearchlimit) + similar = groovesharkApi.getSimilarArtists(artistId, limit = self.artistsearchlimit) if (len(similar) > 0): self._add_artists_directory(similar) else: @@ -543,17 +633,14 @@ class Groveshark: return '' # Login to grooveshark - def _get_login(self, version = 2): + def _get_login(self): if (self.username == "" or self.password == ""): dialog = xbmcgui.Dialog() dialog.ok('Grooveshark XBMC', 'Unable to login.', 'Check username and password in settings.') return 0 else: if self.userid == 0: - if version == 1: - uid = self.groovesharkApiv1.login(self.username, self.password) - else: - uid = groovesharkApi.login(self.username, self.password) + uid = groovesharkApi.login(self.username, self.password) if (uid != 0): return uid else: @@ -615,6 +702,8 @@ class Groveshark: songid = song[1] songalbum = song[2] songartist = song[4] + + u=sys.argv[0]+"?mode="+str(MODE_SONG)+"&name="+urllib.quote_plus(songname)+"&id="+str(songid) \ +"&album="+urllib.quote_plus(songalbum) \ +"&artist="+urllib.quote_plus(songartist) \ @@ -626,14 +715,13 @@ class Groveshark: unfav = unfav +str(MODE_FAVORITES) else: menuItems.append(("Grooveshark favorite", "XBMC.RunPlugin("+fav+")")) - if self.sessionidv1 != '': - menuItems.append(("Not Grooveshark favorite", "XBMC.RunPlugin("+unfav+")")) - if playlistid > 0: - rmplaylstsong=sys.argv[0]+"?playlistid="+str(playlistid)+"&id="+str(id+1)+"&mode="+str(MODE_REMOVE_PLAYLIST_SONG)+"&name="+playlistname - menuItems.append(("Remove from Grooveshark playlist", "XBMC.RunPlugin("+rmplaylstsong+")")) - else: - addplaylstsong=sys.argv[0]+"?id="+str(songid)+"&mode="+str(MODE_ADD_PLAYLIST_SONG) - menuItems.append(("Add to Grooveshark playlist", "XBMC.RunPlugin("+addplaylstsong+")")) + menuItems.append(("Not Grooveshark favorite", "XBMC.RunPlugin("+unfav+")")) + if playlistid > 0: + rmplaylstsong=sys.argv[0]+"?playlistid="+str(playlistid)+"&id="+str(songid)+"&mode="+str(MODE_REMOVE_PLAYLIST_SONG)+"&name="+playlistname + menuItems.append(("Remove from Grooveshark playlist", "XBMC.RunPlugin("+rmplaylstsong+")")) + else: + addplaylstsong=sys.argv[0]+"?id="+str(songid)+"&mode="+str(MODE_ADD_PLAYLIST_SONG) + menuItems.append(("Add to Grooveshark playlist", "XBMC.RunPlugin("+addplaylstsong+")")) item.addContextMenuItems(menuItems, replaceItems=False) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=item,isFolder=False, totalItems=len(songs)) id = id + 1 @@ -659,8 +747,9 @@ class Groveshark: albumImage = self._get_icon(album[4], 'album-' + str(albumID)) self._add_dir(albumName + " - " + albumArtistName, '', MODE_ALBUM, albumImage, albumID, n) i = i + 1 - if artistid > 0 and self.sessionidv1 != '': - self._add_dir('Similar artists...', '', MODE_SIMILAR_ARTISTS, self.artistImg, artistid) + # Not supported by key + #if artistid > 0: + # self._add_dir('Similar artists...', '', MODE_SIMILAR_ARTISTS, self.artistImg, artistid) xbmcplugin.setContent(self._handle, 'albums') xbmcplugin.addSortMethod(self._handle, xbmcplugin.SORT_METHOD_ALBUM_IGNORE_THE) xbmcplugin.setPluginFanart(int(sys.argv[1]), self.fanImg) @@ -706,17 +795,17 @@ class Groveshark: dir.setInfo( type="Music", infoLabels={ "title": name } ) # Custom menu items - if self.sessionidv1 != '': - menuItems = [] - if mode == MODE_ALBUM: - mkplaylst=sys.argv[0]+"?mode="+str(MODE_MAKE_PLAYLIST)+"&name="+name+"&id="+str(id) - menuItems.append(("Make Grooveshark playlist", "XBMC.RunPlugin("+mkplaylst+")")) - if mode == MODE_PLAYLIST: - rmplaylst=sys.argv[0]+"?mode="+str(MODE_REMOVE_PLAYLIST)+"&name="+urllib.quote_plus(name)+"&id="+str(id) - menuItems.append(("Delete Grooveshark playlist", "XBMC.RunPlugin("+rmplaylst+")")) - mvplaylst=sys.argv[0]+"?mode="+str(MODE_RENAME_PLAYLIST)+"&name="+urllib.quote_plus(name)+"&id="+str(id) - menuItems.append(("Rename Grooveshark playlist", "XBMC.RunPlugin("+mvplaylst+")")) - dir.addContextMenuItems(menuItems, replaceItems=False) + menuItems = [] + if mode == MODE_ALBUM: + mkplaylst=sys.argv[0]+"?mode="+str(MODE_MAKE_PLAYLIST)+"&name="+name+"&id="+str(id) + menuItems.append(("Make Grooveshark playlist", "XBMC.RunPlugin("+mkplaylst+")")) + # Broken rename/delete are broken in API + if mode == MODE_PLAYLIST: + rmplaylst=sys.argv[0]+"?mode="+str(MODE_REMOVE_PLAYLIST)+"&name="+urllib.quote_plus(name)+"&id="+str(id) + menuItems.append(("Delete Grooveshark playlist", "XBMC.RunPlugin("+rmplaylst+")")) + mvplaylst=sys.argv[0]+"?mode="+str(MODE_RENAME_PLAYLIST)+"&name="+urllib.quote_plus(name)+"&id="+str(id) + menuItems.append(("Rename Grooveshark playlist", "XBMC.RunPlugin("+mvplaylst+")")) + dir.addContextMenuItems(menuItems, replaceItems=False) return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=dir,isFolder=True, totalItems=items) @@ -743,6 +832,49 @@ class Groveshark: except: xbmc.log("An error occurred saving songs") pass + + def _getSongDuration(self, songid): + path = os.path.join(cacheDir, 'duration.dmp') + id = int(songid) + durations = [] + duration = -1 + + # Try cache first + try: + f = open(path, 'rb') + durations = pickle.load(f) + for song in durations: + if song[0] == id: + duration = song[1] + f.close() + except: + pass + + if duration < 0: + stream = groovesharkApi.getSubscriberStreamKey(songid) + usecs = stream['uSecs'] + if usecs < 60000000: + usecs = usecs * 10 # Some durations are 10x to small + duration = usecs / 1000000 + song = [id, duration] + durations.append(song) + self._setSongDuration(durations) + + return duration + + def _setSongDuration(self, durations): + try: + # Create the 'data' directory if it doesn't exist. + if not os.path.exists(cacheDir): + os.makedirs(cacheDir) + path = os.path.join(cacheDir, 'duration.dmp') + f = open(path, 'wb') + pickle.dump(durations, f, protocol=pickle.HIGHEST_PROTOCOL) + f.close() + except: + xbmc.log("An error occurred saving durations") + pass + # Parse URL parameters def get_params(): diff --git a/default.tbn b/default.tbn dissimilarity index 99% index 0d4c247..13716fb 100644 Binary files a/default.tbn and b/default.tbn differ diff --git a/description.xml b/description.xml index f34ea24..0ac420e 100644 --- a/description.xml +++ b/description.xml @@ -15,10 +15,10 @@ 9 - Grooveshark XBMC. + Grooveshark XBMC - 0.4.0 + 0.5.1