コード例 #1
0
def get_library(cookie, x_goog_authuser):
    auth = YTMusic.setup(headers_raw=f"cookie: {cookie}\nx-goog-authuser: {x_goog_authuser}")
    api = YTMusic(auth)

    playlists = [api.get_playlist(lpl['playlistId'], limit=10 ** 9) for lpl in api.get_library_playlists()]
    songs = api.get_library_songs(limit=10 ** 9)

    return strip_library(songs, playlists)
コード例 #2
0
class YoutubeMusicApiSingleton:
    __instance__ = None

    def __init__(self):
        """ Constructor."""
        self.__youtube_music_api = YTMusic(HEADERS_AUTH_FILE_PATH)
        if YoutubeMusicApiSingleton.__instance__ is None:
            YoutubeMusicApiSingleton.__instance__ = self
        else:
            raise Exception(
                "You cannot create another YoutubeMusicApiSingleton class, this class is a singleton"
            )

    @staticmethod
    def get_instance() -> 'YoutubeMusicApiSingleton':
        """Static method to get the current instance

        Returns:
            YoutubeMusicApiSingleton: Instance of the YoutubeMusicApiSingleton
        """
        if not YoutubeMusicApiSingleton.__instance__:
            YoutubeMusicApiSingleton()
        return YoutubeMusicApiSingleton.__instance__

    def add_song_to_library(self, song_to_add: Song) -> bool:
        """Add a song to the current user's Youtube Music library

        Args:
            song_to_add (Song): Song to add to library

        Returns:
            bool: True -> Song was successfully added to library | False -> Song was NOT added to library
        """
        response = self.__youtube_music_api.edit_song_library_status(song_to_add.feedback_tokens.add)
        # If responseContext is empty then song is already in library
        if not response['responseContext']:
            return True
        return to_bool(response['feedbackResponses'][0]['isProcessed'])

    def delete_library_uploaded_song(self, uploaded_song_to_delete: Song) -> None:
        """Delete an uploaded song from the current user's Youtube Music Library

        Args:
            uploaded_song_to_delete (Song): Uploaded song to delete
        """
        self.__youtube_music_api.delete_upload_entity(uploaded_song_to_delete.entity_id)

    def get_library_uploaded_songs(self, song_limit: int, order: Order) -> List[Song]:
        """Get a list of uploaded songs from the current user's Youtube Music Library

        Args:
            song_limit (int): Max amount of songs to retrieve
            order (Order): Order to retrieve the songs in

        Returns:
            List[Song]: List of uploaded library songs
        """
        return [Song(
            id=song_dict['videoId'],
            title=song_dict['title'],
            artists=[artist['name'] for artist in song_dict['artists']],
            album=song_dict['album']['name'],
            length=song_dict['duration'],
            like_status=LikeStatuses[song_dict['likeStatus']],
            entity_id=song_dict['entityId']) for song_dict in self.__youtube_music_api.get_library_upload_songs(song_limit, order.value)]

    def get_simple_library_playlists(self, playlist_limit: int) -> List[Playlist]:
        """Get a list of simple (no song info included) playlist information from the current user's Youtube Music library

        Args:
            playlist_limit (int): Max amount of playlists to retrieve

        Returns:
            List[Playlist]: List of simple library playlist information
        """
        return [Playlist(
            id=playlist_dict['playlistId'],
            title=playlist_dict['title'],
            song_count=playlist_dict.get('count', None)) for playlist_dict in self.__youtube_music_api.get_library_playlists(playlist_limit)]

    def rate_song(self, song: Song, rating: LikeStatuses) -> None:
        """Rate a song (Like / Dislike / Indifferent)

        Args:
            song (Song): Song to rate
        """
        self.__youtube_music_api.rate_song(song.id, rating)

    def perform_search(
            self, query: str, item_type: ItemType, item_search_limit: int, ignore_spelling: bool) -> List[Dict]:
        """Search for an item in Youtube Music

        Args:
            query (str): Search query
            item_type (ItemType): Item type to search for
            item_search_limit (int): Max amount of items in the search results
            ignore_spelling (bool): True -> Ignore spelling suggestions | False -> Use autocorrected text

        Returns:
            List[Dict]: List of search result items
        """

        search_results = []
        for result in self.__youtube_music_api.search(query=query, filter=item_type.value, limit=item_search_limit, ignore_spelling=ignore_spelling):

            if result['resultType'] == "song":
                search_results.append(
                    Song(
                        id=result['videoId'],
                        title=result['title'],
                        artists=[artist['name'] for artist in result['artists']],
                        album=result['album']['name'],
                        explicit=to_bool(result['isExplicit']),
                        length=result['duration'],
                        feedback_tokens=FeedbackTokens(
                            add=result['feedbackTokens']['add'],
                            remove=result['feedbackTokens']['remove'])))

            if result['resultType'] == "playlist":
                search_results.append(
                    Playlist(
                        id=result['browseId'],
                        title=result['title'],
                        song_count=result['itemCount']))

        return search_results

    def get_complete_playlist(self, playlist: Playlist, playlist_song_limit: int) -> Playlist:
        """Get complete information about the given playlist (including information about the items in the playlist)

        Args:
            playlist (Playlist): Playlist to get information for
            playlist_song_limit (int): Max amount of songs to retrieve from the playlist

        Returns:
            Playlist: Complete playlist
        """
        response = self.__youtube_music_api.get_playlist(playlist.id, playlist_song_limit)
        return Playlist(
            id=response['id'],
            title=response['title'],
            song_count=response['trackCount'],
            songs=[Song(
                id=song_dict['videoId'],
                title=song_dict['title'],
                artists=[artist['name'] for artist in song_dict['artists']],
                album=song_dict['album']['name'],
                explicit=to_bool(song_dict['isExplicit']),
                length=song_dict.get('duration'),
                like_status=LikeStatuses(song_dict['likeStatus']) if song_dict['likeStatus'] else None,
                set_id=song_dict['setVideoId'],
                feedback_tokens=FeedbackTokens(
                    add=song_dict.get('feedbackTokens', {}).get('add'),
                    remove=song_dict.get('feedbackTokens', {}).get('remove'))) for song_dict in response['tracks']])

    def remove_songs_from_playlist(self, playlist: Playlist, songs: List[Song]) -> None:
        """Remove songs from a playlist in Youtube Music

        Args:
            playlist (Playlist): Playlist to remove the song from (Complete playlist info required)
            songs (List[Song]): List of songs to remove from the playlist
        """
        self.__youtube_music_api.remove_playlist_items(playlist.id,
                                                       [{'videoId': song.id, 'setVideoId': song.set_id}
                                                        for song in songs])
コード例 #3
0
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.")
コード例 #4
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.")
コード例 #5
0
ytmusic = YTMusic('headers_auth.json')

#music_files = subprocess.check_output("find %s" % config['music']['local'], shell=True)
#music_files = music_files.decode("utf-8").splitlines()

#music_tags = []
#for music_file in music_files:
#    music_tags.append(TinyTag.get(music_file))

playlist_files = subprocess.check_output('find %s -name "*m3u*"' %
                                         config['playlists']['local'],
                                         shell=True)
playlist_files = playlist_files.decode("utf-8").splitlines()

remote_songs = ytmusic.get_library_upload_songs(99999)
remote_playlists = ytmusic.get_library_playlists()
print(len(remote_songs))
playlist_content = {}

for playlist in playlist_files:
    f = open(playlist, "r")
    for remote_playlist in remote_playlists:
        if remote_playlist["title"] in playlist:
            playlist_content[remote_playlist["playlistId"]] = f.readlines()

for playlist in playlist_content:
    upload = []
    pl_tracks = ytmusic.get_playlist(playlist, 9999)["tracks"]
    pl_trackid = []
    for track in pl_tracks:
        pl_trackid.append(track["videoId"])
コード例 #6
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)
コード例 #7
0
class yTubeMusicComponent(MediaPlayerEntity):
    def __init__(self, hass, config):
        self.hass = hass
        self._name = DOMAIN
        self._playlist = "input_select." + config.get(CONF_SELECT_PLAYLIST,
                                                      DEFAULT_SELECT_PLAYLIST)
        self._playMode = "input_select." + config.get(CONF_SELECT_PLAYMODE,
                                                      DEFAULT_SELECT_PLAYMODE)
        self._media_player = "input_select." + config.get(
            CONF_SELECT_SPEAKERS, DEFAULT_SELECT_SPEAKERS)
        self._source = "input_select." + config.get(CONF_SELECT_SOURCE,
                                                    DEFAULT_SELECT_SOURCE)
        self._speakersList = config.get(CONF_RECEIVERS)

        default_header_file = os.path.join(hass.config.path(STORAGE_DIR),
                                           DEFAULT_HEADER_FILENAME)

        _LOGGER.debug("YtubeMediaPlayer config: ")
        _LOGGER.debug("\tHeader path: " +
                      config.get(CONF_HEADER_PATH, default_header_file))
        _LOGGER.debug("\tplaylist: " + self._playlist)
        _LOGGER.debug("\tmediaplayer: " + self._media_player)
        _LOGGER.debug("\tsource: " + self._source)
        _LOGGER.debug("\tspeakerlist: " + str(self._speakersList))
        _LOGGER.debug("\tplayModes: " + str(self._playMode))

        if (os.path.exists(config.get(CONF_HEADER_PATH, default_header_file))):
            self._api = YTMusic(
                config.get(CONF_HEADER_PATH, default_header_file))
        else:
            msg = "can't file header file at " + config.get(
                CONF_HEADER_PATH, default_header_file)
            _LOGGER.error(msg)
            data = {"title": "yTubeMediaPlayer error", "message": msg}
            self.hass.services.call("persistent_notification", "create", data)
            self._api = None

        self._js = ""
        self._get_cipher('BB2mjBuAtiQ')
        #		embed_url = f"https://www.youtube.com/embed/D7oPc6PNCZ0"

        self._entity_ids = []  ## media_players - aka speakers
        self._playlists = []
        self._playlist_to_index = {}
        self._tracks = []
        self._track = []
        self._attributes = {}
        self._next_track_no = 0
        self._allow_next = False
        self._last_auto_advance = datetime.datetime.now()

        hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self._update_sources)
        hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self._get_speakers)
        hass.bus.listen('ytubemusic_player.sync_media', self._update_sources)
        hass.bus.listen('ytubemusic_player.play_media',
                        self._ytubemusic_play_media)

        self._shuffle = config.get(CONF_SHUFFLE, DEFAULT_SHUFFLE)
        self._shuffle_mode = config.get(CONF_SHUFFLE_MODE,
                                        DEFAULT_SHUFFLE_MODE)

        self._playing = False
        self._state = STATE_OFF
        self._volume = 0.0
        self._is_mute = False
        self._track_name = None
        self._track_artist = None
        self._track_album_name = None
        self._track_album_cover = None
        self._track_artist_cover = None
        self._attributes['_player_state'] = STATE_OFF

#		asyncio.run_coroutine_threadsafe(self.test(), hass.loop)

#	async def test(self):
#		self._reg = await device_registry.async_get_registry(self.hass)
#		reg = self._reg._data_to_save()
#		for dev in reg.devices:
#
#		_LOGGER.error("called get registry")

    @property
    def name(self):
        """ Return the name of the player. """
        return self._name

    @property
    def icon(self):
        return 'mdi:music-circle'

    @property
    def supported_features(self):
        """ Flag media player features that are supported. """
        return SUPPORT_YTUBEMUSIC_PLAYER

    @property
    def should_poll(self):
        """ No polling needed. """
        return False

    @property
    def state(self):
        """ Return the state of the device. """
        return self._state

    @property
    def device_state_attributes(self):
        """ Return the device state attributes. """
        return self._attributes

    @property
    def is_volume_muted(self):
        """ Return True if device is muted """
        return self._is_mute

    @property
    def is_on(self):
        """ Return True if device is on. """
        return self._playing

    @property
    def media_content_type(self):
        """ Content type of current playing media. """
        return MEDIA_TYPE_MUSIC

    @property
    def media_title(self):
        """ Title of current playing media. """
        return self._track_name

    @property
    def media_artist(self):
        """ Artist of current playing media """
        return self._track_artist

    @property
    def media_album_name(self):
        """ Album name of current playing media """
        return self._track_album_name

    @property
    def media_image_url(self):
        """ Image url of current playing media. """
        return self._track_album_cover

    @property
    def media_image_remotely_accessible(self):
        # True  returns: entity_picture: http://lh3.googleusercontent.com/Ndilu...
        # False returns: entity_picture: /api/media_player_proxy/media_player.gmusic_player?token=4454...
        return True

    @property
    def shuffle(self):
        """ Boolean if shuffling is enabled. """
        return self._shuffle

    @property
    def volume_level(self):
        """ Volume level of the media player (0..1). """
        return self._volume

    def turn_on(self, *args, **kwargs):
        """ Turn on the selected media_player from input_select """
        if (self._api == None):
            _LOGGER.error("Can't start the player, no header file")
            return
        _LOGGER.debug("TURNON")

        self._playing = False
        if not self._update_entity_ids():
            return
        _player = self.hass.states.get(self._entity_ids)
        data = {ATTR_ENTITY_ID: _player.entity_id}

        self._allow_next = False
        track_state_change(self.hass, _player.entity_id, self._sync_player)
        track_state_change(self.hass, self._playMode, self._update_playmode)
        self._turn_on_media_player(data)
        #_LOGGER.error("subscribe to changes of ")

        self._get_cipher('BB2mjBuAtiQ')

        # display imidiatly a loading state to provide feedback to the user
        self._track_name = "loading..."
        self._track_album_name = ""
        self._track_artist = ""
        self._track_artist_cover = None
        self._track_album_cover = None
        self._state = STATE_PLAYING  # a bit early otherwise no info will be shown
        self.schedule_update_ha_state()

        # grabbing data from API, might take a 1-3 sec
        self._load_playlist()

    def _turn_on_media_player(self, data=None):
        """Fire the on action."""
        if data is None:
            data = {ATTR_ENTITY_ID: self._entity_ids}
        self._state = STATE_IDLE
        self.schedule_update_ha_state()
        self.hass.services.call(DOMAIN_MP, 'turn_on', data)

    def turn_off(self,
                 entity_id=None,
                 old_state=None,
                 new_state=None,
                 **kwargs):
        """ Turn off the selected media_player """
        self._playing = False
        self._track_name = None
        self._track_artist = None
        self._track_album_name = None
        self._track_album_cover = None

        _player = self.hass.states.get(self._entity_ids)
        data = {ATTR_ENTITY_ID: _player.entity_id}
        self._turn_off_media_player(data)

    def _turn_off_media_player(self, data=None):
        """Fire the off action."""
        self._playing = False
        self._state = STATE_OFF
        self._attributes['_player_state'] = STATE_OFF
        self.schedule_update_ha_state()
        if data is None:
            data = {ATTR_ENTITY_ID: self._entity_ids}
        self.hass.services.call(DOMAIN_MP, 'turn_off', data)

    def _update_entity_ids(self):
        """ sets the current media_player from input_select """
        media_player = self.hass.states.get(
            self._media_player
        )  # Example: self.hass.states.get(input_select.gmusic_player_speakers)
        if media_player is None:
            _LOGGER.error("(%s) is not a valid input_select entity.",
                          self._media_player)
            return False
        _entity_ids = "media_player." + media_player.state
        if self.hass.states.get(_entity_ids) is None:
            _LOGGER.error("(%s) is not a valid media player.",
                          media_player.state)
            return False
        # Example: self._entity_ids = media_player.bedroom_stereo
        self._entity_ids = _entity_ids
        return True

    def _get_cipher(self, videoId):
        embed_url = "https://www.youtube.com/embed/" + videoId
        embed_html = request.get(url=embed_url)
        js_url = extract.js_url(embed_html)
        self._js = request.get(js_url)
        self._cipher = Cipher(js=self._js)
        #2do some sort of check if tis worked

    def _sync_player(self, entity_id=None, old_state=None, new_state=None):
        """ Perform actions based on the state of the selected (Speakers) media_player """
        if not self._playing:
            return
        """ _player = The selected speakers """
        _player = self.hass.states.get(self._entity_ids)

        #""" Entire state of the _player, include attributes. """
        # self._attributes['_player'] = _player
        """ entity_id of selected speakers. """
        self._attributes['_player_id'] = _player.entity_id
        """ _player state - Example [playing -or- idle]. """
        self._attributes['_player_state'] = _player.state

        #_LOGGER.error("State change of ")
        #_LOGGER.error(self._entity_ids)
        #_LOGGER.error(" to ")
        #_LOGGER.error(_player.state)
        #try:
        #	_LOGGER.error(_player.attributes['media_position'])
        #except:
        #	pass

        if 'media_position' in _player.attributes:
            if _player.state == 'playing' and _player.attributes[
                    'media_position'] > 0:
                self._allow_next = True
        if _player.state == 'idle':
            if self._allow_next:
                if (datetime.datetime.now() -
                        self._last_auto_advance).total_seconds() > 10:
                    self._allow_next = False
                    self._last_auto_advance = datetime.datetime.now()
                    self._get_track()
        elif _player.state == 'off':
            self._state = STATE_OFF
            self.turn_off()
        """ Set new volume if it has been changed on the _player """
        if 'volume_level' in _player.attributes:
            self._volume = round(_player.attributes['volume_level'], 2)
        self.schedule_update_ha_state()

    def _ytubemusic_play_media(self, event):

        _speak = event.data.get('speakers')
        _source = event.data.get('source')
        _media = event.data.get('name')

        if event.data['shuffle_mode']:
            self._shuffle_mode = event.data.get('shuffle_mode')
            _LOGGER.info("SHUFFLE_MODE: %s", self._shuffle_mode)

        if event.data['shuffle']:
            self.set_shuffle(event.data.get('shuffle'))
            _LOGGER.info("SHUFFLE: %s", self._shuffle)

        _LOGGER.debug("YTUBEMUSIC PLAY MEDIA")
        _LOGGER.debug("Speakers: (%s) | Source: (%s) | Name: (%s)", _speak,
                      _source, _media)
        self.play_media(_source, _media, _speak)

    def _update_sources(self, now=None):
        _LOGGER.debug("Load source lists")
        self._update_playlists()
        #self._update_library()
        #self._update_songs()

    def _get_speakers(self, now=None):
        defaultPlayer = ''
        try:
            speakersList = list(self._speakersList)
        except:
            speakersList = list()
        if (len(speakersList) <= 1):
            if (len(speakersList) == 1):
                defaultPlayer = speakersList[0]
            all_entities = self.hass.states.all()
            for e in all_entities:
                if (e.entity_id.startswith(media_player.DOMAIN)):
                    speakersList.append(
                        e.entity_id.replace(media_player.DOMAIN + ".", ""))
        speakersList = list(dict.fromkeys(speakersList))
        data = {
            input_select.ATTR_OPTIONS: list(speakersList),
            ATTR_ENTITY_ID: self._media_player
        }
        self.hass.services.call(input_select.DOMAIN,
                                input_select.SERVICE_SET_OPTIONS, data)
        if (defaultPlayer != ''):
            if (defaultPlayer in speakersList):
                data = {
                    input_select.ATTR_OPTION: defaultPlayer,
                    ATTR_ENTITY_ID: self._media_player
                }
                self.hass.services.call(input_select.DOMAIN,
                                        input_select.SERVICE_SELECT_OPTION,
                                        data)

    def _update_playlists(self, now=None):
        """ Sync playlists from Google Music library """
        if (self._api == None):
            return
        self._playlist_to_index = {}
        self._playlists = self._api.get_library_playlists(limit=99)
        idx = -1
        for playlist in self._playlists:
            idx = idx + 1
            name = playlist.get('title', '')
            if len(name) < 1:
                continue
            self._playlist_to_index[name] = idx
            #  the "your likes" playlist won't return a count of tracks
            if not ('count' in playlist):
                extra_info = self._api.get_playlist(
                    playlistId=playlist['playlistId'])
                try:
                    self._playlists[idx]['count'] = max(
                        25,
                        int(''.join([
                            x for x in extra_info['duration'] if x.isdigit()
                        ])))
                except:
                    self._playlists[idx]['count'] = 25

        playlists = list(self._playlist_to_index.keys())
        self._attributes['playlists'] = playlists

        data = {"options": list(playlists), "entity_id": self._playlist}
        self.hass.services.call(input_select.DOMAIN,
                                input_select.SERVICE_SET_OPTIONS, data)

    def _load_playlist(self, playlist=None, play=True):
        _LOGGER.info("Reloading Playlist!")
        """ Load selected playlist to the track_queue """
        if not self._update_entity_ids():
            return
        """ if source == Playlist """
        _playlist_id = self.hass.states.get(self._playlist)
        if _playlist_id is None:
            _LOGGER.error("(%s) is not a valid input_select entity.",
                          self._playlist)
            return
        if playlist is None:
            playlist = _playlist_id.state
        idx = self._playlist_to_index.get(playlist)
        if idx is None:
            _LOGGER.error("playlist to index is none!")
            self._turn_off_media_player()
            return
        self._tracks = None

        _source = self.hass.states.get(self._source)
        if _source is None:
            _LOGGER.error("(%s) is not a valid input_select entity.",
                          self._source)
            return

        my_radio = self._api.get_playlist(
            playlistId=self._playlists[idx]['playlistId'],
            limit=int(self._playlists[idx]['count']))['tracks']
        #my_radio = self._api.get_watch_playlist(playlistId = self._playlists[idx]['playlistId'])#, limit = int(self._playlists[idx]['count']))
        if _source.state != 'Playlist':
            r_track = my_radio[random.randrange(0, len(my_radio) - 1)]
            self._tracks = self._api.get_watch_playlist(
                videoId=r_track['videoId'])
        else:
            self._tracks = my_radio
        _LOGGER.debug("New Track database loaded, contains " +
                      str(len(self._tracks)) + " Tracks")

        self._total_tracks = len(self._tracks)
        #self.log("Loading [{}] Tracks From: {}".format(len(self._tracks), _playlist_id))

        # get current playmode
        self._update_playmode()

        if self._shuffle and self._shuffle_mode != 2:
            random.shuffle(self._tracks)
        if play:
            self._play()

    # called from HA when th user changes the input entry, will read selection to membervar
    def _update_playmode(self, entity_id=None, old_state=None, new_state=None):
        _LOGGER.debug("running update playmode")
        if (entity_id == None):
            _playmode = self.hass.states.get(self._playMode)
        else:
            _playmode = self.hass.states.get(entity_id)
        if _playmode != None:
            if (_playmode.state == "Shuffle"):
                self._shuffle = True
                self._shuffle_mode = 1
            elif (_playmode.state == "Random"):
                self._shuffle = True
                self._shuffle_mode = 2
            if (_playmode.state == "Shuffle Random"):
                self._shuffle = True
                self._shuffle_mode = 3
            if (_playmode.state == "Direct"):
                self._shuffle = False
                self._shuffle_mode = 0
        self.set_shuffle(self._shuffle)
        # if we've change the dropdown, reload the playlist and start playing
        # else only change the mode
        if (old_state != None and new_state != None):
            self._allow_next = False  # player will change to idle, avoid auto_advance
            self._load_playlist(play=True)

    def _play(self):
        self._playing = True
        self._next_track_no = -1
        self._get_track()

    def _get_track(self,
                   entity_id=None,
                   old_state=None,
                   new_state=None,
                   retry=3):
        """ Get a track and play it from the track_queue. """
        _LOGGER.info(" NEXT TRACK ")
        """ grab next track from prefetched list """
        _track = None
        if self._shuffle and self._shuffle_mode != 1:
            self._next_track_no = random.randrange(self._total_tracks) - 1
        else:
            self._next_track_no = self._next_track_no + 1
            if self._next_track_no >= self._total_tracks:
                # we've reached the end of the playlist
                # reset the inner playlist counter, call _update_playlist to update lib
                self._next_track_no = 0
                self._load_playlist(play=False)
        try:
            _track = self._tracks[self._next_track_no]
        except IndexError:
            _LOGGER.error(
                "Out of range! Number of tracks in track_queue == (%s)",
                self._total_tracks)
            self._turn_off_media_player()
            return
        if _track is None:
            _LOGGER.error("_track is None!")
            self._turn_off_media_player()
            return
        """ Find the unique track id. """
        uid = ''
        if 'videoId' in _track:
            uid = _track['videoId']
        else:
            _LOGGER.error("Failed to get ID for track: (%s)", _track)
            _LOGGER.error(_track)
            if retry < 1:
                self._turn_off_media_player()
                return
            return self._get_track(retry=retry - 1)
        """ If available, get track information. """
        self._track_album_name = None
        self._track_artist_cover = None
        self._track_name = None
        self._track_artist = None
        self._track_album_cover = None
        if 'title' in _track:
            self._track_name = _track['title']
        if 'byline' in _track:
            self._track_artist = _track['byline']
        elif 'artists' in _track:
            self._track_artist = _track['artists'][0]['name']
        if 'thumbnail' in _track:
            _album_art_ref = _track['thumbnail']  ## returns a list,
            # thumbnail [0] is super tiny 32x32? / thumbnail [1] is ok-ish / thumbnail [2] is quite nice quality
            self._track_album_cover = _album_art_ref[len(_album_art_ref) -
                                                     1]['url']
        elif 'thumbnails' in _track:
            _album_art_ref = _track['thumbnails']  ## returns a list
            self._track_album_cover = _album_art_ref[len(_album_art_ref) -
                                                     1]['url']
        self.schedule_update_ha_state()
        """@@@ Get the stream URL and play on media_player @@@"""
        _url = ''
        try:
            _LOGGER.debug("-- try to find streaming url --")
            streamingData = self._api.get_streaming_data(_track['videoId'])
            if ('adaptiveFormats' in streamingData):
                streamingData = streamingData['adaptiveFormats']
            elif (
                    'formats' in streamingData
            ):  #backup, not sure if that is ever needed, or if adaptiveFormats are always present
                streamingData = streamingData['formats']
            streamId = 0
            # try to find audio only stream
            for i in range(0, len(streamingData)):
                if (streamingData[i]['mimeType'].startswith('audio/mp4')):
                    streamId = i
                    break
                elif (streamingData[i]['mimeType'].startswith('audio')):
                    streamId = i
            if (streamingData[streamId].get('url') is None):
                sigCipher_ch = streamingData[streamId]['signatureCipher']
                sigCipher_ex = sigCipher_ch.split('&')
                res = dict({'s': '', 'url': ''})
                for sig in sigCipher_ex:
                    for key in res:
                        if (sig.find(key + "=") >= 0):
                            res[key] = unquote(sig[len(key + "="):])
                # I'm just not sure if the original video from the init will stay online forever
                # in case it's down the player might not load and thus we won't have a javascript loaded
                # so if that happens: we try with this url, might work better (at least the file should be online)
                # the only trouble i could see is that this video is private and thus also won't load the player ..
                if (self._js == ""):
                    self._get_cipher(_track['videoId'])
                signature = self._cipher.get_signature(
                    ciphered_signature=res['s'])
                _url = res['url'] + "&sig=" + signature
            else:
                _url = streamingData[streamId]['url']

        except Exception as err:
            _LOGGER.error(
                "Failed to get own(!) URL for track, further details below. Will not try YouTube method"
            )
            _LOGGER.error(traceback.format_exc())
            _LOGGER.error(_track['videoId'])
            _LOGGER.error(self._api.get_song(_track['videoId']))

        # backup: run youtube stack, only if we failed
        if (_url == ""):
            try:
                streams = YouTube('https://www.youtube.com/watch?v=' +
                                  _track['videoId']).streams
                streams_audio = streams.filter(only_audio=True)
                if (len(streams_audio)):
                    _url = streams_audio.order_by('abr').last().url
                else:
                    _url = streams.order_by('abr').last().url
                _LOGGER.error("ultimatly")
                _LOGGER.error(_url)

            except Exception as err:
                _LOGGER.error(traceback.format_exc())
                _LOGGER.error("Failed to get URL for track: (%s)", uid)
                _LOGGER.error(err)
                if retry < 1:
                    self._turn_off_media_player()
                    return
                else:
                    _LOGGER.error("Retry with: (%i)", retry)
                return self._get_track(retry=retry - 1)

        self._state = STATE_PLAYING
        self.schedule_update_ha_state()
        data = {
            ATTR_MEDIA_CONTENT_ID: _url,
            ATTR_MEDIA_CONTENT_TYPE: "audio/mp3",
            ATTR_ENTITY_ID: self._entity_ids
        }
        self.hass.services.call(DOMAIN_MP, SERVICE_PLAY_MEDIA, data)
        """@@@ Get the stream URL and play on media_player @@@"""
        #_LOGGER.error("register call later")
        # just to make sure that we check the status of the media player to free the "go to next"
        call_later(self.hass, 15, self._sync_player)

    def play_media(self, media_type, media_id, _player=None, **kwargs):
        if not self._update_entity_ids():
            return

        # Should skip this if input_select does not exist
        if _player is not None:
            _option = {"option": _player, "entity_id": self._media_player}
            self.hass.services.call(input_select.DOMAIN,
                                    input_select.SERVICE_SELECT_OPTION,
                                    _option)

        _source = {"option": "Playlist", "entity_id": self._source}
        _option = {"option": media_id, "entity_id": self._playlist}
        self.hass.services.call(input_select.DOMAIN,
                                input_select.SERVICE_SELECT_OPTION, _source)
        self.hass.services.call(input_select.DOMAIN,
                                input_select.SERVICE_SELECT_OPTION, _option)

        _player = self.hass.states.get(self._entity_ids)

        if self._playing == True:
            self.media_stop()
            self.media_play()
        elif self._playing == False and self._state == STATE_OFF:
            if _player.state == STATE_OFF:
                self.turn_on()
            else:
                data = {ATTR_ENTITY_ID: _player.entity_id}
                self._turn_off_media_player(data)
                call_later(self.hass, 1, self.turn_on)
        else:
            _LOGGER.error("self._state is: (%s).", self._state)

    def media_play(self,
                   entity_id=None,
                   old_state=None,
                   new_state=None,
                   **kwargs):
        """Send play command."""
        if self._state == STATE_PAUSED:
            self._state = STATE_PLAYING
            self.schedule_update_ha_state()
            data = {ATTR_ENTITY_ID: self._entity_ids}
            self.hass.services.call(DOMAIN_MP, 'media_play', data)
        else:
            _source = self.hass.states.get(self._source)
            source = _source.state
            self._load_playlist()

    def media_pause(self, **kwargs):
        """ Send media pause command to media player """
        self._state = STATE_PAUSED
        #_LOGGER.error(" PAUSE ")
        self.schedule_update_ha_state()
        data = {ATTR_ENTITY_ID: self._entity_ids}
        self.hass.services.call(DOMAIN_MP, 'media_pause', data)

    def media_play_pause(self, **kwargs):
        """Simulate play pause media player."""
        if self._state == STATE_PLAYING:
            self._allow_next = False
            self.media_pause()
        else:
            self._allow_next = False
            self.media_play()

    def media_previous_track(self, **kwargs):
        """Send the previous track command."""
        if self._playing:
            self._next_track_no = max(self._next_track_no - 2, -1)
            self._allow_next = False
            self._get_track()

    def media_next_track(self, **kwargs):
        """Send next track command."""
        if self._playing:
            self._allow_next = False
            self._get_track()

    def media_stop(self, **kwargs):
        """Send stop command."""
        self._state = STATE_IDLE
        self._playing = False
        self._track_artist = None
        self._track_album_name = None
        self._track_name = None
        self._track_album_cover = None
        self.schedule_update_ha_state()
        data = {ATTR_ENTITY_ID: self._entity_ids}
        self.hass.services.call(DOMAIN_MP, 'media_stop', data)

    def set_shuffle(self, shuffle):
        self._shuffle = shuffle
        if self._shuffle_mode == 1:
            self._attributes['shuffle_mode'] = 'Shuffle'
        elif self._shuffle_mode == 2:
            self._attributes['shuffle_mode'] = 'Random'
        elif self._shuffle_mode == 3:
            self._attributes['shuffle_mode'] = 'Shuffle Random'
        else:
            self._attributes['shuffle_mode'] = self._shuffle_mode
        return self.schedule_update_ha_state()

    def set_volume_level(self, volume):
        """Set volume level."""
        self._volume = round(volume, 2)
        data = {ATTR_ENTITY_ID: self._entity_ids, 'volume_level': self._volume}
        self.hass.services.call(DOMAIN_MP, 'volume_set', data)
        self.schedule_update_ha_state()

    def volume_up(self, **kwargs):
        """Volume up the media player."""
        newvolume = min(self._volume + 0.05, 1)
        self.set_volume_level(newvolume)

    def volume_down(self, **kwargs):
        """Volume down media player."""
        newvolume = max(self._volume - 0.05, 0.01)
        self.set_volume_level(newvolume)

    def mute_volume(self, mute):
        """Send mute command."""
        if self._is_mute == False:
            self._is_mute = True
        else:
            self._is_mute = False
        self.schedule_update_ha_state()
        data = {
            ATTR_ENTITY_ID: self._entity_ids,
            "is_volume_muted": self._is_mute
        }
        self.hass.services.call(DOMAIN_MP, 'volume_mute', data)
コード例 #8
0
from ytmusicapi import YTMusic

ytmusic = YTMusic('headers_auth.json')
playlists = ytmusic.get_library_playlists(50)
for i in playlists:
    print(i['title'], i['playlistId'])
コード例 #9
0
class YoutubeMusicLibrary(MusicLibrary):
    def __init__(self):
        self.ytmusic = YTMusic("headers_auth.json")

    def add_album(self, album: Album):
        raise NotImplementedError()

    def like_track(self, track: Track):
        raise NotImplementedError()

    def subscribe_to_artist(self, artist: Artist):
        raise NotImplementedError()

    def create_playlist(self, playlist: Playlist):
        raise NotImplementedError()

    def add_tracks_to_playlist(self, playlist: Playlist, tracks: List[Track]):
        return super().add_tracks_to_playlist(playlist, tracks)

    def get_subscribed_artists(self):
        return [
            Artist(name=artist["artist"])
            for artist in self.ytmusic.get_library_subscriptions(limit=10000)
        ]

    def get_playlists(self):
        playlists = []
        for basic_playlist_data in self.ytmusic.get_library_playlists(
                limit=1000):
            playlist_id = basic_playlist_data["playlistId"]
            playlist_data = self.ytmusic.get_playlist(playlist_id, limit=1000)
            public = True if playlist_data["privacy"] == "PUBLIC" else False
            playlists.append(
                Playlist(
                    name=basic_playlist_data["title"],
                    id=basic_playlist_data["playlistId"],
                    count=int(playlist_data.get("trackCount", 0)),
                    public=public,
                    description=playlist_data.get("description", None),
                    tracks=[
                        self._track_data_to_track(track_data)
                        for track_data in playlist_data["tracks"]
                    ],
                ))
        return playlists

    def get_liked_songs(self):
        tracks = []
        for track_data in self.ytmusic.get_liked_songs(limit=10000)["tracks"]:
            track = self._track_data_to_track(track_data)
            tracks.append(track)
        return tracks

    def get_albums(self):
        album_data = self.ytmusic.get_library_albums(limit=10000)
        albums: List[Album] = []
        for album in album_data:
            artists = Artist(album["artists"][0]["name"])
            albums.append(
                Album(
                    artists=artists,
                    year=album["year"],
                    name=album["title"],
                    type_=album["type"],
                ))
        return albums

    @staticmethod
    def _track_data_to_track(track_data) -> Track:
        artist = Artist(name=track_data["artists"][0]["name"])
        album = None
        if track_data["album"]:
            album = Album(
                name=track_data["album"]["name"],
                artists=artist,
                year=None,
                type_="Album",
            )
        return Track(album=album, artist=artist, name=track_data["title"])

    def _get_playlist_tracks(self, playlist: Playlist):
        return [
            self._track_data_to_track(track_data)
            for track_data in self.ytmusic.get_playlist(playlist.id)["tracks"]
        ]