Last active
June 24, 2023 11:10
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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