Save 37% off PRO during our Black Friday Sale! »

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

40954a76aa6a74c6ffc57fe9a2f7516a?s=128

Michael Fötsch

July 04, 2013
Tweet

Transcript

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

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

  3. Libspotify

  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
  5. 5

  6. 6 developer.spotify.com

  7. 7

  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
  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
  10. 10 Premium Account + App Key

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

  12. 12 github.com/mopidy/pyspotify

  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()
  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().
  15. 15 Section name • Logged in / logged out • Metadata updates

    • Connection errors • Music delivery Session Manager Callbacks Section name
  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()
  17. 17 if playlist.is_loaded() • Asynchronous loading • Callback-based notifications

  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()
  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 …
  20. 20

  21. 21 Example: Searching • Paged results (offset / limit) • Artists, albums,

    tracks, playlists • Search term suggestions (“Did you mean …?”)
  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()
  23. 23

  24. 24 Example: Playback • music_delivery callback delivers PCM audio samples • spotify.audiosink

    wraps ALSA, OSS, PortAudio, Gstreamer
  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)
  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)
  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)
  28. Section name 28 Playback Controls • Play / pause • Seek • Bitrate

    (96 / 160 / 320 kbps)
  29. 29 Example: Spotify URIs

  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()
  31. Echo Nest

  32. 32 The Echo Nest •  “The world’s leading music intelligence

    company” •  Over a trillion data points on over 30 million songs
  33. Section name 33 Echo Nest API • HTTP + JSON/XML • Artist

    profiles, bios, reviews, etc. • Similar songs, artists • Song tempo, mood, genre, etc. • …
  34. 34 developer.echonest.com github.com/echonest/pyechonest Requires an API key

  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
  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
  37. 37

  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, …)
  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”]
  40. 40

  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
  42. 42

  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
  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
  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”
  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
  47. 47

  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)
  49. 49 • Tempo, bars, beats • Fade-in time/fade-out time • … * Not

    available for all tracks Audio analysis*
  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 …
  51. 51 Echo Nest Remix

  52. 52

  53. 53

  54. Last.fm

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

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

  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())
  58. 58 Example: “Now Playing”

  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)
  60. 60 Example: User’s library

  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)
  62. 62

  63. July 4, 2013 Check out spotify.com/jobs or @Spotifyjobs for more

    information. Want to join the band?
  64. 64 realmike.org @mfoetsch