Exemplo n.º 1
0
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.")
Exemplo n.º 3
0
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.")
Exemplo n.º 4
0
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)