Taylor Fox Dahlin
commited on
Improvement/reduce network calls (#842)
Browse files* Added package-wide variables for caching player js and js_url.
- docs/user/playlist.rst +3 -3
- pytube/__init__.py +2 -0
- pytube/__main__.py +11 -8
- tests/test_main.py +22 -0
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
|
43 |
-
Python Tutorial for
|
44 |
-
Python Tutorial for
|
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 |
+
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.
|
pytube/__init__.py
CHANGED
@@ -8,6 +8,8 @@ __title__ = "pytube3"
|
|
8 |
__author__ = "Nick Ficano, Harold Martin"
|
9 |
__license__ = "MIT License"
|
10 |
__copyright__ = "Copyright 2019 Nick Ficano"
|
|
|
|
|
11 |
|
12 |
from pytube.version import __version__
|
13 |
from pytube.streams import Stream
|
|
|
8 |
__author__ = "Nick Ficano, Harold Martin"
|
9 |
__license__ = "MIT License"
|
10 |
__copyright__ = "Copyright 2019 Nick Ficano"
|
11 |
+
__js__ = None
|
12 |
+
__js_url__ = None
|
13 |
|
14 |
from pytube.version import __version__
|
15 |
from pytube.streams import Stream
|
pytube/__main__.py
CHANGED
@@ -14,6 +14,7 @@ from typing import List
|
|
14 |
from typing import Optional
|
15 |
from urllib.parse import parse_qsl
|
16 |
|
|
|
17 |
from pytube import Caption
|
18 |
from pytube import CaptionQuery
|
19 |
from pytube import extract
|
@@ -165,12 +166,6 @@ class YouTube:
|
|
165 |
apply_descrambler(self.vid_info, fmt)
|
166 |
apply_descrambler(self.player_config_args, fmt)
|
167 |
|
168 |
-
if not self.js:
|
169 |
-
if not self.embed_html:
|
170 |
-
self.embed_html = request.get(url=self.embed_url)
|
171 |
-
self.js_url = extract.js_url(self.embed_html)
|
172 |
-
self.js = request.get(self.js_url)
|
173 |
-
|
174 |
apply_signature(self.player_config_args, fmt, self.js)
|
175 |
|
176 |
# build instances of :class:`Stream <Stream>`
|
@@ -206,17 +201,25 @@ class YouTube:
|
|
206 |
self.vid_info_url = extract.video_info_url_age_restricted(
|
207 |
self.video_id, self.watch_url
|
208 |
)
|
|
|
209 |
else:
|
210 |
self.vid_info_url = extract.video_info_url(
|
211 |
video_id=self.video_id, watch_url=self.watch_url
|
212 |
)
|
|
|
213 |
|
214 |
self.initial_data = extract.initial_data(self.watch_html)
|
215 |
|
216 |
self.vid_info_raw = request.get(self.vid_info_url)
|
217 |
-
|
218 |
-
|
|
|
|
|
219 |
self.js = request.get(self.js_url)
|
|
|
|
|
|
|
|
|
220 |
|
221 |
def initialize_stream_objects(self, fmt: str) -> None:
|
222 |
"""Convert manifest data to instances of :class:`Stream <Stream>`.
|
|
|
14 |
from typing import Optional
|
15 |
from urllib.parse import parse_qsl
|
16 |
|
17 |
+
import pytube
|
18 |
from pytube import Caption
|
19 |
from pytube import CaptionQuery
|
20 |
from pytube import extract
|
|
|
166 |
apply_descrambler(self.vid_info, fmt)
|
167 |
apply_descrambler(self.player_config_args, fmt)
|
168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
apply_signature(self.player_config_args, fmt, self.js)
|
170 |
|
171 |
# build instances of :class:`Stream <Stream>`
|
|
|
201 |
self.vid_info_url = extract.video_info_url_age_restricted(
|
202 |
self.video_id, self.watch_url
|
203 |
)
|
204 |
+
self.js_url = extract.js_url(self.embed_html)
|
205 |
else:
|
206 |
self.vid_info_url = extract.video_info_url(
|
207 |
video_id=self.video_id, watch_url=self.watch_url
|
208 |
)
|
209 |
+
self.js_url = extract.js_url(self.watch_html)
|
210 |
|
211 |
self.initial_data = extract.initial_data(self.watch_html)
|
212 |
|
213 |
self.vid_info_raw = request.get(self.vid_info_url)
|
214 |
+
|
215 |
+
# If the js_url doesn't match the cached url, fetch the new js and update
|
216 |
+
# the cache; otherwise, load the cache.
|
217 |
+
if pytube.__js_url__ != self.js_url:
|
218 |
self.js = request.get(self.js_url)
|
219 |
+
pytube.__js__ = self.js
|
220 |
+
pytube.__js_url__ = self.js_url
|
221 |
+
else:
|
222 |
+
self.js = pytube.__js__
|
223 |
|
224 |
def initialize_stream_objects(self, fmt: str) -> None:
|
225 |
"""Convert manifest data to instances of :class:`Stream <Stream>`.
|
tests/test_main.py
CHANGED
@@ -3,6 +3,7 @@ from unittest import mock
|
|
3 |
|
4 |
import pytest
|
5 |
|
|
|
6 |
from pytube import YouTube
|
7 |
from pytube.exceptions import VideoUnavailable
|
8 |
|
@@ -54,3 +55,24 @@ def test_video_keywords(cipher_signature):
|
|
54 |
'BLACKPINK', 'Year in Review'
|
55 |
]
|
56 |
assert cipher_signature.keywords == expected
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
import pytest
|
5 |
|
6 |
+
import pytube
|
7 |
from pytube import YouTube
|
8 |
from pytube.exceptions import VideoUnavailable
|
9 |
|
|
|
55 |
'BLACKPINK', 'Year in Review'
|
56 |
]
|
57 |
assert cipher_signature.keywords == expected
|
58 |
+
|
59 |
+
|
60 |
+
def test_js_caching(cipher_signature):
|
61 |
+
assert pytube.__js__ is not None
|
62 |
+
assert pytube.__js_url__ is not None
|
63 |
+
assert pytube.__js__ == cipher_signature.js
|
64 |
+
assert pytube.__js_url__ == cipher_signature.js_url
|
65 |
+
|
66 |
+
with mock.patch('pytube.request.urlopen') as mock_urlopen:
|
67 |
+
mock_urlopen_object = mock.Mock()
|
68 |
+
|
69 |
+
# We should never read the js from this
|
70 |
+
mock_urlopen_object.read.side_effect = [
|
71 |
+
cipher_signature.watch_html.encode('utf-8'),
|
72 |
+
cipher_signature.vid_info_raw.encode('utf-8'),
|
73 |
+
cipher_signature.js.encode('utf-8')
|
74 |
+
]
|
75 |
+
|
76 |
+
mock_urlopen.return_value = mock_urlopen_object
|
77 |
+
cipher_signature.prefetch()
|
78 |
+
assert mock_urlopen.call_count == 2
|