Skip to content
This repository was archived by the owner on Jun 30, 2022. It is now read-only.

Commit

Permalink
👌 Updated async Playlist class to have getNextVideos
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmercerind committed Mar 24, 2021
1 parent 0ec5120 commit 1d51610
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 5 deletions.
49 changes: 49 additions & 0 deletions youtubesearchpython/__future__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ print(videosResult)
"height": 270
}
],
"richThumbnail": {
"url": "https://i.ytimg.com/an_webp/K4DyBUG242c/mqdefault_6s.webp?du=3000&sqp=COCn64IG&rs=AOn4CLBeYxeJ_5lME4jXbFQlv7kIN37kmw",
"width": 320,
"height": 180
},
"descriptionSnippet": [
{
"text": "NCS: Music Without Limitations NCS Spotify: http://spoti.fi/NCS Free Download / Stream: http://ncs.io/onandon \u25bd Connect with\u00a0..."
Expand Down Expand Up @@ -108,6 +113,11 @@ print(videosResult)
"height": 270
}
],
"richThumbnail": {
"url": "https://i.ytimg.com/an_webp/K4DyBUG242c/mqdefault_6s.webp?du=3000&sqp=COCn64IG&rs=AOn4CLBeYxeJ_5lME4jXbFQlv7kIN37kmw",
"width": 320,
"height": 180
},
"descriptionSnippet": [
{
"text": "NCS: Music Without Limitations NCS Spotify: http://spoti.fi/NCS Free Download / Stream: http://ncs.io/mortals Connect with NCS:\u00a0..."
Expand Down Expand Up @@ -518,6 +528,11 @@ print(customResult)
"height": 404
}
],
"richThumbnail": {
"url": "https://i.ytimg.com/an_webp/K4DyBUG242c/mqdefault_6s.webp?du=3000&sqp=COCn64IG&rs=AOn4CLBeYxeJ_5lME4jXbFQlv7kIN37kmw",
"width": 320,
"height": 180
},
"descriptionSnippet": [
{
"text": "Don't forget to like & share if you enjoy it."
Expand Down Expand Up @@ -856,6 +871,40 @@ print(videoFormats)

</details>


#### More to the playlists

You can directly instanciate the `Playlist` class as follows to access its information & videos in the `info` and `videos` fields respectively.

YouTube offers only 100 videos in a single request, for getting more videos present in the playlist, you can check `hasMoreVideos` bool to see if playlist contains more videos.
If playlist has more videos, then you can call `getNextVideos` to fetch more videos.

Example below demonstrates a simple way to retrive all videos of a playlist.

```python
playlist = Playlist('https://www.youtube.com/playlist?list=PLRBp0Fe2GpgmsW46rJyudVFlY6IYjFBIK')
while playlist.hasMoreVideos:
print('Getting more videos...')
await playlist.getNextVideos()
print(f'Videos Retrieved: {len(playlist.videos)}')

print('Found all the videos.')
```

<details>
<summary> Example Result</summary>

```bash
Videos Retrieved: 100
Getting more videos...
Videos Retrieved: 200
Getting more videos...
Videos Retrieved: 209
Found all the videos.
```

</details>

#### Getting search suggestions

```python
Expand Down
41 changes: 41 additions & 0 deletions youtubesearchpython/__future__/extras.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
from youtubesearchpython.__future__.internal.extras import *
from youtubesearchpython.__future__.internal.constants import *

Expand Down Expand Up @@ -571,6 +572,46 @@ async def get(query: str, language: str = 'en', region: str = 'US'):


class Playlist:
'''Fetches information and videos for the given playlist link.
Returns None if playlist is unavailable.
The information of the playlist can be accessed in the `info` field of the class.
And the retrieved videos of the playlist are present inside the `videos` field of the class, as a list.
Due to limit of being able to retrieve only 100 videos at a time, call `getNextVideos` method to get more videos of the playlist,
which will be appended to the `videos` list.
`hasMoreVideos` stores boolean to indicate whether more videos are present in the playlist.
If this field is True, then you can call `getNextVideos` method again to get more videos of the playlist.
Args:
playlistLink (str): link of the playlist on YouTube.
'''
playlistLink = None
videos = []
info = None
hasMoreVideos = True
__playlist = None

def __init__(self, playlistLink: str):
self.playlistLink = playlistLink

'''Fetches more susequent videos of the playlist, and appends to the `videos` list.
`hasMoreVideos` bool indicates whether more videos can be fetched or not.
'''
async def getNextVideos(self) -> None:
if not self.info:
self.__playlist = PlaylistInternal(self.playlistLink, None)
await self.__playlist.get()
self.info = copy.deepcopy(self.__playlist.playlistComponent)
self.videos = self.__playlist.playlistComponent['videos']
self.hasMoreVideos = self.__playlist.continuationKey != None
self.info.pop('videos')
else:
await self.__playlist.next()
self.videos = self.__playlist.playlistComponent['videos']
self.hasMoreVideos = self.__playlist.continuationKey != None

@staticmethod
async def get(playlistLink: str) -> Union[dict, str, None]:
'''Fetches information and videos for the given playlist link.
Expand Down
61 changes: 59 additions & 2 deletions youtubesearchpython/__future__/internal/extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,24 @@ async def __getValue(self, source: dict, path: List[str]) -> Union[str, int, dic


class PlaylistInternal:
playlistComponent = None
result = None
continuationKey = None

def __init__(self, playlistLink: str, componentMode: str):
self.playlistLink = playlistLink
self.componentMode = componentMode

async def get(self):
statusCode = await self.__makeRequest(self.playlistLink)
await self.__makeRequest(self.playlistLink)
await self.__getComponents()

async def __makeRequest(self, playlistLink: str) -> int:
async def next(self):
if self.continuationKey:
await self.__makeNextRequest()
await self.__getNextComponents()

async def __makeRequest(self, playlistLink: str) -> None:
playlistLink.strip('/')
try:
async with httpx.AsyncClient() as client:
Expand All @@ -138,6 +146,24 @@ async def __makeRequest(self, playlistLink: str) -> int:
self.responseSource = response.json()
except:
raise Exception('ERROR: Could not make request.')

async def __makeNextRequest(self, requestBody = requestPayload) -> None:
requestBody['continuation'] = self.continuationKey
try:
async with httpx.AsyncClient() as client:
response = await client.post(
'https://www.youtube.com/youtubei/v1/browse',
params = {
'key': searchKey,
},
headers = {
'User-Agent': userAgent,
},
json = requestBody,
)
self.responseSource = response.json()
except:
raise Exception('ERROR: Could not make request.')

async def __getComponents(self) -> None:
for response in self.responseSource:
Expand All @@ -149,6 +175,35 @@ async def __getComponents(self) -> None:
if not playlistElement['info']:
raise Exception('ERROR: Could not parse YouTube response.')
self.playlistComponent = await self.__getPlaylistComponent(playlistElement, self.componentMode)

async def __getNextComponents(self) -> None:
self.continuationKey = None
playlistComponent = {
'videos': [],
}
continuationElements = await self.__getValue(self.responseSource, ['onResponseReceivedActions', 0, 'appendContinuationItemsAction', 'continuationItems'])
for videoElement in continuationElements:
if playlistVideoKey in videoElement.keys():
videoComponent = {
'id': await self.__getValue(videoElement, [playlistVideoKey, 'videoId']),
'title': await self.__getValue(videoElement, [playlistVideoKey, 'title', 'runs', 0, 'text']),
'thumbnails': await self.__getValue(videoElement, [playlistVideoKey, 'thumbnail', 'thumbnails']),
'channel': {
'name': await self.__getValue(videoElement, [playlistVideoKey, 'shortBylineText', 'runs', 0, 'text']),
'id': await self.__getValue(videoElement, [playlistVideoKey, 'shortBylineText', 'runs', 0, 'navigationEndpoint', 'browseEndpoint', 'browseId']),
},
'duration': await self.__getValue(videoElement, [playlistVideoKey, 'lengthText', 'simpleText']),
'accessibility': {
'title': await self.__getValue(videoElement, [playlistVideoKey, 'title', 'accessibility', 'accessibilityData', 'label']),
'duration': await self.__getValue(videoElement, [playlistVideoKey, 'lengthText', 'accessibility', 'accessibilityData', 'label']),
},
}
playlistComponent['videos'].append(
videoComponent
)
if continuationItemKey in videoElement.keys():
self.continuationKey = await self.__getValue(videoElement, continuationKeyPath)
self.playlistComponent['videos'].extend(playlistComponent['videos'])

async def __getPlaylistComponent(self, element: dict, mode: str) -> dict:
playlistComponent = {}
Expand Down Expand Up @@ -199,6 +254,8 @@ async def __getPlaylistComponent(self, element: dict, mode: str) -> dict:
playlistComponent['videos'].append(
videoComponent
)
if continuationItemKey in videoElement.keys():
self.continuationKey = await self.__getValue(videoElement, continuationKeyPath)
return playlistComponent

async def __getValue(self, source: dict, path: List[str]) -> Union[str, int, dict, None]:
Expand Down
3 changes: 0 additions & 3 deletions youtubesearchpython/__future__/internal/streamurlfetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ async def _getDecipheredURLs(self, videoFormats: dict) -> None:
'eurl': f'https://youtube.googleapis.com/v/{videoFormats["id"]}',
'sts': None,
},
headers = {
'User-Agent': userAgent,
},
timeout = None,
)
''' Google returns content as a query string instead of a JSON. '''
Expand Down

0 comments on commit 1d51610

Please sign in to comment.