Skip to content

Instantly share code, notes, and snippets.

@rmnmjw
Last active June 24, 2023 11:10
Show Gist options
  • Save rmnmjw/c56ff20b75675948bdbe58b78761868a to your computer and use it in GitHub Desktop.
Save rmnmjw/c56ff20b75675948bdbe58b78761868a to your computer and use it in GitHub Desktop.
Quick and dirty program to play Idobi radio songs from last.fm on Spotify and save those songs into a playlist
# inspiration: https://github.com/razzledazze/Now-Playing/blob/master/Now%20Playing.py
from functools import lru_cache
from lxml import html
import random
import re
import requests
import spotipy
import time
CLIENT_ID = 'XXX'
CLIENT_SECRET = 'XXX'
PLAYLIST = 'spotify:playlist:XXX'
PERMISSIONS = ','.join("""
app-remote-control
user-follow-modify
user-follow-read
user-library-modify
user-library-read
user-modify-playback-state
user-read-currently-playing
user-read-playback-position
user-read-playback-state
user-read-private
user-read-recently-played
playlist-read-private
playlist-read-collaborative
playlist-modify-private
playlist-modify-public
""".strip().split('\n'))
def get_ttl_hash(seconds=60*60):
return round(time.time() / seconds)
@lru_cache()
def get_last_fm(hash):
URL = 'https://www.last.fm/user/idobiradio/library'
return html.fromstring(requests.get(URL, timeout=30).content)
def get_spotify():
auth = spotipy.SpotifyOAuth(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uri="http://localhost:8888/callback/",
scope=PERMISSIONS
)
sp = spotipy.Spotify(auth_manager=auth)
return sp
def get_song_lastfm(skip=0):
h = get_last_fm(get_ttl_hash())
artists = h.xpath('//td[@class="chartlist-artist"]//a//text()')
tracks = h.xpath('//td[@class="chartlist-name"]//a//text()')
IGNORE = ['idobi', '@geekgirlriot']
for a, t in reversed(list(zip(artists, tracks))):
a = a.lower().strip()
t = t.lower().replace('(idobi session)', '').strip()
if a.lower().strip() in IGNORE:
continue
if t in IGNORE:
continue
if skip >= 0:
skip -= 1
continue
return a, t
return None
sp = get_spotify()
playlist_tracks = [item['track']['uri'] for item in sp.playlist_tracks(PLAYLIST)['items']]
def str_filter(s):
s = s.lower()
s = re.sub(r'\(.*\)', '', s)
return s
def get_song(artist, song):
# exact match
res = sp.search(q="artist:" + artist + " track:" + song, type="track")
for t in res['tracks']['items']:
name = t['name'].lower()
if song in name or name in song:
return t
# try again without parentheses
artist = str_filter(artist)
song = str_filter(song)
res = sp.search(q="artist:" + artist + " track:" + song, type="track")
for t in res['tracks']['items']:
name = str_filter(t['name'])
if song in name or name in song:
return t
return None
def wait_for_playback():
time.sleep(2)
for i in range(20):
curr = sp.current_playback()
if curr is None or not curr['is_playing']:
time.sleep(1)
continue
return
def play(uri, artist='?', song='?'):
print('(' + str(uri) + ')', artist, '-', song, flush=True, end='\n')
sp.start_playback(uris=[uri])
if uri not in playlist_tracks:
playlist_tracks.append(uri)
sp.playlist_add_items(PLAYLIST, [uri])
wait_for_playback()
last = playlist_tracks[-30:]
def play_random_song():
track = random.choice(playlist_tracks)
play(track)
def play_song_not_in(res, uris):
artist, song = res
track = get_song(artist, song)
if track is not None:
uri = track['uri']
if uri not in uris:
play(uri, artist, song)
last.append(uri)
return True
return False
def find_and_play_next_song():
global playlist_tracks, last
# new songs
for i in range(50):
res = get_song_lastfm(i)
if res is None:
return play_random_song()
if play_song_not_in(res, playlist_tracks):
return
# songs from playlist, but not recently added
for i in range(50):
res = get_song_lastfm(i)
if res is None:
return play_random_song()
if play_song_not_in(res, last):
return
def is_playing_and_sleep():
curr = sp.current_playback()
if curr is not None and curr['is_playing']:
playback_left = curr['item']['duration_ms']/1000 - curr['progress_ms']/1000
if playback_left < 10:
print('.', flush=True, end='')
time.sleep(1)
elif playback_left < 60:
print('~', flush=True, end='')
time.sleep(5)
else:
print('#', flush=True, end='')
time.sleep(50)
return True
return False
while True:
if is_playing_and_sleep():
continue
else:
print(flush=True, end='\n')
find_and_play_next_song()
while len(last) > 30:
last = last[1:]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment