nficano commited on
Commit
97f04a9
·
1 Parent(s): 0e6edd9

more coverage

Browse files
docs/index.rst CHANGED
@@ -36,7 +36,7 @@ Release v\ |version|. (:ref:`Installation <install>`)
36
  >>> YouTube('http://youtube.com/watch?v=9bZkp7q19f0').streams.first().download()
37
  >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
38
  >>> yt.streams
39
- ... .filter(progressive=True, subtype='mp4')
40
  ... .order_by('resolution')
41
  ... .desc()
42
  ... .first()
 
36
  >>> YouTube('http://youtube.com/watch?v=9bZkp7q19f0').streams.first().download()
37
  >>> yt = YouTube('http://youtube.com/watch?v=9bZkp7q19f0')
38
  >>> yt.streams
39
+ ... .filter(progressive=True, file_extension='mp4')
40
  ... .order_by('resolution')
41
  ... .desc()
42
  ... .first()
pytube/cipher.py CHANGED
@@ -51,7 +51,7 @@ def get_transform_plan(js):
51
  :param str js:
52
  The contents of the base.js asset file.
53
 
54
- Sample Output:
55
 
56
  .. code-block:: python
57
 
@@ -85,7 +85,7 @@ def get_transform_object(js, var):
85
  The obfuscated variable name that stores an object with all functions
86
  that descrambles the signature.
87
 
88
- Sample Output:
89
 
90
  .. code-block:: python
91
 
@@ -138,7 +138,7 @@ def reverse(arr, b):
138
  This method takes an unused ``b`` variable as their transform functions
139
  universally sent two arguments.
140
 
141
- Example usage:
142
  ~~~~~~~~~~~~~~
143
  >>> reverse([1, 2, 3, 4])
144
  [4, 3, 2, 1]
@@ -155,7 +155,7 @@ def splice(arr, b):
155
 
156
  function(a, b) { a.splice(0, b) }.
157
 
158
- Example usage:
159
  ~~~~~~~~~~~~~~
160
  >>> splice([1, 2, 3, 4], 2)
161
  [1, 2]
@@ -172,7 +172,7 @@ def swap(arr, b):
172
 
173
  function(a, b) { var c=a[0];a[0]=a[b%a.length];a[b]=c }.
174
 
175
- Example usage:
176
  ~~~~~~~~~~~~~~
177
  >>> swap([1, 2, 3, 4], 2)
178
  [3, 2, 1, 4]
@@ -209,7 +209,7 @@ def map_functions(js_func):
209
  def parse_function(js_func):
210
  """Parse the Javascript transform function.
211
 
212
- Break a JavaScript transform function down into a two element tuple
213
  containing the function name and some integer-based argument.
214
 
215
  Sample input:
@@ -228,21 +228,22 @@ def parse_function(js_func):
228
  The JavaScript version of the transform function.
229
 
230
  """
231
- pattern = r'\w+\.(\w+)\(\w,(\d+)\)'
232
  logger.debug('parsing transform function')
233
- return regex_search(pattern, js_func, groups=True)
234
 
235
 
236
  def get_signature(js, ciphered_signature):
237
  """Decipher the signature.
238
 
239
- Taking the ciphered signature, applies the transform functions and
240
- returns the decrypted version.
241
 
242
  :param str js:
243
  The contents of the base.js asset file.
244
  :param str ciphered_signature:
245
  The ciphered signature sent in the ``player_config``.
 
 
 
246
 
247
  """
248
  tplan = get_transform_plan(js)
 
51
  :param str js:
52
  The contents of the base.js asset file.
53
 
54
+ **Sample Output**:
55
 
56
  .. code-block:: python
57
 
 
85
  The obfuscated variable name that stores an object with all functions
86
  that descrambles the signature.
87
 
88
+ **Sample Output**:
89
 
90
  .. code-block:: python
91
 
 
138
  This method takes an unused ``b`` variable as their transform functions
139
  universally sent two arguments.
140
 
141
+ **Example usage**:
142
  ~~~~~~~~~~~~~~
143
  >>> reverse([1, 2, 3, 4])
144
  [4, 3, 2, 1]
 
155
 
156
  function(a, b) { a.splice(0, b) }.
157
 
158
+ **Example usage**:
159
  ~~~~~~~~~~~~~~
160
  >>> splice([1, 2, 3, 4], 2)
161
  [1, 2]
 
172
 
173
  function(a, b) { var c=a[0];a[0]=a[b%a.length];a[b]=c }.
174
 
175
+ **Example usage**:
176
  ~~~~~~~~~~~~~~
177
  >>> swap([1, 2, 3, 4], 2)
178
  [3, 2, 1, 4]
 
209
  def parse_function(js_func):
210
  """Parse the Javascript transform function.
211
 
212
+ Break a JavaScript transform function down into a two element ``tuple``
213
  containing the function name and some integer-based argument.
214
 
215
  Sample input:
 
228
  The JavaScript version of the transform function.
229
 
230
  """
 
231
  logger.debug('parsing transform function')
232
+ return regex_search(r'\w+\.(\w+)\(\w,(\d+)\)', js_func, groups=True)
233
 
234
 
235
  def get_signature(js, ciphered_signature):
236
  """Decipher the signature.
237
 
238
+ Taking the ciphered signature, applies the transform functions.
 
239
 
240
  :param str js:
241
  The contents of the base.js asset file.
242
  :param str ciphered_signature:
243
  The ciphered signature sent in the ``player_config``.
244
+ :rtype: str
245
+ :returns:
246
+ Decrypted signature required to download the media content.
247
 
248
  """
249
  tplan = get_transform_plan(js)
pytube/cli.py CHANGED
@@ -139,7 +139,7 @@ def on_progress(stream, file_handle, bytes_remaining):
139
  :param file_handle:
140
  The file handle where the media is being written to.
141
  :type file_handle:
142
- :class:`_io.BufferedWriter`
143
  :param int bytes_remaining:
144
  How many bytes have been downloaded.
145
 
 
139
  :param file_handle:
140
  The file handle where the media is being written to.
141
  :type file_handle:
142
+ :py:class:`io.BufferedWriter`
143
  :param int bytes_remaining:
144
  How many bytes have been downloaded.
145
 
pytube/extract.py CHANGED
@@ -75,12 +75,17 @@ def js_url(watch_html):
75
  def mime_type_codec(mime_type_codec):
76
  """Parse the type data.
77
 
78
- Breaks up the data in the ``type`` key of the manifest, which contains mime
79
- type and codecs serialized together (e.g.: 'audio/webm; codecs="opus"'),
80
- and splits them into separate elements. (e.g.: 'audio/webm', ['opus'])
81
 
82
  :param str mime_type_codec:
83
- String containing mime type and codecs.
 
 
 
 
 
84
 
85
  """
86
  pattern = r'(\w+\/\w+)\;\scodecs=\"([a-zA-Z-0-9.,\s]*)\"'
@@ -97,7 +102,9 @@ def get_ytplayer_config(watch_html):
97
 
98
  :param str watch_html:
99
  The html contents of the watch page.
100
-
 
 
101
  """
102
  pattern = r';ytplayer\.config\s*=\s*({.*?});'
103
  yt_player_config = regex_search(pattern, watch_html, group=1)
 
75
  def mime_type_codec(mime_type_codec):
76
  """Parse the type data.
77
 
78
+ Breaks up the data in the ``type`` key of the manifest, which contains the
79
+ mime type and codecs serialized together, and splits them into separate
80
+ elements.
81
 
82
  :param str mime_type_codec:
83
+ String containing mime type and codecs, for example:
84
+ 'audio/webm; codecs="opus"'
85
+ :rtype: tuple
86
+ :returns:
87
+ The mime type and a list of codecs, for example:
88
+ ('audio/webm', ['opus'])
89
 
90
  """
91
  pattern = r'(\w+\/\w+)\;\scodecs=\"([a-zA-Z-0-9.,\s]*)\"'
 
102
 
103
  :param str watch_html:
104
  The html contents of the watch page.
105
+ :rtype: str
106
+ :returns:
107
+ Substring of the html containing the serialized manifest data.
108
  """
109
  pattern = r';ytplayer\.config\s*=\s*({.*?});'
110
  yt_player_config = regex_search(pattern, watch_html, group=1)
pytube/query.py CHANGED
@@ -21,43 +21,82 @@ class StreamQuery:
21
  ):
22
  """Apply the given filtering criterion.
23
 
24
- :param int fps:
25
- (optional) The frames per second (30 or 60)
26
- :param str resolution:
 
 
 
27
  (optional) Alias to ``res``.
28
- :param str res:
29
- (optional) The video resolution (e.g.: 480p, 720p, 1080p)
30
- :param str mime_type:
 
 
 
 
 
 
31
  (optional) Two-part identifier for file formats and format contents
32
- composed of a "type", a "subtype" (e.g.: video/mp4).
33
- :param str type:
34
- (optional) Type part of the ``mime_type`` (e.g.: "audio", "video").
35
- :param str subtype:
36
- (optional) Sub-type part of the ``mime_type`` (e.g.: "mp4", "mov").
37
- :param str file_extension:
 
 
 
 
 
 
 
 
 
38
  (optional) Alias to ``sub_type``.
39
- :param str abr:
 
 
 
40
  (optional) Average bitrate (ABR) refers to the average amount of
41
- data transferred per unit of time (e.g.: 64kbps, 192kbps)
42
- :param str bitrate:
 
 
 
43
  (optional) Alias to ``abr``.
44
- :param str video_codec:
45
- (optional) Digital video compression format (e.g.: vp9, mp4v.20.3).
46
- :param str audio_codec:
47
- (optional) Digital audio compression format (e.g.: vorbis, mp4).
 
 
 
 
 
 
 
 
 
48
  :param bool progressive:
49
  Excludes adaptive streams (one file contains both audio and video
50
  tracks).
 
51
  :param bool adaptive:
52
  Excludes progressive streams (audio and video are on separate
53
  tracks).
 
54
  :param bool only_audio:
55
  Excludes streams with video tracks.
 
56
  :param bool only_video:
57
  Excludes streams with audio tracks.
58
- :param list custom_filter_functions:
 
59
  (optional) Interface for defining complex filters without
60
  subclassing.
 
 
61
 
62
  """
63
  filters = []
@@ -135,7 +174,7 @@ class StreamQuery:
135
  return self
136
 
137
  def get_by_itag(self, itag):
138
- """Get a :class:`Stream <Stream>` for an itag, or None if not found.
139
 
140
  :param str itag:
141
  YouTube format identifier code.
@@ -149,7 +188,7 @@ class StreamQuery:
149
  def first(self):
150
  """Get the first element in the results.
151
 
152
- Return the first result of this query or None if the result doesn't
153
  contain any streams.
154
 
155
  """
@@ -161,7 +200,7 @@ class StreamQuery:
161
  def last(self):
162
  """Get the last element in the results.
163
 
164
- Return the last result of this query or None if the result doesn't
165
  contain any streams.
166
 
167
  """
 
21
  ):
22
  """Apply the given filtering criterion.
23
 
24
+ :param fps:
25
+ (optional) The frames per second.
26
+ :type fps:
27
+ int or None
28
+
29
+ :param resolution:
30
  (optional) Alias to ``res``.
31
+ :type res:
32
+ str or None
33
+
34
+ :param res:
35
+ (optional) The video resolution.
36
+ :type resolution:
37
+ str or None
38
+
39
+ :param mime_type:
40
  (optional) Two-part identifier for file formats and format contents
41
+ composed of a "type", a "subtype".
42
+ :type mime_type:
43
+ str or None
44
+
45
+ :param type:
46
+ (optional) Type part of the ``mime_type`` (e.g.: audio, video).
47
+ :type type:
48
+ str or None
49
+
50
+ :param subtype:
51
+ (optional) Sub-type part of the ``mime_type`` (e.g.: mp4, mov).
52
+ :type subtype:
53
+ str or None
54
+
55
+ :param file_extension:
56
  (optional) Alias to ``sub_type``.
57
+ :type file_extension:
58
+ str or None
59
+
60
+ :param abr:
61
  (optional) Average bitrate (ABR) refers to the average amount of
62
+ data transferred per unit of time (e.g.: 64kbps, 192kbps).
63
+ :type abr:
64
+ str or None
65
+
66
+ :param bitrate:
67
  (optional) Alias to ``abr``.
68
+ :type bitrate:
69
+ str or None
70
+
71
+ :param video_codec:
72
+ (optional) Video compression format.
73
+ :type video_codec:
74
+ str or None
75
+
76
+ :param audio_codec:
77
+ (optional) Audio compression format.
78
+ :type audio_codec:
79
+ str or None
80
+
81
  :param bool progressive:
82
  Excludes adaptive streams (one file contains both audio and video
83
  tracks).
84
+
85
  :param bool adaptive:
86
  Excludes progressive streams (audio and video are on separate
87
  tracks).
88
+
89
  :param bool only_audio:
90
  Excludes streams with video tracks.
91
+
92
  :param bool only_video:
93
  Excludes streams with audio tracks.
94
+
95
+ :param custom_filter_functions:
96
  (optional) Interface for defining complex filters without
97
  subclassing.
98
+ :type custom_filter_functions:
99
+ list or None
100
 
101
  """
102
  filters = []
 
174
  return self
175
 
176
  def get_by_itag(self, itag):
177
+ """Get a :class:`Stream <Stream>` for an itag, or ``None`` if not found.
178
 
179
  :param str itag:
180
  YouTube format identifier code.
 
188
  def first(self):
189
  """Get the first element in the results.
190
 
191
+ Return the first result of this query or ``None`` if the result doesn't
192
  contain any streams.
193
 
194
  """
 
200
  def last(self):
201
  """Get the last element in the results.
202
 
203
+ Return the last result of this query or ``None`` if the result doesn't
204
  contain any streams.
205
 
206
  """
pytube/streams.py CHANGED
@@ -183,7 +183,7 @@ class Stream(object):
183
  :param file_handle:
184
  The file handle where the media is being written to.
185
  :type file_handle:
186
- :class:`~_io.BufferedWriter`
187
  :param int bytes_remaining:
188
  The delta between the total file size in bytes and amount already
189
  downloaded.
@@ -206,10 +206,11 @@ class Stream(object):
206
 
207
  def on_complete(self, file_handle):
208
  """On download complete handler function.
 
209
  :param file_handle:
210
  The file handle where the media is being written to.
211
  :type file_handle:
212
- :class:`~_io.BufferedWriter`
213
 
214
  """
215
  logger.debug('download finished')
 
183
  :param file_handle:
184
  The file handle where the media is being written to.
185
  :type file_handle:
186
+ :py:class:`io.BufferedWriter`
187
  :param int bytes_remaining:
188
  The delta between the total file size in bytes and amount already
189
  downloaded.
 
206
 
207
  def on_complete(self, file_handle):
208
  """On download complete handler function.
209
+
210
  :param file_handle:
211
  The file handle where the media is being written to.
212
  :type file_handle:
213
+ :py:class:`io.BufferedWriter`
214
 
215
  """
216
  logger.debug('download finished')
tests/requirements.txt CHANGED
@@ -5,6 +5,7 @@ mock==1.3.0
5
  pre-commit==0.15.0
6
  pytest==3.1.3
7
  pytest-cov==2.4.0
 
8
  pytest-sugar==0.9.0
9
  sphinx==1.6.4
10
  sphinx-rtd-theme==0.2.4
 
5
  pre-commit==0.15.0
6
  pytest==3.1.3
7
  pytest-cov==2.4.0
8
+ pytest-mock==1.6.3
9
  pytest-sugar==0.9.0
10
  sphinx==1.6.4
11
  sphinx-rtd-theme==0.2.4
tests/test_streams.py CHANGED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import random
3
+
4
+ import mock
5
+
6
+ from pytube import request
7
+
8
+
9
+ def test_filesize(gangnam_style, mocker):
10
+ mocker.patch.object(request, 'get')
11
+ request.get.return_value = {'Content-Length': '6796391'}
12
+ assert gangnam_style.streams.first().filesize == 6796391
13
+
14
+
15
+ def test_default_filename(gangnam_style):
16
+ expected = 'PSY - GANGNAM STYLE(강남스타일) MV.mp4'
17
+ stream = gangnam_style.streams.first()
18
+ assert stream.default_filename == expected
19
+
20
+
21
+ def test_download(gangnam_style, mocker):
22
+ mocker.patch.object(request, 'get')
23
+ request.get.side_effect = [
24
+ {'Content-Length': '16384'},
25
+ {'Content-Length': '16384'},
26
+ iter([str(random.getrandbits(8 * 1024))]),
27
+ ]
28
+ with mock.patch('pytube.streams.open', mock.mock_open(), create=True):
29
+ stream = gangnam_style.streams.first()
30
+ stream.download()
31
+
32
+
33
+ def test_progressive_streams_return_includes_audio_track(gangnam_style):
34
+ stream = gangnam_style.streams.filter(progressive=True).first()
35
+ assert stream.includes_audio_track
36
+
37
+
38
+ def test_progressive_streams_return_includes_video_track(gangnam_style):
39
+ stream = gangnam_style.streams.filter(progressive=True).first()
40
+ assert stream.includes_video_track