class ytmUploader: """class to use ytmusicapi (https://github.com/sigma67/ytmusicapi) to upload spotifyplaylists""" def __init__(self, yt_channel_id, auth_filepath='headers_auth.json'): self.ytmusic = YTMusic(auth_filepath) self.yt_channel_id = yt_channel_id def uploadSpotifyPlaylist(self, spotifyPL): plTitle = spotifyPL.get('playlistName') print(f"Retrieving songs for album {plTitle}") formerPlaylistID = self.getPlaylistID(plTitle) # convert Spotify songs to ytmusic video ids # Has to be done regardless, to update if exists videoIDs = self.ytVideoIDs(spotifyPL) if formerPlaylistID: # Delete first print(f"Deleting: {plTitle}") self.ytmusic.delete_playlist(formerPlaylistID) # Create playlist print(f"Creating: {plTitle}") plID = self.ytmusic.create_playlist(title=plTitle, description=f"{plTitle} from Spotify - magic from playmaker script", privacy_status="PUBLIC", video_ids=list(videoIDs)) def ytVideoIDs(self, spotifyPL): ids = [] """ plTitle = spotifyPL.get('playlistName') if plTitle == 'RapCaviar': return rapCaviarIDs elif plTitle == 'Most Necessary': return mostNecessaryIDs elif plTitle == 'Get Turnt': return getTurntIDs """ for song in spotifyPL.get('tracks'): songname = song[0] ytSongs = self.ytmusic.search(songname, "songs") if ytSongs: topMatchingSong = ytSongs[0] ids.append(topMatchingSong.get('videoId')) return ids def getPlaylistID(self, plTitle): userInfo = self.ytmusic.get_user(self.yt_channel_id) if 'playlists' in userInfo: for song in userInfo.get('playlists').get('results'): if song["title"] == plTitle: return song["playlistId"] return ""
class YTMusicTransfer: def __init__(self): self.api = YTMusic(settings['youtube']['headers'], settings['youtube']['user_id']) def create_playlist(self, name, info, privacy="PRIVATE", tracks=None): return self.api.create_playlist(name, info, privacy, video_ids=tracks) def get_best_fit_song_id(self, results, song): match_score = {} title_score = {} for res in results: if res['resultType'] not in ['song', 'video']: continue durationMatch = None if 'duration' in res and res['duration']: durationItems = res['duration'].split(':') duration = int(durationItems[0]) * 60 + int(durationItems[1]) durationMatch = 1 - abs( duration - song['duration']) * 2 / song['duration'] title = res['title'] # for videos, if res['resultType'] == 'video': titleSplit = title.split('-') if len(titleSplit) == 2: title = titleSplit[1] artists = ' '.join([a['name'] for a in res['artists']]) title_score[res['videoId']] = difflib.SequenceMatcher( a=title.lower(), b=song['name'].lower()).ratio() scores = [ title_score[res['videoId']], difflib.SequenceMatcher(a=artists.lower(), b=song['artist'].lower()).ratio() ] if durationMatch: scores.append(durationMatch * 5) #add album for songs only if res['resultType'] == 'song' and res['album'] is not None: scores.append( difflib.SequenceMatcher(a=res['album']['name'].lower(), b=song['album'].lower()).ratio()) match_score[res['videoId']] = sum(scores) / len(scores) * max( 1, int(res['resultType'] == 'song') * 1.5) if len(match_score) == 0: return None #don't return songs with titles <45% match max_score = max(match_score, key=match_score.get) return max_score def search_songs(self, tracks): videoIds = [] songs = list(tracks) notFound = list() for i, song in enumerate(songs): name = re.sub(r' \(feat.*\..+\)', '', song['name']) query = song['artist'] + ' ' + name query = query.replace(" &", "") result = self.api.search(query, ignore_spelling=True) if len(result) == 0: notFound.append(query) else: targetSong = self.get_best_fit_song_id(result, song) if targetSong is None: notFound.append(query) else: videoIds.append(targetSong) if i > 0 and i % 10 == 0: print(f"YouTube tracks: {i}/{len(songs)}") with open(path + 'noresults_youtube.txt', 'w', encoding="utf-8") as f: f.write("\n".join(notFound)) f.write("\n") f.close() return videoIds def add_playlist_items(self, playlistId, videoIds): videoIds = OrderedDict.fromkeys(videoIds) self.api.add_playlist_items(playlistId, videoIds) def get_playlist_id(self, name): pl = self.api.get_library_playlists(10000) try: playlist = next(x for x in pl if x['title'].find(name) != -1)['playlistId'] return playlist except: raise Exception("Playlist title not found in playlists") def remove_songs(self, playlistId): items = self.api.get_playlist(playlistId, 10000)['tracks'] if len(items) > 0: self.api.remove_playlist_items(playlistId, items) def remove_playlists(self, pattern): playlists = self.api.get_library_playlists(10000) p = re.compile("{0}".format(pattern)) matches = [pl for pl in playlists if p.match(pl['title'])] print("The following playlists will be removed:") print("\n".join([pl['title'] for pl in matches])) print("Please confirm (y/n):") choice = input().lower() if choice[:1] == 'y': [self.api.delete_playlist(pl['playlistId']) for pl in matches] print(str(len(matches)) + " playlists deleted.") else: print("Aborted. No playlists were deleted.")
class YTMusicTransfer: def __init__(self): self.api = YTMusic() def create_playlist(self, name, info, privacy="PRIVATE", tracks=None): return self.api.create_playlist(name, info, privacy, video_ids=tracks) def get_best_fit_song(self, results, song): match_score = {} title_score = {} for res in results: if res['resultType'] not in ['song', 'video']: continue durationMatch = None if res['duration']: durationItems = res['duration'].split(':') duration = int(durationItems[0]) * 60 + int(durationItems[1]) durationMatch = 1 - abs(duration - song['duration']) * 2 / song['duration'] title = res['title'] # for videos, if res['resultType'] == 'video': titleSplit = title.split('-') if len(titleSplit) == 2: title = titleSplit[1] artists = ' '.join([a['name'] for a in res['artists']]) title_score[res['videoId']] = difflib.SequenceMatcher(a=title.lower(), b=song['name'].lower()).ratio() scores = [title_score[res['videoId']], difflib.SequenceMatcher(a=artists.lower(), b=song['artist'].lower()).ratio()] if durationMatch: scores.append(durationMatch * 5) #add album for songs only if res['resultType'] == 'song' and res['album'] is not None: scores.append(difflib.SequenceMatcher(a=res['album']['name'].lower(), b=song['album'].lower()).ratio()) match_score[res['videoId']] = sum(scores) / (len(scores) + 1) * max(1, int(res['resultType'] == 'song') * 1.5) if len(match_score) == 0: return None #don't return songs with titles <45% match max_score = max(match_score, key=match_score.get) return [el for el in results if el['resultType'] in ['song', 'video'] and el['videoId'] == max_score][0] def search_songs(self, tracks): videos = [] songs = list(tracks) notFound = list() for i, song in enumerate(songs): query = song['artist'] + ' ' + song['name'] query = query.replace(" &", "") try: result = self.api.search(query) except: print(f'Fail for {song["artist"]} - {song["name"]}') if len(result) == 0: notFound.append(query) else: targetSong = self.get_best_fit_song(result, song) if targetSong is None: notFound.append(query) else: video = self.format_song(targetSong) videos.append(video) if i > 0 and i % 10 == 0: print(str(i) + ' searched') print(notFound) return videos def format_song(self, video): video['_id'] = video['videoId'] video['durationDisplay'] = video['duration'] if len(video['durationDisplay'].split(':')) == 3: video['duration'] = int(video['duration'].split(':')[0]) * 3600 + int(video['duration'].split(':')[1]) * 60 + int(video['duration'].split(':')[2]) if len(video['durationDisplay'].split(':')) == 2: video['duration'] = int(video['duration'].split(':')[0]) * 60 + int(video['duration'].split(':')[1]) video['thumbnail'] = video['thumbnails'][-1]['url'] video['artist'] = video['artists'][0]['name'] video['album'] = video['album']['name'] if 'album' in video and 'name' in video['album'] else None return video def add_playlist_items(self, playlistId, videoIds): videoIds = OrderedDict.fromkeys(videoIds) self.api.add_playlist_items(playlistId, videoIds) def get_playlist_id(self, name): pl = self.api.get_library_playlists(10000) try: playlist = next(x for x in pl if x['title'].find(name) != -1)['playlistId'] return playlist except: raise Exception("Playlist title not found in playlists") def remove_songs(self, playlistId): items = self.api.get_playlist(playlistId, 10000)['tracks'] if len(items) > 0: self.api.remove_playlist_items(playlistId, items) def remove_playlists(self, pattern): playlists = self.api.get_library_playlists(10000) p = re.compile("{0}".format(pattern)) matches = [pl for pl in playlists if p.match(pl['title'])] print("The following playlists will be removed:") print("\n".join([pl['title'] for pl in matches])) print("Please confirm (y/n):") choice = input().lower() if choice[:1] == 'y': [self.api.delete_playlist(pl['playlistId']) for pl in matches] print(str(len(matches)) + " playlists deleted.") else: print("Aborted. No playlists were deleted.")
class youtube_music_tasker: def __init__(self, auth_json: str): self.api = YTMusic(auth_json) # Return: # [ # { # "id": "playlistid1", # "title": "playlist_title1", # "thumbnail": "url_to_playlist1_1st_thumbnail" # }, # { # "id": "playlistid2", # "title": "playlist_title2", # "thumbnail": "url_to_playlist2_1st_thumbnail" # } # ] # def show_playlist(self): list_of_playlist = [] try: library_playlists = self.api.get_library_playlists( limit=50) # Hopefully, no one has 50+ playlists. for pl in library_playlists: # Only showing non-empty well-formed playlists if 'count' in pl and int( pl['count'] ) > 0 and 'playlistId' in pl and 'title' in pl and 'thumbnails' in pl: playlist = {} playlist['id'] = pl['playlistId'] playlist['title'] = pl['title'] if len(pl['thumbnails']) > 0: playlist['thumbnail'] = pl['thumbnails'][0]['url'] else: playlist['thumbnail'] = DEFAULT_IMG_URL list_of_playlist.append(playlist) except Exception as e: print("Unexpected Error in show_playlist:", e) return json.dumps(list_of_playlist) # Return: # [ # { # "title": "name", # "artist": "someone", # "album": "the album" # }, # { # "title": "name", # "artist": "any", # "album": "any" # } # ] # def show_song_in_playlist(self, playlist_id: str): list_of_song = [] try: pl_detail = self.api.get_playlist(playlistId=playlist_id) if 'tracks' in pl_detail: for track in pl_detail['tracks']: if 'title' in track: new_track = { 'title': track['title'], 'artist': 'any', 'album': 'any' } if 'artists' in track and len(track['artists']) > 0: new_track['artist'] = track['artists'][0]['name'] if 'album' in track and track[ 'album'] != None and 'name' in track['album']: new_track['album'] = track['album']['name'] list_of_song.append(new_track) except Exception as e: print("Unexpected Error in show_song_in_playlist:", e) return json.dumps(list_of_song) # access: 'PRIVATE', 'PUBLIC', 'UNLISTED' # Return: A tuple of (create_status, playlist_id, add_status) def new_playlist(self, playlist_name: str, desc: str = "A playlist created by PlaySync on " + str(datetime.today().strftime('%Y-%m-%d')), access: str = 'PRIVATE', tracks=[]): try: playlist_id = self.api.create_playlist(title=playlist_name, description=desc, privacy_status=access) if type(playlist_id) == str: # It is an id if len(tracks) > 0: status = self.api.add_playlist_items(playlist_id, tracks) return (0, playlist_id, status ) # Creation successful, add status attached else: return (0, playlist_id, "NULL" ) # Creation successful, didn't add else: # Status message, means error in creation return (-1, 0, playlist_id) except Exception as e: print("Unexpected Error in new_playlist:", e) return (-2, 0, e) # Didn't crash gracefully def search_song(self, song_title: str, song_artist: str = "", song_misc: str = ""): song_list = [] try: search_results = self.api.search(query=song_title + song_artist + song_misc, limit=10) for song_found in search_results: if (song_found['resultType'] in ['song', 'video']): new_song = { 'id': song_found['videoId'], 'title': song_found['title'], 'artist': 'None', 'album': 'None', 'duration': 'Unknown' } if len(song_found['artists']) > 0: new_song['artist'] = song_found['artists'][0]['name'] if 'album' in song_found: new_song['artist'] = song_found['album']['name'] if 'duration' in song_found: new_song['duration'] = song_found['duration'] song_list.append(new_song) except Exception as e: print("Unexpected Error in search_song:", e) return json.dumps(song_list) def add_songs(self, playlist_id: str, tracks=[]): try: status = self.api.add_playlist_items(playlist_id, tracks) return (0, playlist_id, status ) # Creation successful, add status attached except Exception as e: print("Unexpected Error in add_songs:", e) return (-2, 0, 0) # Didn't crash gracefully def del_songs(self, playlist_id: str, tracks=[]): try: if len(tracks) > 0: status = self.api.remove_playlist_items(playlist_id, videos=tracks) return status except Exception as e: return "UNCAUGHT ERROR" + str(e) return "NULL" def del_playlist(self, playlist_id: str): try: status = self.api.delete_playlist(playlist_id) return status except Exception as e: return "UNCAUGHT ERROR" + str(e)