def getArt(spTrackURL, x=False): """ Takes a uri to a spotify track -> Use uri to query Spotify web service for album art path -> modify art path to produce 300 px image (larger than default, no logo) -> return art path as string """ if (not x): log("getArt", "for '" + spTrackURL + "' -> Getting cover art from Spotify") spEmbedUrl = ('https://embed.spotify.com/oembed/?url=' + spTrackURL + '&callback=?') try: r = requests.get(spEmbedUrl) while (r.text == ''): time.sleep(1) t = r.text.split(',') for i in t: if (i.find('thumbnail_url') != -1): t = i t = t.replace('"thumbnail_url":"', '') t = t.replace('"', '') t = t.replace('\\', '') t = t.replace('cover', '300') # print t except: t = '' # print 'something bad happened when getting art, trying again' t = getArt(spTrackURL, True) return t
def GET(self, cmd, opt=""): web.header('Content-Type', 'application/json') web.header('Access-Control-Allow-Origin', '*') web.header('Access-Control-Allow-Credentials', 'true') web.header('Access-Control-Allow-Methods', 'GET') log("GET", "'/cmd/" + cmd + "/" + opt + "-> handleCMD") return handleCMD(cmd, opt)
def thumbsUp(opt): # h = pconfig['Heart'] name, artist, album = '', '', '' trackURI = opt['spURL'] if (opt['spURL'].find('spotify:local:') == -1): name, artist, album = opt['name'], opt['artist'], opt['album'] else: s = opt['spURL'].replace('spotify:local:', '').replace(":", "|||") s = urllib.unquote(s) s = s.replace('+', ' ') s = s.encode('ascii', 'replace') s = s.replace('??', '?') s = s.split('|||') artist, album, name, duration = s[0], s[1], s[2], s[3] localTrack = [artist, album, name, duration] # print localTrack # if (h['Rate_5_star_in_iTunes'] == True): local.itunesThumbsUp(localTrack) log("thumbsUp", "Rated 5-stars:'" + str(localTrack) + "' --> iTunes") # if (h['Rate_5_star_in_local_tag'] == True): local.rateLocalFile(localTrack, 252) log("thumbsUp", "Rated 5-stars:'" + str(localTrack) + "' --> local file") conn = sql.connect(config.db) c = conn.cursor() c.execute('CREATE TABLE IF NOT EXISTS thumbs_up(track TEXT' + ', artist TEXT, album TEXT, trackURI TEXT, date TEXT);') c.execute("INSERT INTO thumbs_up VALUES(?, ?, ?, ?, date('now'));", (name, artist, album, trackURI)) conn.commit() conn.close()
def archive(opt): t = opt['trackURI'] abs_path = '' if (t.find('spotify:local:') != -1): info = local.getLocalTrackInfo(t) dst_path = config.local_archive_folder split = os.path.split(info['location']) src_path, fname = split[0], split[1] abs_path = os.path.join(dst_path, fname) if not os.path.isdir(config.local_archive_folder): os.makedirs(config.local_archive_folder) log("thumbsDown", "Moving '" + fname + "' to '" + dst_path + "'") try: os.rename(os.path.join(src_path, fname), abs_path) except WindowsError: pass conn = sql.connect(config.db) c = conn.cursor() c.execute('CREATE TABLE IF NOT EXISTS archive' + '(track TEXT,spURI TEXT, playlists TEXT,' + ' date TEXT, path TEXT);') c.execute('''INSERT INTO ARCHIVE VALUES(?,?,?,date('now'),?);''', (opt['name'], opt['trackURI'], opt['plURIs'], abs_path)) conn.commit() conn.close()
def fireCommand(msg): """ Send provided message to spapp using websockets """ log('fireCommand', "Sending message '" + msg + "' to PM Spotify app using WebSockets") ws = create_connection(ws_url) ws.send(msg) ws.close()
def POST(self, cmd): web.header('Content-Type', 'application/json') web.header('Access-Control-Allow-Origin', '*') web.header('Access-Control-Allow-Credentials', 'true') web.header('Access-Control-Allow-Methods', 'POST') log("POST", "'/cmd/" + cmd + "' -> handleCMD") # print web.input() # print type(web.input()) return handleCMD(cmd, web.input())
def getStarted(): """ Called at server startup. Performs initial preparation Sends request for current track info and status Read settings from 'settings.ini' file into pconfig object """ log("getStarted", "Sending 'refresh' message to PoleyMote Spotify app; reading settings") net.fireCommand('refresh') config.readConfig()
def trackUpdate(d): """ Calling trackUpdate with dict with data POSTed to /trackinfo Server copy of now playing info is updated with data from this dict and some other sources Includes: - song - artist - album - starred - year - arturl - playlist name - play/pause state - sonos connected state - spotify uris for artist, album - flag indicating if track is local - local track path (if local) """ global trackInfo, tid log("trackUpdate", "for '" + urllib.unquote(d.song) + "' -> Received 'Now Playing' information") trackInfo['song'] = d.song trackInfo['artist'] = d.artist trackInfo['album'] = d.album trackInfo['starred'] = d.starred trackInfo['playing'] = d.playing trackInfo['playlist'] = d.playlist # trackInfo['sonos_connected'] = isSonosConnected() if (d.local == 'false'): trackInfo['year'] = d.year trackInfo['artURL'] = getArt(d.spotifyURI) trackInfo['artistURI'] = d.artistURI trackInfo['albumURI'] = d.albumURI elif (d.local == 'true'): lt = local.getLocalTrackInfo(d.spotifyURI) if lt is not None: trackInfo['artURL'] = lt['img'] trackInfo['year'] = lt['year'] trackInfo['artistURI'] = "local" trackInfo['albumURI'] = "local" else: trackInfo['artURL'] = '/static/artwork/no_art.png' trackInfo['year'] = '2048' trackInfo['artistURI'] = "local" trackInfo['albumURI'] = "local" trackInfo['id'] += 1 tid = trackInfo['id'] log('Now Playing', '\'' + urllib.unquote(d.song) + '\' by \'' + urllib.unquote(d.artist) + '\' on \'' + urllib.unquote(d.album) + '\'')
def itunesThumbsUp(track): log('itunesThumbsUp', 'Called for ' + str(track)) t = getLocalTrackInfo(track) pythoncom.CoInitialize() iTunes = Dispatch("iTunes.Application") sources = iTunes.Sources library = sources.ItemByName("Library") music = library.Playlists.ItemByName("Music") allTracks = music.Tracks tr = allTracks.ItemByPersistentID(t['pid_low'], t['pid_high']) tr.Rating = 100 pythoncom.CoUninitialize()
def getLocalTrackInfo(track): s = track if type(s) == str or type(s) == unicode: s = parseSPurl(s) # log("getLocalTrackInfo","Finding local file in index") artist = '%' + (s[0].split('?'))[0] + '%' album = '%' + (s[1].split('?'))[0] + '%' title = '%' + (s[2].split('?'))[0] + '%' duration = s[3] log('getLocalTrackInfo', "Called for '" + str(s) + "'") log('getLocalTrackInfo', "Searching index using artist:'" + urllib.unquote(s[0]) + "', album: '" + urllib.unquote(s[1]) + "', title: '" + urllib.unquote(s[2]) + "', duration: '" + duration + "'") conn = sql.connect(config.db) c = conn.cursor() c.execute('SELECT * FROM itunes WHERE artist LIKE ? AND album ' + 'LIKE ? AND name LIKE ? AND duration = ?;', (artist, album, title, duration)) r = c.fetchone() if type(r) != tuple: c.execute('SELECT * FROM itunes WHERE artist LIKE ?' + ' AND album LIKE ? AND name LIKE ?;', (artist, album, title)) r = c.fetchone() if type(r) != tuple: c.execute('SELECT * FROM itunes WHERE artist LIKE ? AND name LIKE ?;', (artist, title)) r = c.fetchone() if r is not None: t = {} t['location'] = eval(r[1]) t['artist'] = r[2] t['album'] = r[3] t['name'] = r[4] t['year'] = r[5] t['track'] = r[6] t['duration'] = r[7] t['persistent_id'] = r[8] t['pid_low'] = r[9] t['pid_high'] = r[10] t['img'] = 'http://' + net.getAddress() + '/' + r[11] conn.close() return t else: t = None conn.close() return t
def readConfig(): global pconfig log('readConfig', "Reading 'pm_settings.ini' into pconfig object") # try: # f = open("pm_settings.ini") # pconfig = eval(f.read()) # f.close() # return pconfig # except IOError: # log('readConfig', # "'pm_settings.ini' not found; calling resetDefaultConfig") return resetDefaultConfig()
def deleteFromItunes(track): log('deleteFromItunes', 'Called for ' + str(track)) try: pythoncom.CoInitialize() iTunes = Dispatch("iTunes.Application") sources = iTunes.Sources library = sources.ItemByName("Library") music = library.Playlists.ItemByName("Music") allTracks = music.Tracks t = getLocalTrackInfo(track) tr = allTracks.ItemByPersistentID(t['pid_low'], t['pid_high']) tr.delete() except AttributeError: pass pythoncom.CoUninitialize()
def increasePlayCount(track): """ increasePlayCount increments the playcount in the ID3 tag of a local file whenever it is played in Spotify """ log('increasePlayCount', 'Called on ' + str(track)) p = (getLocalTrackInfo(track))['location'] # p = r"Z:/test/Reflections of the Television.mp3" t = ID3(p) if 'PCNT' in t: if str(t['PCNT']).find('count') != -1: t['PCNT'].count = 1 + t['PCNT'].count else: t.add(PCNT(count=1)) t.update_to_v23() t.save(p, 2, 3)
def rateLocalFile(track, rat): """ rateLocalFile is used the set the rating in the local file ID3 tag when rated by the user. rate 1 for 1 star rate 252 for 5 star """ log('rateLocalFile', 'Called on ' + str(track)) p = (getLocalTrackInfo(track))['location'] t = ID3(p) if 'PCNT' in t: if str(t['PCNT']).find('rating') != -1: t['PCNT'].rating = rat else: t.add(POPM(email=u'no@email', rating=rat, count=1)) t.update_to_v23() t.save(p, 2, 3)
def deleteLocalFile(track): log('deleteLocalFile', 'Called on ' + str(track)) lp = (getLocalTrackInfo(track))['location'] # Z://iTunes//iTunes Media//Music//Following// # KCRW's Today's Top Tune//Following.mp3 dst_path = config.local_delete_folder # Z:/iTunes/Deleted/ split = os.path.split(lp) src_path = split[0] fname = split[1] if not os.path.isdir(dst_path): os.makedirs(dst_path) try: os.rename(os.path.join(src_path, fname), os.path.join(dst_path, fname)) except WindowsError: pass
def thumbsDown(opt): trackURI = opt['spURL'] name, artist, album = '', '', '' if (opt['spURL'].find('spotify:local:') == -1): name, artist, album = opt['name'], opt['artist'], opt['album'] conn = sql.connect(config.db) c = conn.cursor() c.execute('CREATE TABLE IF NOT EXISTS thumbs_down(track TEXT' ', artist TEXT, album TEXT, trackURI TEXT, date TEXT);') c.execute("INSERT INTO thumbs_down VALUES(?, ?, ? , ?, date('now'));", (name, artist, album, trackURI)) conn.commit() conn.close() else: s = opt['spURL'].replace('spotify:local:', '').replace(":", "|||") s = urllib.unquote(s) s = s.replace('+', ' ') s = s.encode('ascii', 'replace') s = s.replace('??', '?') s = s.split('|||') artist, album, name, duration = s[0], s[1], s[2], s[3] # d = pconfig['Delete'] # if (d['Move_to_purgatory_folder'] == True): localTrack = [artist, album, name, duration] # print localTrack log("thumbsDown", "Moving '" + str(localTrack) + "' to '" + config.local_delete_folder + "'") # elif (d['Delete_local_file'] == True): local.deleteLocalFile(localTrack) # elif (d['Rate_1_star_in_local_tag'] == True): # local.rateLocalFile(localTrackPath,1) # log("thumbsDown","Rated 1 star '"+localTrackPath+ # "' in local file") # if (d['Delete_from_iTunes'] == True): local.deleteFromItunes(localTrack) log("thumbsDown", "Deleting '" + str(localTrack) + "' from iTunes")
def handleCMD(cmd, opt): global trackInfo, tid, bmInfo, qInfo if (cmd == 'spotify'): net.fireCommand(config.sp_app_name + opt) return 0 elif (cmd == "getsettings"): log("handleCMD", "'/cmd/getsettings' -> downloading settings from server") return json.dumps(config.readConfig()) elif (cmd == "updatesettings"): log("handleCMD", "'/cmd/updatesettings' -> settings are being updated") config.updateConfig(opt) return elif (cmd == "connectsonos"): log("handleCMD", "'/cmd/connectsonos -> reconnect to the Sonos") from pm_server_airfoil import connectSonos as cs cs() elif (cmd == "disconnectsonos"): log("handleCMD", "'/cmd/disconnectsonos -> disconnect from the Sonos") from pm_server_airfoil import disconnectSonos as ds ds() elif (cmd == "gettrackinfo"): while (int(opt) == tid): time.sleep(1) log("handleCMD", "'/cmd/gettrackinfo/" + opt + "' -> Sending 'Now Playing' info to PoleyMote client") log('gettrackinfo', 'sending artURL: ' + trackInfo['artURL']) return json.dumps(trackInfo) elif (cmd == "updatetrackinfo"): log("handleCMD", "'/cmd/updatetrackinfo' -> " + "trackUpdate(New 'Now Playing' info being received)") trackUpdate(opt) return 0 elif (cmd == "getthumbsdown"): log("handleCMD", "'/cmd/getThumbsDown' -> Sending list of 'thumbs down' tracks") return json.dumps(getThumbsDown()) elif (cmd == "thumbsdown"): log("handleCMD", "'/cmd/thumbsdown' -> " + "thumbs down called on local file") thumbsDown(opt) elif (cmd == "thumbsup"): log("handleCMD", "'/cmd/thumbsup' -> " + "thumbs up called on local file") thumbsUp(opt) elif (cmd == "archive"): log("handleCMD", "'/cmd/archive' -> " + "track archived") archive(opt) elif (cmd == "gettracksforartist"): log("handleCMD", "'/cmd/gettracksforartist' -> " + opt) return json.dumps(getTracksForArtist(opt)) elif (cmd == "gettracksforalbum"): log("handleCMD", "'/cmd/gettracksforalbum' -> " + opt) return json.dumps(getTracksForAlbum(opt)) elif (cmd == "gettracksforlocal"): log("handleCMD", "'/cmd/gettracksforlocal' -> " + opt) return json.dumps(getSpURIsForLocal(opt)) elif (cmd == "migrate"): log("handleCMD", "'/cmd/migrate'") tracks = opt['tracks'].split(',') return json.dumps(migrate(tracks))
def GET(self): log("GET", "'/controls' -> render.pm_controls() -> template/pm_controls.html") return render.pm_controls()
def GET(self): log("GET", "'/' -> render.pm_web_new() -> template/pm_web_new.html") return render.pm_web_new()
def resetDefaultConfig(): global pconfig log('resetDefaultConfig', "Changing all settings to default values," + " creating new 'pm_settings.ini'") defaults = { "Local": { "Use_iTunes": True, "Index_Local_Music": True, }, "AirFoil": { "Use_Airfoil": True, "Display_warning_if_not_connected": False }, "Playlists": { "Favorite_Playlists": [ { "Name": "Electronic/Dance", "uri": "spotify:user:jerblack:playlist:0m2cGNVm9Zp6l9e09SiffL" }, { "Name": "Ambient/Downtempo", "uri": "spotify:user:jerblack:playlist:7a9mjhowih1tHU94Yve7lx" }, { "Name": "24 Hours - The Starck Mix", "uri": "spotify:user:jerblack:playlist:47r1MDzcY916KcduXZDuGU" }, { "Name": "iTunes Music", "uri": "spotify:user:jerblack:playlist:71ti4Mn94w0M2LgWzfk7kw" }, { "Name": "Classical", "uri": "spotify:user:jerblack:playlist:1kHTBSItrsSjJMmmlzXVfc" } ], "Shuffle_Playlists": [ { "Name": "Electronic/Dance", "uri": "spotify:user:jerblack:playlist:0m2cGNVm9Zp6l9e09SiffL" }, { "Name": "Spotify Library 1", "uri": "spotify:user:jerblack:playlist:5XnrfPufI8J3WuDXSJrj3m" }, { "Name": "Spotify Library 2", "uri": "spotify:user:jerblack:playlist:3L5VxdSBxnUPVhwXCoThiG" }, { "Name": "Spotify Library 3", "uri": "spotify:user:jerblack:playlist:3HESEQC2UvmA1Ap1q4Q2m1" }, { "Name": "Spotify Library 4", "uri": "spotify:user:jerblack:playlist:6MmxBfR7jiX4f9cSi5O2PW" } , { "Name": "iTunes Music", "uri": "spotify:user:jerblack:playlist:71ti4Mn94w0M2LgWzfk7kw" } ], "Shuffle_Playlist_Size": 250, "Automatically_add_music_to_queue_when_nearing_end": True, "Spl_base_name": 'Shuffle Playlist', "Spl_folder": 'Shuffle Playlists' }, "Bookmarks": { "Support_Multiple_Users": True, # Users are created in Settings in the dashboard "Users": [ { "Name": "Jeremy", "uri": "spotify:user:jerblack:playlist:4aSwU3mYsVoMV5Wnxo4AbB" }, { "Name": "Maria", "uri": "spotify:user:jerblack:playlist:6b82pMJqlIBygf3cHgZZ5p" } ], "Support_Bookmarks": True, "Use_Custom_Playlist": False, "Automatically_star_track_if_bookmarked": True }, "Delete": { "Delete_from_current_playlist": True, "Delete_from_all_shuffle_playlists": True, "Delete_from_all_favorite_playlists": True, "Save_in_purgatory_playlist": False, "Custom_purgatory_playlist": "", "Delete_local_file": True, "Delete_from_iTunes": True, "Rate_1_star_in_iTunes": True, "Rate_1_star_in_local_tag": True, "Move_to_purgatory_folder": True, "Custom_purgatory_folder": "", "Show_option_for_deleting_all_by_artist": True, "Show_option_for_deleting_all_by_album": True, "Delete_Track_Queue":"spotify:user:jerblack:playlist:4EtycmfHvTNiQAJmq6kvJi", "Delete_Artist_Queue":"spotify:user:jerblack:playlist:4sZzEkCXWz2OZakwsicMYu", "Delete_Album_Queue": "spotify:user:jerblack:playlist:6g63R6jX0jncHn6NF2Vfkj" }, "Archive": { "Archive_from_current_playlist": True, "Archive_from_all_shuffle_playlists": True, "Archive_from_all_favorite_playlists": True, "Archive_duration": "PLACEHOLDER", "Restore_to_original_playlists": True, "Restore_to_custom_playlist": False, "Custom_restore_playlist": "PLACEHOLDER URI", "Archive_Track_Queue":"spotify:user:jerblack:playlist:7g4Wk46Gt6aKVa7CsyZqHC" }, "Heart": { "Star_in_Spotify": True, "Add_to_bookmarks": True, "Rate_5_star_in_iTunes": True, "Rate_5_star_in_local_tag": True }, "Logging": { "Log_to_file": True, "Custom_log_filename": "", "Custom_log_path": "", "Verbose_Logging": True } } f = open("pm_settings.ini", "w") pp.pprint(defaults, f) f.close() pconfig = defaults return pconfig
t = getArt(spTrackURL, True) return t # -------------------------------------- # # End of Track and Art Metadata Handling # # -------------------------------------- # # --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- # # ---- # # main # # ---- # if __name__ == "__main__": log('Hello', "Welcome to PoleyMote") log('IP', 'PoleyMote now running on http://' + net.getAddress()) mw = web.httpserver.StaticMiddleware app = web.application(urls, globals()).wsgifunc(mw) try: threading.Timer(1, net.startBroadcastServer).start() threading.Timer(3, getStarted).start() WSGIServer(('', config.http_port), app).serve_forever() except KeyboardInterrupt: sys.exit() # ----------- # # end of main # # ----------- # # -------------- #