예제 #1
0
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
예제 #2
0
 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)
예제 #3
0
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()
예제 #4
0
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()
예제 #5
0
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()
예제 #6
0
 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())
예제 #7
0
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()
예제 #8
0
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) + '\'')
예제 #9
0
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()
예제 #10
0
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
예제 #11
0
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()
예제 #12
0
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()
예제 #13
0
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)
예제 #14
0
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)
예제 #15
0
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
예제 #16
0
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")
예제 #17
0
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))
예제 #18
0
 def GET(self):
     log("GET",
         "'/controls' -> render.pm_controls() -> template/pm_controls.html")
     return render.pm_controls()
예제 #19
0
 def GET(self):
     log("GET",
         "'/' -> render.pm_web_new() -> template/pm_web_new.html")
     return render.pm_web_new()
예제 #20
0
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
예제 #21
0
        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 #
# ----------- #


# -------------- #