linting
Browse files- .pre-commit-config.yaml +22 -0
- pytube/__main__.py +61 -30
- pytube/api.py +105 -96
- pytube/jsinterp.py +8 -5
- pytube/models.py +9 -7
- pytube/utils.py +5 -4
- setup.py +27 -27
- tests/mock_data/youtube_age_restricted.html +20 -20
- tests/mock_data/youtube_gangnam_style.html +23 -23
- tests/requirements.txt +3 -2
- tests/test_p3_pytube.py +0 -18
- tests/test_pytube.py +12 -12
- tests/test_utils.py +7 -5
.pre-commit-config.yaml
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
2 |
+
sha: v0.8.0
|
3 |
+
hooks:
|
4 |
+
- id: autopep8-wrapper
|
5 |
+
- id: check-ast
|
6 |
+
- id: check-case-conflict
|
7 |
+
- id: check-merge-conflict
|
8 |
+
- id: detect-private-key
|
9 |
+
- id: double-quote-string-fixer
|
10 |
+
- id: end-of-file-fixer
|
11 |
+
- id: flake8
|
12 |
+
- id: requirements-txt-fixer
|
13 |
+
- id: trailing-whitespace
|
14 |
+
- repo: https://github.com/asottile/reorder_python_imports
|
15 |
+
sha: v0.3.4
|
16 |
+
hooks:
|
17 |
+
- id: reorder-python-imports
|
18 |
+
language_version: python3.6
|
19 |
+
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
|
20 |
+
sha: v1.1.0
|
21 |
+
hooks:
|
22 |
+
- id: python-safety-dependencies-check
|
pytube/__main__.py
CHANGED
@@ -1,30 +1,57 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import print_function
|
4 |
-
|
5 |
-
import os
|
6 |
import argparse
|
|
|
|
|
|
|
7 |
|
8 |
from . import YouTube
|
9 |
-
from .utils import print_status, FullPaths
|
10 |
from .exceptions import PytubeError
|
11 |
-
from
|
|
|
12 |
|
13 |
|
14 |
def main():
|
15 |
parser = argparse.ArgumentParser(description='YouTube video downloader')
|
16 |
-
parser.add_argument(
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
parser.add_argument(
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
parser.add_argument(
|
27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
args = parser.parse_args()
|
30 |
|
@@ -36,7 +63,7 @@ def main():
|
|
36 |
res = video.resolution
|
37 |
videos.append((ext, res))
|
38 |
except PytubeError:
|
39 |
-
print(
|
40 |
sys.exit(1)
|
41 |
|
42 |
if args.show_available:
|
@@ -48,8 +75,8 @@ def main():
|
|
48 |
|
49 |
if args.ext or args.res:
|
50 |
if not all([args.ext, args.res]):
|
51 |
-
print(
|
52 |
-
|
53 |
print_available_vids(videos)
|
54 |
sys.exit(1)
|
55 |
|
@@ -59,7 +86,7 @@ def main():
|
|
59 |
# Check if there's a video returned
|
60 |
if not vid:
|
61 |
print("There's no video with the specified format/resolution "
|
62 |
-
|
63 |
pprint(videos)
|
64 |
sys.exit(1)
|
65 |
|
@@ -68,7 +95,7 @@ def main():
|
|
68 |
videos = yt.filter(extension=args.ext)
|
69 |
# Check if we have a video
|
70 |
if not videos:
|
71 |
-
print(
|
72 |
sys.exit(1)
|
73 |
# Select the highest resolution one
|
74 |
vid = max(videos)
|
@@ -77,8 +104,8 @@ def main():
|
|
77 |
videos = yt.filter(resolution=args.res)
|
78 |
# Check if we have a video
|
79 |
if not videos:
|
80 |
-
print(
|
81 |
-
|
82 |
sys.exit(1)
|
83 |
# Select the highest resolution one
|
84 |
vid = max(videos)
|
@@ -87,25 +114,29 @@ def main():
|
|
87 |
print_available_vids(videos)
|
88 |
while True:
|
89 |
try:
|
90 |
-
choice = int(input(
|
91 |
vid = yt.get(*videos[choice])
|
92 |
break
|
93 |
except (ValueError, IndexError):
|
94 |
-
print(
|
|
|
95 |
except KeyboardInterrupt:
|
96 |
sys.exit(2)
|
97 |
|
98 |
try:
|
99 |
vid.download(path=args.path, on_progress=print_status)
|
100 |
except KeyboardInterrupt:
|
101 |
-
print(
|
102 |
sys.exit(1)
|
103 |
|
|
|
104 |
def print_available_vids(videos):
|
105 |
-
formatString =
|
106 |
-
print(formatString.format(
|
107 |
-
print(
|
108 |
-
print(
|
|
|
|
|
109 |
|
110 |
if __name__ == '__main__':
|
111 |
main()
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import print_function
|
4 |
+
|
|
|
5 |
import argparse
|
6 |
+
import os
|
7 |
+
import sys
|
8 |
+
from pprint import pprint
|
9 |
|
10 |
from . import YouTube
|
|
|
11 |
from .exceptions import PytubeError
|
12 |
+
from .utils import FullPaths
|
13 |
+
from .utils import print_status
|
14 |
|
15 |
|
16 |
def main():
|
17 |
parser = argparse.ArgumentParser(description='YouTube video downloader')
|
18 |
+
parser.add_argument(
|
19 |
+
'url',
|
20 |
+
help='The URL of the Video to be downloaded'
|
21 |
+
)
|
22 |
+
parser.add_argument(
|
23 |
+
'--extension',
|
24 |
+
'-e',
|
25 |
+
dest='ext',
|
26 |
+
help='The requested format of the video'
|
27 |
+
)
|
28 |
+
parser.add_argument(
|
29 |
+
'--resolution',
|
30 |
+
'-r',
|
31 |
+
dest='res',
|
32 |
+
help='The requested resolution'
|
33 |
+
)
|
34 |
+
parser.add_argument(
|
35 |
+
'--path',
|
36 |
+
'-p',
|
37 |
+
action=FullPaths,
|
38 |
+
default=os.getcwd(),
|
39 |
+
dest='path',
|
40 |
+
help='The path to save the video to.'
|
41 |
+
)
|
42 |
+
parser.add_argument(
|
43 |
+
'--filename',
|
44 |
+
'-f',
|
45 |
+
dest='filename',
|
46 |
+
help='The filename, without extension, to save the video in.',
|
47 |
+
)
|
48 |
+
parser.add_argument(
|
49 |
+
'--show_available',
|
50 |
+
'-s',
|
51 |
+
action='store_true',
|
52 |
+
dest='show_available',
|
53 |
+
help='Prints a list of available formats for download.'
|
54 |
+
)
|
55 |
|
56 |
args = parser.parse_args()
|
57 |
|
|
|
63 |
res = video.resolution
|
64 |
videos.append((ext, res))
|
65 |
except PytubeError:
|
66 |
+
print('Incorrect video URL.')
|
67 |
sys.exit(1)
|
68 |
|
69 |
if args.show_available:
|
|
|
75 |
|
76 |
if args.ext or args.res:
|
77 |
if not all([args.ext, args.res]):
|
78 |
+
print('Make sure you give either of the below specified '
|
79 |
+
'format/resolution combination.')
|
80 |
print_available_vids(videos)
|
81 |
sys.exit(1)
|
82 |
|
|
|
86 |
# Check if there's a video returned
|
87 |
if not vid:
|
88 |
print("There's no video with the specified format/resolution "
|
89 |
+
'combination.')
|
90 |
pprint(videos)
|
91 |
sys.exit(1)
|
92 |
|
|
|
95 |
videos = yt.filter(extension=args.ext)
|
96 |
# Check if we have a video
|
97 |
if not videos:
|
98 |
+
print('There are no videos in the specified format.')
|
99 |
sys.exit(1)
|
100 |
# Select the highest resolution one
|
101 |
vid = max(videos)
|
|
|
104 |
videos = yt.filter(resolution=args.res)
|
105 |
# Check if we have a video
|
106 |
if not videos:
|
107 |
+
print('There are no videos in the specified in the specified '
|
108 |
+
'resolution.')
|
109 |
sys.exit(1)
|
110 |
# Select the highest resolution one
|
111 |
vid = max(videos)
|
|
|
114 |
print_available_vids(videos)
|
115 |
while True:
|
116 |
try:
|
117 |
+
choice = int(input('Enter choice: '))
|
118 |
vid = yt.get(*videos[choice])
|
119 |
break
|
120 |
except (ValueError, IndexError):
|
121 |
+
print('Requires an integer in range 0-{}'
|
122 |
+
.format(len(videos) - 1))
|
123 |
except KeyboardInterrupt:
|
124 |
sys.exit(2)
|
125 |
|
126 |
try:
|
127 |
vid.download(path=args.path, on_progress=print_status)
|
128 |
except KeyboardInterrupt:
|
129 |
+
print('Download interrupted.')
|
130 |
sys.exit(1)
|
131 |
|
132 |
+
|
133 |
def print_available_vids(videos):
|
134 |
+
formatString = '{:<2} {:<15} {:<15}'
|
135 |
+
print(formatString.format('', 'Resolution', 'Extension'))
|
136 |
+
print('-' * 28)
|
137 |
+
print('\n'.join([formatString.format(index, *formatTuple)
|
138 |
+
for index, formatTuple in enumerate(videos)]))
|
139 |
+
|
140 |
|
141 |
if __name__ == '__main__':
|
142 |
main()
|
pytube/api.py
CHANGED
@@ -1,14 +1,22 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import absolute_import
|
4 |
-
|
5 |
import json
|
6 |
import logging
|
7 |
import re
|
8 |
import warnings
|
9 |
-
from
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
from .jsinterp import JSInterpreter
|
13 |
from .models import Video
|
14 |
from .utils import safe_filename
|
@@ -18,65 +26,66 @@ log = logging.getLogger(__name__)
|
|
18 |
# YouTube quality and codecs id map.
|
19 |
QUALITY_PROFILES = {
|
20 |
# flash
|
21 |
-
5: (
|
22 |
|
23 |
# 3gp
|
24 |
-
17: (
|
25 |
-
36: (
|
26 |
|
27 |
# webm
|
28 |
-
43: (
|
29 |
-
100: (
|
30 |
|
31 |
# mpeg4
|
32 |
-
18: (
|
33 |
-
22: (
|
34 |
-
82: (
|
35 |
-
83: (
|
36 |
-
84: (
|
37 |
-
85: (
|
38 |
-
|
39 |
-
160: (
|
40 |
-
133: (
|
41 |
-
134: (
|
42 |
-
135: (
|
43 |
-
136: (
|
44 |
-
298: (
|
45 |
-
|
46 |
-
137: (
|
47 |
-
299: (
|
48 |
-
264: (
|
49 |
-
266: (
|
50 |
-
|
51 |
-
242: (
|
52 |
-
243: (
|
53 |
-
244: (
|
54 |
-
247: (
|
55 |
-
248: (
|
56 |
-
271: (
|
57 |
-
278: (
|
58 |
-
302: (
|
59 |
-
303: (
|
60 |
-
308: (
|
61 |
-
313: (
|
62 |
-
315: (
|
63 |
}
|
64 |
|
65 |
# The keys corresponding to the quality/codec map above.
|
66 |
QUALITY_PROFILE_KEYS = (
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
)
|
75 |
|
76 |
|
77 |
class YouTube(object):
|
78 |
"""Class representation of a single instance of a YouTube session.
|
79 |
"""
|
|
|
80 |
def __init__(self, url=None):
|
81 |
"""Initializes YouTube API wrapper.
|
82 |
|
@@ -103,17 +112,17 @@ class YouTube(object):
|
|
103 |
:param str url:
|
104 |
The url to the YouTube video.
|
105 |
"""
|
106 |
-
warnings.warn(
|
107 |
-
|
108 |
self.from_url(url)
|
109 |
|
110 |
@property
|
111 |
def video_id(self):
|
112 |
"""Gets the video id by parsing and extracting it from the url."""
|
113 |
parts = urlparse(self._video_url)
|
114 |
-
qs = getattr(parts,
|
115 |
if qs:
|
116 |
-
video_id = parse_qs(qs).get(
|
117 |
if video_id:
|
118 |
return video_id.pop()
|
119 |
|
@@ -135,8 +144,8 @@ class YouTube(object):
|
|
135 |
:param str filename:
|
136 |
The filename of the video.
|
137 |
"""
|
138 |
-
warnings.warn(
|
139 |
-
|
140 |
self.set_filename(filename)
|
141 |
|
142 |
def set_filename(self, filename):
|
@@ -162,8 +171,8 @@ class YouTube(object):
|
|
162 |
"""Gets all videos. (This method is deprecated. Use `get_videos()`
|
163 |
instead.
|
164 |
"""
|
165 |
-
warnings.warn(
|
166 |
-
|
167 |
return self._videos
|
168 |
|
169 |
def from_url(self, url):
|
@@ -183,40 +192,40 @@ class YouTube(object):
|
|
183 |
video_data = self.get_video_data()
|
184 |
|
185 |
# Set the title from the title.
|
186 |
-
self.title = video_data.get(
|
187 |
|
188 |
# Rewrite and add the url to the javascript file, we'll need to fetch
|
189 |
# this if YouTube doesn't provide us with the signature.
|
190 |
-
js_partial_url = video_data.get(
|
191 |
if js_partial_url.startswith('//'):
|
192 |
js_url = 'http:' + js_partial_url
|
193 |
elif js_partial_url.startswith('/'):
|
194 |
js_url = 'https://youtube.com' + js_partial_url
|
195 |
|
196 |
# Just make these easily accessible as variables.
|
197 |
-
stream_map = video_data.get(
|
198 |
-
video_urls = stream_map.get(
|
199 |
|
200 |
# For each video url, identify the quality profile and add it to list
|
201 |
# of available videos.
|
202 |
for i, url in enumerate(video_urls):
|
203 |
-
log.debug(
|
204 |
try:
|
205 |
itag, quality_profile = self._get_quality_profile_from_url(url)
|
206 |
if not quality_profile:
|
207 |
-
log.warn(
|
208 |
continue
|
209 |
except (TypeError, KeyError) as e:
|
210 |
-
log.exception(
|
211 |
continue
|
212 |
|
213 |
# Check if we have the signature, otherwise we'll need to get the
|
214 |
# cipher from the js.
|
215 |
-
if
|
216 |
-
log.debug(
|
217 |
-
|
218 |
-
signature = self._get_cipher(stream_map[
|
219 |
-
url =
|
220 |
self._add_video(url, self.filename, **quality_profile)
|
221 |
# Clear the cached js. Make sure to keep this at the end of
|
222 |
# `from_url()` so we can mock inject the js in unit tests.
|
@@ -246,11 +255,11 @@ class YouTube(object):
|
|
246 |
result.append(v)
|
247 |
matches = len(result)
|
248 |
if matches <= 0:
|
249 |
-
raise DoesNotExist(
|
250 |
elif matches == 1:
|
251 |
return result[0]
|
252 |
else:
|
253 |
-
raise MultipleObjectsReturned(
|
254 |
|
255 |
def filter(self, extension=None, resolution=None, profile=None):
|
256 |
"""Gets a filtered list of videos given a file extention and/or
|
@@ -282,26 +291,26 @@ class YouTube(object):
|
|
282 |
self.title = None
|
283 |
response = urlopen(self.url)
|
284 |
if not response:
|
285 |
-
raise PytubeError(
|
286 |
|
287 |
html = response.read()
|
288 |
if isinstance(html, str):
|
289 |
-
restriction_pattern =
|
290 |
else:
|
291 |
-
restriction_pattern = bytes(
|
292 |
|
293 |
if restriction_pattern in html:
|
294 |
-
raise AgeRestricted(
|
295 |
-
|
296 |
|
297 |
# Extract out the json data from the html response body.
|
298 |
json_object = self._get_json_data(html)
|
299 |
|
300 |
# Here we decode the stream map and bundle it into the json object. We
|
301 |
# do this just so we just can return one object for the video data.
|
302 |
-
encoded_stream_map = json_object.get(
|
303 |
-
|
304 |
-
json_object[
|
305 |
encoded_stream_map)
|
306 |
return json_object
|
307 |
|
@@ -315,18 +324,18 @@ class YouTube(object):
|
|
315 |
dct = defaultdict(list)
|
316 |
|
317 |
# Split the comma separated videos.
|
318 |
-
videos = blob.split(
|
319 |
|
320 |
# Unquote the characters and split to parameters.
|
321 |
-
videos = [video.split(
|
322 |
|
323 |
# Split at the equals sign so we can break this key value pairs and
|
324 |
# toss it into a dictionary.
|
325 |
for video in videos:
|
326 |
for kv in video:
|
327 |
-
key, value = kv.split(
|
328 |
dct[key].append(unquote(value))
|
329 |
-
log.debug(
|
330 |
return dct
|
331 |
|
332 |
def _get_json_data(self, html):
|
@@ -337,23 +346,23 @@ class YouTube(object):
|
|
337 |
"""
|
338 |
# 18 represents the length of "ytplayer.config = ".
|
339 |
if isinstance(html, str):
|
340 |
-
json_start_pattern =
|
341 |
else:
|
342 |
-
json_start_pattern = bytes(
|
343 |
pattern_idx = html.find(json_start_pattern)
|
344 |
# In case video is unable to play
|
345 |
if(pattern_idx == -1):
|
346 |
-
raise PytubeError(
|
347 |
start = pattern_idx + 18
|
348 |
html = html[start:]
|
349 |
|
350 |
offset = self._get_json_offset(html)
|
351 |
if not offset:
|
352 |
-
raise PytubeError(
|
353 |
if isinstance(html, str):
|
354 |
json_content = json.loads(html[:offset])
|
355 |
else:
|
356 |
-
json_content = json.loads(html[:offset].decode(
|
357 |
|
358 |
return json_content
|
359 |
|
@@ -368,14 +377,14 @@ class YouTube(object):
|
|
368 |
for i, ch in enumerate(html):
|
369 |
if isinstance(ch, int):
|
370 |
ch = chr(ch)
|
371 |
-
if ch ==
|
372 |
unmatched_brackets_num += 1
|
373 |
-
elif ch ==
|
374 |
unmatched_brackets_num -= 1
|
375 |
if unmatched_brackets_num == 0:
|
376 |
break
|
377 |
else:
|
378 |
-
raise PytubeError(
|
379 |
return index + i
|
380 |
|
381 |
def _get_cipher(self, signature, url):
|
@@ -391,7 +400,7 @@ class YouTube(object):
|
|
391 |
if not self._js_cache:
|
392 |
response = urlopen(url)
|
393 |
if not response:
|
394 |
-
raise PytubeError(
|
395 |
self._js_cache = response.read().decode()
|
396 |
try:
|
397 |
matches = reg_exp.search(self._js_cache)
|
@@ -404,8 +413,8 @@ class YouTube(object):
|
|
404 |
return initial_function([signature])
|
405 |
except Exception as e:
|
406 |
raise CipherError("Couldn't cipher the signature. Maybe YouTube "
|
407 |
-
|
408 |
-
|
409 |
return False
|
410 |
|
411 |
def _get_quality_profile_from_url(self, video_url):
|
@@ -429,11 +438,11 @@ class YouTube(object):
|
|
429 |
# corresponding quality profile, referenced by the itag.
|
430 |
return itag, dict(list(zip(QUALITY_PROFILE_KEYS, quality_profile)))
|
431 |
if not itag:
|
432 |
-
raise PytubeError(
|
433 |
elif len(itag) > 1:
|
434 |
-
log.warn(
|
435 |
-
raise PytubeError(
|
436 |
-
|
437 |
return False
|
438 |
|
439 |
def _add_video(self, url, filename, **kwargs):
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import absolute_import
|
4 |
+
|
5 |
import json
|
6 |
import logging
|
7 |
import re
|
8 |
import warnings
|
9 |
+
from collections import defaultdict
|
10 |
+
|
11 |
+
from .compat import parse_qs
|
12 |
+
from .compat import unquote
|
13 |
+
from .compat import urlopen
|
14 |
+
from .compat import urlparse
|
15 |
+
from .exceptions import AgeRestricted
|
16 |
+
from .exceptions import CipherError
|
17 |
+
from .exceptions import DoesNotExist
|
18 |
+
from .exceptions import MultipleObjectsReturned
|
19 |
+
from .exceptions import PytubeError
|
20 |
from .jsinterp import JSInterpreter
|
21 |
from .models import Video
|
22 |
from .utils import safe_filename
|
|
|
26 |
# YouTube quality and codecs id map.
|
27 |
QUALITY_PROFILES = {
|
28 |
# flash
|
29 |
+
5: ('flv', '240p', 'Sorenson H.263', 'N/A', '0.25', 'MP3', '64'),
|
30 |
|
31 |
# 3gp
|
32 |
+
17: ('3gp', '144p', 'MPEG-4 Visual', 'Simple', '0.05', 'AAC', '24'),
|
33 |
+
36: ('3gp', '240p', 'MPEG-4 Visual', 'Simple', '0.17', 'AAC', '38'),
|
34 |
|
35 |
# webm
|
36 |
+
43: ('webm', '360p', 'VP8', 'N/A', '0.5', 'Vorbis', '128'),
|
37 |
+
100: ('webm', '360p', 'VP8', '3D', 'N/A', 'Vorbis', '128'),
|
38 |
|
39 |
# mpeg4
|
40 |
+
18: ('mp4', '360p', 'H.264', 'Baseline', '0.5', 'AAC', '96'),
|
41 |
+
22: ('mp4', '720p', 'H.264', 'High', '2-2.9', 'AAC', '192'),
|
42 |
+
82: ('mp4', '360p', 'H.264', '3D', '0.5', 'AAC', '96'),
|
43 |
+
83: ('mp4', '240p', 'H.264', '3D', '0.5', 'AAC', '96'),
|
44 |
+
84: ('mp4', '720p', 'H.264', '3D', '2-2.9', 'AAC', '152'),
|
45 |
+
85: ('mp4', '1080p', 'H.264', '3D', '2-2.9', 'AAC', '152'),
|
46 |
+
|
47 |
+
160: ('mp4', '144p', 'H.264', 'Main', '0.1', '', ''),
|
48 |
+
133: ('mp4', '240p', 'H.264', 'Main', '0.2-0.3', '', ''),
|
49 |
+
134: ('mp4', '360p', 'H.264', 'Main', '0.3-0.4', '', ''),
|
50 |
+
135: ('mp4', '480p', 'H.264', 'Main', '0.5-1', '', ''),
|
51 |
+
136: ('mp4', '720p', 'H.264', 'Main', '1-1.5', '', ''),
|
52 |
+
298: ('mp4', '720p HFR', 'H.264', 'Main', '3-3.5', '', ''),
|
53 |
+
|
54 |
+
137: ('mp4', '1080p', 'H.264', 'High', '2.5-3', '', ''),
|
55 |
+
299: ('mp4', '1080p HFR', 'H.264', 'High', '5.5', '', ''),
|
56 |
+
264: ('mp4', '2160p-2304p', 'H.264', 'High', '12.5-16', '', ''),
|
57 |
+
266: ('mp4', '2160p-4320p', 'H.264', 'High', '13.5-25', '', ''),
|
58 |
+
|
59 |
+
242: ('webm', '240p', 'vp9', 'n/a', '0.1-0.2', '', ''),
|
60 |
+
243: ('webm', '360p', 'vp9', 'n/a', '0.25', '', ''),
|
61 |
+
244: ('webm', '480p', 'vp9', 'n/a', '0.5', '', ''),
|
62 |
+
247: ('webm', '720p', 'vp9', 'n/a', '0.7-0.8', '', ''),
|
63 |
+
248: ('webm', '1080p', 'vp9', 'n/a', '1.5', '', ''),
|
64 |
+
271: ('webm', '1440p', 'vp9', 'n/a', '9', '', ''),
|
65 |
+
278: ('webm', '144p 15 fps', 'vp9', 'n/a', '0.08', '', ''),
|
66 |
+
302: ('webm', '720p HFR', 'vp9', 'n/a', '2.5', '', ''),
|
67 |
+
303: ('webm', '1080p HFR', 'vp9', 'n/a', '5', '', ''),
|
68 |
+
308: ('webm', '1440p HFR', 'vp9', 'n/a', '10', '', ''),
|
69 |
+
313: ('webm', '2160p', 'vp9', 'n/a', '13-15', '', ''),
|
70 |
+
315: ('webm', '2160p HFR', 'vp9', 'n/a', '20-25', '', '')
|
71 |
}
|
72 |
|
73 |
# The keys corresponding to the quality/codec map above.
|
74 |
QUALITY_PROFILE_KEYS = (
|
75 |
+
'extension',
|
76 |
+
'resolution',
|
77 |
+
'video_codec',
|
78 |
+
'profile',
|
79 |
+
'video_bitrate',
|
80 |
+
'audio_codec',
|
81 |
+
'audio_bitrate'
|
82 |
)
|
83 |
|
84 |
|
85 |
class YouTube(object):
|
86 |
"""Class representation of a single instance of a YouTube session.
|
87 |
"""
|
88 |
+
|
89 |
def __init__(self, url=None):
|
90 |
"""Initializes YouTube API wrapper.
|
91 |
|
|
|
112 |
:param str url:
|
113 |
The url to the YouTube video.
|
114 |
"""
|
115 |
+
warnings.warn('url setter deprecated, use `from_url()` '
|
116 |
+
'instead.', DeprecationWarning)
|
117 |
self.from_url(url)
|
118 |
|
119 |
@property
|
120 |
def video_id(self):
|
121 |
"""Gets the video id by parsing and extracting it from the url."""
|
122 |
parts = urlparse(self._video_url)
|
123 |
+
qs = getattr(parts, 'query')
|
124 |
if qs:
|
125 |
+
video_id = parse_qs(qs).get('v')
|
126 |
if video_id:
|
127 |
return video_id.pop()
|
128 |
|
|
|
144 |
:param str filename:
|
145 |
The filename of the video.
|
146 |
"""
|
147 |
+
warnings.warn('filename setter deprecated. Use `set_filename()` '
|
148 |
+
'instead.', DeprecationWarning)
|
149 |
self.set_filename(filename)
|
150 |
|
151 |
def set_filename(self, filename):
|
|
|
171 |
"""Gets all videos. (This method is deprecated. Use `get_videos()`
|
172 |
instead.
|
173 |
"""
|
174 |
+
warnings.warn('videos property deprecated. Use `get_videos()` '
|
175 |
+
'instead.', DeprecationWarning)
|
176 |
return self._videos
|
177 |
|
178 |
def from_url(self, url):
|
|
|
192 |
video_data = self.get_video_data()
|
193 |
|
194 |
# Set the title from the title.
|
195 |
+
self.title = video_data.get('args', {}).get('title')
|
196 |
|
197 |
# Rewrite and add the url to the javascript file, we'll need to fetch
|
198 |
# this if YouTube doesn't provide us with the signature.
|
199 |
+
js_partial_url = video_data.get('assets', {}).get('js')
|
200 |
if js_partial_url.startswith('//'):
|
201 |
js_url = 'http:' + js_partial_url
|
202 |
elif js_partial_url.startswith('/'):
|
203 |
js_url = 'https://youtube.com' + js_partial_url
|
204 |
|
205 |
# Just make these easily accessible as variables.
|
206 |
+
stream_map = video_data.get('args', {}).get('stream_map')
|
207 |
+
video_urls = stream_map.get('url')
|
208 |
|
209 |
# For each video url, identify the quality profile and add it to list
|
210 |
# of available videos.
|
211 |
for i, url in enumerate(video_urls):
|
212 |
+
log.debug('attempting to get quality profile from url: %s', url)
|
213 |
try:
|
214 |
itag, quality_profile = self._get_quality_profile_from_url(url)
|
215 |
if not quality_profile:
|
216 |
+
log.warn('unable to identify profile for itag=%s', itag)
|
217 |
continue
|
218 |
except (TypeError, KeyError) as e:
|
219 |
+
log.exception('passing on exception %s', e)
|
220 |
continue
|
221 |
|
222 |
# Check if we have the signature, otherwise we'll need to get the
|
223 |
# cipher from the js.
|
224 |
+
if 'signature=' not in url:
|
225 |
+
log.debug('signature not in url, attempting to resolve the '
|
226 |
+
'cipher.')
|
227 |
+
signature = self._get_cipher(stream_map['s'][i], js_url)
|
228 |
+
url = '{0}&signature={1}'.format(url, signature)
|
229 |
self._add_video(url, self.filename, **quality_profile)
|
230 |
# Clear the cached js. Make sure to keep this at the end of
|
231 |
# `from_url()` so we can mock inject the js in unit tests.
|
|
|
255 |
result.append(v)
|
256 |
matches = len(result)
|
257 |
if matches <= 0:
|
258 |
+
raise DoesNotExist('No videos met this criteria.')
|
259 |
elif matches == 1:
|
260 |
return result[0]
|
261 |
else:
|
262 |
+
raise MultipleObjectsReturned('Multiple videos met this criteria.')
|
263 |
|
264 |
def filter(self, extension=None, resolution=None, profile=None):
|
265 |
"""Gets a filtered list of videos given a file extention and/or
|
|
|
291 |
self.title = None
|
292 |
response = urlopen(self.url)
|
293 |
if not response:
|
294 |
+
raise PytubeError('Unable to open url: {0}'.format(self.url))
|
295 |
|
296 |
html = response.read()
|
297 |
if isinstance(html, str):
|
298 |
+
restriction_pattern = 'og:restrictions:age'
|
299 |
else:
|
300 |
+
restriction_pattern = bytes('og:restrictions:age', 'utf-8')
|
301 |
|
302 |
if restriction_pattern in html:
|
303 |
+
raise AgeRestricted('Age restricted video. Unable to download '
|
304 |
+
'without being signed in.')
|
305 |
|
306 |
# Extract out the json data from the html response body.
|
307 |
json_object = self._get_json_data(html)
|
308 |
|
309 |
# Here we decode the stream map and bundle it into the json object. We
|
310 |
# do this just so we just can return one object for the video data.
|
311 |
+
encoded_stream_map = json_object.get('args', {}).get(
|
312 |
+
'url_encoded_fmt_stream_map')
|
313 |
+
json_object['args']['stream_map'] = self._parse_stream_map(
|
314 |
encoded_stream_map)
|
315 |
return json_object
|
316 |
|
|
|
324 |
dct = defaultdict(list)
|
325 |
|
326 |
# Split the comma separated videos.
|
327 |
+
videos = blob.split(',')
|
328 |
|
329 |
# Unquote the characters and split to parameters.
|
330 |
+
videos = [video.split('&') for video in videos]
|
331 |
|
332 |
# Split at the equals sign so we can break this key value pairs and
|
333 |
# toss it into a dictionary.
|
334 |
for video in videos:
|
335 |
for kv in video:
|
336 |
+
key, value = kv.split('=')
|
337 |
dct[key].append(unquote(value))
|
338 |
+
log.debug('decoded stream map: %s', dct)
|
339 |
return dct
|
340 |
|
341 |
def _get_json_data(self, html):
|
|
|
346 |
"""
|
347 |
# 18 represents the length of "ytplayer.config = ".
|
348 |
if isinstance(html, str):
|
349 |
+
json_start_pattern = 'ytplayer.config = '
|
350 |
else:
|
351 |
+
json_start_pattern = bytes('ytplayer.config = ', 'utf-8')
|
352 |
pattern_idx = html.find(json_start_pattern)
|
353 |
# In case video is unable to play
|
354 |
if(pattern_idx == -1):
|
355 |
+
raise PytubeError('Unable to find start pattern.')
|
356 |
start = pattern_idx + 18
|
357 |
html = html[start:]
|
358 |
|
359 |
offset = self._get_json_offset(html)
|
360 |
if not offset:
|
361 |
+
raise PytubeError('Unable to extract json.')
|
362 |
if isinstance(html, str):
|
363 |
json_content = json.loads(html[:offset])
|
364 |
else:
|
365 |
+
json_content = json.loads(html[:offset].decode('utf-8'))
|
366 |
|
367 |
return json_content
|
368 |
|
|
|
377 |
for i, ch in enumerate(html):
|
378 |
if isinstance(ch, int):
|
379 |
ch = chr(ch)
|
380 |
+
if ch == '{':
|
381 |
unmatched_brackets_num += 1
|
382 |
+
elif ch == '}':
|
383 |
unmatched_brackets_num -= 1
|
384 |
if unmatched_brackets_num == 0:
|
385 |
break
|
386 |
else:
|
387 |
+
raise PytubeError('Unable to determine json offset.')
|
388 |
return index + i
|
389 |
|
390 |
def _get_cipher(self, signature, url):
|
|
|
400 |
if not self._js_cache:
|
401 |
response = urlopen(url)
|
402 |
if not response:
|
403 |
+
raise PytubeError('Unable to open url: {0}'.format(self.url))
|
404 |
self._js_cache = response.read().decode()
|
405 |
try:
|
406 |
matches = reg_exp.search(self._js_cache)
|
|
|
413 |
return initial_function([signature])
|
414 |
except Exception as e:
|
415 |
raise CipherError("Couldn't cipher the signature. Maybe YouTube "
|
416 |
+
'has changed the cipher algorithm. Notify this '
|
417 |
+
'issue on GitHub: {0}'.format(e))
|
418 |
return False
|
419 |
|
420 |
def _get_quality_profile_from_url(self, video_url):
|
|
|
438 |
# corresponding quality profile, referenced by the itag.
|
439 |
return itag, dict(list(zip(QUALITY_PROFILE_KEYS, quality_profile)))
|
440 |
if not itag:
|
441 |
+
raise PytubeError('Unable to get encoding profile, no itag found.')
|
442 |
elif len(itag) > 1:
|
443 |
+
log.warn('Multiple itags found: %s', itag)
|
444 |
+
raise PytubeError('Unable to get encoding profile, multiple itags '
|
445 |
+
'found.')
|
446 |
return False
|
447 |
|
448 |
def _add_video(self, url, filename, **kwargs):
|
pytube/jsinterp.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
|
|
4 |
import json
|
5 |
import operator
|
6 |
import re
|
|
|
7 |
from .exceptions import ExtractorError
|
8 |
|
9 |
_OPERATORS = [
|
@@ -118,9 +120,8 @@ class JSInterpreter(object):
|
|
118 |
except ValueError:
|
119 |
pass
|
120 |
|
121 |
-
m = re.match(
|
122 |
-
|
123 |
-
expr)
|
124 |
if m:
|
125 |
variable = m.group('var')
|
126 |
member = m.group('member')
|
@@ -212,7 +213,8 @@ class JSInterpreter(object):
|
|
212 |
obj = {}
|
213 |
obj_m = re.search(
|
214 |
(r'(?:var\s+)?%s\s*=\s*\{' % re.escape(objname)) +
|
215 |
-
r'\s*(?P<fields>([a-zA-Z$0-9]+\s*:\s*function\(.*?\)\s*\
|
|
|
216 |
r'\}\s*;',
|
217 |
self.code)
|
218 |
fields = obj_m.group('fields')
|
@@ -223,7 +225,8 @@ class JSInterpreter(object):
|
|
223 |
fields)
|
224 |
for f in fields_m:
|
225 |
argnames = f.group('args').split(',')
|
226 |
-
obj[f.group('key')] = self.build_function(
|
|
|
227 |
|
228 |
return obj
|
229 |
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
4 |
+
|
5 |
import json
|
6 |
import operator
|
7 |
import re
|
8 |
+
|
9 |
from .exceptions import ExtractorError
|
10 |
|
11 |
_OPERATORS = [
|
|
|
120 |
except ValueError:
|
121 |
pass
|
122 |
|
123 |
+
m = re.match(r'(?P<var>%s)\.(?P<member>[^(]+)'
|
124 |
+
r'(?:\(+(?P<args>[^()]*)\))?$' % _NAME_RE, expr)
|
|
|
125 |
if m:
|
126 |
variable = m.group('var')
|
127 |
member = m.group('member')
|
|
|
213 |
obj = {}
|
214 |
obj_m = re.search(
|
215 |
(r'(?:var\s+)?%s\s*=\s*\{' % re.escape(objname)) +
|
216 |
+
r'\s*(?P<fields>([a-zA-Z$0-9]+\s*:\s*function\(.*?\)\s*\'' +
|
217 |
+
r'{.*?\}(?:,\s*)?)*)' +
|
218 |
r'\}\s*;',
|
219 |
self.code)
|
220 |
fields = obj_m.group('fields')
|
|
|
225 |
fields)
|
226 |
for f in fields_m:
|
227 |
argnames = f.group('args').split(',')
|
228 |
+
obj[f.group('key')] = self.build_function(
|
229 |
+
argnames, f.group('code'))
|
230 |
|
231 |
return obj
|
232 |
|
pytube/models.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
|
|
4 |
import os
|
5 |
from time import clock
|
6 |
|
@@ -13,6 +14,7 @@ except ImportError:
|
|
13 |
class Video(object):
|
14 |
"""Class representation of a single instance of a YouTube video.
|
15 |
"""
|
|
|
16 |
def __init__(self, url, filename, extension, resolution=None,
|
17 |
video_codec=None, profile=None, video_bitrate=None,
|
18 |
audio_codec=None, audio_bitrate=None):
|
@@ -70,7 +72,7 @@ class Video(object):
|
|
70 |
if not os.path.isdir(path):
|
71 |
raise OSError('Make sure path exists.')
|
72 |
|
73 |
-
filename =
|
74 |
path = os.path.join(path, filename)
|
75 |
# TODO: If it's not a path, this should raise an ``OSError``.
|
76 |
# TODO: Move this into cli, this kind of logic probably shouldn't be
|
@@ -108,7 +110,7 @@ class Video(object):
|
|
108 |
# to disable this.
|
109 |
os.remove(path)
|
110 |
raise KeyboardInterrupt(
|
111 |
-
|
112 |
|
113 |
def file_size(self, response):
|
114 |
"""Gets the file size from the response
|
@@ -117,12 +119,12 @@ class Video(object):
|
|
117 |
Response of a opened url.
|
118 |
"""
|
119 |
meta_data = dict(response.info().items())
|
120 |
-
return int(meta_data.get(
|
121 |
-
meta_data.get(
|
122 |
|
123 |
def __repr__(self):
|
124 |
"""A clean representation of the class instance."""
|
125 |
-
return
|
126 |
self.video_codec, self.extension, self.resolution, self.profile)
|
127 |
|
128 |
def __lt__(self, other):
|
@@ -133,6 +135,6 @@ class Video(object):
|
|
133 |
The instance of the other video instance for comparison.
|
134 |
"""
|
135 |
if isinstance(other, Video):
|
136 |
-
v1 =
|
137 |
-
v2 =
|
138 |
return (v1 > v2) - (v1 < v2) < 0
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
4 |
+
|
5 |
import os
|
6 |
from time import clock
|
7 |
|
|
|
14 |
class Video(object):
|
15 |
"""Class representation of a single instance of a YouTube video.
|
16 |
"""
|
17 |
+
|
18 |
def __init__(self, url, filename, extension, resolution=None,
|
19 |
video_codec=None, profile=None, video_bitrate=None,
|
20 |
audio_codec=None, audio_bitrate=None):
|
|
|
72 |
if not os.path.isdir(path):
|
73 |
raise OSError('Make sure path exists.')
|
74 |
|
75 |
+
filename = '{0}.{1}'.format(self.filename, self.extension)
|
76 |
path = os.path.join(path, filename)
|
77 |
# TODO: If it's not a path, this should raise an ``OSError``.
|
78 |
# TODO: Move this into cli, this kind of logic probably shouldn't be
|
|
|
110 |
# to disable this.
|
111 |
os.remove(path)
|
112 |
raise KeyboardInterrupt(
|
113 |
+
'Interrupt signal given. Deleting incomplete video.')
|
114 |
|
115 |
def file_size(self, response):
|
116 |
"""Gets the file size from the response
|
|
|
119 |
Response of a opened url.
|
120 |
"""
|
121 |
meta_data = dict(response.info().items())
|
122 |
+
return int(meta_data.get('Content-Length') or
|
123 |
+
meta_data.get('content-length'))
|
124 |
|
125 |
def __repr__(self):
|
126 |
"""A clean representation of the class instance."""
|
127 |
+
return '<Video: {0} (.{1}) - {2} - {3}>'.format(
|
128 |
self.video_codec, self.extension, self.resolution, self.profile)
|
129 |
|
130 |
def __lt__(self, other):
|
|
|
135 |
The instance of the other video instance for comparison.
|
136 |
"""
|
137 |
if isinstance(other, Video):
|
138 |
+
v1 = '{0} {1}'.format(self.extension, self.resolution)
|
139 |
+
v2 = '{0} {1}'.format(other.extension, other.resolution)
|
140 |
return (v1 > v2) - (v1 < v2) < 0
|
pytube/utils.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
import argparse
|
4 |
-
import re
|
5 |
import math
|
6 |
-
|
7 |
from os import path
|
8 |
from sys import stdout
|
9 |
from time import clock
|
@@ -11,6 +10,7 @@ from time import clock
|
|
11 |
|
12 |
class FullPaths(argparse.Action):
|
13 |
"""Expand user- and relative-paths"""
|
|
|
14 |
def __call__(self, parser, namespace, values, option_string=None):
|
15 |
setattr(namespace, self.dest, path.abspath(path.expanduser(values)))
|
16 |
|
@@ -50,10 +50,11 @@ def sizeof(byts):
|
|
50 |
"""
|
51 |
sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']
|
52 |
power = int(math.floor(math.log(byts, 1024)))
|
53 |
-
value = int(byts/float(1024**power))
|
54 |
suffix = sizes[power] if byts != 1 else 'byte'
|
55 |
return '{0} {1}'.format(value, suffix)
|
56 |
|
|
|
57 |
def print_status(progress, file_size, start):
|
58 |
"""
|
59 |
This function - when passed as `on_progress` to `Video.download` - prints
|
@@ -71,7 +72,7 @@ def print_status(progress, file_size, start):
|
|
71 |
done = int(50 * progress / int(file_size))
|
72 |
dt = (clock() - start)
|
73 |
if dt > 0:
|
74 |
-
stdout.write(
|
75 |
('=' * done, ' ' * (50 - done), percent_done,
|
76 |
sizeof(file_size), sizeof(progress // dt)))
|
77 |
stdout.flush()
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
import argparse
|
|
|
4 |
import math
|
5 |
+
import re
|
6 |
from os import path
|
7 |
from sys import stdout
|
8 |
from time import clock
|
|
|
10 |
|
11 |
class FullPaths(argparse.Action):
|
12 |
"""Expand user- and relative-paths"""
|
13 |
+
|
14 |
def __call__(self, parser, namespace, values, option_string=None):
|
15 |
setattr(namespace, self.dest, path.abspath(path.expanduser(values)))
|
16 |
|
|
|
50 |
"""
|
51 |
sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']
|
52 |
power = int(math.floor(math.log(byts, 1024)))
|
53 |
+
value = int(byts / float(1024**power))
|
54 |
suffix = sizes[power] if byts != 1 else 'byte'
|
55 |
return '{0} {1}'.format(value, suffix)
|
56 |
|
57 |
+
|
58 |
def print_status(progress, file_size, start):
|
59 |
"""
|
60 |
This function - when passed as `on_progress` to `Video.download` - prints
|
|
|
72 |
done = int(50 * progress / int(file_size))
|
73 |
dt = (clock() - start)
|
74 |
if dt > 0:
|
75 |
+
stdout.write('\r [%s%s][%3.2f%%] %s at %s/s ' %
|
76 |
('=' * done, ' ' * (50 - done), percent_done,
|
77 |
sizeof(file_size), sizeof(progress // dt)))
|
78 |
stdout.flush()
|
setup.py
CHANGED
@@ -12,12 +12,12 @@ with open('LICENSE.txt') as readme_file:
|
|
12 |
license = readme_file.read()
|
13 |
|
14 |
setup(
|
15 |
-
name=
|
16 |
-
version=
|
17 |
-
author=
|
18 |
-
author_email=
|
19 |
packages=['pytube'],
|
20 |
-
url=
|
21 |
license=license,
|
22 |
entry_points={
|
23 |
'console_scripts': [
|
@@ -25,29 +25,29 @@ setup(
|
|
25 |
],
|
26 |
},
|
27 |
classifiers=[
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
],
|
50 |
-
description=(
|
51 |
long_description=readme,
|
52 |
zip_safe=True,
|
53 |
|
|
|
12 |
license = readme_file.read()
|
13 |
|
14 |
setup(
|
15 |
+
name='pytube',
|
16 |
+
version='6.3.1',
|
17 |
+
author='Nick Ficano',
|
18 |
+
author_email='nficano@gmail.com',
|
19 |
packages=['pytube'],
|
20 |
+
url='https://github.com/nficano/pytube',
|
21 |
license=license,
|
22 |
entry_points={
|
23 |
'console_scripts': [
|
|
|
25 |
],
|
26 |
},
|
27 |
classifiers=[
|
28 |
+
'Development Status :: 5 - Production/Stable',
|
29 |
+
'Environment :: Console',
|
30 |
+
'Intended Audience :: Developers',
|
31 |
+
'License :: OSI Approved :: MIT License',
|
32 |
+
'Natural Language :: English',
|
33 |
+
'Operating System :: MacOS',
|
34 |
+
'Operating System :: Microsoft',
|
35 |
+
'Operating System :: POSIX',
|
36 |
+
'Operating System :: Unix',
|
37 |
+
'Programming Language :: Python :: 2.6',
|
38 |
+
'Programming Language :: Python :: 2.7',
|
39 |
+
'Programming Language :: Python :: 3.3',
|
40 |
+
'Programming Language :: Python :: 3.4',
|
41 |
+
'Programming Language :: Python :: 3.5',
|
42 |
+
'Programming Language :: Python :: 3.6',
|
43 |
+
'Programming Language :: Python',
|
44 |
+
'Topic :: Internet',
|
45 |
+
'Topic :: Multimedia :: Video',
|
46 |
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
47 |
+
'Topic :: Terminals',
|
48 |
+
'Topic :: Utilities',
|
49 |
],
|
50 |
+
description=('A Python library for downloading YouTube videos.'),
|
51 |
long_description=readme,
|
52 |
zip_safe=True,
|
53 |
|
tests/mock_data/youtube_age_restricted.html
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<!DOCTYPE html><html lang="en" data-cast-api-enabled="true"><head><style name="www-roboto">@font-face{font-family:'Roboto';font-style:normal;font-weight:400;src:local('Roboto Regular'),local('Roboto-Regular'),url(//fonts.gstatic.com/s/roboto/v15/zN7GBFwfMP4uA6AR0HCoLQ.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;src:local('Roboto Medium'),local('Roboto-Medium'),url(//fonts.gstatic.com/s/roboto/v15/RxZJdnzeo3R5zSexge8UUaCWcynf_cDxXwCLxiixG1c.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:italic;font-weight:400;src:local('Roboto Italic'),local('Roboto-Italic'),url(//fonts.gstatic.com/s/roboto/v15/W4wDsBUluyw0tK3tykhXEfesZW2xOQ-xsNqO47m55DA.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:italic;font-weight:500;src:local('Roboto Medium Italic'),local('Roboto-MediumItalic'),url(//fonts.gstatic.com/s/roboto/v15/OLffGBTaF0XFOW1gnuHF0Z0EAVxt0G0biEntp43Qt6E.ttf)format('truetype');}</style><script name="www-roboto">if (document.fonts && document.fonts.load) {document.fonts.load("400 10pt Roboto", "E");document.fonts.load("500 10pt Roboto", "E");}</script><script>var ytcsi = {gt: function(n) {n = (n || '') + 'data_';return ytcsi[n] || (ytcsi[n] = {tick: {},span: {},info: {}});},tick: function(l, t, n) {ytcsi.gt(n).tick[l] = t || +new Date();},span: function(l, s, e, n) {ytcsi.gt(n).span[l] = (e ? e : +new Date()) - ytcsi.gt(n).tick[s];},setSpan: function(l, s, n) {ytcsi.gt(n).span[l] = s;},info: function(k, v, n) {ytcsi.gt(n).info[k] = v;},setStart: function(s, t, n) {ytcsi.info('yt_sts', s, n);ytcsi.tick('_start', t, n);}};(function(w, d) {ytcsi.perf = w.performance || w.mozPerformance ||w.msPerformance || w.webkitPerformance;ytcsi.setStart('dhs', ytcsi.perf ? ytcsi.perf.timing.responseStart : null);var isPrerender = (d.visibilityState || d.webkitVisibilityState) == 'prerender';var vName = d.webkitVisibilityState ? 'webkitvisibilitychange' : 'visibilitychange';if (isPrerender) {ytcsi.info('prerender', 1);var startTick = function() {ytcsi.setStart('dhs');d.removeEventListener(vName, startTick);};d.addEventListener(vName, startTick, false);}if (d.addEventListener) {d.addEventListener(vName, function() {ytcsi.tick('vc');}, false);}})(window, document);</script><script>var ytcfg = {d: function() {return (window.yt && yt.config_) || ytcfg.data_ || (ytcfg.data_ = {});},get: function(k, o) {return (k in ytcfg.d()) ? ytcfg.d()[k] : o;},set: function() {var a = arguments;if (a.length > 1) {ytcfg.d()[a[0]] = a[1];} else {for (var k in a[0]) {ytcfg.d()[k] = a[0][k];}}}};</script> <script>ytcfg.set("LACT", null);</script>
|
2 |
-
|
3 |
|
4 |
|
5 |
|
@@ -30,8 +30,8 @@ var m=["yt","www","masthead","sizing","runBeforeBodyIsReady"],n=this;m[0]in n||!
|
|
30 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-player-new-vfl1mGUtZ.css" name="www-player">
|
31 |
|
32 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-pageframe-vflAoSuzf.css" name="www-pageframe">
|
33 |
-
|
34 |
-
|
35 |
|
36 |
<title>Kara Tointon (Age-restricted video) - YouTube</title><link rel="search" type="application/opensearchdescription+xml" href="https://www.youtube.com/opensearch?locale=en_US" title="YouTube Video Search"><link rel="shortcut icon" href="https://s.ytimg.com/yts/img/favicon-vflz7uhzw.ico" type="image/x-icon"> <link rel="icon" href="//s.ytimg.com/yts/img/favicon_32-vfl8NGn4k.png" sizes="32x32"><link rel="icon" href="//s.ytimg.com/yts/img/favicon_48-vfl1s0rGh.png" sizes="48x48"><link rel="icon" href="//s.ytimg.com/yts/img/favicon_96-vfldSA3ca.png" sizes="96x96"><link rel="icon" href="//s.ytimg.com/yts/img/favicon_144-vflWmzoXw.png" sizes="144x144"><link rel="canonical" href="http://www.youtube.com/watch?v=nzNgkc6t260"><link rel="alternate" media="handheld" href="http://m.youtube.com/watch?v=nzNgkc6t260"><link rel="alternate" media="only screen and (max-width: 640px)" href="http://m.youtube.com/watch?v=nzNgkc6t260"><link rel="shortlink" href="https://youtu.be/nzNgkc6t260"> <meta name="title" content="Kara Tointon (Age-restricted video)">
|
37 |
|
@@ -110,9 +110,9 @@ var m=["yt","www","masthead","sizing","runBeforeBodyIsReady"],n=this;m[0]in n||!
|
|
110 |
<meta name="twitter:player:width" content="1280">
|
111 |
<meta name="twitter:player:height" content="720">
|
112 |
|
113 |
-
|
114 |
<style>.exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: inline-block;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: none;}@media only screen and (min-width: 850px) {.exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: none;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: inline-block;}}</style></head> <body dir="ltr" id="body" class=" ltr exp-hamburglar exp-responsive exp-scrollable-guide exp-watch-controls-overlay site-center-aligned site-as-giant-card appbar-hidden not-nirvana-dogfood not-yt-legacy-css flex-width-enabled flex-width-enabled-snap delayed-frame-styles-not-in " data-spf-name="watch">
|
115 |
-
<div id="early-body"></div><div id="body-container"><div id="a11y-announcements-container" role="alert"><div id="a11y-announcements-message"></div></div><form name="logoutForm" method="POST" action="/logout"><input type="hidden" name="action_logout" value="1"></form><div id="masthead-positioner">
|
116 |
<div id="yt-masthead-container" class="clearfix yt-base-gutter"> <button id="a11y-skip-nav" class="skip-nav" data-target-id="main" tabindex="3">
|
117 |
Skip navigation
|
118 |
</button>
|
@@ -142,7 +142,7 @@ Loading...
|
|
142 |
</div>
|
143 |
</div>
|
144 |
|
145 |
-
</div><div class="alerts-wrapper"><div id="alerts" class="content-alignment">
|
146 |
<div id="editor-progress-alert-container"></div>
|
147 |
<div class="yt-alert yt-alert-default yt-alert-warn hid " id="editor-progress-alert-template"> <div class="yt-alert-icon">
|
148 |
<span class="icon master-sprite yt-sprite"></span>
|
@@ -185,11 +185,11 @@ Loading...
|
|
185 |
</div>
|
186 |
|
187 |
<div id="player-api" class="player-width player-height off-screen-target player-api" tabIndex="-1"></div>
|
188 |
-
|
189 |
|
190 |
<div id="watch-queue-mole" class="video-mole mole-collapsed hid"><div id="watch-queue" class="watch-playlist player-height"><div class="main-content"><div class="watch-queue-header"><div class="watch-queue-info"><div class="watch-queue-info-icon"><span class="tv-queue-list-icon yt-sprite"></span></div><h3 class="watch-queue-title">Watch Queue</h3><h3 class="tv-queue-title">TV Queue</h3><span class="tv-queue-details"></span></div><div class="watch-queue-control-bar control-bar-button"><div class="watch-queue-mole-info"><div class="watch-queue-control-bar-icon"><span class="watch-queue-icon yt-sprite"></span></div><div class="watch-queue-title-container"><span class="watch-queue-count"></span><span class="watch-queue-title">Watch Queue</span><span class="tv-queue-title">TV Queue</span></div></div> <span class="dark-overflow-action-menu">
|
191 |
-
|
192 |
-
|
193 |
<button onclick=";return false;" class="flip control-bar-button yt-uix-button yt-uix-button-dark-overflow-action-menu yt-uix-button-size-default yt-uix-button-has-icon no-icon-markup yt-uix-button-empty" type="button" aria-label="Actions for the queue" aria-expanded="false" aria-haspopup="true" ><span class="yt-uix-button-arrow yt-sprite"></span><ul class="watch-queue-menu yt-uix-button-menu yt-uix-button-menu-dark-overflow-action-menu hid" role="menu" aria-haspopup="true"><li role="menuitem"><span onclick=";return false;" class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="remove-all" >Remove all</span></li><li role="menuitem"><span onclick=";return false;" class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="disconnect" >Disconnect</span></li></ul></button>
|
194 |
</span>
|
195 |
<div class="watch-queue-controls">
|
@@ -234,7 +234,7 @@ Loading...
|
|
234 |
</div>
|
235 |
</div></div>
|
236 |
<div id="player-playlist" class=" content-alignment watch-player-playlist ">
|
237 |
-
|
238 |
|
239 |
</div>
|
240 |
|
@@ -301,7 +301,7 @@ Loading...
|
|
301 |
<div id="watch7-headline" class="clearfix">
|
302 |
<div id="watch-headline-title">
|
303 |
<h1 class="yt watch-title-container" >
|
304 |
-
|
305 |
|
306 |
<span id="eow-title" class="watch-title " dir="ltr" title="Kara Tointon (Age-restricted video)">
|
307 |
Kara Tointon (Age-restricted video)
|
@@ -327,9 +327,9 @@ Loading...
|
|
327 |
<div class="yt-user-info">
|
328 |
<a href="/channel/UCAX1W4xcZRiDUVjqwBr5x-g" class="yt-uix-sessionlink g-hovercard spf-link " data-ytid="UCAX1W4xcZRiDUVjqwBr5x-g" data-sessionlink="itct=CAwQ4TkiEwjq3t_a5LvIAhXaVL4KHRUCDPko-B0" >Buzz Rock @MrGreglaw</a>
|
329 |
</div>
|
330 |
-
<span id="watch7-subscription-container"><span class=" yt-uix-button-subscription-container"><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-subscribe-branded yt-uix-button-has-icon no-icon-markup yt-uix-subscription-button yt-can-buffer" type="button" onclick=";return false;" aria-live="polite" aria-busy="false" data-style-type="branded" data-href="https://accounts.google.com/ServiceLogin?continue=http%3A%2F%2Fwww.youtube.com%2Fsignin%3Fhl%3Den%26action_handle_signin%3Dtrue%26feature%3Dsubscribe%26next%3D%252Fchannel%252FUCAX1W4xcZRiDUVjqwBr5x-g%26continue_action%3DQUFFLUhqbDRuRDZfV1N1MzViWl9GTHFFT0I0SXZiMEFpd3xBQ3Jtc0ttTk5tQ19ubENRcUNYV0psSGNOS21wQldrM0NTbUtkR2tOa2ZBR3lnZ0wxdGNjcS02ajVnRmN1bDJ3QkdaX29JcG1IM3RpVG9lQW52WjFzM3ZCMEVaMzVqT09OVElhanI3cVNzc3BxbGpINl9JUmVGQnp0NThVdWtuRmdQby1ZMTU1UnY1d0tpdThneFdKaWp6dHJyeFkzZGJSaEZBMlBNaXVzVnd6aUZoWnVndlJ3T2hPaVhpU3h5SHJBcFNGTTFIQVR5YUdZXzlObWRFX2x6Y2otTWR4Q19VOWN3%26app%3Ddesktop&hl=en&service=youtube&passive=true&uilel=3" data-channel-external-id="UCAX1W4xcZRiDUVjqwBr5x-g" data-clicktracking="itct=CA0QmysiEwjq3t_a5LvIAhXaVL4KHRUCDPko-B0yBXdhdGNo"><span class="yt-uix-button-content"><span class="subscribe-label" aria-label="Subscribe">Subscribe</span><span class="subscribed-label" aria-label="Unsubscribe">Subscribed</span><span class="unsubscribe-label" aria-label="Unsubscribe">Unsubscribe</span></span></button><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default yt-uix-button-empty yt-uix-button-has-icon yt-uix-subscription-preferences-button" type="button" onclick=";return false;" aria-role="button" aria-live="polite" aria-label="Subscription preferences" aria-busy="false" data-channel-external-id="UCAX1W4xcZRiDUVjqwBr5x-g"><span class="yt-uix-button-icon-wrapper"><span class="yt-uix-button-icon yt-uix-button-icon-subscription-preferences yt-sprite"></span></span></button><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-subscriber-count" title="2,598" aria-label="2,598" tabindex="0">2,598</span><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-short-subscriber-count" title="2K" aria-label="2K" tabindex="0">2K</span>
|
331 |
<div class="yt-uix-overlay " data-overlay-style="primary" data-overlay-shape="tiny">
|
332 |
-
|
333 |
<div class="yt-dialog hid ">
|
334 |
<div class="yt-dialog-base">
|
335 |
<span class="yt-dialog-align"></span>
|
@@ -520,7 +520,7 @@ Loading...
|
|
520 |
</div>
|
521 |
</div>
|
522 |
|
523 |
-
|
524 |
<div id="action-panel-rental-required" class="action-panel-content hid">
|
525 |
<div id="watch-actions-rental-required">
|
526 |
<strong>Rating is available when the video has been rented.</strong>
|
@@ -584,8 +584,8 @@ Loading...
|
|
584 |
<div class="cmt_iframe_holder" data-href="http://www.youtube.com/watch?v=nzNgkc6t260" data-viewtype="FILTERED" style="display: none;"></div>
|
585 |
|
586 |
<div id="watch-discussion" class="branded-page-box yt-card">
|
587 |
-
|
588 |
-
|
589 |
<div class="comments-iframe-container">
|
590 |
<div id="comments-test-iframe"></div>
|
591 |
<div id="distiller-spinner" class="action-panel-loading">
|
@@ -613,7 +613,7 @@ Loading...
|
|
613 |
<div id="watch7-sidebar-contents" class="watch-sidebar-gutter yt-card yt-card-has-padding yt-uix-expander yt-uix-expander-collapsed">
|
614 |
|
615 |
<div id="watch7-sidebar-ads">
|
616 |
-
|
617 |
</div>
|
618 |
<div id="watch7-sidebar-modules">
|
619 |
</div>
|
@@ -869,7 +869,6 @@ Add to
|
|
869 |
yt.setConfig('BLOCK_USER_AJAX_XSRF', 'QUFFLUhqbUVRNm9RcEJoTjBLRmduZE9RRi1fX25pdE4yQXxBQ3Jtc0tub1ZOMUJueXhodlJjVFdrMkU5aEdGa2JjVC1SaTVUZjBEWXRNUWxSdkdoV2ZTcXNCVmt3MnFTclljTExLNU02RmdnaU1Cd0szNlZ2MGlMSHNLUTB0YWVjWjdVWTFSczA1UnNJX3FQYW1GajRyc3J2T2Jpb1VFR28yZ0o0dzZCYVBVWkNuLTlaa0dmSFZKQ3BFaGoyMXpYOTgyUGc=');
|
870 |
|
871 |
|
872 |
-
|
873 |
|
874 |
|
875 |
|
@@ -877,7 +876,8 @@ Add to
|
|
877 |
|
878 |
|
879 |
|
880 |
-
|
|
|
881 |
|
882 |
yt.setConfig({
|
883 |
'GUIDED_HELP_LOCALE': "en_US",
|
@@ -927,4 +927,4 @@ ytcsi.setSpan('st', 113);yt.setConfig({'CSI_SERVICE_NAME': "youtube",'TIMING_ACT
|
|
927 |
});
|
928 |
yt.setConfig('THUMB_DELAY_LOAD_BUFFER', 0);
|
929 |
if (window.ytcsi) {window.ytcsi.tick("jl", null, '');}</script>
|
930 |
-
</body></html>
|
|
|
1 |
<!DOCTYPE html><html lang="en" data-cast-api-enabled="true"><head><style name="www-roboto">@font-face{font-family:'Roboto';font-style:normal;font-weight:400;src:local('Roboto Regular'),local('Roboto-Regular'),url(//fonts.gstatic.com/s/roboto/v15/zN7GBFwfMP4uA6AR0HCoLQ.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;src:local('Roboto Medium'),local('Roboto-Medium'),url(//fonts.gstatic.com/s/roboto/v15/RxZJdnzeo3R5zSexge8UUaCWcynf_cDxXwCLxiixG1c.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:italic;font-weight:400;src:local('Roboto Italic'),local('Roboto-Italic'),url(//fonts.gstatic.com/s/roboto/v15/W4wDsBUluyw0tK3tykhXEfesZW2xOQ-xsNqO47m55DA.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:italic;font-weight:500;src:local('Roboto Medium Italic'),local('Roboto-MediumItalic'),url(//fonts.gstatic.com/s/roboto/v15/OLffGBTaF0XFOW1gnuHF0Z0EAVxt0G0biEntp43Qt6E.ttf)format('truetype');}</style><script name="www-roboto">if (document.fonts && document.fonts.load) {document.fonts.load("400 10pt Roboto", "E");document.fonts.load("500 10pt Roboto", "E");}</script><script>var ytcsi = {gt: function(n) {n = (n || '') + 'data_';return ytcsi[n] || (ytcsi[n] = {tick: {},span: {},info: {}});},tick: function(l, t, n) {ytcsi.gt(n).tick[l] = t || +new Date();},span: function(l, s, e, n) {ytcsi.gt(n).span[l] = (e ? e : +new Date()) - ytcsi.gt(n).tick[s];},setSpan: function(l, s, n) {ytcsi.gt(n).span[l] = s;},info: function(k, v, n) {ytcsi.gt(n).info[k] = v;},setStart: function(s, t, n) {ytcsi.info('yt_sts', s, n);ytcsi.tick('_start', t, n);}};(function(w, d) {ytcsi.perf = w.performance || w.mozPerformance ||w.msPerformance || w.webkitPerformance;ytcsi.setStart('dhs', ytcsi.perf ? ytcsi.perf.timing.responseStart : null);var isPrerender = (d.visibilityState || d.webkitVisibilityState) == 'prerender';var vName = d.webkitVisibilityState ? 'webkitvisibilitychange' : 'visibilitychange';if (isPrerender) {ytcsi.info('prerender', 1);var startTick = function() {ytcsi.setStart('dhs');d.removeEventListener(vName, startTick);};d.addEventListener(vName, startTick, false);}if (d.addEventListener) {d.addEventListener(vName, function() {ytcsi.tick('vc');}, false);}})(window, document);</script><script>var ytcfg = {d: function() {return (window.yt && yt.config_) || ytcfg.data_ || (ytcfg.data_ = {});},get: function(k, o) {return (k in ytcfg.d()) ? ytcfg.d()[k] : o;},set: function() {var a = arguments;if (a.length > 1) {ytcfg.d()[a[0]] = a[1];} else {for (var k in a[0]) {ytcfg.d()[k] = a[0][k];}}}};</script> <script>ytcfg.set("LACT", null);</script>
|
2 |
+
|
3 |
|
4 |
|
5 |
|
|
|
30 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-player-new-vfl1mGUtZ.css" name="www-player">
|
31 |
|
32 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-pageframe-vflAoSuzf.css" name="www-pageframe">
|
33 |
+
|
34 |
+
|
35 |
|
36 |
<title>Kara Tointon (Age-restricted video) - YouTube</title><link rel="search" type="application/opensearchdescription+xml" href="https://www.youtube.com/opensearch?locale=en_US" title="YouTube Video Search"><link rel="shortcut icon" href="https://s.ytimg.com/yts/img/favicon-vflz7uhzw.ico" type="image/x-icon"> <link rel="icon" href="//s.ytimg.com/yts/img/favicon_32-vfl8NGn4k.png" sizes="32x32"><link rel="icon" href="//s.ytimg.com/yts/img/favicon_48-vfl1s0rGh.png" sizes="48x48"><link rel="icon" href="//s.ytimg.com/yts/img/favicon_96-vfldSA3ca.png" sizes="96x96"><link rel="icon" href="//s.ytimg.com/yts/img/favicon_144-vflWmzoXw.png" sizes="144x144"><link rel="canonical" href="http://www.youtube.com/watch?v=nzNgkc6t260"><link rel="alternate" media="handheld" href="http://m.youtube.com/watch?v=nzNgkc6t260"><link rel="alternate" media="only screen and (max-width: 640px)" href="http://m.youtube.com/watch?v=nzNgkc6t260"><link rel="shortlink" href="https://youtu.be/nzNgkc6t260"> <meta name="title" content="Kara Tointon (Age-restricted video)">
|
37 |
|
|
|
110 |
<meta name="twitter:player:width" content="1280">
|
111 |
<meta name="twitter:player:height" content="720">
|
112 |
|
113 |
+
|
114 |
<style>.exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: inline-block;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: none;}@media only screen and (min-width: 850px) {.exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: none;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: inline-block;}}</style></head> <body dir="ltr" id="body" class=" ltr exp-hamburglar exp-responsive exp-scrollable-guide exp-watch-controls-overlay site-center-aligned site-as-giant-card appbar-hidden not-nirvana-dogfood not-yt-legacy-css flex-width-enabled flex-width-enabled-snap delayed-frame-styles-not-in " data-spf-name="watch">
|
115 |
+
<div id="early-body"></div><div id="body-container"><div id="a11y-announcements-container" role="alert"><div id="a11y-announcements-message"></div></div><form name="logoutForm" method="POST" action="/logout"><input type="hidden" name="action_logout" value="1"></form><div id="masthead-positioner">
|
116 |
<div id="yt-masthead-container" class="clearfix yt-base-gutter"> <button id="a11y-skip-nav" class="skip-nav" data-target-id="main" tabindex="3">
|
117 |
Skip navigation
|
118 |
</button>
|
|
|
142 |
</div>
|
143 |
</div>
|
144 |
|
145 |
+
</div><div class="alerts-wrapper"><div id="alerts" class="content-alignment">
|
146 |
<div id="editor-progress-alert-container"></div>
|
147 |
<div class="yt-alert yt-alert-default yt-alert-warn hid " id="editor-progress-alert-template"> <div class="yt-alert-icon">
|
148 |
<span class="icon master-sprite yt-sprite"></span>
|
|
|
185 |
</div>
|
186 |
|
187 |
<div id="player-api" class="player-width player-height off-screen-target player-api" tabIndex="-1"></div>
|
188 |
+
|
189 |
|
190 |
<div id="watch-queue-mole" class="video-mole mole-collapsed hid"><div id="watch-queue" class="watch-playlist player-height"><div class="main-content"><div class="watch-queue-header"><div class="watch-queue-info"><div class="watch-queue-info-icon"><span class="tv-queue-list-icon yt-sprite"></span></div><h3 class="watch-queue-title">Watch Queue</h3><h3 class="tv-queue-title">TV Queue</h3><span class="tv-queue-details"></span></div><div class="watch-queue-control-bar control-bar-button"><div class="watch-queue-mole-info"><div class="watch-queue-control-bar-icon"><span class="watch-queue-icon yt-sprite"></span></div><div class="watch-queue-title-container"><span class="watch-queue-count"></span><span class="watch-queue-title">Watch Queue</span><span class="tv-queue-title">TV Queue</span></div></div> <span class="dark-overflow-action-menu">
|
191 |
+
|
192 |
+
|
193 |
<button onclick=";return false;" class="flip control-bar-button yt-uix-button yt-uix-button-dark-overflow-action-menu yt-uix-button-size-default yt-uix-button-has-icon no-icon-markup yt-uix-button-empty" type="button" aria-label="Actions for the queue" aria-expanded="false" aria-haspopup="true" ><span class="yt-uix-button-arrow yt-sprite"></span><ul class="watch-queue-menu yt-uix-button-menu yt-uix-button-menu-dark-overflow-action-menu hid" role="menu" aria-haspopup="true"><li role="menuitem"><span onclick=";return false;" class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="remove-all" >Remove all</span></li><li role="menuitem"><span onclick=";return false;" class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="disconnect" >Disconnect</span></li></ul></button>
|
194 |
</span>
|
195 |
<div class="watch-queue-controls">
|
|
|
234 |
</div>
|
235 |
</div></div>
|
236 |
<div id="player-playlist" class=" content-alignment watch-player-playlist ">
|
237 |
+
|
238 |
|
239 |
</div>
|
240 |
|
|
|
301 |
<div id="watch7-headline" class="clearfix">
|
302 |
<div id="watch-headline-title">
|
303 |
<h1 class="yt watch-title-container" >
|
304 |
+
|
305 |
|
306 |
<span id="eow-title" class="watch-title " dir="ltr" title="Kara Tointon (Age-restricted video)">
|
307 |
Kara Tointon (Age-restricted video)
|
|
|
327 |
<div class="yt-user-info">
|
328 |
<a href="/channel/UCAX1W4xcZRiDUVjqwBr5x-g" class="yt-uix-sessionlink g-hovercard spf-link " data-ytid="UCAX1W4xcZRiDUVjqwBr5x-g" data-sessionlink="itct=CAwQ4TkiEwjq3t_a5LvIAhXaVL4KHRUCDPko-B0" >Buzz Rock @MrGreglaw</a>
|
329 |
</div>
|
330 |
+
<span id="watch7-subscription-container"><span class=" yt-uix-button-subscription-container"><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-subscribe-branded yt-uix-button-has-icon no-icon-markup yt-uix-subscription-button yt-can-buffer" type="button" onclick=";return false;" aria-live="polite" aria-busy="false" data-style-type="branded" data-href="https://accounts.google.com/ServiceLogin?continue=http%3A%2F%2Fwww.youtube.com%2Fsignin%3Fhl%3Den%26action_handle_signin%3Dtrue%26feature%3Dsubscribe%26next%3D%252Fchannel%252FUCAX1W4xcZRiDUVjqwBr5x-g%26continue_action%3DQUFFLUhqbDRuRDZfV1N1MzViWl9GTHFFT0I0SXZiMEFpd3xBQ3Jtc0ttTk5tQ19ubENRcUNYV0psSGNOS21wQldrM0NTbUtkR2tOa2ZBR3lnZ0wxdGNjcS02ajVnRmN1bDJ3QkdaX29JcG1IM3RpVG9lQW52WjFzM3ZCMEVaMzVqT09OVElhanI3cVNzc3BxbGpINl9JUmVGQnp0NThVdWtuRmdQby1ZMTU1UnY1d0tpdThneFdKaWp6dHJyeFkzZGJSaEZBMlBNaXVzVnd6aUZoWnVndlJ3T2hPaVhpU3h5SHJBcFNGTTFIQVR5YUdZXzlObWRFX2x6Y2otTWR4Q19VOWN3%26app%3Ddesktop&hl=en&service=youtube&passive=true&uilel=3" data-channel-external-id="UCAX1W4xcZRiDUVjqwBr5x-g" data-clicktracking="itct=CA0QmysiEwjq3t_a5LvIAhXaVL4KHRUCDPko-B0yBXdhdGNo"><span class="yt-uix-button-content"><span class="subscribe-label" aria-label="Subscribe">Subscribe</span><span class="subscribed-label" aria-label="Unsubscribe">Subscribed</span><span class="unsubscribe-label" aria-label="Unsubscribe">Unsubscribe</span></span></button><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default yt-uix-button-empty yt-uix-button-has-icon yt-uix-subscription-preferences-button" type="button" onclick=";return false;" aria-role="button" aria-live="polite" aria-label="Subscription preferences" aria-busy="false" data-channel-external-id="UCAX1W4xcZRiDUVjqwBr5x-g"><span class="yt-uix-button-icon-wrapper"><span class="yt-uix-button-icon yt-uix-button-icon-subscription-preferences yt-sprite"></span></span></button><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-subscriber-count" title="2,598" aria-label="2,598" tabindex="0">2,598</span><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-short-subscriber-count" title="2K" aria-label="2K" tabindex="0">2K</span>
|
331 |
<div class="yt-uix-overlay " data-overlay-style="primary" data-overlay-shape="tiny">
|
332 |
+
|
333 |
<div class="yt-dialog hid ">
|
334 |
<div class="yt-dialog-base">
|
335 |
<span class="yt-dialog-align"></span>
|
|
|
520 |
</div>
|
521 |
</div>
|
522 |
|
523 |
+
|
524 |
<div id="action-panel-rental-required" class="action-panel-content hid">
|
525 |
<div id="watch-actions-rental-required">
|
526 |
<strong>Rating is available when the video has been rented.</strong>
|
|
|
584 |
<div class="cmt_iframe_holder" data-href="http://www.youtube.com/watch?v=nzNgkc6t260" data-viewtype="FILTERED" style="display: none;"></div>
|
585 |
|
586 |
<div id="watch-discussion" class="branded-page-box yt-card">
|
587 |
+
|
588 |
+
|
589 |
<div class="comments-iframe-container">
|
590 |
<div id="comments-test-iframe"></div>
|
591 |
<div id="distiller-spinner" class="action-panel-loading">
|
|
|
613 |
<div id="watch7-sidebar-contents" class="watch-sidebar-gutter yt-card yt-card-has-padding yt-uix-expander yt-uix-expander-collapsed">
|
614 |
|
615 |
<div id="watch7-sidebar-ads">
|
616 |
+
|
617 |
</div>
|
618 |
<div id="watch7-sidebar-modules">
|
619 |
</div>
|
|
|
869 |
yt.setConfig('BLOCK_USER_AJAX_XSRF', 'QUFFLUhqbUVRNm9RcEJoTjBLRmduZE9RRi1fX25pdE4yQXxBQ3Jtc0tub1ZOMUJueXhodlJjVFdrMkU5aEdGa2JjVC1SaTVUZjBEWXRNUWxSdkdoV2ZTcXNCVmt3MnFTclljTExLNU02RmdnaU1Cd0szNlZ2MGlMSHNLUTB0YWVjWjdVWTFSczA1UnNJX3FQYW1GajRyc3J2T2Jpb1VFR28yZ0o0dzZCYVBVWkNuLTlaa0dmSFZKQ3BFaGoyMXpYOTgyUGc=');
|
870 |
|
871 |
|
|
|
872 |
|
873 |
|
874 |
|
|
|
876 |
|
877 |
|
878 |
|
879 |
+
|
880 |
+
|
881 |
|
882 |
yt.setConfig({
|
883 |
'GUIDED_HELP_LOCALE': "en_US",
|
|
|
927 |
});
|
928 |
yt.setConfig('THUMB_DELAY_LOAD_BUFFER', 0);
|
929 |
if (window.ytcsi) {window.ytcsi.tick("jl", null, '');}</script>
|
930 |
+
</body></html>
|
tests/mock_data/youtube_gangnam_style.html
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<!DOCTYPE html><html lang="en" data-cast-api-enabled="true"><head><style name="www-roboto">@font-face{font-family:'Roboto';font-style:italic;font-weight:400;src:local('Roboto Italic'),local('Roboto-Italic'),url(//fonts.gstatic.com/s/roboto/v15/W4wDsBUluyw0tK3tykhXEfesZW2xOQ-xsNqO47m55DA.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:italic;font-weight:500;src:local('Roboto Medium Italic'),local('Roboto-MediumItalic'),url(//fonts.gstatic.com/s/roboto/v15/OLffGBTaF0XFOW1gnuHF0Z0EAVxt0G0biEntp43Qt6E.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;src:local('Roboto Regular'),local('Roboto-Regular'),url(//fonts.gstatic.com/s/roboto/v15/zN7GBFwfMP4uA6AR0HCoLQ.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;src:local('Roboto Medium'),local('Roboto-Medium'),url(//fonts.gstatic.com/s/roboto/v15/RxZJdnzeo3R5zSexge8UUaCWcynf_cDxXwCLxiixG1c.ttf)format('truetype');}</style><script name="www-roboto">if (document.fonts && document.fonts.load) {document.fonts.load("400 10pt Roboto", "E");document.fonts.load("500 10pt Roboto", "E");}</script><script>var ytcsi = {gt: function(n) {n = (n || '') + 'data_';return ytcsi[n] || (ytcsi[n] = {tick: {},span: {},info: {}});},tick: function(l, t, n) {ytcsi.gt(n).tick[l] = t || +new Date();},span: function(l, s, e, n) {ytcsi.gt(n).span[l] = (e ? e : +new Date()) - ytcsi.gt(n).tick[s];},setSpan: function(l, s, n) {ytcsi.gt(n).span[l] = s;},info: function(k, v, n) {ytcsi.gt(n).info[k] = v;},setStart: function(s, t, n) {ytcsi.info('yt_sts', s, n);ytcsi.tick('_start', t, n);}};(function(w, d) {ytcsi.perf = w.performance || w.mozPerformance ||w.msPerformance || w.webkitPerformance;ytcsi.setStart('dhs', ytcsi.perf ? ytcsi.perf.timing.responseStart : null);var isPrerender = (d.visibilityState || d.webkitVisibilityState) == 'prerender';var vName = d.webkitVisibilityState ? 'webkitvisibilitychange' : 'visibilitychange';if (isPrerender) {ytcsi.info('prerender', 1);var startTick = function() {ytcsi.setStart('dhs');d.removeEventListener(vName, startTick);};d.addEventListener(vName, startTick, false);}if (d.addEventListener) {d.addEventListener(vName, function() {ytcsi.tick('vc');}, false);}})(window, document);</script><script>var ytcfg = {d: function() {return (window.yt && yt.config_) || ytcfg.data_ || (ytcfg.data_ = {});},get: function(k, o) {return (k in ytcfg.d()) ? ytcfg.d()[k] : o;},set: function() {var a = arguments;if (a.length > 1) {ytcfg.d()[a[0]] = a[1];} else {for (var k in a[0]) {ytcfg.d()[k] = a[0][k];}}}};</script> <script>ytcfg.set("LACT", null);</script>
|
2 |
-
|
3 |
|
4 |
|
5 |
|
@@ -27,7 +27,7 @@ var m=["yt","www","masthead","sizing","runBeforeBodyIsReady"],n=this;m[0]in n||!
|
|
27 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-player-new-vfliB0u8F.css" name="www-player">
|
28 |
|
29 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-pageframe-vfly1fQ8j.css" name="www-pageframe">
|
30 |
-
|
31 |
<script>ytimg.preload("https:\/\/r5---sn-ab5l6nle.googlevideo.com\/crossdomain.xml");ytimg.preload("https:\/\/r5---sn-ab5l6nle.googlevideo.com\/generate_204");</script>
|
32 |
|
33 |
|
@@ -109,9 +109,9 @@ var m=["yt","www","masthead","sizing","runBeforeBodyIsReady"],n=this;m[0]in n||!
|
|
109 |
<meta name="twitter:player:width" content="1280">
|
110 |
<meta name="twitter:player:height" content="720">
|
111 |
|
112 |
-
<meta name=attribution content=ygent/>
|
113 |
<style>li.mc-channel-permission-present { width: 100%; } .exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: inline-block;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: none;}@media only screen and (min-width: 850px) {.exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: none;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: inline-block;}}</style></head> <body dir="ltr" id="body" class=" ltr exp-hamburglar exp-responsive exp-scrollable-guide exp-watch-controls-overlay site-center-aligned site-as-giant-card appbar-hidden not-nirvana-dogfood not-yt-legacy-css flex-width-enabled flex-width-enabled-snap delayed-frame-styles-not-in " data-spf-name="watch">
|
114 |
-
<div id="early-body"></div><div id="body-container"><div id="a11y-announcements-container" role="alert"><div id="a11y-announcements-message"></div></div><form name="logoutForm" method="POST" action="/logout"><input type="hidden" name="action_logout" value="1"></form><div id="masthead-positioner">
|
115 |
<div id="yt-masthead-container" class="clearfix yt-base-gutter"> <button id="a11y-skip-nav" class="skip-nav" data-target-id="main" tabindex="3">
|
116 |
Skip navigation
|
117 |
</button>
|
@@ -141,7 +141,7 @@ Loading...
|
|
141 |
</div>
|
142 |
</div>
|
143 |
|
144 |
-
</div><div class="alerts-wrapper"><div id="alerts" class="content-alignment">
|
145 |
<div id="editor-progress-alert-container"></div>
|
146 |
<div class="yt-alert yt-alert-default yt-alert-warn hid " id="editor-progress-alert-template"> <div class="yt-alert-icon">
|
147 |
<span class="icon master-sprite yt-sprite"></span>
|
@@ -181,8 +181,8 @@ Loading...
|
|
181 |
|
182 |
|
183 |
<div id="watch-queue-mole" class="video-mole mole-collapsed hid"><div id="watch-queue" class="watch-playlist player-height"><div class="main-content"><div class="watch-queue-header"><div class="watch-queue-info"><div class="watch-queue-info-icon"><span class="tv-queue-list-icon yt-sprite"></span></div><h3 class="watch-queue-title">Watch Queue</h3><h3 class="tv-queue-title">TV Queue</h3><span class="tv-queue-details"></span></div><div class="watch-queue-control-bar control-bar-button"><div class="watch-queue-mole-info"><div class="watch-queue-control-bar-icon"><span class="watch-queue-icon yt-sprite"></span></div><div class="watch-queue-title-container"><span class="watch-queue-count"></span><span class="watch-queue-title">Watch Queue</span><span class="tv-queue-title">TV Queue</span></div></div> <span class="dark-overflow-action-menu">
|
184 |
-
|
185 |
-
|
186 |
<button class="flip control-bar-button yt-uix-button yt-uix-button-dark-overflow-action-menu yt-uix-button-size-default yt-uix-button-has-icon no-icon-markup yt-uix-button-empty" type="button" aria-expanded="false" aria-label="Actions for the queue" aria-haspopup="true" onclick=";return false;" ><span class="yt-uix-button-arrow yt-sprite"></span><ul class="watch-queue-menu yt-uix-button-menu yt-uix-button-menu-dark-overflow-action-menu hid" role="menu" aria-haspopup="true"><li role="menuitem"><span class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="remove-all" onclick=";return false;" >Remove all</span></li><li role="menuitem"><span class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="disconnect" onclick=";return false;" >Disconnect</span></li></ul></button>
|
187 |
</span>
|
188 |
<div class="watch-queue-controls">
|
@@ -227,7 +227,7 @@ Loading...
|
|
227 |
</div>
|
228 |
</div></div>
|
229 |
<div id="player-playlist" class=" content-alignment watch-player-playlist ">
|
230 |
-
|
231 |
|
232 |
<div id="watch-appbar-playlist" class="watch-playlist player-height">
|
233 |
<div class="main-content">
|
@@ -235,7 +235,7 @@ Loading...
|
|
235 |
<div class="playlist-header-content" data-loop-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-initial-loop-state="false" data-list-author="" data-full-list-id="PLirAqAtl_h2r5g8xGajEwdXd3x1sZh8hC" data-shuffle-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-normal-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-loop_shuffle-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-shareable="True" data-list-title="Most Viewed Videos of All Time・(Over 100 million views)">
|
236 |
<div class="appbar-playlist-controls clearfix">
|
237 |
|
238 |
-
|
239 |
|
240 |
<span class="yt-uix-clickcard">
|
241 |
<span class="yt-uix-clickcard-target" data-position="bottomright" data-orientation="vertical">
|
@@ -244,7 +244,7 @@ Loading...
|
|
244 |
<div class="signin-clickcard yt-uix-clickcard-content">
|
245 |
<h3 class="signin-clickcard-header">Sign in to YouTube</h3>
|
246 |
<div class="signin-clickcard-message">
|
247 |
-
|
248 |
</div>
|
249 |
<a href="https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26app%3Ddesktop%26feature%3D__FEATURE__%26next%3D%252Fwatch%253Fv%253D9bZkp7q19f0%2526list%253DPLirAqAtl_h2r5g8xGajEwdXd3x1sZh8hC%2526index%253D1%26hl%3Den&hl=en&uilel=3&service=youtube" class="yt-uix-button signin-button yt-uix-sessionlink yt-uix-button-primary yt-uix-button-size-default" data-sessionlink="ei=4NURVo25LIbK-AX7mK_oDA"><span class="yt-uix-button-content">Sign in</span></a>
|
250 |
</div>
|
@@ -5762,7 +5762,7 @@ Loading...
|
|
5762 |
<div id="watch7-headline" class="clearfix">
|
5763 |
<div id="watch-headline-title">
|
5764 |
<h1 class="yt watch-title-container" >
|
5765 |
-
|
5766 |
|
5767 |
<span id="eow-title" class="watch-title " dir="ltr" title="PSY - GANGNAM STYLE(강남스타일) M/V">
|
5768 |
PSY - GANGNAM STYLE(강남스타일) M/V
|
@@ -5787,12 +5787,12 @@ Loading...
|
|
5787 |
</a>
|
5788 |
<div class="yt-user-info">
|
5789 |
<a href="/channel/UCrDkAvwZum-UTjHmzDI2iIw" class="yt-uix-sessionlink g-hovercard spf-link " data-ytid="UCrDkAvwZum-UTjHmzDI2iIw" data-sessionlink="itct=CPsBEOE5IhMIjamRrpqqyAIVBiW-Ch17zAvNKPgd" >officialpsy</a>
|
5790 |
-
|
5791 |
<span aria-label="Verified" class="yt-channel-title-icon-verified yt-uix-tooltip yt-sprite" data-tooltip-text="Verified"></span>
|
5792 |
</div>
|
5793 |
-
<span id="watch7-subscription-container"><span class=" yt-uix-button-subscription-container"><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-subscribe-branded yt-uix-button-has-icon no-icon-markup yt-uix-subscription-button yt-can-buffer" type="button" onclick=";return false;" aria-live="polite" aria-busy="false" data-channel-external-id="UCrDkAvwZum-UTjHmzDI2iIw" data-href="https://accounts.google.com/ServiceLogin?passive=true&continue=http%3A%2F%2Fwww.youtube.com%2Fsignin%3Fapp%3Ddesktop%26feature%3Dsubscribe%26action_handle_signin%3Dtrue%26next%3D%252Fchannel%252FUCrDkAvwZum-UTjHmzDI2iIw%26continue_action%3DQUFFLUhqbTVxeGwyWktCZGRuQlNjczRTTW9OT3Y4aE85QXxBQ3Jtc0ttZGJCOW1URzZoLUQ1YTR1R2NIR09NcTN3NmJKeXU4SnAwS3FSeU9VWkNkSVpTc185cU91dklGejNoU0hHbWVwOEJEQzlsYnBDMDRiZFcxTTV4SGVxQnByY2xXV2lUWjV4SjBPNFhPNjBZMV85OFlRSHl6aU1IRkxUSkxwdGF2NElDa3ktNVhELVZqZmU5dTFOaFBDT1hCLU9OanFXV2JLWkFvcjhWT09TU05kYm9MNnhNZ3BnUmlJd1JfUDVCU09jUG5CdTh0RFR1TG1JeGFDdWFxcEJPQ0JudEJR%26hl%3Den&hl=en&uilel=3&service=youtube" data-style-type="branded" data-clicktracking="itct=CPwBEJsrIhMIjamRrpqqyAIVBiW-Ch17zAvNKPgdMgV3YXRjaA"><span class="yt-uix-button-content"><span class="subscribe-label" aria-label="Subscribe">Subscribe</span><span class="subscribed-label" aria-label="Unsubscribe">Subscribed</span><span class="unsubscribe-label" aria-label="Unsubscribe">Unsubscribe</span></span></button><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default yt-uix-button-empty yt-uix-button-has-icon yt-uix-subscription-preferences-button" type="button" onclick=";return false;" aria-role="button" aria-label="Subscription preferences" aria-live="polite" aria-busy="false" data-channel-external-id="UCrDkAvwZum-UTjHmzDI2iIw"><span class="yt-uix-button-icon-wrapper"><span class="yt-uix-button-icon yt-uix-button-icon-subscription-preferences yt-sprite"></span></span></button><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-subscriber-count" title="8,185,162" aria-label="8,185,162" tabindex="0">8,185,162</span><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-short-subscriber-count" title="8M" aria-label="8M" tabindex="0">8M</span>
|
5794 |
<div class="yt-uix-overlay " data-overlay-style="primary" data-overlay-shape="tiny">
|
5795 |
-
|
5796 |
<div class="yt-dialog hid ">
|
5797 |
<div class="yt-dialog-base">
|
5798 |
<span class="yt-dialog-align"></span>
|
@@ -5983,7 +5983,7 @@ Loading...
|
|
5983 |
</div>
|
5984 |
</div>
|
5985 |
|
5986 |
-
|
5987 |
<div id="action-panel-rental-required" class="action-panel-content hid">
|
5988 |
<div id="watch-actions-rental-required">
|
5989 |
<strong>Rating is available when the video has been rented.</strong>
|
@@ -6038,8 +6038,8 @@ Loading...
|
|
6038 |
<div class="cmt_iframe_holder" data-href="http://www.youtube.com/watch?v=9bZkp7q19f0&list=PLirAqAtl_h2r5g8xGajEwdXd3x1sZh8hC&index=1" data-viewtype="FILTERED" style="display: none;"></div>
|
6039 |
|
6040 |
<div id="watch-discussion" class="branded-page-box yt-card">
|
6041 |
-
|
6042 |
-
|
6043 |
<div class="comments-iframe-container">
|
6044 |
<div id="comments-test-iframe"></div>
|
6045 |
<div id="distiller-spinner" class="action-panel-loading">
|
@@ -6066,7 +6066,7 @@ Loading...
|
|
6066 |
|
6067 |
<div id="watch7-sidebar-contents" class="watch-sidebar-gutter yt-card yt-card-has-padding yt-uix-expander yt-uix-expander-collapsed">
|
6068 |
<div id="watch7-sidebar-offer">
|
6069 |
-
|
6070 |
</div>
|
6071 |
|
6072 |
<div id="watch7-sidebar-ads">
|
@@ -6081,13 +6081,13 @@ Advertisement
|
|
6081 |
|
6082 |
</div>
|
6083 |
<div id="watch7-sidebar-modules">
|
6084 |
-
|
6085 |
<div class="watch-sidebar-section">
|
6086 |
<div class="watch-sidebar-body">
|
6087 |
<ul id="watch-related" class="video-list">
|
6088 |
<li class="video-list-item related-list-item show-video-time related-list-item-compact-radio"><a href="/watch?v=9bZkp7q19f0&list=RD9bZkp7q19f0" class="yt-uix-sessionlink related-playlist yt-pl-thumb-link spf-link mix-playlist resumable-list spf-link " data-sessionlink="itct=CPQBEKMwGAAiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCmxpc3Rfb3RoZXJI_evX1fuUmdv1AQ" data-secondary-video-url="/watch?v=ASO_zypdnsQ&list=RD9bZkp7q19f0" rel="spf-prefetch">
|
6089 |
<span class="yt-pl-thumb is-small yt-mix-thumb">
|
6090 |
-
|
6091 |
<span class="video-thumb yt-thumb yt-thumb-120"
|
6092 |
>
|
6093 |
<span class="yt-thumb-default">
|
@@ -6878,7 +6878,6 @@ Add to
|
|
6878 |
yt.setConfig('BLOCK_USER_AJAX_XSRF', 'QUFFLUhqbUdSQlg1RlhVR25uSkotc21PX2xva2trNEx0Z3xBQ3Jtc0trdGgyeFFGUUR2Vzhua0xNVW9sMWNUOGVFTXBXRGxrTFVHOFktYzV4T2xUcDNzU2RkUngxSWl6cEwyN0lNM1pacXVYdzBWMXVEWXNlRjFQRVd0QkJFaFliV0J0OHMzOXJtWFFnWmR3cGpNdUtFWEFybmpFNVZvNUdMaktQNjc3U2hkbmhVMWg1QjlCNmJSem5jRWYySTRIY2tldFE=');
|
6879 |
|
6880 |
|
6881 |
-
|
6882 |
|
6883 |
|
6884 |
|
@@ -6886,7 +6885,8 @@ Add to
|
|
6886 |
|
6887 |
|
6888 |
|
6889 |
-
|
|
|
6890 |
|
6891 |
yt.setConfig({
|
6892 |
'GUIDED_HELP_LOCALE': "en_US",
|
@@ -6936,4 +6936,4 @@ ytcsi.setSpan('st', 783);yt.setConfig({'CSI_SERVICE_NAME': "youtube",'TIMING_ACT
|
|
6936 |
});
|
6937 |
yt.setConfig('THUMB_DELAY_LOAD_BUFFER', 0);
|
6938 |
if (window.ytcsi) {window.ytcsi.tick("jl", null, '');}</script>
|
6939 |
-
</body></html>
|
|
|
1 |
<!DOCTYPE html><html lang="en" data-cast-api-enabled="true"><head><style name="www-roboto">@font-face{font-family:'Roboto';font-style:italic;font-weight:400;src:local('Roboto Italic'),local('Roboto-Italic'),url(//fonts.gstatic.com/s/roboto/v15/W4wDsBUluyw0tK3tykhXEfesZW2xOQ-xsNqO47m55DA.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:italic;font-weight:500;src:local('Roboto Medium Italic'),local('Roboto-MediumItalic'),url(//fonts.gstatic.com/s/roboto/v15/OLffGBTaF0XFOW1gnuHF0Z0EAVxt0G0biEntp43Qt6E.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;src:local('Roboto Regular'),local('Roboto-Regular'),url(//fonts.gstatic.com/s/roboto/v15/zN7GBFwfMP4uA6AR0HCoLQ.ttf)format('truetype');}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;src:local('Roboto Medium'),local('Roboto-Medium'),url(//fonts.gstatic.com/s/roboto/v15/RxZJdnzeo3R5zSexge8UUaCWcynf_cDxXwCLxiixG1c.ttf)format('truetype');}</style><script name="www-roboto">if (document.fonts && document.fonts.load) {document.fonts.load("400 10pt Roboto", "E");document.fonts.load("500 10pt Roboto", "E");}</script><script>var ytcsi = {gt: function(n) {n = (n || '') + 'data_';return ytcsi[n] || (ytcsi[n] = {tick: {},span: {},info: {}});},tick: function(l, t, n) {ytcsi.gt(n).tick[l] = t || +new Date();},span: function(l, s, e, n) {ytcsi.gt(n).span[l] = (e ? e : +new Date()) - ytcsi.gt(n).tick[s];},setSpan: function(l, s, n) {ytcsi.gt(n).span[l] = s;},info: function(k, v, n) {ytcsi.gt(n).info[k] = v;},setStart: function(s, t, n) {ytcsi.info('yt_sts', s, n);ytcsi.tick('_start', t, n);}};(function(w, d) {ytcsi.perf = w.performance || w.mozPerformance ||w.msPerformance || w.webkitPerformance;ytcsi.setStart('dhs', ytcsi.perf ? ytcsi.perf.timing.responseStart : null);var isPrerender = (d.visibilityState || d.webkitVisibilityState) == 'prerender';var vName = d.webkitVisibilityState ? 'webkitvisibilitychange' : 'visibilitychange';if (isPrerender) {ytcsi.info('prerender', 1);var startTick = function() {ytcsi.setStart('dhs');d.removeEventListener(vName, startTick);};d.addEventListener(vName, startTick, false);}if (d.addEventListener) {d.addEventListener(vName, function() {ytcsi.tick('vc');}, false);}})(window, document);</script><script>var ytcfg = {d: function() {return (window.yt && yt.config_) || ytcfg.data_ || (ytcfg.data_ = {});},get: function(k, o) {return (k in ytcfg.d()) ? ytcfg.d()[k] : o;},set: function() {var a = arguments;if (a.length > 1) {ytcfg.d()[a[0]] = a[1];} else {for (var k in a[0]) {ytcfg.d()[k] = a[0][k];}}}};</script> <script>ytcfg.set("LACT", null);</script>
|
2 |
+
|
3 |
|
4 |
|
5 |
|
|
|
27 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-player-new-vfliB0u8F.css" name="www-player">
|
28 |
|
29 |
<link rel="stylesheet" href="//s.ytimg.com/yts/cssbin/www-pageframe-vfly1fQ8j.css" name="www-pageframe">
|
30 |
+
|
31 |
<script>ytimg.preload("https:\/\/r5---sn-ab5l6nle.googlevideo.com\/crossdomain.xml");ytimg.preload("https:\/\/r5---sn-ab5l6nle.googlevideo.com\/generate_204");</script>
|
32 |
|
33 |
|
|
|
109 |
<meta name="twitter:player:width" content="1280">
|
110 |
<meta name="twitter:player:height" content="720">
|
111 |
|
112 |
+
<meta name=attribution content=ygent/>
|
113 |
<style>li.mc-channel-permission-present { width: 100%; } .exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: inline-block;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: none;}@media only screen and (min-width: 850px) {.exp-responsive #content .yt-uix-button-subscription-container .yt-short-subscriber-count {display: none;}.exp-responsive #content .yt-uix-button-subscription-container .yt-subscriber-count {display: inline-block;}}</style></head> <body dir="ltr" id="body" class=" ltr exp-hamburglar exp-responsive exp-scrollable-guide exp-watch-controls-overlay site-center-aligned site-as-giant-card appbar-hidden not-nirvana-dogfood not-yt-legacy-css flex-width-enabled flex-width-enabled-snap delayed-frame-styles-not-in " data-spf-name="watch">
|
114 |
+
<div id="early-body"></div><div id="body-container"><div id="a11y-announcements-container" role="alert"><div id="a11y-announcements-message"></div></div><form name="logoutForm" method="POST" action="/logout"><input type="hidden" name="action_logout" value="1"></form><div id="masthead-positioner">
|
115 |
<div id="yt-masthead-container" class="clearfix yt-base-gutter"> <button id="a11y-skip-nav" class="skip-nav" data-target-id="main" tabindex="3">
|
116 |
Skip navigation
|
117 |
</button>
|
|
|
141 |
</div>
|
142 |
</div>
|
143 |
|
144 |
+
</div><div class="alerts-wrapper"><div id="alerts" class="content-alignment">
|
145 |
<div id="editor-progress-alert-container"></div>
|
146 |
<div class="yt-alert yt-alert-default yt-alert-warn hid " id="editor-progress-alert-template"> <div class="yt-alert-icon">
|
147 |
<span class="icon master-sprite yt-sprite"></span>
|
|
|
181 |
|
182 |
|
183 |
<div id="watch-queue-mole" class="video-mole mole-collapsed hid"><div id="watch-queue" class="watch-playlist player-height"><div class="main-content"><div class="watch-queue-header"><div class="watch-queue-info"><div class="watch-queue-info-icon"><span class="tv-queue-list-icon yt-sprite"></span></div><h3 class="watch-queue-title">Watch Queue</h3><h3 class="tv-queue-title">TV Queue</h3><span class="tv-queue-details"></span></div><div class="watch-queue-control-bar control-bar-button"><div class="watch-queue-mole-info"><div class="watch-queue-control-bar-icon"><span class="watch-queue-icon yt-sprite"></span></div><div class="watch-queue-title-container"><span class="watch-queue-count"></span><span class="watch-queue-title">Watch Queue</span><span class="tv-queue-title">TV Queue</span></div></div> <span class="dark-overflow-action-menu">
|
184 |
+
|
185 |
+
|
186 |
<button class="flip control-bar-button yt-uix-button yt-uix-button-dark-overflow-action-menu yt-uix-button-size-default yt-uix-button-has-icon no-icon-markup yt-uix-button-empty" type="button" aria-expanded="false" aria-label="Actions for the queue" aria-haspopup="true" onclick=";return false;" ><span class="yt-uix-button-arrow yt-sprite"></span><ul class="watch-queue-menu yt-uix-button-menu yt-uix-button-menu-dark-overflow-action-menu hid" role="menu" aria-haspopup="true"><li role="menuitem"><span class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="remove-all" onclick=";return false;" >Remove all</span></li><li role="menuitem"><span class="watch-queue-menu-choice overflow-menu-choice yt-uix-button-menu-item" data-action="disconnect" onclick=";return false;" >Disconnect</span></li></ul></button>
|
187 |
</span>
|
188 |
<div class="watch-queue-controls">
|
|
|
227 |
</div>
|
228 |
</div></div>
|
229 |
<div id="player-playlist" class=" content-alignment watch-player-playlist ">
|
230 |
+
|
231 |
|
232 |
<div id="watch-appbar-playlist" class="watch-playlist player-height">
|
233 |
<div class="main-content">
|
|
|
235 |
<div class="playlist-header-content" data-loop-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-initial-loop-state="false" data-list-author="" data-full-list-id="PLirAqAtl_h2r5g8xGajEwdXd3x1sZh8hC" data-shuffle-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-normal-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-loop_shuffle-auto-clicktracking="CBUQ0TkiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCGF1dG9wbGF5" data-shareable="True" data-list-title="Most Viewed Videos of All Time・(Over 100 million views)">
|
236 |
<div class="appbar-playlist-controls clearfix">
|
237 |
|
238 |
+
|
239 |
|
240 |
<span class="yt-uix-clickcard">
|
241 |
<span class="yt-uix-clickcard-target" data-position="bottomright" data-orientation="vertical">
|
|
|
244 |
<div class="signin-clickcard yt-uix-clickcard-content">
|
245 |
<h3 class="signin-clickcard-header">Sign in to YouTube</h3>
|
246 |
<div class="signin-clickcard-message">
|
247 |
+
|
248 |
</div>
|
249 |
<a href="https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26app%3Ddesktop%26feature%3D__FEATURE__%26next%3D%252Fwatch%253Fv%253D9bZkp7q19f0%2526list%253DPLirAqAtl_h2r5g8xGajEwdXd3x1sZh8hC%2526index%253D1%26hl%3Den&hl=en&uilel=3&service=youtube" class="yt-uix-button signin-button yt-uix-sessionlink yt-uix-button-primary yt-uix-button-size-default" data-sessionlink="ei=4NURVo25LIbK-AX7mK_oDA"><span class="yt-uix-button-content">Sign in</span></a>
|
250 |
</div>
|
|
|
5762 |
<div id="watch7-headline" class="clearfix">
|
5763 |
<div id="watch-headline-title">
|
5764 |
<h1 class="yt watch-title-container" >
|
5765 |
+
|
5766 |
|
5767 |
<span id="eow-title" class="watch-title " dir="ltr" title="PSY - GANGNAM STYLE(강남스타일) M/V">
|
5768 |
PSY - GANGNAM STYLE(강남스타일) M/V
|
|
|
5787 |
</a>
|
5788 |
<div class="yt-user-info">
|
5789 |
<a href="/channel/UCrDkAvwZum-UTjHmzDI2iIw" class="yt-uix-sessionlink g-hovercard spf-link " data-ytid="UCrDkAvwZum-UTjHmzDI2iIw" data-sessionlink="itct=CPsBEOE5IhMIjamRrpqqyAIVBiW-Ch17zAvNKPgd" >officialpsy</a>
|
5790 |
+
|
5791 |
<span aria-label="Verified" class="yt-channel-title-icon-verified yt-uix-tooltip yt-sprite" data-tooltip-text="Verified"></span>
|
5792 |
</div>
|
5793 |
+
<span id="watch7-subscription-container"><span class=" yt-uix-button-subscription-container"><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-subscribe-branded yt-uix-button-has-icon no-icon-markup yt-uix-subscription-button yt-can-buffer" type="button" onclick=";return false;" aria-live="polite" aria-busy="false" data-channel-external-id="UCrDkAvwZum-UTjHmzDI2iIw" data-href="https://accounts.google.com/ServiceLogin?passive=true&continue=http%3A%2F%2Fwww.youtube.com%2Fsignin%3Fapp%3Ddesktop%26feature%3Dsubscribe%26action_handle_signin%3Dtrue%26next%3D%252Fchannel%252FUCrDkAvwZum-UTjHmzDI2iIw%26continue_action%3DQUFFLUhqbTVxeGwyWktCZGRuQlNjczRTTW9OT3Y4aE85QXxBQ3Jtc0ttZGJCOW1URzZoLUQ1YTR1R2NIR09NcTN3NmJKeXU4SnAwS3FSeU9VWkNkSVpTc185cU91dklGejNoU0hHbWVwOEJEQzlsYnBDMDRiZFcxTTV4SGVxQnByY2xXV2lUWjV4SjBPNFhPNjBZMV85OFlRSHl6aU1IRkxUSkxwdGF2NElDa3ktNVhELVZqZmU5dTFOaFBDT1hCLU9OanFXV2JLWkFvcjhWT09TU05kYm9MNnhNZ3BnUmlJd1JfUDVCU09jUG5CdTh0RFR1TG1JeGFDdWFxcEJPQ0JudEJR%26hl%3Den&hl=en&uilel=3&service=youtube" data-style-type="branded" data-clicktracking="itct=CPwBEJsrIhMIjamRrpqqyAIVBiW-Ch17zAvNKPgdMgV3YXRjaA"><span class="yt-uix-button-content"><span class="subscribe-label" aria-label="Subscribe">Subscribe</span><span class="subscribed-label" aria-label="Unsubscribe">Subscribed</span><span class="unsubscribe-label" aria-label="Unsubscribe">Unsubscribe</span></span></button><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default yt-uix-button-empty yt-uix-button-has-icon yt-uix-subscription-preferences-button" type="button" onclick=";return false;" aria-role="button" aria-label="Subscription preferences" aria-live="polite" aria-busy="false" data-channel-external-id="UCrDkAvwZum-UTjHmzDI2iIw"><span class="yt-uix-button-icon-wrapper"><span class="yt-uix-button-icon yt-uix-button-icon-subscription-preferences yt-sprite"></span></span></button><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-subscriber-count" title="8,185,162" aria-label="8,185,162" tabindex="0">8,185,162</span><span class="yt-subscription-button-subscriber-count-branded-horizontal yt-short-subscriber-count" title="8M" aria-label="8M" tabindex="0">8M</span>
|
5794 |
<div class="yt-uix-overlay " data-overlay-style="primary" data-overlay-shape="tiny">
|
5795 |
+
|
5796 |
<div class="yt-dialog hid ">
|
5797 |
<div class="yt-dialog-base">
|
5798 |
<span class="yt-dialog-align"></span>
|
|
|
5983 |
</div>
|
5984 |
</div>
|
5985 |
|
5986 |
+
|
5987 |
<div id="action-panel-rental-required" class="action-panel-content hid">
|
5988 |
<div id="watch-actions-rental-required">
|
5989 |
<strong>Rating is available when the video has been rented.</strong>
|
|
|
6038 |
<div class="cmt_iframe_holder" data-href="http://www.youtube.com/watch?v=9bZkp7q19f0&list=PLirAqAtl_h2r5g8xGajEwdXd3x1sZh8hC&index=1" data-viewtype="FILTERED" style="display: none;"></div>
|
6039 |
|
6040 |
<div id="watch-discussion" class="branded-page-box yt-card">
|
6041 |
+
|
6042 |
+
|
6043 |
<div class="comments-iframe-container">
|
6044 |
<div id="comments-test-iframe"></div>
|
6045 |
<div id="distiller-spinner" class="action-panel-loading">
|
|
|
6066 |
|
6067 |
<div id="watch7-sidebar-contents" class="watch-sidebar-gutter yt-card yt-card-has-padding yt-uix-expander yt-uix-expander-collapsed">
|
6068 |
<div id="watch7-sidebar-offer">
|
6069 |
+
|
6070 |
</div>
|
6071 |
|
6072 |
<div id="watch7-sidebar-ads">
|
|
|
6081 |
|
6082 |
</div>
|
6083 |
<div id="watch7-sidebar-modules">
|
6084 |
+
|
6085 |
<div class="watch-sidebar-section">
|
6086 |
<div class="watch-sidebar-body">
|
6087 |
<ul id="watch-related" class="video-list">
|
6088 |
<li class="video-list-item related-list-item show-video-time related-list-item-compact-radio"><a href="/watch?v=9bZkp7q19f0&list=RD9bZkp7q19f0" class="yt-uix-sessionlink related-playlist yt-pl-thumb-link spf-link mix-playlist resumable-list spf-link " data-sessionlink="itct=CPQBEKMwGAAiEwiNqZGumqrIAhUGJb4KHXvMC80o-B0yCmxpc3Rfb3RoZXJI_evX1fuUmdv1AQ" data-secondary-video-url="/watch?v=ASO_zypdnsQ&list=RD9bZkp7q19f0" rel="spf-prefetch">
|
6089 |
<span class="yt-pl-thumb is-small yt-mix-thumb">
|
6090 |
+
|
6091 |
<span class="video-thumb yt-thumb yt-thumb-120"
|
6092 |
>
|
6093 |
<span class="yt-thumb-default">
|
|
|
6878 |
yt.setConfig('BLOCK_USER_AJAX_XSRF', 'QUFFLUhqbUdSQlg1RlhVR25uSkotc21PX2xva2trNEx0Z3xBQ3Jtc0trdGgyeFFGUUR2Vzhua0xNVW9sMWNUOGVFTXBXRGxrTFVHOFktYzV4T2xUcDNzU2RkUngxSWl6cEwyN0lNM1pacXVYdzBWMXVEWXNlRjFQRVd0QkJFaFliV0J0OHMzOXJtWFFnWmR3cGpNdUtFWEFybmpFNVZvNUdMaktQNjc3U2hkbmhVMWg1QjlCNmJSem5jRWYySTRIY2tldFE=');
|
6879 |
|
6880 |
|
|
|
6881 |
|
6882 |
|
6883 |
|
|
|
6885 |
|
6886 |
|
6887 |
|
6888 |
+
|
6889 |
+
|
6890 |
|
6891 |
yt.setConfig({
|
6892 |
'GUIDED_HELP_LOCALE': "en_US",
|
|
|
6936 |
});
|
6937 |
yt.setConfig('THUMB_DELAY_LOAD_BUFFER', 0);
|
6938 |
if (window.ytcsi) {window.ytcsi.tick("jl", null, '');}</script>
|
6939 |
+
</body></html>
|
tests/requirements.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
|
|
|
|
1 |
mock==1.3.0
|
2 |
nose==1.3.7
|
3 |
-
|
4 |
-
bumpversion==0.5.3
|
|
|
1 |
+
bumpversion==0.5.3
|
2 |
+
coveralls==1.0
|
3 |
mock==1.3.0
|
4 |
nose==1.3.7
|
5 |
+
pre-commit==0.15.0
|
|
tests/test_p3_pytube.py
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
#!/usr/bin/env python3
|
2 |
-
# -*- coding: utf-8 -*-
|
3 |
-
from __future__ import unicode_literals
|
4 |
-
import warnings
|
5 |
-
import mock
|
6 |
-
from nose.tools import eq_, raises
|
7 |
-
from pytube import api
|
8 |
-
from pytube.exceptions import MultipleObjectsReturned, AgeRestricted, \
|
9 |
-
DoesNotExist, PytubeError
|
10 |
-
|
11 |
-
|
12 |
-
class TestPytube(object):
|
13 |
-
def setUp(self):
|
14 |
-
self.url = 'http://www.youtube.com/watch?v=9bZkp7q19f0'
|
15 |
-
|
16 |
-
def test_YT_create_from_url(self):
|
17 |
-
'test creation of YouYube Object from url'
|
18 |
-
yt = api.YouTube(self.url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_pytube.py
CHANGED
@@ -1,12 +1,18 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
|
|
4 |
import warnings
|
|
|
5 |
import mock
|
6 |
-
from nose.tools import eq_
|
|
|
|
|
7 |
from pytube import api
|
8 |
-
from pytube.exceptions import
|
9 |
-
|
|
|
|
|
10 |
|
11 |
|
12 |
class TestPytube(object):
|
@@ -99,7 +105,7 @@ class TestPytube(object):
|
|
99 |
"""Deprecation warnings get triggered on url set"""
|
100 |
with warnings.catch_warnings(record=True) as w:
|
101 |
# Cause all warnings to always be triggered.
|
102 |
-
warnings.simplefilter(
|
103 |
with mock.patch('pytube.api.urlopen') as urlopen:
|
104 |
urlopen.return_value.read.return_value = self.mock_html
|
105 |
yt = api.YouTube()
|
@@ -111,7 +117,7 @@ class TestPytube(object):
|
|
111 |
"""Deprecation warnings get triggered on filename set"""
|
112 |
with warnings.catch_warnings(record=True) as w:
|
113 |
# Cause all warnings to always be triggered.
|
114 |
-
warnings.simplefilter(
|
115 |
self.yt.filename = 'Gangnam Style'
|
116 |
eq_(len(w), 1)
|
117 |
|
@@ -119,7 +125,7 @@ class TestPytube(object):
|
|
119 |
"""Deprecation warnings get triggered on video getter"""
|
120 |
with warnings.catch_warnings(record=True) as w:
|
121 |
# Cause all warnings to always be triggered.
|
122 |
-
warnings.simplefilter(
|
123 |
self.yt.videos
|
124 |
eq_(len(w), 1)
|
125 |
|
@@ -132,9 +138,3 @@ class TestPytube(object):
|
|
132 |
def test_get_json_offset_with_bad_html(self):
|
133 |
"""Raise exception if json offset cannot be found"""
|
134 |
self.yt._get_json_offset('asdfasdf')
|
135 |
-
|
136 |
-
def test_YT_create_from_url(self):
|
137 |
-
'test creation of YouYube Object from url'
|
138 |
-
url = 'http://www.youtube.com/watch?v=9bZkp7q19f0'
|
139 |
-
|
140 |
-
yt = api.YouTube(url)
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
4 |
+
|
5 |
import warnings
|
6 |
+
|
7 |
import mock
|
8 |
+
from nose.tools import eq_
|
9 |
+
from nose.tools import raises
|
10 |
+
|
11 |
from pytube import api
|
12 |
+
from pytube.exceptions import AgeRestricted
|
13 |
+
from pytube.exceptions import DoesNotExist
|
14 |
+
from pytube.exceptions import MultipleObjectsReturned
|
15 |
+
from pytube.exceptions import PytubeError
|
16 |
|
17 |
|
18 |
class TestPytube(object):
|
|
|
105 |
"""Deprecation warnings get triggered on url set"""
|
106 |
with warnings.catch_warnings(record=True) as w:
|
107 |
# Cause all warnings to always be triggered.
|
108 |
+
warnings.simplefilter('always')
|
109 |
with mock.patch('pytube.api.urlopen') as urlopen:
|
110 |
urlopen.return_value.read.return_value = self.mock_html
|
111 |
yt = api.YouTube()
|
|
|
117 |
"""Deprecation warnings get triggered on filename set"""
|
118 |
with warnings.catch_warnings(record=True) as w:
|
119 |
# Cause all warnings to always be triggered.
|
120 |
+
warnings.simplefilter('always')
|
121 |
self.yt.filename = 'Gangnam Style'
|
122 |
eq_(len(w), 1)
|
123 |
|
|
|
125 |
"""Deprecation warnings get triggered on video getter"""
|
126 |
with warnings.catch_warnings(record=True) as w:
|
127 |
# Cause all warnings to always be triggered.
|
128 |
+
warnings.simplefilter('always')
|
129 |
self.yt.videos
|
130 |
eq_(len(w), 1)
|
131 |
|
|
|
138 |
def test_get_json_offset_with_bad_html(self):
|
139 |
"""Raise exception if json offset cannot be found"""
|
140 |
self.yt._get_json_offset('asdfasdf')
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_utils.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
|
|
4 |
from nose.tools import eq_
|
|
|
5 |
from pytube import utils
|
6 |
|
7 |
|
8 |
class TestUtils(object):
|
9 |
-
blob =
|
10 |
|
11 |
def test_truncate(self):
|
12 |
"""Truncate string works as expected"""
|
@@ -15,10 +17,10 @@ class TestUtils(object):
|
|
15 |
|
16 |
def test_safe_filename(self):
|
17 |
"""Unsafe characters get stripped from generated filename"""
|
18 |
-
eq_(utils.safe_filename(
|
19 |
-
eq_(utils.safe_filename(
|
20 |
-
eq_(utils.safe_filename(
|
21 |
-
eq_(utils.safe_filename(
|
22 |
|
23 |
def test_sizeof(self):
|
24 |
"""Accurately converts the bytes to its humanized equivalent"""
|
|
|
1 |
#!/usr/bin/env python
|
2 |
# -*- coding: utf-8 -*-
|
3 |
from __future__ import unicode_literals
|
4 |
+
|
5 |
from nose.tools import eq_
|
6 |
+
|
7 |
from pytube import utils
|
8 |
|
9 |
|
10 |
class TestUtils(object):
|
11 |
+
blob = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
12 |
|
13 |
def test_truncate(self):
|
14 |
"""Truncate string works as expected"""
|
|
|
17 |
|
18 |
def test_safe_filename(self):
|
19 |
"""Unsafe characters get stripped from generated filename"""
|
20 |
+
eq_(utils.safe_filename('abc1245$$'), 'abc1245')
|
21 |
+
eq_(utils.safe_filename('abc##'), 'abc')
|
22 |
+
eq_(utils.safe_filename('abc:foo'), 'abc -foo')
|
23 |
+
eq_(utils.safe_filename('abc_foo'), 'abc foo')
|
24 |
|
25 |
def test_sizeof(self):
|
26 |
"""Accurately converts the bytes to its humanized equivalent"""
|