nficano commited on
Commit
ccd4117
·
1 Parent(s): a6e1ce1

unit tests

Browse files
pytube/__main__.py CHANGED
@@ -19,7 +19,6 @@ from pytube import Stream
19
  from pytube import StreamQuery
20
  from pytube.compat import parse_qsl
21
  from pytube.helpers import apply_mixin
22
- from pytube.helpers import memoize
23
 
24
 
25
  logger = logging.getLogger(__name__)
@@ -159,7 +158,6 @@ class YouTube(object):
159
  self.fmt_streams.append(video)
160
 
161
  @property
162
- @memoize
163
  def streams(self):
164
  """Interface to query both adaptive (DASH) and progressive streams."""
165
  return StreamQuery([s for s in self.fmt_streams])
 
19
  from pytube import StreamQuery
20
  from pytube.compat import parse_qsl
21
  from pytube.helpers import apply_mixin
 
22
 
23
 
24
  logger = logging.getLogger(__name__)
 
158
  self.fmt_streams.append(video)
159
 
160
  @property
 
161
  def streams(self):
162
  """Interface to query both adaptive (DASH) and progressive streams."""
163
  return StreamQuery([s for s in self.fmt_streams])
pytube/cipher.py CHANGED
@@ -23,7 +23,6 @@ import re
23
  from itertools import chain
24
 
25
  from pytube.exceptions import RegexMatchError
26
- from pytube.helpers import memoize
27
  from pytube.helpers import regex_search
28
 
29
 
@@ -104,7 +103,6 @@ def get_transform_object(js, var):
104
  )
105
 
106
 
107
- @memoize
108
  def get_transform_map(js, var):
109
  """Build a transform function lookup.
110
 
@@ -235,7 +233,6 @@ def parse_function(js_func):
235
  return regex_search(pattern, js_func, groups=True)
236
 
237
 
238
- @memoize
239
  def get_signature(js, ciphered_signature):
240
  """Decipher the signature.
241
 
 
23
  from itertools import chain
24
 
25
  from pytube.exceptions import RegexMatchError
 
26
  from pytube.helpers import regex_search
27
 
28
 
 
103
  )
104
 
105
 
 
106
  def get_transform_map(js, var):
107
  """Build a transform function lookup.
108
 
 
233
  return regex_search(pattern, js_func, groups=True)
234
 
235
 
 
236
  def get_signature(js, ciphered_signature):
237
  """Decipher the signature.
238
 
pytube/extract.py CHANGED
@@ -5,7 +5,6 @@ from collections import OrderedDict
5
 
6
  from pytube.compat import quote
7
  from pytube.compat import urlencode
8
- from pytube.helpers import memoize
9
  from pytube.helpers import regex_search
10
 
11
 
@@ -88,7 +87,6 @@ def mime_type_codec(mime_type_codec):
88
  return mime_type, [c.strip() for c in codecs.split(',')]
89
 
90
 
91
- @memoize
92
  def get_ytplayer_config(watch_html):
93
  """Get the YouTube player configuration data from the watch html.
94
 
 
5
 
6
  from pytube.compat import quote
7
  from pytube.compat import urlencode
 
8
  from pytube.helpers import regex_search
9
 
10
 
 
87
  return mime_type, [c.strip() for c in codecs.split(',')]
88
 
89
 
 
90
  def get_ytplayer_config(watch_html):
91
  """Get the YouTube player configuration data from the watch html.
92
 
pytube/helpers.py CHANGED
@@ -2,7 +2,6 @@
2
  """Various helper functions implemented by pytube."""
3
  from __future__ import absolute_import
4
 
5
- import functools
6
  import logging
7
  import pprint
8
  import re
@@ -88,16 +87,3 @@ def safe_filename(s, max_length=255):
88
  regex = re.compile(pattern, re.UNICODE)
89
  filename = regex.sub('', s)
90
  return filename[:max_length].rsplit(' ', 0)[0]
91
-
92
-
93
- def memoize(func):
94
- """Decorate that caches input arguments for return values."""
95
- cache = func.cache = {}
96
-
97
- @functools.wraps(func)
98
- def wrapper(*args, **kwargs):
99
- key = str(args) + str(kwargs)
100
- if key not in cache:
101
- cache[key] = func(*args, **kwargs)
102
- return cache[key]
103
- return wrapper
 
2
  """Various helper functions implemented by pytube."""
3
  from __future__ import absolute_import
4
 
 
5
  import logging
6
  import pprint
7
  import re
 
87
  regex = re.compile(pattern, re.UNICODE)
88
  filename = regex.sub('', s)
89
  return filename[:max_length].rsplit(' ', 0)[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
pytube/mixins.py CHANGED
@@ -34,6 +34,7 @@ def apply_signature(config_args, fmt, js):
34
  # For certain videos, YouTube will just provide them pre-signed, in
35
  # which case there's no real magic to download them and we can skip
36
  # the whole signature decrambling entirely.
 
37
  continue
38
 
39
  signature = cipher.get_signature(js, stream['s'])
@@ -63,4 +64,7 @@ def apply_descrambler(stream_data, key):
63
  {k: unquote(v) for k, v in parse_qsl(i)}
64
  for i in stream_data[key].split(',')
65
  ]
66
- logger.debug('applying descrambler\n%s', pprint.pformat(stream_data[key]))
 
 
 
 
34
  # For certain videos, YouTube will just provide them pre-signed, in
35
  # which case there's no real magic to download them and we can skip
36
  # the whole signature decrambling entirely.
37
+ logger.debug('signature found, skip decipher')
38
  continue
39
 
40
  signature = cipher.get_signature(js, stream['s'])
 
64
  {k: unquote(v) for k, v in parse_qsl(i)}
65
  for i in stream_data[key].split(',')
66
  ]
67
+ logger.debug(
68
+ 'applying descrambler\n%s',
69
+ pprint.pformat(stream_data[key], indent=2),
70
+ )
pytube/streams.py CHANGED
@@ -15,7 +15,6 @@ import pprint
15
 
16
  from pytube import extract
17
  from pytube import request
18
- from pytube.helpers import memoize
19
  from pytube.helpers import safe_filename
20
  from pytube.itags import get_format_profile
21
 
@@ -133,7 +132,6 @@ class Stream(object):
133
  return video, audio
134
 
135
  @property
136
- @memoize
137
  def filesize(self):
138
  """File size of the media stream in bytes."""
139
  headers = request.get(self.url, headers=True)
 
15
 
16
  from pytube import extract
17
  from pytube import request
 
18
  from pytube.helpers import safe_filename
19
  from pytube.itags import get_format_profile
20
 
 
132
  return video, audio
133
 
134
  @property
 
135
  def filesize(self):
136
  """File size of the media stream in bytes."""
137
  headers = request.get(self.url, headers=True)
tests/conftest.py CHANGED
@@ -1,5 +1,6 @@
1
  # -*- coding: utf-8 -*-
2
  """Reusable dependency injected testing components."""
 
3
  import json
4
  import os
5
 
@@ -8,13 +9,11 @@ import pytest
8
  from pytube import YouTube
9
 
10
 
11
- @pytest.fixture
12
- def gangnam_style():
13
- """Youtube instance initialized with video id 9bZkp7q19f0."""
14
  cur_dir = os.path.dirname(os.path.realpath(__file__))
15
- fp = os.path.join(cur_dir, 'mocks', 'yt-video-9bZkp7q19f0.json')
16
  video = None
17
- with open(fp, 'r') as fh:
18
  video = json.loads(fh.read())
19
  yt = YouTube(
20
  url='https://www.youtube.com/watch?v=9bZkp7q19f0',
@@ -25,3 +24,17 @@ def gangnam_style():
25
  yt.vid_info = video['video_info']
26
  yt.init()
27
  return yt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # -*- coding: utf-8 -*-
2
  """Reusable dependency injected testing components."""
3
+ import gzip
4
  import json
5
  import os
6
 
 
9
  from pytube import YouTube
10
 
11
 
12
+ def load_from_playback_file(filename):
 
 
13
  cur_dir = os.path.dirname(os.path.realpath(__file__))
14
+ fp = os.path.join(cur_dir, 'mocks', filename)
15
  video = None
16
+ with gzip.open(fp, 'rb') as fh:
17
  video = json.loads(fh.read())
18
  yt = YouTube(
19
  url='https://www.youtube.com/watch?v=9bZkp7q19f0',
 
24
  yt.vid_info = video['video_info']
25
  yt.init()
26
  return yt
27
+
28
+
29
+ @pytest.fixture
30
+ def gangnam_style():
31
+ """Youtube instance initialized with video id 9bZkp7q19f0."""
32
+ filename = 'yt-video-9bZkp7q19f0-1507588332.json.tar.gz'
33
+ return load_from_playback_file(filename)
34
+
35
+
36
+ @pytest.fixture
37
+ def youtube_captions_and_subtitles():
38
+ """Youtube instance initialized with video id QRS8MkLhQmM."""
39
+ filename = 'yt-video-QRS8MkLhQmM-1507588031.json.tar.gz'
40
+ return load_from_playback_file(filename)
tests/mocks/yt-video-9bZkp7q19f0-1507588332.json.tar.gz ADDED
Binary file (483 kB). View file
 
tests/mocks/yt-video-9bZkp7q19f0.json DELETED
The diff for this file is too large to render. See raw diff
 
tests/mocks/yt-video-QRS8MkLhQmM-1507588031.json.tar.gz ADDED
Binary file (367 kB). View file
 
tests/test_mixins.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ def test_pre_signed_video(youtube_captions_and_subtitles):
3
+ assert youtube_captions_and_subtitles.streams.count() == 15
tests/test_query.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Unit tests for the :class:`StreamQuery <StreamQuery>` class."""
3
+ import pytest
4
+
5
+
6
+ def test_count(gangnam_style):
7
+ assert gangnam_style.streams.count() == 22
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ 'test_input,expected', [
12
+ ({'progressive': True}, ['22', '43', '18', '36', '17']),
13
+ ({'resolution': '720p'}, ['22', '136', '247']),
14
+ ({'res': '720p'}, ['22', '136', '247']),
15
+ ({'fps': 30, 'resolution': '480p'}, ['135', '244']),
16
+ ({'mime_type': 'audio/mp4'}, ['140']),
17
+ ({'type': 'audio'}, ['140', '171', '249', '250', '251']),
18
+ ({'subtype': '3gpp'}, ['36', '17']),
19
+ ({'abr': '128kbps'}, ['43', '140', '171']),
20
+ ({'bitrate': '128kbps'}, ['43', '140', '171']),
21
+ ({'audio_codec': 'vorbis'}, ['43', '171']),
22
+ ({'video_codec': 'vp9'}, ['248', '247', '244', '243', '242', '278']),
23
+ ({'only_audio': True}, ['140', '171', '249', '250', '251']),
24
+ ({'only_video': True, 'video_codec': 'avc1.4d4015'}, ['133']),
25
+ ({'progressive': True}, ['22', '43', '18', '36', '17']),
26
+ ({'adaptive': True, 'resolution': '1080p'}, ['137', '248']),
27
+ ({'custom_filter_functions': [lambda s: s.itag == '22']}, ['22']),
28
+ ],
29
+ )
30
+ def test_filters(test_input, expected, gangnam_style):
31
+ result = [s.itag for s in gangnam_style.streams.filter(**test_input).all()]
32
+ assert result == expected
33
+
34
+
35
+ def test_get_last(gangnam_style):
36
+ assert gangnam_style.streams.last().itag == '251'
37
+
38
+
39
+ def test_get_last_empty_results(gangnam_style):
40
+ assert not gangnam_style.streams.filter(video_codec='vp20').last()
41
+
42
+
43
+ def test_get_first(gangnam_style):
44
+ assert gangnam_style.streams.first().itag == '22'
45
+
46
+
47
+ def test_get_first_empty_results(gangnam_style):
48
+ assert not gangnam_style.streams.filter(video_codec='vp20').first()
49
+
50
+
51
+ def test_order_by(gangnam_style):
52
+ itags = [
53
+ s.itag for s in gangnam_style.streams
54
+ .filter(progressive=True)
55
+ .order_by('itag')
56
+ .all()
57
+ ]
58
+
59
+ assert itags == ['17', '18', '22', '36', '43']
60
+
61
+
62
+ def test_order_by_descending(gangnam_style):
63
+ itags = [
64
+ s.itag for s in gangnam_style.streams
65
+ .filter(progressive=True)
66
+ .order_by('itag')
67
+ .desc()
68
+ .all()
69
+ ]
70
+
71
+ assert itags == ['43', '36', '22', '18', '17']
72
+
73
+
74
+ def test_order_by_ascending(gangnam_style):
75
+ itags = [
76
+ s.itag for s in gangnam_style.streams
77
+ .filter(progressive=True)
78
+ .order_by('itag')
79
+ .asc()
80
+ .all()
81
+ ]
82
+
83
+ assert itags == ['17', '18', '22', '36', '43']
84
+
85
+
86
+ def test_get_by_itag(gangnam_style):
87
+ assert gangnam_style.streams.get_by_itag(22).itag == '22'
88
+
89
+
90
+ def test_get_by_non_existent_itag(gangnam_style):
91
+ assert not gangnam_style.streams.get_by_itag(22983)