from ytmusicapi.continuations import * from ytmusicapi.mixins._protocol import MixinProtocol from ytmusicapi.navigation import * from ytmusicapi.parsers.browsing import parse_content_list from ytmusicapi.parsers.playlists import parse_playlist_header from ytmusicapi.parsers.podcasts import * from ytmusicapi.type_alias import JsonDict, JsonList, ParseFuncType, RequestFuncType from ._utils import * class PodcastsMixin(MixinProtocol): """Podcasts Mixin""" def get_channel(self, channelId: str) -> JsonDict: """ Get information about a podcast channel (episodes, podcasts). For episodes, a maximum of 10 episodes are returned, the full list of episodes can be retrieved via :py:func:`get_channel_episodes` :param channelId: channel id :return: Dict containing channel info Example:: { "title": 'Stanford Graduate School of Business', "thumbnails": [...] "episodes": { "browseId": "UCGwuxdEeCf0TIA2RbPOj-8g", "results": [ { "index": 0, "title": "The Brain Gain: The Impact of Immigration on American Innovation with Rebecca Diamond", "description": "Immigrants' contributions to America ...", "duration": "24 min", "videoId": "TS3Ovvk3VAA", "browseId": "MPEDTS3Ovvk3VAA", "videoType": "MUSIC_VIDEO_TYPE_PODCAST_EPISODE", "date": "Mar 6, 2024", "thumbnails": [...] }, ], "params": "6gPiAUdxWUJXcFlCQ3BN..." }, "podcasts": { "browseId": null, "results": [ { "title": "Stanford GSB Podcasts", "channel": { "id": "UCGwuxdEeCf0TIA2RbPOj-8g", "name": "Stanford Graduate School of Business" }, "browseId": "MPSPPLxq_lXOUlvQDUNyoBYLkN8aVt5yAwEtG9", "podcastId": "PLxq_lXOUlvQDUNyoBYLkN8aVt5yAwEtG9", "thumbnails": [...] } ] } } """ body = {"browseId": channelId} endpoint = "browse" response = self._send_request(endpoint, body) channel = { "title": nav(response, [*HEADER_MUSIC_VISUAL, *TITLE_TEXT]), "thumbnails": nav(response, [*HEADER_MUSIC_VISUAL, *THUMBNAILS]), } results = nav(response, SINGLE_COLUMN_TAB + SECTION_LIST) channel.update(self.parser.parse_channel_contents(results)) return channel def get_channel_episodes(self, channelId: str, params: str) -> JsonList: """ Get all channel episodes. This endpoint is currently unlimited :param channelId: channelId of the user :param params: params obtained by :py:func:`get_channel` :return: List of channel episodes in the format of :py:func:`get_channel` "episodes" key """ body = {"browseId": channelId, "params": params} endpoint = "browse" response = self._send_request(endpoint, body) results = nav(response, SINGLE_COLUMN_TAB + SECTION_LIST_ITEM + GRID_ITEMS) return parse_content_list(results, parse_episode, MMRIR) def get_podcast(self, playlistId: str, limit: int | None = 100) -> JsonDict: """ Returns podcast metadata and episodes .. note:: To add a podcast to your library, you need to call ``rate_playlist`` on it :param playlistId: Playlist id :param limit: How many songs to return. ``None`` retrieves them all. Default: 100 :return: Dict with podcast information Example:: { "author": { "name": "Stanford Graduate School of Business", "id": "UCGwuxdEeCf0TIA2RbPOj-8g" }, "title": "Think Fast, Talk Smart: The Podcast", "description": "Join Matt Abrahams, a lecturer of...", "saved": false, "episodes": [ { "index": 0, "title": "132. Lean Into Failure: How to Make Mistakes That Work | Think Fast, Talk Smart: Communication...", "description": "Effective and productive teams and...", "duration": "25 min", "videoId": "xAEGaW2my7E", "browseId": "MPEDxAEGaW2my7E", "videoType": "MUSIC_VIDEO_TYPE_PODCAST_EPISODE", "date": "Mar 5, 2024", "thumbnails": [...] } ] } """ browseId = "MPSP" + playlistId if not playlistId.startswith("MPSP") else playlistId body = {"browseId": browseId} endpoint = "browse" response = self._send_request(endpoint, body) two_columns = nav(response, TWO_COLUMN_RENDERER) header = nav(two_columns, [*TAB_CONTENT, *SECTION_LIST_ITEM, *RESPONSIVE_HEADER]) podcast: JsonDict = parse_podcast_header(header) results = nav(two_columns, ["secondaryContents", *SECTION_LIST_ITEM, *MUSIC_SHELF]) parse_func: ParseFuncType = lambda contents: parse_content_list(contents, parse_episode, MMRIR) episodes = parse_func(results["contents"]) if "continuations" in results: request_func: RequestFuncType = lambda additionalParams: self._send_request( endpoint, body, additionalParams ) remaining_limit = None if limit is None else (limit - len(episodes)) episodes.extend( get_continuations( results, "musicShelfContinuation", remaining_limit, request_func, parse_func ) ) podcast["episodes"] = episodes return podcast def get_episode(self, videoId: str) -> JsonDict: """ Retrieve episode data for a single episode .. note:: To save an episode, you need to call ``add_playlist_items`` to add it to the ``SE`` (saved episodes) playlist. :param videoId: browseId (MPED..) or videoId for a single episode :return: Dict containing information about the episode The description elements are based on a custom dataclass, not shown in the example below The description text items also contain "\\n" to indicate newlines, removed below due to RST issues Example:: { "author": { "name": "Stanford GSB Podcasts", "id": "MPSPPLxq_lXOUlvQDUNyoBYLkN8aVt5yAwEtG9" }, "title": "124. Making Meetings Me...", "date": "Jan 16, 2024", "duration": "25 min", "saved": false, "playlistId": "MPSPPLxq_lXOUlvQDUNyoBYLkN8aVt5yAwEtG9", "description": [ { "text": "Delve into why people hate meetings, ... Karin Reed (" }, { "text": "https://speakerdynamics.com/team/", "url": "https://speakerdynamics.com/team/" }, { "text": ")Chapters:(" }, { "text": "00:00", "seconds": 0 }, { "text": ") Introduction Host Matt Abrahams...(" }, { "text": "01:30", "seconds": 90 }, ] } """ browseId = "MPED" + videoId if not videoId.startswith("MPED") else videoId body = {"browseId": browseId} endpoint = "browse" response = self._send_request(endpoint, body) two_columns = nav(response, TWO_COLUMN_RENDERER) header = nav(two_columns, [*TAB_CONTENT, *SECTION_LIST_ITEM, *RESPONSIVE_HEADER]) episode = parse_episode_header(header) episode["description"] = None description_runs = nav( two_columns, ["secondaryContents", *SECTION_LIST_ITEM, *DESCRIPTION_SHELF, "description", "runs"], True, ) if description_runs: episode["description"] = Description.from_runs(description_runs) return episode def get_episodes_playlist(self, playlist_id: str = "RDPN") -> JsonDict: """ Get all episodes in an episodes playlist. Currently the only known playlist is the "New Episodes" auto-generated playlist :param playlist_id: Playlist ID, defaults to "RDPN", the id of the New Episodes playlist :return: Dictionary in the format of :py:func:`get_podcast` """ browseId = "VL" + playlist_id if not playlist_id.startswith("VL") else playlist_id body = {"browseId": browseId} endpoint = "browse" response = self._send_request(endpoint, body) playlist = parse_playlist_header(response) results = nav(response, [*TWO_COLUMN_RENDERER, "secondaryContents", *SECTION_LIST_ITEM, *MUSIC_SHELF]) parse_func: ParseFuncType = lambda contents: parse_content_list(contents, parse_episode, MMRIR) playlist["episodes"] = parse_func(results["contents"]) return playlist