hbmartin commited on
Commit
b1fe428
·
1 Parent(s): 3047241

more type checking

Browse files
README.md CHANGED
@@ -229,6 +229,8 @@ Finally, if you're filing a bug report, the cli contains a switch called ``--bui
229
 
230
  Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
231
 
 
 
232
  #### Virtual environment
233
 
234
  Virtual environment is setup with [pipenv](https://pipenv-fork.readthedocs.io/en/latest/) and can be automatically activated with [direnv](https://direnv.net/docs/installation.html)
 
229
 
230
  Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
231
 
232
+ To run code checking before a PR use ``make test``
233
+
234
  #### Virtual environment
235
 
236
  Virtual environment is setup with [pipenv](https://pipenv-fork.readthedocs.io/en/latest/) and can be automatically activated with [direnv](https://direnv.net/docs/installation.html)
pytube/__main__.py CHANGED
@@ -91,7 +91,7 @@ class YouTube(object):
91
  if not defer_prefetch_init:
92
  self.prefetch_descramble()
93
 
94
- def prefetch_descramble(self):
95
  """Download data, descramble it, and build Stream instances.
96
 
97
  :rtype: None
@@ -100,7 +100,7 @@ class YouTube(object):
100
  self.prefetch()
101
  self.descramble()
102
 
103
- def descramble(self):
104
  """Descramble the stream data and build Stream instances.
105
 
106
  The initialization process takes advantage of Python's
@@ -158,7 +158,7 @@ class YouTube(object):
158
  self.initialize_caption_objects()
159
  logger.info("init finished successfully")
160
 
161
- def prefetch(self):
162
  """Eagerly download all necessary data.
163
 
164
  Eagerly executes all necessary network requests so all other
@@ -185,7 +185,7 @@ class YouTube(object):
185
  self.js_url = extract.js_url(self.watch_html, self.age_restricted)
186
  self.js = request.get(self.js_url)
187
 
188
- def initialize_stream_objects(self, fmt: str):
189
  """Convert manifest data to instances of :class:`Stream <Stream>`.
190
 
191
  Take the unscrambled stream data and uses it to initialize
@@ -208,7 +208,7 @@ class YouTube(object):
208
  )
209
  self.fmt_streams.append(video)
210
 
211
- def initialize_caption_objects(self):
212
  """Populate instances of :class:`Caption <Caption>`.
213
 
214
  Take the unscrambled player response data, and use it to initialize
@@ -230,7 +230,7 @@ class YouTube(object):
230
  self.caption_tracks.append(Caption(caption_track))
231
 
232
  @property
233
- def captions(self):
234
  """Interface to query caption tracks.
235
 
236
  :rtype: :class:`CaptionQuery <CaptionQuery>`.
@@ -238,7 +238,7 @@ class YouTube(object):
238
  return CaptionQuery([c for c in self.caption_tracks])
239
 
240
  @property
241
- def streams(self):
242
  """Interface to query both adaptive (DASH) and progressive streams.
243
 
244
  :rtype: :class:`StreamQuery <StreamQuery>`.
@@ -246,7 +246,7 @@ class YouTube(object):
246
  return StreamQuery([s for s in self.fmt_streams])
247
 
248
  @property
249
- def thumbnail_url(self):
250
  """Get the thumbnail url image.
251
 
252
  :rtype: str
@@ -255,7 +255,7 @@ class YouTube(object):
255
  return self.player_config_args["thumbnail_url"]
256
 
257
  @property
258
- def title(self):
259
  """Get the video title.
260
 
261
  :rtype: str
@@ -264,7 +264,7 @@ class YouTube(object):
264
  return self.player_config_args["title"]
265
 
266
  @property
267
- def description(self):
268
  """Get the video description.
269
 
270
  :rtype: str
 
91
  if not defer_prefetch_init:
92
  self.prefetch_descramble()
93
 
94
+ def prefetch_descramble(self) -> None:
95
  """Download data, descramble it, and build Stream instances.
96
 
97
  :rtype: None
 
100
  self.prefetch()
101
  self.descramble()
102
 
103
+ def descramble(self) -> None:
104
  """Descramble the stream data and build Stream instances.
105
 
106
  The initialization process takes advantage of Python's
 
158
  self.initialize_caption_objects()
159
  logger.info("init finished successfully")
160
 
161
+ def prefetch(self) -> None:
162
  """Eagerly download all necessary data.
163
 
164
  Eagerly executes all necessary network requests so all other
 
185
  self.js_url = extract.js_url(self.watch_html, self.age_restricted)
186
  self.js = request.get(self.js_url)
187
 
188
+ def initialize_stream_objects(self, fmt: str) -> None:
189
  """Convert manifest data to instances of :class:`Stream <Stream>`.
190
 
191
  Take the unscrambled stream data and uses it to initialize
 
208
  )
209
  self.fmt_streams.append(video)
210
 
211
+ def initialize_caption_objects(self) -> None:
212
  """Populate instances of :class:`Caption <Caption>`.
213
 
214
  Take the unscrambled player response data, and use it to initialize
 
230
  self.caption_tracks.append(Caption(caption_track))
231
 
232
  @property
233
+ def captions(self) -> CaptionQuery:
234
  """Interface to query caption tracks.
235
 
236
  :rtype: :class:`CaptionQuery <CaptionQuery>`.
 
238
  return CaptionQuery([c for c in self.caption_tracks])
239
 
240
  @property
241
+ def streams(self) -> StreamQuery:
242
  """Interface to query both adaptive (DASH) and progressive streams.
243
 
244
  :rtype: :class:`StreamQuery <StreamQuery>`.
 
246
  return StreamQuery([s for s in self.fmt_streams])
247
 
248
  @property
249
+ def thumbnail_url(self) -> str:
250
  """Get the thumbnail url image.
251
 
252
  :rtype: str
 
255
  return self.player_config_args["thumbnail_url"]
256
 
257
  @property
258
+ def title(self) -> str:
259
  """Get the video title.
260
 
261
  :rtype: str
 
264
  return self.player_config_args["title"]
265
 
266
  @property
267
+ def description(self) -> str:
268
  """Get the video description.
269
 
270
  :rtype: str
pytube/captions.py CHANGED
@@ -2,6 +2,7 @@
2
  import math
3
  import time
4
  import xml.etree.ElementTree as ElementTree
 
5
 
6
  from pytube import request
7
  from html import unescape
@@ -10,7 +11,7 @@ from html import unescape
10
  class Caption:
11
  """Container for caption tracks."""
12
 
13
- def __init__(self, caption_track):
14
  """Construct a :class:`Caption <Caption>`.
15
 
16
  :param dict caption_track:
@@ -33,7 +34,7 @@ class Caption:
33
  """
34
  return self.xml_caption_to_srt(self.xml_captions)
35
 
36
- def float_to_srt_time_format(self, d):
37
  """Convert decimal durations into proper srt format.
38
 
39
  :rtype: str
 
2
  import math
3
  import time
4
  import xml.etree.ElementTree as ElementTree
5
+ from typing import Dict
6
 
7
  from pytube import request
8
  from html import unescape
 
11
  class Caption:
12
  """Container for caption tracks."""
13
 
14
+ def __init__(self, caption_track: Dict):
15
  """Construct a :class:`Caption <Caption>`.
16
 
17
  :param dict caption_track:
 
34
  """
35
  return self.xml_caption_to_srt(self.xml_captions)
36
 
37
+ def float_to_srt_time_format(self, d: float) -> str:
38
  """Convert decimal durations into proper srt format.
39
 
40
  :rtype: str
pytube/cli.py CHANGED
@@ -8,6 +8,7 @@ import json
8
  import logging
9
  import os
10
  import sys
 
11
 
12
  from pytube import __version__
13
  from pytube import YouTube
@@ -66,7 +67,7 @@ def main():
66
  download(args.url, args.itag)
67
 
68
 
69
- def build_playback_report(url: str):
70
  """Serialize the request data to json for offline debugging.
71
 
72
  :param str url:
@@ -95,13 +96,15 @@ def build_playback_report(url: str):
95
  )
96
 
97
 
98
- def get_terminal_size():
99
  """Return the terminal size in rows and columns."""
100
  rows, columns = os.popen("stty size", "r").read().split()
101
  return int(rows), int(columns)
102
 
103
 
104
- def display_progress_bar(bytes_received, filesize, ch="█", scale=0.55):
 
 
105
  """Display a simple, pretty progress bar.
106
 
107
  Example:
@@ -150,7 +153,7 @@ def on_progress(stream, chunk, file_handle, bytes_remaining):
150
  display_progress_bar(bytes_received, filesize)
151
 
152
 
153
- def download(url: str, itag: str):
154
  """Start downloading a YouTube video.
155
 
156
  :param str url:
@@ -162,7 +165,10 @@ def download(url: str, itag: str):
162
  # TODO(nficano): allow download target to be specified
163
  # TODO(nficano): allow dash itags to be selected
164
  yt = YouTube(url, on_progress_callback=on_progress)
165
- stream = yt.streams.get_by_itag(itag)
 
 
 
166
  print("\n{fn} | {fs} bytes".format(fn=stream.default_filename, fs=stream.filesize,))
167
  try:
168
  stream.download()
@@ -171,7 +177,7 @@ def download(url: str, itag: str):
171
  sys.exit()
172
 
173
 
174
- def display_streams(url: str):
175
  """Probe YouTube video and lists its available formats.
176
 
177
  :param str url:
 
8
  import logging
9
  import os
10
  import sys
11
+ from typing import Tuple
12
 
13
  from pytube import __version__
14
  from pytube import YouTube
 
67
  download(args.url, args.itag)
68
 
69
 
70
+ def build_playback_report(url: str) -> None:
71
  """Serialize the request data to json for offline debugging.
72
 
73
  :param str url:
 
96
  )
97
 
98
 
99
+ def get_terminal_size() -> Tuple[int, int]:
100
  """Return the terminal size in rows and columns."""
101
  rows, columns = os.popen("stty size", "r").read().split()
102
  return int(rows), int(columns)
103
 
104
 
105
+ def display_progress_bar(
106
+ bytes_received: int, filesize: int, ch: str = "█", scale: float = 0.55
107
+ ) -> None:
108
  """Display a simple, pretty progress bar.
109
 
110
  Example:
 
153
  display_progress_bar(bytes_received, filesize)
154
 
155
 
156
+ def download(url: str, itag: str) -> None:
157
  """Start downloading a YouTube video.
158
 
159
  :param str url:
 
165
  # TODO(nficano): allow download target to be specified
166
  # TODO(nficano): allow dash itags to be selected
167
  yt = YouTube(url, on_progress_callback=on_progress)
168
+ stream = yt.streams.get_by_itag(int(itag))
169
+ if stream is None:
170
+ print("Could not find a stream with itag: " + itag)
171
+ sys.exit()
172
  print("\n{fn} | {fs} bytes".format(fn=stream.default_filename, fs=stream.filesize,))
173
  try:
174
  stream.download()
 
177
  sys.exit()
178
 
179
 
180
+ def display_streams(url: str) -> None:
181
  """Probe YouTube video and lists its available formats.
182
 
183
  :param str url:
pytube/exceptions.py CHANGED
@@ -15,7 +15,7 @@ class PytubeError(Exception):
15
  class ExtractError(PytubeError):
16
  """Data extraction based exception."""
17
 
18
- def __init__(self, msg: str, video_id: str = None):
19
  """Construct an instance of a :class:`ExtractError <ExtractError>`.
20
 
21
  :param str msg:
 
15
  class ExtractError(PytubeError):
16
  """Data extraction based exception."""
17
 
18
+ def __init__(self, msg: str, video_id: str = "unknown id"):
19
  """Construct an instance of a :class:`ExtractError <ExtractError>`.
20
 
21
  :param str msg:
pytube/extract.py CHANGED
@@ -4,7 +4,7 @@ import json
4
  from collections import OrderedDict
5
 
6
  from html.parser import HTMLParser
7
- from typing import Any
8
  from urllib.parse import quote
9
  from urllib.parse import urlencode
10
  from pytube.exceptions import RegexMatchError
@@ -95,7 +95,7 @@ def eurl(video_id: str) -> str:
95
  def video_info_url(
96
  video_id: str,
97
  watch_url: str,
98
- watch_html: str,
99
  embed_html: str,
100
  age_restricted: bool,
101
  ) -> str:
 
4
  from collections import OrderedDict
5
 
6
  from html.parser import HTMLParser
7
+ from typing import Any, Optional
8
  from urllib.parse import quote
9
  from urllib.parse import urlencode
10
  from pytube.exceptions import RegexMatchError
 
95
  def video_info_url(
96
  video_id: str,
97
  watch_url: str,
98
+ watch_html: Optional[str],
99
  embed_html: str,
100
  age_restricted: bool,
101
  ) -> str:
pytube/itags.py CHANGED
@@ -1,5 +1,6 @@
1
  # -*- coding: utf-8 -*-
2
  """This module contains a lookup table of YouTube's itag values."""
 
3
 
4
  ITAGS = {
5
  5: ("240p", "64kbps"),
 
1
  # -*- coding: utf-8 -*-
2
  """This module contains a lookup table of YouTube's itag values."""
3
+ from typing import Dict
4
 
5
  ITAGS = {
6
  5: ("240p", "64kbps"),
pytube/mixins.py CHANGED
@@ -17,7 +17,7 @@ from pytube.exceptions import LiveStreamError
17
  logger = logging.getLogger(__name__)
18
 
19
 
20
- def apply_signature(config_args: Dict, fmt: str, js: str):
21
  """Apply the decrypted signature to the stream manifest.
22
 
23
  :param dict config_args:
@@ -67,7 +67,7 @@ def apply_signature(config_args: Dict, fmt: str, js: str):
67
  stream_manifest[i]["url"] = url + "&sig=" + signature
68
 
69
 
70
- def apply_descrambler(stream_data: Dict, key: str):
71
  """Apply various in-place transforms to YouTube's media stream data.
72
 
73
  Creates a ``list`` of dictionaries by string splitting on commas, then
 
17
  logger = logging.getLogger(__name__)
18
 
19
 
20
+ def apply_signature(config_args: Dict, fmt: str, js: str) -> None:
21
  """Apply the decrypted signature to the stream manifest.
22
 
23
  :param dict config_args:
 
67
  stream_manifest[i]["url"] = url + "&sig=" + signature
68
 
69
 
70
+ def apply_descrambler(stream_data: Dict, key: str) -> None:
71
  """Apply various in-place transforms to YouTube's media stream data.
72
 
73
  Creates a ``list`` of dictionaries by string splitting on commas, then
pytube/query.py CHANGED
@@ -206,7 +206,7 @@ class StreamQuery:
206
  sorted(has_attribute, key=lambda s: getattr(s, attribute_name))
207
  )
208
 
209
- def desc(self):
210
  """Sort streams in descending order.
211
 
212
  :rtype: :class:`StreamQuery <StreamQuery>`
@@ -214,7 +214,7 @@ class StreamQuery:
214
  """
215
  return StreamQuery(self.fmt_streams[::-1])
216
 
217
- def asc(self):
218
  """Sort streams in ascending order.
219
 
220
  :rtype: :class:`StreamQuery <StreamQuery>`
@@ -222,7 +222,7 @@ class StreamQuery:
222
  """
223
  return self
224
 
225
- def get_by_itag(self, itag):
226
  """Get the corresponding :class:`Stream <Stream>` for a given itag.
227
 
228
  :param int itag:
@@ -233,12 +233,9 @@ class StreamQuery:
233
  not found.
234
 
235
  """
236
- try:
237
- return self.itag_index[int(itag)]
238
- except KeyError:
239
- pass
240
 
241
- def first(self):
242
  """Get the first :class:`Stream <Stream>` in the results.
243
 
244
  :rtype: :class:`Stream <Stream>` or None
@@ -250,7 +247,7 @@ class StreamQuery:
250
  try:
251
  return self.fmt_streams[0]
252
  except IndexError:
253
- pass
254
 
255
  def last(self):
256
  """Get the last :class:`Stream <Stream>` in the results.
 
206
  sorted(has_attribute, key=lambda s: getattr(s, attribute_name))
207
  )
208
 
209
+ def desc(self) -> "StreamQuery":
210
  """Sort streams in descending order.
211
 
212
  :rtype: :class:`StreamQuery <StreamQuery>`
 
214
  """
215
  return StreamQuery(self.fmt_streams[::-1])
216
 
217
+ def asc(self) -> "StreamQuery":
218
  """Sort streams in ascending order.
219
 
220
  :rtype: :class:`StreamQuery <StreamQuery>`
 
222
  """
223
  return self
224
 
225
+ def get_by_itag(self, itag) -> Optional[Stream]:
226
  """Get the corresponding :class:`Stream <Stream>` for a given itag.
227
 
228
  :param int itag:
 
233
  not found.
234
 
235
  """
236
+ return self.itag_index.get(int(itag))
 
 
 
237
 
238
+ def first(self) -> Optional[Stream]:
239
  """Get the first :class:`Stream <Stream>` in the results.
240
 
241
  :rtype: :class:`Stream <Stream>` or None
 
247
  try:
248
  return self.fmt_streams[0]
249
  except IndexError:
250
+ return None
251
 
252
  def last(self):
253
  """Get the last :class:`Stream <Stream>` in the results.
pytube/streams.py CHANGED
@@ -83,7 +83,7 @@ class Stream(object):
83
  # streams return NoneType for audio/video depending.
84
  self.video_codec, self.audio_codec = self.parse_codecs()
85
 
86
- def set_attributes_from_dict(self, dct: Dict):
87
  """Set class attributes from dictionary items.
88
 
89
  :rtype: None
@@ -199,7 +199,12 @@ class Stream(object):
199
  filename = safe_filename(self.title)
200
  return "{filename}.{s.subtype}".format(filename=filename, s=self)
201
 
202
- def download(self, output_path=None, filename=None, filename_prefix=None):
 
 
 
 
 
203
  """Write the media stream to disk.
204
 
205
  :param output_path:
@@ -248,7 +253,7 @@ class Stream(object):
248
  self.on_complete(fh)
249
  return fp
250
 
251
- def stream_to_buffer(self):
252
  """Write the media stream to buffer
253
 
254
  :rtype: io.BytesIO buffer
 
83
  # streams return NoneType for audio/video depending.
84
  self.video_codec, self.audio_codec = self.parse_codecs()
85
 
86
+ def set_attributes_from_dict(self, dct: Dict) -> None:
87
  """Set class attributes from dictionary items.
88
 
89
  :rtype: None
 
199
  filename = safe_filename(self.title)
200
  return "{filename}.{s.subtype}".format(filename=filename, s=self)
201
 
202
+ def download(
203
+ self,
204
+ output_path: Optional[str] = None,
205
+ filename: Optional[str] = None,
206
+ filename_prefix: Optional[str] = None,
207
+ ) -> str:
208
  """Write the media stream to disk.
209
 
210
  :param output_path:
 
253
  self.on_complete(fh)
254
  return fp
255
 
256
+ def stream_to_buffer(self) -> io.BytesIO:
257
  """Write the media stream to buffer
258
 
259
  :rtype: io.BytesIO buffer