lots of whitespace
Browse files- .flake8 +1 -1
- pytube/__main__.py +7 -28
- pytube/captions.py +0 -3
- pytube/cipher.py +11 -11
- pytube/exceptions.py +1 -0
- pytube/helpers.py +1 -0
- pytube/monostate.py +1 -0
- pytube/query.py +1 -0
- pytube/request.py +3 -4
- pytube/version.py +1 -0
- tests/test_streams.py +3 -1
.flake8
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
[flake8]
|
| 2 |
-
ignore = E231,E203,W503,Q000,WPS111,WPS305,WPS348,WPS602,D400,DAR201,S101,DAR101,C812,D104,I001,WPS306,WPS214,D401,WPS229,WPS420
|
| 3 |
max-line-length = 89
|
| 4 |
|
| 5 |
[isort]
|
|
|
|
| 1 |
[flake8]
|
| 2 |
+
ignore = E231,E203,W503,Q000,WPS111,WPS305,WPS348,WPS602,D400,DAR201,S101,DAR101,C812,D104,I001,WPS306,WPS214,D401,WPS229,WPS420,WPS230,WPS414,WPS114,WPS226,WPS442,C819,WPS601,T001,RST304
|
| 3 |
max-line-length = 89
|
| 4 |
|
| 5 |
[isort]
|
pytube/__main__.py
CHANGED
|
@@ -218,8 +218,7 @@ class YouTube:
|
|
| 218 |
:rtype: List[Caption]
|
| 219 |
"""
|
| 220 |
raw_tracks = (
|
| 221 |
-
self.player_response
|
| 222 |
-
.get("captions", {})
|
| 223 |
.get("playerCaptionsTracklistRenderer", {})
|
| 224 |
.get("captionTracks", [])
|
| 225 |
)
|
|
@@ -267,9 +266,7 @@ class YouTube:
|
|
| 267 |
|
| 268 |
"""
|
| 269 |
return self.player_config_args.get("title") or (
|
| 270 |
-
self.player_response
|
| 271 |
-
.get("videoDetails", {})
|
| 272 |
-
.get("title")
|
| 273 |
)
|
| 274 |
|
| 275 |
@property
|
|
@@ -280,9 +277,7 @@ class YouTube:
|
|
| 280 |
|
| 281 |
"""
|
| 282 |
return self.vid_descr or (
|
| 283 |
-
self.player_response
|
| 284 |
-
.get("videoDetails", {})
|
| 285 |
-
.get("shortDescription")
|
| 286 |
)
|
| 287 |
|
| 288 |
@property
|
|
@@ -292,11 +287,7 @@ class YouTube:
|
|
| 292 |
:rtype: float
|
| 293 |
|
| 294 |
"""
|
| 295 |
-
return (
|
| 296 |
-
self.player_response
|
| 297 |
-
.get("videoDetails", {})
|
| 298 |
-
.get("averageRating")
|
| 299 |
-
)
|
| 300 |
|
| 301 |
@property
|
| 302 |
def length(self) -> int:
|
|
@@ -307,11 +298,7 @@ class YouTube:
|
|
| 307 |
"""
|
| 308 |
return int(
|
| 309 |
self.player_config_args.get("length_seconds")
|
| 310 |
-
or (
|
| 311 |
-
self.player_response
|
| 312 |
-
.get("videoDetails", {})
|
| 313 |
-
.get("lengthSeconds")
|
| 314 |
-
)
|
| 315 |
)
|
| 316 |
|
| 317 |
@property
|
|
@@ -321,22 +308,14 @@ class YouTube:
|
|
| 321 |
:rtype: str
|
| 322 |
|
| 323 |
"""
|
| 324 |
-
return int(
|
| 325 |
-
self.player_response
|
| 326 |
-
.get("videoDetails", {})
|
| 327 |
-
.get("viewCount")
|
| 328 |
-
)
|
| 329 |
|
| 330 |
@property
|
| 331 |
def author(self) -> str:
|
| 332 |
"""Get the video author.
|
| 333 |
:rtype: str
|
| 334 |
"""
|
| 335 |
-
return (
|
| 336 |
-
self.player_response
|
| 337 |
-
.get("videoDetails", {})
|
| 338 |
-
.get("author", "unknown")
|
| 339 |
-
)
|
| 340 |
|
| 341 |
def register_on_progress_callback(self, func: OnProgress):
|
| 342 |
"""Register a download progress callback function post initialization.
|
|
|
|
| 218 |
:rtype: List[Caption]
|
| 219 |
"""
|
| 220 |
raw_tracks = (
|
| 221 |
+
self.player_response.get("captions", {})
|
|
|
|
| 222 |
.get("playerCaptionsTracklistRenderer", {})
|
| 223 |
.get("captionTracks", [])
|
| 224 |
)
|
|
|
|
| 266 |
|
| 267 |
"""
|
| 268 |
return self.player_config_args.get("title") or (
|
| 269 |
+
self.player_response.get("videoDetails", {}).get("title")
|
|
|
|
|
|
|
| 270 |
)
|
| 271 |
|
| 272 |
@property
|
|
|
|
| 277 |
|
| 278 |
"""
|
| 279 |
return self.vid_descr or (
|
| 280 |
+
self.player_response.get("videoDetails", {}).get("shortDescription")
|
|
|
|
|
|
|
| 281 |
)
|
| 282 |
|
| 283 |
@property
|
|
|
|
| 287 |
:rtype: float
|
| 288 |
|
| 289 |
"""
|
| 290 |
+
return self.player_response.get("videoDetails", {}).get("averageRating")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
@property
|
| 293 |
def length(self) -> int:
|
|
|
|
| 298 |
"""
|
| 299 |
return int(
|
| 300 |
self.player_config_args.get("length_seconds")
|
| 301 |
+
or (self.player_response.get("videoDetails", {}).get("lengthSeconds"))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
)
|
| 303 |
|
| 304 |
@property
|
|
|
|
| 308 |
:rtype: str
|
| 309 |
|
| 310 |
"""
|
| 311 |
+
return int(self.player_response.get("videoDetails", {}).get("viewCount"))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
@property
|
| 314 |
def author(self) -> str:
|
| 315 |
"""Get the video author.
|
| 316 |
:rtype: str
|
| 317 |
"""
|
| 318 |
+
return self.player_response.get("videoDetails", {}).get("author", "unknown")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
|
| 320 |
def register_on_progress_callback(self, func: OnProgress):
|
| 321 |
"""Register a download progress callback function post initialization.
|
pytube/captions.py
CHANGED
|
@@ -4,10 +4,8 @@ import os
|
|
| 4 |
import time
|
| 5 |
import xml.etree.ElementTree as ElementTree
|
| 6 |
from typing import Dict, Optional
|
| 7 |
-
|
| 8 |
from pytube import request
|
| 9 |
from html import unescape
|
| 10 |
-
|
| 11 |
from pytube.helpers import safe_filename, target_directory
|
| 12 |
|
| 13 |
|
|
@@ -105,7 +103,6 @@ class Caption:
|
|
| 105 |
:type filename_prefix: str or None
|
| 106 |
|
| 107 |
:rtype: str
|
| 108 |
-
|
| 109 |
"""
|
| 110 |
if title.endswith(".srt") or title.endswith(".xml"):
|
| 111 |
filename = ".".join(title.split(".")[:-1])
|
|
|
|
| 4 |
import time
|
| 5 |
import xml.etree.ElementTree as ElementTree
|
| 6 |
from typing import Dict, Optional
|
|
|
|
| 7 |
from pytube import request
|
| 8 |
from html import unescape
|
|
|
|
| 9 |
from pytube.helpers import safe_filename, target_directory
|
| 10 |
|
| 11 |
|
|
|
|
| 103 |
:type filename_prefix: str or None
|
| 104 |
|
| 105 |
:rtype: str
|
|
|
|
| 106 |
"""
|
| 107 |
if title.endswith(".srt") or title.endswith(".xml"):
|
| 108 |
filename = ".".join(title.split(".")[:-1])
|
pytube/cipher.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
"""
|
| 3 |
This module contains all logic necessary to decipher the signature.
|
| 4 |
|
|
@@ -26,7 +27,6 @@ logger = create_logger()
|
|
| 26 |
|
| 27 |
def get_initial_function_name(js: str) -> str:
|
| 28 |
"""Extract the name of the function responsible for computing the signature.
|
| 29 |
-
|
| 30 |
:param str js:
|
| 31 |
The contents of the base.js asset file.
|
| 32 |
:rtype: str
|
|
@@ -52,10 +52,10 @@ def get_initial_function_name(js: str) -> str:
|
|
| 52 |
logger.debug("finding initial function name")
|
| 53 |
for pattern in function_patterns:
|
| 54 |
regex = re.compile(pattern)
|
| 55 |
-
|
| 56 |
-
if
|
| 57 |
logger.debug("finished regex search, matched: %s", pattern)
|
| 58 |
-
return
|
| 59 |
|
| 60 |
raise RegexMatchError(caller="get_initial_function_name", pattern="multiple")
|
| 61 |
|
|
@@ -112,11 +112,11 @@ def get_transform_object(js: str, var: str) -> List[str]:
|
|
| 112 |
pattern = r"var %s={(.*?)};" % re.escape(var)
|
| 113 |
logger.debug("getting transform object")
|
| 114 |
regex = re.compile(pattern, flags=re.DOTALL)
|
| 115 |
-
|
| 116 |
-
if not
|
| 117 |
raise RegexMatchError(caller="get_transform_object", pattern=pattern)
|
| 118 |
|
| 119 |
-
return
|
| 120 |
|
| 121 |
|
| 122 |
def get_transform_map(js: str, var: str) -> Dict:
|
|
@@ -245,10 +245,10 @@ def parse_function(js_func: str) -> Tuple[str, int]:
|
|
| 245 |
logger.debug("parsing transform function")
|
| 246 |
pattern = r"\w+\.(\w+)\(\w,(\d+)\)"
|
| 247 |
regex = re.compile(pattern)
|
| 248 |
-
|
| 249 |
-
if not
|
| 250 |
raise RegexMatchError(caller="parse_function", pattern=pattern)
|
| 251 |
-
fn_name, fn_arg =
|
| 252 |
return fn_name, int(fn_arg)
|
| 253 |
|
| 254 |
|
|
@@ -269,7 +269,7 @@ def get_signature(js: str, ciphered_signature: str) -> str:
|
|
| 269 |
transform_plan = get_transform_plan(js)
|
| 270 |
var, _ = transform_plan[0].split(".")
|
| 271 |
transform_map = get_transform_map(js, var)
|
| 272 |
-
signature =
|
| 273 |
|
| 274 |
for js_func in transform_plan:
|
| 275 |
name, argument = parse_function(js_func)
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
"""
|
| 4 |
This module contains all logic necessary to decipher the signature.
|
| 5 |
|
|
|
|
| 27 |
|
| 28 |
def get_initial_function_name(js: str) -> str:
|
| 29 |
"""Extract the name of the function responsible for computing the signature.
|
|
|
|
| 30 |
:param str js:
|
| 31 |
The contents of the base.js asset file.
|
| 32 |
:rtype: str
|
|
|
|
| 52 |
logger.debug("finding initial function name")
|
| 53 |
for pattern in function_patterns:
|
| 54 |
regex = re.compile(pattern)
|
| 55 |
+
function_match = regex.search(js)
|
| 56 |
+
if function_match:
|
| 57 |
logger.debug("finished regex search, matched: %s", pattern)
|
| 58 |
+
return function_match.group(1)
|
| 59 |
|
| 60 |
raise RegexMatchError(caller="get_initial_function_name", pattern="multiple")
|
| 61 |
|
|
|
|
| 112 |
pattern = r"var %s={(.*?)};" % re.escape(var)
|
| 113 |
logger.debug("getting transform object")
|
| 114 |
regex = re.compile(pattern, flags=re.DOTALL)
|
| 115 |
+
transform_match = regex.search(js)
|
| 116 |
+
if not transform_match:
|
| 117 |
raise RegexMatchError(caller="get_transform_object", pattern=pattern)
|
| 118 |
|
| 119 |
+
return transform_match.group(1).replace("\n", " ").split(", ")
|
| 120 |
|
| 121 |
|
| 122 |
def get_transform_map(js: str, var: str) -> Dict:
|
|
|
|
| 245 |
logger.debug("parsing transform function")
|
| 246 |
pattern = r"\w+\.(\w+)\(\w,(\d+)\)"
|
| 247 |
regex = re.compile(pattern)
|
| 248 |
+
parse_match = regex.search(js_func)
|
| 249 |
+
if not parse_match:
|
| 250 |
raise RegexMatchError(caller="parse_function", pattern=pattern)
|
| 251 |
+
fn_name, fn_arg = parse_match.groups()
|
| 252 |
return fn_name, int(fn_arg)
|
| 253 |
|
| 254 |
|
|
|
|
| 269 |
transform_plan = get_transform_plan(js)
|
| 270 |
var, _ = transform_plan[0].split(".")
|
| 271 |
transform_map = get_transform_map(js, var)
|
| 272 |
+
signature = list(ciphered_signature)
|
| 273 |
|
| 274 |
for js_func in transform_plan:
|
| 275 |
name, argument = parse_function(js_func)
|
pytube/exceptions.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
"""Library specific exception definitions."""
|
| 3 |
from typing import Union, Pattern
|
| 4 |
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
"""Library specific exception definitions."""
|
| 4 |
from typing import Union, Pattern
|
| 5 |
|
pytube/helpers.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
"""Various helper functions implemented by pytube."""
|
| 3 |
import functools
|
| 4 |
import logging
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
"""Various helper functions implemented by pytube."""
|
| 4 |
import functools
|
| 5 |
import logging
|
pytube/monostate.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
import io
|
| 3 |
from typing import Any, Optional
|
| 4 |
from typing_extensions import Protocol
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
import io
|
| 4 |
from typing import Any, Optional
|
| 5 |
from typing_extensions import Protocol
|
pytube/query.py
CHANGED
|
@@ -233,6 +233,7 @@ class StreamQuery:
|
|
| 233 |
|
| 234 |
def get_by_resolution(self, resolution: str) -> Optional[Stream]:
|
| 235 |
"""Get the corresponding :class:`Stream <Stream>` for a given resolution.
|
|
|
|
| 236 |
Stream must be a progressive mp4.
|
| 237 |
|
| 238 |
:param str resolution:
|
|
|
|
| 233 |
|
| 234 |
def get_by_resolution(self, resolution: str) -> Optional[Stream]:
|
| 235 |
"""Get the corresponding :class:`Stream <Stream>` for a given resolution.
|
| 236 |
+
|
| 237 |
Stream must be a progressive mp4.
|
| 238 |
|
| 239 |
:param str resolution:
|
pytube/request.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
"""Implements a simple wrapper around urlopen."""
|
| 3 |
from typing import Any, Iterable, Dict
|
| 4 |
from urllib.request import Request
|
|
@@ -25,10 +26,8 @@ def get(url) -> str:
|
|
| 25 |
|
| 26 |
def stream(url: str, chunk_size: int = 8192) -> Iterable[bytes]:
|
| 27 |
"""Read the response in chunks.
|
| 28 |
-
:param str url:
|
| 29 |
-
|
| 30 |
-
:param int chunk_size:
|
| 31 |
-
The size in bytes of each chunk. Defaults to 8*1024
|
| 32 |
:rtype: Iterable[bytes]
|
| 33 |
"""
|
| 34 |
response = _execute_request(url)
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
"""Implements a simple wrapper around urlopen."""
|
| 4 |
from typing import Any, Iterable, Dict
|
| 5 |
from urllib.request import Request
|
|
|
|
| 26 |
|
| 27 |
def stream(url: str, chunk_size: int = 8192) -> Iterable[bytes]:
|
| 28 |
"""Read the response in chunks.
|
| 29 |
+
:param str url: The URL to perform the GET request for.
|
| 30 |
+
:param int chunk_size: The size in bytes of each chunk. Defaults to 8*1024
|
|
|
|
|
|
|
| 31 |
:rtype: Iterable[bytes]
|
| 32 |
"""
|
| 33 |
response = _execute_request(url)
|
pytube/version.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
__version__ = "9.6.1"
|
| 3 |
|
| 4 |
if __name__ == "__main__":
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
__version__ = "9.6.1"
|
| 4 |
|
| 5 |
if __name__ == "__main__":
|
tests/test_streams.py
CHANGED
|
@@ -209,7 +209,9 @@ def test_author(cipher_signature):
|
|
| 209 |
|
| 210 |
def test_thumbnail_when_in_details(cipher_signature):
|
| 211 |
expected = "some url"
|
| 212 |
-
cipher_signature.player_response = {
|
|
|
|
|
|
|
| 213 |
assert cipher_signature.thumbnail_url == expected
|
| 214 |
|
| 215 |
|
|
|
|
| 209 |
|
| 210 |
def test_thumbnail_when_in_details(cipher_signature):
|
| 211 |
expected = "some url"
|
| 212 |
+
cipher_signature.player_response = {
|
| 213 |
+
"videoDetails": {"thumbnail": {"thumbnails": [{"url": expected}]}}
|
| 214 |
+
}
|
| 215 |
assert cipher_signature.thumbnail_url == expected
|
| 216 |
|
| 217 |
|