Upgrade to Pro — share decks privately, control downloads, hide ads and more …

libspotify: Adding Music to Python

libspotify: Adding Music to Python

This EuroPython 2013 talk enables you to build cool stuff with and around music. libspotify is a library that gives your application access to the Spotify music streaming service with its 20 million songs and 1 billion playlists.

- How to use libspotify from Python
- Other music APIs that you can use in conjunction with Spotify, like Echo Nest and Last.fm

https://ep2013.europython.eu/conference/talks/libspotify-add-music-to-python

Michael Fötsch

July 04, 2013
Tweet

Other Decks in Programming

Transcript

  1. July 4, 2013
    EuroPython 2013
    Michael Fötsch – @mfoetsch
    Adding Music
    to Python

    View Slide

  2. 2
    Section name
    Libspotify
    Echo Nest
    Last.fm
    Music APIs

    View Slide

  3. Libspotify

    View Slide

  4. Section name 4
    What is Spotify?
    The right music for every moment.
    ● Over 6 million paying subscribers
    ● Over 24 million active users
    ● Over 20 million songs
    ● Over 20,000 songs added per day
    ● Over 1 billion playlists created
    ● Available in 28 countries

    View Slide

  5. 5

    View Slide

  6. 6
    developer.spotify.com

    View Slide

  7. 7

    View Slide

  8. 8
    Section name
    Can be used to build a full-featured
    Spotify client
    ● Multi-platform C/C++ API
    ● High-quality streaming
    ● Playlist management
    ● Searching
    ● Artist, album, track metadata
    ● Facebook / Last.fm scrobbling
    Libspotify Features

    View Slide

  9. Section name 9
    Limitations/
    Shortcomings
    ● C/C++ API
    ● No support for in-client apps
    ● No support for latest social and
    Discovery features
    ● No Radio support

    View Slide

  10. 10
    Premium Account + App Key

    View Slide

  11. 11
    Windows, OS X, iOS, Linux (x86, x86_64, ARM)

    View Slide

  12. 12
    github.com/mopidy/pyspotify

    View Slide

  13. 13
    SpotifySessionManager
    import spotify.manager
    class SessionManager(spotify.manager.SpotifySessionManager):
    def logged_in(self, session, error):
    print “User”, session.display_name(), “has been logged in”
    sess = SessionManager(username, password)
    sess.connect()

    View Slide

  14. 14
    SpotifySessionManager
    import spotify.manager
    class SessionManager(spotify.manager.SpotifySessionManager):
    def logged_in(self, session, error):
    print “User”, session.display_name(), “has been logged in”
    sess = SessionManager(username, password)
    sess.connect() ß Does not return until logged out.
    Log out by calling disconnect().

    View Slide

  15. 15
    Section name
    ● Logged in / logged out
    ● Metadata updates
    ● Connection errors
    ● Music delivery
    Session Manager
    Callbacks
    Section name

    View Slide

  16. 16
    Example: Loading User’s Playlists
    class SessionManager(spotify.manager.SpotifySessionManager,
    spotify.manager.SpotifyContainerManager):
    def logged_in(self, session, error):
    self.watch(session.playlist_container())
    def container_loaded(self, playlist_container, userdata):
    for item in playlist_container:
    if item.type() == “playlist” and item.is_loaded():
    print item.name()

    View Slide

  17. 17
    if playlist.is_loaded()
    ● Asynchronous loading
    ● Callback-based notifications

    View Slide

  18. 18
    Example: Loading User’s Playlists
    def container_loaded(self, playlist_container, userdata):

    if item.type() == “playlist” and not item.is_loaded():
    item.add_playlist_state_changed_callback(
    self.playlist_state_changed)
    def playlist_state_changed(self, playlist, userdata):
    if playlist.is_loaded():
    print playlist.name(), “(%d tracks)” % len(playlist)
    for track in playlist:
    print track.name(), “-”, track.artists()[0].name()

    View Slide

  19. 19
    Playlist Folders
    def container_loaded(self, playlist_container, userdata):
    for item in playlist_container:
    if item.type() == “playlist”:

    elif item.type() == “folder_start”:
    # Playlists between now and “folder_end” go in this folder

    elif item.type() == “folder_end”:
    # Back in parent folder

    View Slide

  20. 20

    View Slide

  21. 21
    Example: Searching
    ● Paged results (offset / limit)
    ● Artists, albums, tracks, playlists
    ● Search term suggestions (“Did you mean …?”)

    View Slide

  22. 22
    Example: Searching
    class SessionManager(spotify.manager.SpotifySessionManager):
    def logged_in(self, session, error):
    session.search(“ABBA”, self.results_loaded, track_offset=0,
    track_count=5, album_offset=0, album_count=5,
    artist_offset=0, artist_count=5)
    def results_loaded(self, results, userdata):
    print results.total_albums(), “album results in total”
    print “First”, len(results.albums()), “albums:”
    for album in results.albums():
    print album.name()

    View Slide

  23. 23

    View Slide

  24. 24
    Example: Playback
    ● music_delivery callback delivers PCM audio samples
    ● spotify.audiosink wraps ALSA, OSS, PortAudio, Gstreamer

    View Slide

  25. 25
    Example: Playback
    from spotify.audiosink import import_audio_sink
    AudioSink = import_audio_sink()
    class SessionManager(spotify.manager.SpotifySessionManager):
    def load_track(self, track):
    self.session.load(track)
    self.audio = AudioSink(backend=self)
    self.audio.start()
    self.session.play(1)
    def music_delivery_safe(self, *args, **kwargs):
    self.audio.music_delivery(*args, **kwargs)

    View Slide

  26. 26
    Example: Playback
    from spotify.audiosink import import_audio_sink
    AudioSink = import_audio_sink()
    class SessionManager(spotify.manager.SpotifySessionManager):
    def load_track(self, track):
    self.session.load(track) ß track must be loaded
    self.audio = AudioSink(backend=self)
    self.audio.start()
    self.session.play(1)
    def music_delivery_safe(self, *args, **kwargs):
    self.audio.music_delivery(*args, **kwargs)

    View Slide

  27. 27
    Example: Loading a track
    def load_track(self, track):
    while not track.is_loaded():
    time.sleep(0.1)
    if track.is_autolinked():
    track = self.load_track(track.playable())
    if track.availability() != 1:
    print "Track not available (%s)" % track.availability()
    # track is loaded
    self.session.load(track)
    self.audio.start()
    self.session.play(1)

    View Slide

  28. Section name 28
    Playback Controls
    ● Play / pause
    ● Seek
    ● Bitrate (96 / 160 / 320 kbps)

    View Slide

  29. 29
    Example: Spotify URIs

    View Slide

  30. 30
    Example: Spotify URIs
    import spotify

    uri = “spotify:track:7FgmnYKW9O87uvMoE2ouTF”
    track_link = spotify.Link.from_string(uri)
    if track_link.type() == spotify.LINK_TRACK:
    assert uri == str(spotify.Link.from_track(track))
    track = track_link.as_track()

    View Slide

  31. Echo Nest

    View Slide

  32. 32
    The Echo Nest
    •  “The world’s leading music intelligence company”
    •  Over a trillion data points on over 30 million songs

    View Slide

  33. Section name 33
    Echo Nest API
    ● HTTP + JSON/XML
    ● Artist profiles, bios, reviews, etc.
    ● Similar songs, artists
    ● Song tempo, mood, genre, etc.
    ● …

    View Slide

  34. 34
    developer.echonest.com
    github.com/echonest/pyechonest
    Requires an API key

    View Slide

  35. 35
    Example: Searching Songs
    from pyechonest import song
    songs = song.search(min_tempo=120, max_tempo=160,
    artist_start_year_after=1980, artist_end_year_before=2000,
    min_danceability=0.6, artist_min_familiarity=0.5,
    buckets=[“id:spotify-WW”, “tracks”])
    for s in songs:
    print s.title, "by", s.artist_name
    for track in s.get_tracks("spotify-WW"):
    uri = track["foreign_id"].replace("spotify-WW", "spotify")
    print " ", uri

    View Slide

  36. 36
    Example: Searching Songs
    from pyechonest import song
    songs = song.search(min_tempo=120, max_tempo=160,
    artist_start_year_after=1980, artist_end_year_before=2000,
    min_danceability=0.6, artist_min_familiarity=0.5,
    buckets=[“id:spotify-WW”, “tracks”]) ß speedup
    for s in songs:
    print s.title, "by", s.artist_name
    for track in s.get_tracks("spotify-WW"):
    uri = track["foreign_id"].replace("spotify-WW", "spotify")
    print " ", uri

    View Slide

  37. 37

    View Slide

  38. 38
    Song Attributes
    ● Title
    ● Artist
    ● Genre
    ● Mood
    ● Live / Studio
    ● Tempo
    ● Duration
    ● Loudness
    ● Artist familiarity
    ● Time period
    ● Region
    ● Danceability
    ● Energy
    ● Speechiness
    ● Acousticness
    ● Musical key (c, c-sharp, d, e-flat, …)

    View Slide

  39. 39
    Example: Artist biographies and reviews
    from pyechonest import artist
    a = artist.Artist("spotify-WW:artist:0LcJLqbBmaGUft1e9Mm8HV")
    for bio in a.biographies:
    print bio["text"]
    print "-", bio["site"], "<%s>" % bio["url”]
    for review in a.reviews:
    print "Review:", review["name"]
    print review["summary"]
    print "-", review["url”]

    View Slide

  40. 40

    View Slide

  41. 41
    Example: Similar artists
    from pyechonest import artist
    artists = [artist.Artist("spotify-WW:artist:2SHhfs4BiDxGQ3oxqf0UHY"),
    artist.Artist("spotify-WW:artist:0LcJLqbBmaGUft1e9Mm8HV")]
    print "Artists similar to", artists[0].name, “and”, artists[1].name
    similars = artist.similar(ids=[a.id for a in artists], buckets=["id:spotify-WW"])
    for a in similars:
    uri = a.get_foreign_id("spotify-WW").replace("spotify-WW", "spotify”)
    print "-", a.name, "(%s)" % uri

    View Slide

  42. 42

    View Slide

  43. 43
    Collection of songs or artists to personalize recommendations
    ● Similar tracks/artists radio stations
    ● Event recommendations
    ● Thumbs up/down and ratings
    Example: Taste Profiles a.k.a. Catalogs

    View Slide

  44. 44
    Section name
    1.  Create a catalog (up to 1,000 per API
    key)
    2.  Add songs/artists
    3.  Use catalog as a seed In queries
    4.  Refine catalog (update, favorite, ban)
    Catalogs: Steps
    Section name

    View Slide

  45. 45
    Example: Create a catalog
    from pyechonest import catalog, track
    cat = catalog.Catalog(“My taste profile”, "song")
    uris = [“spotify-WW:track:5xtwX31B89apd0farwIgir”, …]
    update_items = []
    for uri in uris:
    t = track.track_from_id()
    update_items.append( {"action": "update", "item": {"item_id": uri.split(":")
    [-1], "song_id”: t.song_id}})
    ticket = cat.update(update_items)
    status = cat.status(ticket)["ticket_status"]
    # wait for status to be “complete”

    View Slide

  46. 46
    Example: Create a catalog radio
    from pyechonest import playlist
    pl = playlist.static(type="catalog-radio", seed_catalog=cat,
    buckets=["id:spotify-WW", "tracks"])
    for s in pl:
    print s.title, “by”, s.artist_name
    for t in s.get_tracks("spotify-WW"):
    print " ", t["foreign_id"].replace("spotify-WW", "spotify")
    break # print only the first Spotify track URI

    View Slide

  47. 47

    View Slide

  48. 48
    Example: Refine catalog
    # items must be in the catalog already
    items = [“spotify-WW:track:5xtwX31B89apd0farwIgir”]
    # When an item is played…
    cat.play(items, plays=1)
    # When an item is skipped…
    cat.skip(items, skips=1)
    # When an item is favorited…
    cat.favorite(items, favorite=True)
    # When an item is banned…
    cat.ban(items, ban=True)
    # When an item is rated…
    cat.rate(items, rate=9)

    View Slide

  49. 49
    ● Tempo, bars, beats
    ● Fade-in time/fade-out time
    ● …
    * Not available for all tracks
    Audio analysis*

    View Slide

  50. 50
    Example: Audio analysis
    from pyechonest import track
    t = track.track_from_id(“spotify:track:2uYupCnY6uQwyNuIVvgKsX”)
    t.get_analysis()
    print t.duration
    print t.end_of_fade_in
    print t.start_of_fade_out
    print t.bars[:10]
    print t.beats[:10]
    print t.tempo
    print t.danceability

    View Slide

  51. 51
    Echo Nest Remix

    View Slide

  52. 52

    View Slide

  53. 53

    View Slide

  54. Last.fm

    View Slide

  55. 55
    ● Scrobbling
    ● User profiles
    ● Recommendations
    Last.fm

    View Slide

  56. 56
    last.fm/api
    code.google.com/p/pylast/

    View Slide

  57. 57
    Example: “Now Playing”
    import pylast
    password_hash = pylast.md5(password)
    network = pylast.LastFMNetwork(api_key=API_KEY,
    api_secret=API_SECRET, username=username,
    password_hash=password_hash)
    network.update_now_playing(artist_name, track_title)
    network.scrobble(artist_name, track_title, time.time())

    View Slide

  58. 58
    Example: “Now Playing”

    View Slide

  59. 59
    Example: Mapping Spotify URIs to Last.fm tracks
    import pyechonest.song
    import pyechonest.track

    t = pyechonest.track.track_from_id(spotify_track_uri)
    s = pyechonest_song.Song(t.song_id, buckets=["id:musicbrainz", "tracks"])
    for t in s.get_tracks("musicbrainz"):
    foreign_id = t["foreign_id"].replace("musicbrainz:track:", "")
    try:
    lastfm_track = lastfm_network.get_track_by_mbid(foreign_id)
    except pylast.WSError:
    pass
    else:
    return lastfm_track
    else:
    return pylast.Track(s.artist_name, s.title, lastfm_network)

    View Slide

  60. 60
    Example: User’s library

    View Slide

  61. 61
    Example: User’s library
    print "User's artists:”
    user = network.get_authenticated_user()
    library = user.get_library()
    for item in library.get_artists(limit=5):
    print item.item, "(played %d times)" % item.playcount
    score, shared_artists = user.compare_with_user("averagemike")
    print 'Similarity to user "averagemike":', score
    print "Shared artists:", map(str, shared_artists)

    View Slide

  62. 62

    View Slide

  63. July 4, 2013
    Check out spotify.com/jobs or
    @Spotifyjobs for more information.
    Want to join the band?

    View Slide

  64. 64
    realmike.org
    @mfoetsch

    View Slide