add script to generate test mocks, re-enable tests that depend on it
Browse files- pytube/cipher.py +1 -1
- pytube/extract.py +1 -1
- tests/conftest.py +2 -2
- tests/generate_fixture.py +25 -0
- tests/mocks/yt-video-9bZkp7q19f0-1507588332.json.gz +0 -0
- tests/mocks/yt-video-9bZkp7q19f0.json.gz +0 -0
- tests/mocks/yt-video-QRS8MkLhQmM-1507588031.json.gz +0 -0
- tests/mocks/yt-video-QRS8MkLhQmM.json.gz +0 -0
- tests/test_extract.py +1 -6
- tests/test_streams.py +2 -15
pytube/cipher.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""
|
3 |
-
This module
|
4 |
|
5 |
YouTube's strategy to restrict downloading videos is to send a ciphered version
|
6 |
of the signature to the client, along with the decryption algorithm obfuscated
|
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""
|
3 |
+
This module contains all logic necessary to decipher the signature.
|
4 |
|
5 |
YouTube's strategy to restrict downloading videos is to send a ciphered version
|
6 |
of the signature to the client, along with the decryption algorithm obfuscated
|
pytube/extract.py
CHANGED
@@ -142,7 +142,7 @@ def js_url(html: str, age_restricted: bool = False) -> str:
|
|
142 |
Construct the base JavaScript url, which contains the decipher
|
143 |
"transforms".
|
144 |
|
145 |
-
:param str
|
146 |
The html contents of the watch page.
|
147 |
:param bool age_restricted:
|
148 |
Is video age restricted.
|
|
|
142 |
Construct the base JavaScript url, which contains the decipher
|
143 |
"transforms".
|
144 |
|
145 |
+
:param str html:
|
146 |
The html contents of the watch page.
|
147 |
:param bool age_restricted:
|
148 |
Is video age restricted.
|
tests/conftest.py
CHANGED
@@ -34,14 +34,14 @@ def load_and_init_from_playback_file(filename):
|
|
34 |
@pytest.fixture
|
35 |
def cipher_signature():
|
36 |
"""Youtube instance initialized with video id 9bZkp7q19f0."""
|
37 |
-
filename = 'yt-video-9bZkp7q19f0
|
38 |
return load_and_init_from_playback_file(filename)
|
39 |
|
40 |
|
41 |
@pytest.fixture
|
42 |
def presigned_video():
|
43 |
"""Youtube instance initialized with video id QRS8MkLhQmM."""
|
44 |
-
filename = 'yt-video-QRS8MkLhQmM
|
45 |
return load_and_init_from_playback_file(filename)
|
46 |
|
47 |
|
|
|
34 |
@pytest.fixture
|
35 |
def cipher_signature():
|
36 |
"""Youtube instance initialized with video id 9bZkp7q19f0."""
|
37 |
+
filename = 'yt-video-9bZkp7q19f0.json.gz'
|
38 |
return load_and_init_from_playback_file(filename)
|
39 |
|
40 |
|
41 |
@pytest.fixture
|
42 |
def presigned_video():
|
43 |
"""Youtube instance initialized with video id QRS8MkLhQmM."""
|
44 |
+
filename = 'yt-video-QRS8MkLhQmM.json.gz'
|
45 |
return load_and_init_from_playback_file(filename)
|
46 |
|
47 |
|
tests/generate_fixture.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
|
3 |
+
from os import path
|
4 |
+
import sys
|
5 |
+
import json
|
6 |
+
|
7 |
+
currentdir = path.dirname(path.realpath(__file__))
|
8 |
+
parentdir = path.dirname(currentdir)
|
9 |
+
sys.path.append(parentdir)
|
10 |
+
|
11 |
+
from pytube import YouTube
|
12 |
+
|
13 |
+
yt = YouTube(sys.argv[1], defer_prefetch_init=True)
|
14 |
+
yt.prefetch()
|
15 |
+
output = {
|
16 |
+
"url": sys.argv[1],
|
17 |
+
"watch_html": yt.watch_html,
|
18 |
+
"video_info": yt.vid_info,
|
19 |
+
"js": yt.js,
|
20 |
+
}
|
21 |
+
|
22 |
+
outpath = path.join(currentdir, "mocks", "yt-video-" + yt.video_id + ".json")
|
23 |
+
print("Writing to: " + outpath)
|
24 |
+
with open(outpath, "w") as f:
|
25 |
+
json.dump(output, f)
|
tests/mocks/yt-video-9bZkp7q19f0-1507588332.json.gz
DELETED
Binary file (483 kB)
|
|
tests/mocks/yt-video-9bZkp7q19f0.json.gz
ADDED
Binary file (475 kB). View file
|
|
tests/mocks/yt-video-QRS8MkLhQmM-1507588031.json.gz
DELETED
Binary file (367 kB)
|
|
tests/mocks/yt-video-QRS8MkLhQmM.json.gz
ADDED
Binary file (472 kB). View file
|
|
tests/test_extract.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""Unit tests for the :module:`extract <extract>` module."""
|
3 |
-
import pytest
|
4 |
from pytube import extract
|
5 |
|
6 |
|
@@ -16,7 +15,6 @@ def test_extract_watch_url():
|
|
16 |
assert watch_url == 'https://youtube.com/watch?v=9bZkp7q19f0'
|
17 |
|
18 |
|
19 |
-
@pytest.mark.skip(reason="Regex error")
|
20 |
def test_info_url(cipher_signature):
|
21 |
video_info_url = extract.video_info_url(
|
22 |
video_id=cipher_signature.video_id,
|
@@ -33,7 +31,6 @@ def test_info_url(cipher_signature):
|
|
33 |
assert video_info_url == expected
|
34 |
|
35 |
|
36 |
-
@pytest.mark.skip(reason="Regex error")
|
37 |
def test_js_url(cipher_signature):
|
38 |
expected = 'https://youtube.com/yts/jsbin/player-vflOdyxa4/en_US/base.js'
|
39 |
result = extract.js_url(cipher_signature.watch_html)
|
@@ -44,12 +41,10 @@ def test_age_restricted(age_restricted):
|
|
44 |
assert extract.is_age_restricted(age_restricted['watch_html'])
|
45 |
|
46 |
|
47 |
-
@pytest.mark.skip(reason="Regex error")
|
48 |
def test_non_age_restricted(cipher_signature):
|
49 |
assert not extract.is_age_restricted(cipher_signature.watch_html)
|
50 |
|
51 |
|
52 |
-
@pytest.mark.skip(reason="Regex error")
|
53 |
def test_get_vid_desc(cipher_signature):
|
54 |
-
expected = "PSY -
|
55 |
assert extract.get_vid_descr(cipher_signature.watch_html) == expected
|
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""Unit tests for the :module:`extract <extract>` module."""
|
|
|
3 |
from pytube import extract
|
4 |
|
5 |
|
|
|
15 |
assert watch_url == 'https://youtube.com/watch?v=9bZkp7q19f0'
|
16 |
|
17 |
|
|
|
18 |
def test_info_url(cipher_signature):
|
19 |
video_info_url = extract.video_info_url(
|
20 |
video_id=cipher_signature.video_id,
|
|
|
31 |
assert video_info_url == expected
|
32 |
|
33 |
|
|
|
34 |
def test_js_url(cipher_signature):
|
35 |
expected = 'https://youtube.com/yts/jsbin/player-vflOdyxa4/en_US/base.js'
|
36 |
result = extract.js_url(cipher_signature.watch_html)
|
|
|
41 |
assert extract.is_age_restricted(age_restricted['watch_html'])
|
42 |
|
43 |
|
|
|
44 |
def test_non_age_restricted(cipher_signature):
|
45 |
assert not extract.is_age_restricted(cipher_signature.watch_html)
|
46 |
|
47 |
|
|
|
48 |
def test_get_vid_desc(cipher_signature):
|
49 |
+
expected = "PSY - ‘I LUV IT’ M/V @ https://youtu.be/Xvjnoagk6GU\nPSY - ‘New Face’ M/V @https://youtu.be/OwJPPaEyqhI\nPSY - 8TH ALBUM '4X2=8' on iTunes @\nhttps://smarturl.it/PSY_8thAlbum\nPSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam\n#PSY #싸이 #GANGNAMSTYLE #강남스타일\nMore about PSY@\nhttp://www.youtube.com/officialpsy\nhttp://www.facebook.com/officialpsy\nhttp://twitter.com/psy_oppa\nhttps://www.instagram.com/42psy42\nhttp://iTunes.com/PSY\nhttp://sptfy.com/PSY\nhttp://weibo.com/psyoppa" # noqa
|
50 |
assert extract.get_vid_descr(cipher_signature.watch_html) == expected
|
tests/test_streams.py
CHANGED
@@ -2,27 +2,23 @@
|
|
2 |
import random
|
3 |
|
4 |
from unittest import mock
|
5 |
-
import pytest
|
6 |
|
7 |
from pytube import request
|
8 |
from pytube import Stream
|
9 |
|
10 |
|
11 |
-
@pytest.mark.skip(reason="Regex error")
|
12 |
def test_filesize(cipher_signature, mocker):
|
13 |
mocker.patch.object(request, 'get')
|
14 |
request.get.return_value = {'content-length': '6796391'}
|
15 |
assert cipher_signature.streams.first().filesize == 6796391
|
16 |
|
17 |
|
18 |
-
@pytest.mark.skip(reason="Regex error")
|
19 |
def test_default_filename(cipher_signature):
|
20 |
expected = 'PSY - GANGNAM STYLE(강남스타일) MV.mp4'
|
21 |
stream = cipher_signature.streams.first()
|
22 |
assert stream.default_filename == expected
|
23 |
|
24 |
|
25 |
-
@pytest.mark.skip(reason="Regex error")
|
26 |
def test_title(cipher_signature):
|
27 |
expected = 'PSY - GANGNAM STYLE(강남스타일) M/V'
|
28 |
stream = cipher_signature.streams.first()
|
@@ -39,7 +35,6 @@ def test_title(cipher_signature):
|
|
39 |
assert stream.title == expected
|
40 |
|
41 |
|
42 |
-
@pytest.mark.skip(reason="Regex error")
|
43 |
def test_download(cipher_signature, mocker):
|
44 |
mocker.patch.object(request, 'get')
|
45 |
request.get.side_effect = [
|
@@ -52,19 +47,16 @@ def test_download(cipher_signature, mocker):
|
|
52 |
stream.download()
|
53 |
|
54 |
|
55 |
-
@pytest.mark.skip(reason="Regex error")
|
56 |
def test_progressive_streams_return_includes_audio_track(cipher_signature):
|
57 |
stream = cipher_signature.streams.filter(progressive=True).first()
|
58 |
assert stream.includes_audio_track
|
59 |
|
60 |
|
61 |
-
@pytest.mark.skip(reason="Regex error")
|
62 |
def test_progressive_streams_return_includes_video_track(cipher_signature):
|
63 |
stream = cipher_signature.streams.filter(progressive=True).first()
|
64 |
assert stream.includes_video_track
|
65 |
|
66 |
|
67 |
-
@pytest.mark.skip(reason="Regex error")
|
68 |
def test_on_progress_hook(cipher_signature, mocker):
|
69 |
callback_fn = mock.MagicMock()
|
70 |
cipher_signature.register_on_progress_callback(callback_fn)
|
@@ -85,7 +77,6 @@ def test_on_progress_hook(cipher_signature, mocker):
|
|
85 |
assert isinstance(stream, Stream)
|
86 |
|
87 |
|
88 |
-
@pytest.mark.skip(reason="Regex error")
|
89 |
def test_on_complete_hook(cipher_signature, mocker):
|
90 |
callback_fn = mock.MagicMock()
|
91 |
cipher_signature.register_on_complete_callback(callback_fn)
|
@@ -102,7 +93,6 @@ def test_on_complete_hook(cipher_signature, mocker):
|
|
102 |
assert callback_fn.called
|
103 |
|
104 |
|
105 |
-
@pytest.mark.skip(reason="Regex error")
|
106 |
def test_repr_for_audio_streams(cipher_signature):
|
107 |
stream = str(cipher_signature.streams.filter(only_audio=True).first())
|
108 |
expected = (
|
@@ -112,7 +102,6 @@ def test_repr_for_audio_streams(cipher_signature):
|
|
112 |
assert stream == expected
|
113 |
|
114 |
|
115 |
-
@pytest.mark.skip(reason="Regex error")
|
116 |
def test_repr_for_video_streams(cipher_signature):
|
117 |
stream = str(cipher_signature.streams.filter(only_video=True).first())
|
118 |
expected = (
|
@@ -122,17 +111,15 @@ def test_repr_for_video_streams(cipher_signature):
|
|
122 |
assert stream == expected
|
123 |
|
124 |
|
125 |
-
@pytest.mark.skip(reason="Regex error")
|
126 |
def test_repr_for_progressive_streams(cipher_signature):
|
127 |
stream = str(cipher_signature.streams.filter(progressive=True).first())
|
128 |
expected = (
|
129 |
-
'<Stream: itag="
|
130 |
-
'fps="30fps" vcodec="avc1.
|
131 |
)
|
132 |
assert stream == expected
|
133 |
|
134 |
|
135 |
-
@pytest.mark.skip(reason="Regex error")
|
136 |
def test_repr_for_adaptive_streams(cipher_signature):
|
137 |
stream = str(cipher_signature.streams.filter(adaptive=True).first())
|
138 |
expected = (
|
|
|
2 |
import random
|
3 |
|
4 |
from unittest import mock
|
|
|
5 |
|
6 |
from pytube import request
|
7 |
from pytube import Stream
|
8 |
|
9 |
|
|
|
10 |
def test_filesize(cipher_signature, mocker):
|
11 |
mocker.patch.object(request, 'get')
|
12 |
request.get.return_value = {'content-length': '6796391'}
|
13 |
assert cipher_signature.streams.first().filesize == 6796391
|
14 |
|
15 |
|
|
|
16 |
def test_default_filename(cipher_signature):
|
17 |
expected = 'PSY - GANGNAM STYLE(강남스타일) MV.mp4'
|
18 |
stream = cipher_signature.streams.first()
|
19 |
assert stream.default_filename == expected
|
20 |
|
21 |
|
|
|
22 |
def test_title(cipher_signature):
|
23 |
expected = 'PSY - GANGNAM STYLE(강남스타일) M/V'
|
24 |
stream = cipher_signature.streams.first()
|
|
|
35 |
assert stream.title == expected
|
36 |
|
37 |
|
|
|
38 |
def test_download(cipher_signature, mocker):
|
39 |
mocker.patch.object(request, 'get')
|
40 |
request.get.side_effect = [
|
|
|
47 |
stream.download()
|
48 |
|
49 |
|
|
|
50 |
def test_progressive_streams_return_includes_audio_track(cipher_signature):
|
51 |
stream = cipher_signature.streams.filter(progressive=True).first()
|
52 |
assert stream.includes_audio_track
|
53 |
|
54 |
|
|
|
55 |
def test_progressive_streams_return_includes_video_track(cipher_signature):
|
56 |
stream = cipher_signature.streams.filter(progressive=True).first()
|
57 |
assert stream.includes_video_track
|
58 |
|
59 |
|
|
|
60 |
def test_on_progress_hook(cipher_signature, mocker):
|
61 |
callback_fn = mock.MagicMock()
|
62 |
cipher_signature.register_on_progress_callback(callback_fn)
|
|
|
77 |
assert isinstance(stream, Stream)
|
78 |
|
79 |
|
|
|
80 |
def test_on_complete_hook(cipher_signature, mocker):
|
81 |
callback_fn = mock.MagicMock()
|
82 |
cipher_signature.register_on_complete_callback(callback_fn)
|
|
|
93 |
assert callback_fn.called
|
94 |
|
95 |
|
|
|
96 |
def test_repr_for_audio_streams(cipher_signature):
|
97 |
stream = str(cipher_signature.streams.filter(only_audio=True).first())
|
98 |
expected = (
|
|
|
102 |
assert stream == expected
|
103 |
|
104 |
|
|
|
105 |
def test_repr_for_video_streams(cipher_signature):
|
106 |
stream = str(cipher_signature.streams.filter(only_video=True).first())
|
107 |
expected = (
|
|
|
111 |
assert stream == expected
|
112 |
|
113 |
|
|
|
114 |
def test_repr_for_progressive_streams(cipher_signature):
|
115 |
stream = str(cipher_signature.streams.filter(progressive=True).first())
|
116 |
expected = (
|
117 |
+
'<Stream: itag="18" mime_type="video/mp4" res="360p" '
|
118 |
+
'fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2">'
|
119 |
)
|
120 |
assert stream == expected
|
121 |
|
122 |
|
|
|
123 |
def test_repr_for_adaptive_streams(cipher_signature):
|
124 |
stream = str(cipher_signature.streams.filter(adaptive=True).first())
|
125 |
expected = (
|