Taylor Fox Dahlin commited on
Commit
be07012
·
unverified ·
1 Parent(s): 9f9723b

Improvement/channel docs (#1011)

Browse files

* Docstring improvements

* Documentation for the channel object

* Additional metadata available from the channel object, with unit tests

docs/api.rst CHANGED
@@ -20,6 +20,13 @@ Playlist Object
20
  :members:
21
  :inherited-members:
22
 
 
 
 
 
 
 
 
23
  Stream Object
24
  -------------
25
 
 
20
  :members:
21
  :inherited-members:
22
 
23
+ Channel Object
24
+ --------------
25
+
26
+ .. autoclass:: pytube.contrib.channel.Channel
27
+ :members:
28
+ :inherited-members:
29
+
30
  Stream Object
31
  -------------
32
 
docs/index.rst CHANGED
@@ -58,6 +58,7 @@ of pytube.
58
  user/streams
59
  user/captions
60
  user/playlist
 
61
  user/cli
62
  user/exceptions
63
 
 
58
  user/streams
59
  user/captions
60
  user/playlist
61
+ user/channel
62
  user/cli
63
  user/exceptions
64
 
docs/user/channel.rst ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .. _channel:
2
+
3
+ Using Channels
4
+ ==============
5
+
6
+ This guide will walk you through the basics of working with pytube Channels.
7
+
8
+ Creating a Channel
9
+ ------------------
10
+
11
+ Using pytube to interact with channels is similar to interacting with playlists.
12
+ Begin by importing the Channel class::
13
+
14
+ >>> from pytube import Channel
15
+
16
+ Now let's create a channel object. You can do this by initializing the object with a channel URL::
17
+
18
+ >>> c = Channel('https://www.youtube.com/c/ProgrammingKnowledge')
19
+
20
+ Or you can create one from a link to the channel's video page::
21
+
22
+ >>> c = Channel('https://www.youtube.com/c/ProgrammingKnowledge/videos')
23
+
24
+ Now, we have a :class:`Channel <pytube.Channel>` object called ``c`` that we can do some work with.
25
+
26
+ Interacting with a channel
27
+ --------------------------
28
+
29
+ Fundamentally, a Channel object is just a container for YouTube objects.
30
+
31
+ If, for example, we wanted to download all of the videos created by a channel, we would do the following::
32
+
33
+ >>> print(f'Downloading videos by: {c.channel_name}')
34
+ Downloading videos by: ProgrammingKnowledge
35
+ >>> for video in c.videos:
36
+ >>> video.streams.first().download()
37
+
38
+ Or, if we're only interested in the URLs for the videos, we can look at those as well::
39
+
40
+ >>> for url in c.video_urls[:3]:
41
+ >>> print(url)
42
+ ['https://www.youtube.com/watch?v=tMqMU1U2MCU',
43
+ 'https://www.youtube.com/watch?v=YBfInrtWq8Y',
44
+ 'https://www.youtube.com/watch?v=EP9WrMw6Gzg']
45
+
46
+ And that's basically all there is to it!
docs/user/playlist.rst CHANGED
@@ -39,8 +39,8 @@ Or, if we're only interested in the URLs for the videos, we can look at those as
39
 
40
  >>> for url in p.video_urls[:3]:
41
  >>> print(url)
42
- Python Tutorial for Beginners 1 - Getting Started and Installing Python (For Absolute Beginners)
43
- Python Tutorial for Beginners 2 - Numbers and Math in Python
44
- Python Tutorial for Beginners 3 - Variables and Inputs
45
 
46
- And that's basically all there is to it! The Playlist class is relatively straightforward.
 
39
 
40
  >>> for url in p.video_urls[:3]:
41
  >>> print(url)
42
+ ['https://www.youtube.com/watch?v=41qgdwd3zAg',
43
+ 'https://www.youtube.com/watch?v=Lbs7vmx3YwU',
44
+ 'https://www.youtube.com/watch?v=YtX-Rmoea0M']
45
 
46
+ And that's basically all there is to it!
pytube/__main__.py CHANGED
@@ -36,8 +36,6 @@ class YouTube:
36
 
37
  :param str url:
38
  A valid YouTube watch URL.
39
- :param bool defer_prefetch_init:
40
- Defers executing any network requests.
41
  :param func on_progress_callback:
42
  (Optional) User defined callback function for stream download
43
  progress events.
@@ -420,6 +418,7 @@ class YouTube:
420
  @property
421
  def keywords(self) -> List[str]:
422
  """Get the video keywords.
 
423
  :rtype: List[str]
424
  """
425
  return self.player_response.get('videoDetails', {}).get('keywords', [])
@@ -427,6 +426,7 @@ class YouTube:
427
  @property
428
  def channel_id(self) -> str:
429
  """Get the video poster's channel id.
 
430
  :rtype: str
431
  """
432
  return self.player_response.get('videoDetails', {}).get('channelId', None)
@@ -434,6 +434,7 @@ class YouTube:
434
  @property
435
  def channel_url(self) -> str:
436
  """Construct the channel url for the video's poster from the channel id.
 
437
  :rtype: str
438
  """
439
  return f'https://www.youtube.com/channel/{self.channel_id}'
 
36
 
37
  :param str url:
38
  A valid YouTube watch URL.
 
 
39
  :param func on_progress_callback:
40
  (Optional) User defined callback function for stream download
41
  progress events.
 
418
  @property
419
  def keywords(self) -> List[str]:
420
  """Get the video keywords.
421
+
422
  :rtype: List[str]
423
  """
424
  return self.player_response.get('videoDetails', {}).get('keywords', [])
 
426
  @property
427
  def channel_id(self) -> str:
428
  """Get the video poster's channel id.
429
+
430
  :rtype: str
431
  """
432
  return self.player_response.get('videoDetails', {}).get('channelId', None)
 
434
  @property
435
  def channel_url(self) -> str:
436
  """Construct the channel url for the video's poster from the channel id.
437
+
438
  :rtype: str
439
  """
440
  return f'https://www.youtube.com/channel/{self.channel_id}'
pytube/contrib/channel.py CHANGED
@@ -12,6 +12,13 @@ logger = logging.getLogger(__name__)
12
 
13
  class Channel(Playlist):
14
  def __init__(self, url: str, proxies: Optional[Dict[str, str]] = None):
 
 
 
 
 
 
 
15
  super().__init__(url, proxies)
16
 
17
  self.channel_uri = extract.channel_name(url)
@@ -19,6 +26,7 @@ class Channel(Playlist):
19
  self.channel_url = (
20
  f"https://www.youtube.com{self.channel_uri}"
21
  )
 
22
  self.videos_url = self.channel_url + '/videos'
23
  self.playlists_url = self.channel_url + '/playlists'
24
  self.community_url = self.channel_url + '/community'
@@ -31,8 +39,40 @@ class Channel(Playlist):
31
  self._featured_channels_html = None
32
  self._about_html = None
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  @property
35
  def html(self):
 
 
 
 
36
  if self._html:
37
  return self._html
38
  self._html = request.get(self.videos_url)
@@ -40,6 +80,12 @@ class Channel(Playlist):
40
 
41
  @property
42
  def playlists_html(self):
 
 
 
 
 
 
43
  if self._playlists_html:
44
  return self._playlists_html
45
  else:
@@ -48,6 +94,12 @@ class Channel(Playlist):
48
 
49
  @property
50
  def community_html(self):
 
 
 
 
 
 
51
  if self._community_html:
52
  return self._community_html
53
  else:
@@ -56,6 +108,12 @@ class Channel(Playlist):
56
 
57
  @property
58
  def featured_channels_html(self):
 
 
 
 
 
 
59
  if self._featured_channels_html:
60
  return self._featured_channels_html
61
  else:
@@ -64,6 +122,12 @@ class Channel(Playlist):
64
 
65
  @property
66
  def about_html(self):
 
 
 
 
 
 
67
  if self._about_html:
68
  return self._about_html
69
  else:
 
12
 
13
  class Channel(Playlist):
14
  def __init__(self, url: str, proxies: Optional[Dict[str, str]] = None):
15
+ """Construct a :class:`Channel <Channel>`.
16
+
17
+ :param str url:
18
+ A valid YouTube channel URL.
19
+ :param proxies:
20
+ (Optional) A dictionary of proxies to use for web requests.
21
+ """
22
  super().__init__(url, proxies)
23
 
24
  self.channel_uri = extract.channel_name(url)
 
26
  self.channel_url = (
27
  f"https://www.youtube.com{self.channel_uri}"
28
  )
29
+
30
  self.videos_url = self.channel_url + '/videos'
31
  self.playlists_url = self.channel_url + '/playlists'
32
  self.community_url = self.channel_url + '/community'
 
39
  self._featured_channels_html = None
40
  self._about_html = None
41
 
42
+ @property
43
+ def channel_name(self):
44
+ """Get the name of the YouTube channel.
45
+
46
+ :rtype: str
47
+ """
48
+ return self.initial_data['metadata']['channelMetadataRenderer']['title']
49
+
50
+ @property
51
+ def channel_id(self):
52
+ """Get the ID of the YouTube channel.
53
+
54
+ This will return the underlying ID, not the vanity URL.
55
+
56
+ :rtype: str
57
+ """
58
+ return self.initial_data['metadata']['channelMetadataRenderer']['externalId']
59
+
60
+ @property
61
+ def vanity_url(self):
62
+ """Get the vanity URL of the YouTube channel.
63
+
64
+ Returns None if it doesn't exist.
65
+
66
+ :rtype: str
67
+ """
68
+ return self.initial_data['metadata']['channelMetadataRenderer'].get('vanityChannelUrl', None) # noqa:E501
69
+
70
  @property
71
  def html(self):
72
+ """Get the html for the /videos page.
73
+
74
+ :rtype: str
75
+ """
76
  if self._html:
77
  return self._html
78
  self._html = request.get(self.videos_url)
 
80
 
81
  @property
82
  def playlists_html(self):
83
+ """Get the html for the /playlists page.
84
+
85
+ Currently unused for any functionality.
86
+
87
+ :rtype: str
88
+ """
89
  if self._playlists_html:
90
  return self._playlists_html
91
  else:
 
94
 
95
  @property
96
  def community_html(self):
97
+ """Get the html for the /community page.
98
+
99
+ Currently unused for any functionality.
100
+
101
+ :rtype: str
102
+ """
103
  if self._community_html:
104
  return self._community_html
105
  else:
 
108
 
109
  @property
110
  def featured_channels_html(self):
111
+ """Get the html for the /channels page.
112
+
113
+ Currently unused for any functionality.
114
+
115
+ :rtype: str
116
+ """
117
  if self._featured_channels_html:
118
  return self._featured_channels_html
119
  else:
 
122
 
123
  @property
124
  def about_html(self):
125
+ """Get the html for the /about page.
126
+
127
+ Currently unused for any functionality.
128
+
129
+ :rtype: str
130
+ """
131
  if self._about_html:
132
  return self._about_html
133
  else:
pytube/contrib/playlist.py CHANGED
@@ -30,6 +30,10 @@ class Playlist(Sequence):
30
 
31
  @property
32
  def playlist_id(self):
 
 
 
 
33
  if self._playlist_id:
34
  return self._playlist_id
35
  self._playlist_id = extract.playlist_id(self._input_url)
@@ -37,10 +41,18 @@ class Playlist(Sequence):
37
 
38
  @property
39
  def playlist_url(self):
 
 
 
 
40
  return f"https://www.youtube.com/playlist?list={self.playlist_id}"
41
 
42
  @property
43
  def html(self):
 
 
 
 
44
  if self._html:
45
  return self._html
46
  self._html = request.get(self.playlist_url)
@@ -48,6 +60,10 @@ class Playlist(Sequence):
48
 
49
  @property
50
  def ytcfg(self):
 
 
 
 
51
  if self._ytcfg:
52
  return self._ytcfg
53
  self._ytcfg = extract.get_ytcfg(self.html)
@@ -55,6 +71,10 @@ class Playlist(Sequence):
55
 
56
  @property
57
  def initial_data(self):
 
 
 
 
58
  if self._initial_data:
59
  return self._initial_data
60
  else:
@@ -63,6 +83,10 @@ class Playlist(Sequence):
63
 
64
  @property
65
  def sidebar_info(self):
 
 
 
 
66
  if self._sidebar_info:
67
  return self._sidebar_info
68
  else:
@@ -72,6 +96,10 @@ class Playlist(Sequence):
72
 
73
  @property
74
  def yt_api_key(self):
 
 
 
 
75
  return self.ytcfg['INNERTUBE_API_KEY']
76
 
77
  def _paginate(
 
30
 
31
  @property
32
  def playlist_id(self):
33
+ """Get the playlist id.
34
+
35
+ :rtype: str
36
+ """
37
  if self._playlist_id:
38
  return self._playlist_id
39
  self._playlist_id = extract.playlist_id(self._input_url)
 
41
 
42
  @property
43
  def playlist_url(self):
44
+ """Get the base playlist url.
45
+
46
+ :rtype: str
47
+ """
48
  return f"https://www.youtube.com/playlist?list={self.playlist_id}"
49
 
50
  @property
51
  def html(self):
52
+ """Get the playlist page html.
53
+
54
+ :rtype: str
55
+ """
56
  if self._html:
57
  return self._html
58
  self._html = request.get(self.playlist_url)
 
60
 
61
  @property
62
  def ytcfg(self):
63
+ """Extract the ytcfg from the playlist page html.
64
+
65
+ :rtype: dict
66
+ """
67
  if self._ytcfg:
68
  return self._ytcfg
69
  self._ytcfg = extract.get_ytcfg(self.html)
 
71
 
72
  @property
73
  def initial_data(self):
74
+ """Extract the initial data from the playlist page html.
75
+
76
+ :rtype: dict
77
+ """
78
  if self._initial_data:
79
  return self._initial_data
80
  else:
 
83
 
84
  @property
85
  def sidebar_info(self):
86
+ """Extract the sidebar info from the playlist page html.
87
+
88
+ :rtype: dict
89
+ """
90
  if self._sidebar_info:
91
  return self._sidebar_info
92
  else:
 
96
 
97
  @property
98
  def yt_api_key(self):
99
+ """Extract the INNERTUBE_API_KEY from the playlist ytcfg.
100
+
101
+ :rtype: str
102
+ """
103
  return self.ytcfg['INNERTUBE_API_KEY']
104
 
105
  def _paginate(
tests/contrib/test_channel.py CHANGED
@@ -16,12 +16,39 @@ def test_init_with_url(request_get, channel_videos_html):
16
 
17
 
18
  @mock.patch('pytube.request.get')
19
- def test_channel_name(request_get, channel_videos_html):
20
  request_get.return_value = channel_videos_html
21
 
22
  c = Channel('https://www.youtube.com/c/ProgrammingKnowledge/videos')
23
  assert c.channel_uri == '/c/ProgrammingKnowledge'
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  @mock.patch('pytube.request.get')
27
  def test_channel_video_list(request_get, channel_videos_html):
 
16
 
17
 
18
  @mock.patch('pytube.request.get')
19
+ def test_channel_uri(request_get, channel_videos_html):
20
  request_get.return_value = channel_videos_html
21
 
22
  c = Channel('https://www.youtube.com/c/ProgrammingKnowledge/videos')
23
  assert c.channel_uri == '/c/ProgrammingKnowledge'
24
 
25
+ c = Channel('https://www.youtube.com/channel/UCs6nmQViDpUw0nuIx9c_WvA/videos')
26
+ assert c.channel_uri == '/channel/UCs6nmQViDpUw0nuIx9c_WvA'
27
+
28
+
29
+ @mock.patch('pytube.request.get')
30
+ def test_channel_name(request_get, channel_videos_html):
31
+ request_get.return_value = channel_videos_html
32
+
33
+ c = Channel('https://www.youtube.com/c/ProgrammingKnowledge/videos')
34
+ assert c.channel_name == 'ProgrammingKnowledge'
35
+
36
+
37
+ @mock.patch('pytube.request.get')
38
+ def test_channel_id(request_get, channel_videos_html):
39
+ request_get.return_value = channel_videos_html
40
+
41
+ c = Channel('https://www.youtube.com/c/ProgrammingKnowledge/videos')
42
+ assert c.channel_id == 'UCs6nmQViDpUw0nuIx9c_WvA'
43
+
44
+
45
+ @mock.patch('pytube.request.get')
46
+ def test_channel_vanity_url(request_get, channel_videos_html):
47
+ request_get.return_value = channel_videos_html
48
+
49
+ c = Channel('https://www.youtube.com/c/ProgrammingKnowledge/videos')
50
+ assert c.vanity_url == 'http://www.youtube.com/c/ProgrammingKnowledge'
51
+
52
 
53
  @mock.patch('pytube.request.get')
54
  def test_channel_video_list(request_get, channel_videos_html):