diff --git a/.deepsource.toml b/.deepsource.toml
index 5ffeda411..6431df05d 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -13,4 +13,4 @@ name = "python"
enabled = true
[analyzers.meta]
- runtime_version = "3.x.x"
\ No newline at end of file
+ runtime_version = "3.x.x"
diff --git a/.idea/dictionaries/haroldmartin.xml b/.idea/dictionaries/haroldmartin.xml
index 7c49e3be1..704efb7b5 100644
--- a/.idea/dictionaries/haroldmartin.xml
+++ b/.idea/dictionaries/haroldmartin.xml
@@ -46,4 +46,4 @@
ytplayer
-
\ No newline at end of file
+
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
index 105ce2da2..cc5462daf 100644
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index a2e120dcc..ce8c2e7a9 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7f4..5ace414d8 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/docs/conf.py b/docs/conf.py
index 223bf7de4..7e016167b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -5,38 +5,39 @@
import sys
import sphinx_rtd_theme
-sys.path.insert(0, os.path.abspath('../'))
+
+sys.path.insert(0, os.path.abspath("../"))
from pytube import __version__ # noqa
# -- General configuration ------------------------------------------------
extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.autosummary',
- 'sphinx.ext.todo',
- 'sphinx.ext.intersphinx',
- 'sphinx.ext.viewcode',
+ "sphinx.ext.autodoc",
+ "sphinx.ext.autosummary",
+ "sphinx.ext.todo",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.viewcode",
]
autosummary_generate = True
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = 'pytube3'
-copyright = '2019, Nick Ficano'
-author = 'Nick Ficano, Harold Martin'
+project = "pytube3"
+copyright = "2019, Nick Ficano"
+author = "Nick Ficano, Harold Martin"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -57,16 +58,16 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
intersphinx_mapping = {
- 'python': ('https://docs.python.org/3/', None),
+ "python": ("https://docs.python.org/3/", None),
}
@@ -75,7 +76,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
@@ -87,7 +88,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
@@ -95,12 +96,12 @@
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
- '**': [
- 'about.html',
- 'navigation.html',
- 'relations.html', # needs 'show_related': True theme option to display
- 'searchbox.html',
- 'donate.html',
+ "**": [
+ "about.html",
+ "navigation.html",
+ "relations.html", # needs 'show_related': True theme option to display
+ "searchbox.html",
+ "donate.html",
],
}
@@ -108,7 +109,7 @@
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
-htmlhelp_basename = 'pytube3doc'
+htmlhelp_basename = "pytube3doc"
# -- Options for LaTeX output ---------------------------------------------
@@ -120,8 +121,11 @@
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
- master_doc, 'pytube3.tex', 'pytube3 Documentation',
- 'Nick Ficano', 'manual',
+ master_doc,
+ "pytube3.tex",
+ "pytube3 Documentation",
+ "Nick Ficano",
+ "manual",
),
]
@@ -131,10 +135,7 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- (
- master_doc, 'pytube3', 'pytube3 Documentation',
- [author], 1,
- ),
+ (master_doc, "pytube3", "pytube3 Documentation", [author], 1,),
]
@@ -145,8 +146,12 @@
# dir menu entry, description, category)
texinfo_documents = [
(
- master_doc, 'pytube3', 'pytube3 Documentation',
- author, 'pytube3', 'One line description of project.',
- 'Miscellaneous',
+ master_doc,
+ "pytube3",
+ "pytube3 Documentation",
+ author,
+ "pytube3",
+ "One line description of project.",
+ "Miscellaneous",
),
]
diff --git a/pytube/__main__.py b/pytube/__main__.py
index 90ebb86f8..02de5f52e 100644
--- a/pytube/__main__.py
+++ b/pytube/__main__.py
@@ -7,12 +7,13 @@
smaller peripheral modules and functions.
"""
-
import json
import logging
-from typing import Optional, Dict, List
-from urllib.parse import parse_qsl
from html import unescape
+from typing import Dict
+from typing import List
+from typing import Optional
+from urllib.parse import parse_qsl
from pytube import Caption
from pytube import CaptionQuery
@@ -20,10 +21,14 @@
from pytube import request
from pytube import Stream
from pytube import StreamQuery
-from pytube.extract import apply_descrambler, apply_signature, get_ytplayer_config
-from pytube.helpers import install_proxy
from pytube.exceptions import VideoUnavailable
-from pytube.monostate import OnProgress, OnComplete, Monostate
+from pytube.extract import apply_descrambler
+from pytube.extract import apply_signature
+from pytube.extract import get_ytplayer_config
+from pytube.helpers import install_proxy
+from pytube.monostate import Monostate
+from pytube.monostate import OnComplete
+from pytube.monostate import OnProgress
logger = logging.getLogger(__name__)
@@ -54,17 +59,23 @@ def __init__(
"""
self.js: Optional[str] = None # js fetched by js_url
- self.js_url: Optional[str] = None # the url to the js, parsed from watch html
+ self.js_url: Optional[
+ str
+ ] = None # the url to the js, parsed from watch html
# note: vid_info may eventually be removed. It sounds like it once had
# additional formats, but that doesn't appear to still be the case.
# the url to vid info, parsed from watch html
self.vid_info_url: Optional[str] = None
- self.vid_info_raw: Optional[str] = None # content fetched by vid_info_url
+ self.vid_info_raw: Optional[
+ str
+ ] = None # content fetched by vid_info_url
self.vid_info: Optional[Dict] = None # parsed content of vid_info_raw
- self.watch_html: Optional[str] = None # the html of /watch?v=
+ self.watch_html: Optional[
+ str
+ ] = None # the html of /watch?v=
self.embed_html: Optional[str] = None
self.player_config_args: Dict = {} # inline js in the html containing
self.player_response: Dict = {}
@@ -109,11 +120,15 @@ def descramble(self) -> None:
self.player_config_args = self.vid_info
else:
assert self.watch_html is not None
- self.player_config_args = get_ytplayer_config(self.watch_html)["args"]
+ self.player_config_args = get_ytplayer_config(self.watch_html)[
+ "args"
+ ]
# Fix for KeyError: 'title' issue #434
if "title" not in self.player_config_args: # type: ignore
- i_start = self.watch_html.lower().index("") + len("")
+ i_start = self.watch_html.lower().index("") + len(
+ ""
+ )
i_end = self.watch_html.lower().index("")
title = self.watch_html[i_start:i_end].strip()
index = title.lower().rfind(" - youtube")
@@ -143,7 +158,9 @@ def descramble(self) -> None:
self.initialize_stream_objects(fmt)
# load the player_response object (contains subtitle information)
- self.player_response = json.loads(self.player_config_args["player_response"])
+ self.player_response = json.loads(
+ self.player_config_args["player_response"]
+ )
del self.player_config_args["player_response"]
self.stream_monostate.title = self.title
self.stream_monostate.duration = self.length
@@ -164,7 +181,10 @@ def prefetch(self) -> None:
raise VideoUnavailable(video_id=self.video_id)
self.age_restricted = extract.is_age_restricted(self.watch_html)
- if not self.age_restricted and "This video is private" in self.watch_html:
+ if (
+ not self.age_restricted
+ and "This video is private" in self.watch_html
+ ):
raise VideoUnavailable(video_id=self.video_id)
if self.age_restricted:
@@ -282,7 +302,9 @@ def rating(self) -> float:
:rtype: float
"""
- return self.player_response.get("videoDetails", {}).get("averageRating")
+ return self.player_response.get("videoDetails", {}).get(
+ "averageRating"
+ )
@property
def length(self) -> int:
@@ -293,7 +315,11 @@ def length(self) -> int:
"""
return int(
self.player_config_args.get("length_seconds")
- or (self.player_response.get("videoDetails", {}).get("lengthSeconds"))
+ or (
+ self.player_response.get("videoDetails", {}).get(
+ "lengthSeconds"
+ )
+ )
)
@property
@@ -303,14 +329,18 @@ def views(self) -> int:
:rtype: str
"""
- return int(self.player_response.get("videoDetails", {}).get("viewCount"))
+ return int(
+ self.player_response.get("videoDetails", {}).get("viewCount")
+ )
@property
def author(self) -> str:
"""Get the video author.
:rtype: str
"""
- return self.player_response.get("videoDetails", {}).get("author", "unknown")
+ return self.player_response.get("videoDetails", {}).get(
+ "author", "unknown"
+ )
def register_on_progress_callback(self, func: OnProgress):
"""Register a download progress callback function post initialization.
diff --git a/pytube/captions.py b/pytube/captions.py
index 1aeccc21c..53f2c013b 100644
--- a/pytube/captions.py
+++ b/pytube/captions.py
@@ -3,10 +3,13 @@
import os
import time
import xml.etree.ElementTree as ElementTree
-from typing import Dict, Optional
-from pytube import request
from html import unescape
-from pytube.helpers import safe_filename, target_directory
+from typing import Dict
+from typing import Optional
+
+from pytube import request
+from pytube.helpers import safe_filename
+from pytube.helpers import target_directory
class Caption:
diff --git a/pytube/cipher.py b/pytube/cipher.py
index 26b2ca926..bddb0a490 100644
--- a/pytube/cipher.py
+++ b/pytube/cipher.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-
"""
This module contains all logic necessary to decipher the signature.
@@ -17,10 +16,16 @@
import logging
import re
from itertools import chain
-from typing import List, Tuple, Dict, Callable, Any, Optional
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
from pytube.exceptions import RegexMatchError
-from pytube.helpers import regex_search, cache
+from pytube.helpers import cache
+from pytube.helpers import regex_search
logger = logging.getLogger(__name__)
@@ -84,7 +89,9 @@ def parse_function(self, js_func: str) -> Tuple[str, int]:
logger.debug("parsing transform function")
parse_match = self.js_func_regex.search(js_func)
if not parse_match:
- raise RegexMatchError(caller="parse_function", pattern="js_func_regex")
+ raise RegexMatchError(
+ caller="parse_function", pattern="js_func_regex"
+ )
fn_name, fn_arg = parse_match.groups()
return fn_name, int(fn_arg)
@@ -120,7 +127,9 @@ def get_initial_function_name(js: str) -> str:
logger.debug("finished regex search, matched: %s", pattern)
return function_match.group(1)
- raise RegexMatchError(caller="get_initial_function_name", pattern="multiple")
+ raise RegexMatchError(
+ caller="get_initial_function_name", pattern="multiple"
+ )
def get_transform_plan(js: str) -> List[str]:
diff --git a/pytube/cli.py b/pytube/cli.py
index 05c272577..47f951b2b 100755
--- a/pytube/cli.py
+++ b/pytube/cli.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""A simple command line application to download youtube videos."""
-
import argparse
import datetime as dt
import gzip
@@ -9,14 +8,19 @@
import logging
import os
import shutil
-import sys
import subprocess # nosec
-from typing import List, Optional
+import sys
+from typing import List
+from typing import Optional
-from pytube import __version__, CaptionQuery, Stream, Playlist
+from pytube import __version__
+from pytube import CaptionQuery
+from pytube import Playlist
+from pytube import Stream
from pytube import YouTube
from pytube.exceptions import PytubeError
-from pytube.helpers import safe_filename, setup_logger
+from pytube.helpers import safe_filename
+from pytube.helpers import setup_logger
def main():
@@ -49,7 +53,9 @@ def main():
_perform_args_on_youtube(youtube, args)
-def _perform_args_on_youtube(youtube: YouTube, args: argparse.Namespace) -> None:
+def _perform_args_on_youtube(
+ youtube: YouTube, args: argparse.Namespace
+) -> None:
if args.list:
display_streams(youtube)
if args.build_playback_report:
@@ -65,15 +71,21 @@ def _perform_args_on_youtube(youtube: YouTube, args: argparse.Namespace) -> None
youtube=youtube, resolution=args.resolution, target=args.target
)
if args.audio:
- download_audio(youtube=youtube, filetype=args.audio, target=args.target)
+ download_audio(
+ youtube=youtube, filetype=args.audio, target=args.target
+ )
if args.ffmpeg:
- ffmpeg_process(youtube=youtube, resolution=args.ffmpeg, target=args.target)
+ ffmpeg_process(
+ youtube=youtube, resolution=args.ffmpeg, target=args.target
+ )
def _parse_args(
parser: argparse.ArgumentParser, args: Optional[List] = None
) -> argparse.Namespace:
- parser.add_argument("url", help="The YouTube /watch or /playlist url", nargs="?")
+ parser.add_argument(
+ "url", help="The YouTube /watch or /playlist url", nargs="?"
+ )
parser.add_argument(
"--version", action="version", version="%(prog)s " + __version__,
)
@@ -81,7 +93,10 @@ def _parse_args(
"--itag", type=int, help="The itag for the desired stream",
)
parser.add_argument(
- "-r", "--resolution", type=str, help="The resolution for the desired stream",
+ "-r",
+ "--resolution",
+ type=str,
+ help="The resolution for the desired stream",
)
parser.add_argument(
"-l",
@@ -218,7 +233,9 @@ def on_progress(
def _download(
- stream: Stream, target: Optional[str] = None, filename: Optional[str] = None
+ stream: Stream,
+ target: Optional[str] = None,
+ filename: Optional[str] = None,
) -> None:
filesize_megabytes = stream.filesize // 1048576
print(f"{filename or stream.default_filename} | {filesize_megabytes} MB")
@@ -271,7 +288,9 @@ def ffmpeg_process(
if resolution == "best":
highest_quality_stream = (
- youtube.streams.filter(progressive=False).order_by("resolution").last()
+ youtube.streams.filter(progressive=False)
+ .order_by("resolution")
+ .last()
)
mp4_stream = (
youtube.streams.filter(progressive=False, subtype="mp4")
@@ -298,7 +317,9 @@ def ffmpeg_process(
audio_stream = youtube.streams.get_audio_only(video_stream.subtype)
if not audio_stream:
- audio_stream = youtube.streams.filter(only_audio=True).order_by("abr").last()
+ audio_stream = (
+ youtube.streams.filter(only_audio=True).order_by("abr").last()
+ )
if not audio_stream:
print("Could not find an audio only stream")
sys.exit()
@@ -307,7 +328,9 @@ def ffmpeg_process(
)
-def _ffmpeg_downloader(audio_stream: Stream, video_stream: Stream, target: str) -> None:
+def _ffmpeg_downloader(
+ audio_stream: Stream, video_stream: Stream, target: str
+) -> None:
"""
Given a YouTube Stream object, finds the correct audio stream, downloads them both
giving them a unique name, them uses ffmpeg to create a new file with the audio
@@ -322,29 +345,50 @@ def _ffmpeg_downloader(audio_stream: Stream, video_stream: Stream, target: str)
A valid Path object
"""
video_unique_name = _unique_name(
- safe_filename(video_stream.title), video_stream.subtype, "video", target=target
+ safe_filename(video_stream.title),
+ video_stream.subtype,
+ "video",
+ target=target,
)
audio_unique_name = _unique_name(
- safe_filename(video_stream.title), audio_stream.subtype, "audio", target=target
+ safe_filename(video_stream.title),
+ audio_stream.subtype,
+ "audio",
+ target=target,
)
_download(stream=video_stream, target=target, filename=video_unique_name)
print("Loading audio...")
_download(stream=audio_stream, target=target, filename=audio_unique_name)
- video_path = os.path.join(target, f"{video_unique_name}.{video_stream.subtype}")
- audio_path = os.path.join(target, f"{audio_unique_name}.{audio_stream.subtype}")
+ video_path = os.path.join(
+ target, f"{video_unique_name}.{video_stream.subtype}"
+ )
+ audio_path = os.path.join(
+ target, f"{audio_unique_name}.{audio_stream.subtype}"
+ )
final_path = os.path.join(
target, f"{safe_filename(video_stream.title)}.{video_stream.subtype}"
)
subprocess.run( # nosec
- ["ffmpeg", "-i", video_path, "-i", audio_path, "-codec", "copy", final_path,]
+ [
+ "ffmpeg",
+ "-i",
+ video_path,
+ "-i",
+ audio_path,
+ "-codec",
+ "copy",
+ final_path,
+ ]
)
os.unlink(video_path)
os.unlink(audio_path)
-def download_by_itag(youtube: YouTube, itag: int, target: Optional[str] = None) -> None:
+def download_by_itag(
+ youtube: YouTube, itag: int, target: Optional[str] = None
+) -> None:
"""Start downloading a YouTube video.
:param YouTube youtube:
@@ -409,7 +453,9 @@ def display_streams(youtube: YouTube) -> None:
def _print_available_captions(captions: CaptionQuery) -> None:
- print(f"Available caption codes are: {', '.join(c.code for c in captions)}")
+ print(
+ f"Available caption codes are: {', '.join(c.code for c in captions)}"
+ )
def download_caption(
@@ -432,7 +478,9 @@ def download_caption(
try:
caption = youtube.captions[lang_code]
- downloaded_path = caption.download(title=youtube.title, output_path=target)
+ downloaded_path = caption.download(
+ title=youtube.title, output_path=target
+ )
print(f"Saved caption file to: {downloaded_path}")
except KeyError:
print(f"Unable to find caption with code: {lang_code}")
@@ -454,7 +502,9 @@ def download_audio(
Target directory for download
"""
audio = (
- youtube.streams.filter(only_audio=True, subtype=filetype).order_by("abr").last()
+ youtube.streams.filter(only_audio=True, subtype=filetype)
+ .order_by("abr")
+ .last()
)
if audio is None:
diff --git a/pytube/contrib/playlist.py b/pytube/contrib/playlist.py
index ccb8a794b..96e4011d2 100644
--- a/pytube/contrib/playlist.py
+++ b/pytube/contrib/playlist.py
@@ -1,17 +1,24 @@
# -*- coding: utf-8 -*-
-
"""Module to download a complete playlist from a youtube channel."""
-
import json
import logging
import re
-from datetime import date, datetime
-from typing import List, Optional, Iterable, Dict, Union
-from urllib.parse import parse_qs
from collections.abc import Sequence
+from datetime import date
+from datetime import datetime
+from typing import Dict
+from typing import Iterable
+from typing import List
+from typing import Optional
+from typing import Union
+from urllib.parse import parse_qs
-from pytube import request, YouTube
-from pytube.helpers import cache, deprecated, install_proxy, uniqueify
+from pytube import request
+from pytube import YouTube
+from pytube.helpers import cache
+from pytube.helpers import deprecated
+from pytube.helpers import install_proxy
+from pytube.helpers import uniqueify
logger = logging.getLogger(__name__)
@@ -28,7 +35,9 @@ def __init__(self, url: str, proxies: Optional[Dict[str, str]] = None):
except IndexError: # assume that url is just the id
self.playlist_id = url
- self.playlist_url = f"https://www.youtube.com/playlist?list={self.playlist_id}"
+ self.playlist_url = (
+ f"https://www.youtube.com/playlist?list={self.playlist_id}"
+ )
self.html = request.get(self.playlist_url)
# Needs testing with non-English
@@ -48,7 +57,8 @@ def __init__(self, url: str, proxies: Optional[Dict[str, str]] = None):
def _find_load_more_url(req: str) -> Optional[str]:
"""Given an html page or fragment, returns the "load more" url if found."""
match = re.search(
- r"data-uix-load-more-href=\"(/browse_ajax\?" 'action_continuation=.*?)"',
+ r"data-uix-load-more-href=\"(/browse_ajax\?"
+ 'action_continuation=.*?)"',
req,
)
if match:
@@ -56,7 +66,9 @@ def _find_load_more_url(req: str) -> Optional[str]:
return None
- @deprecated("This function will be removed in the future, please use .video_urls")
+ @deprecated(
+ "This function will be removed in the future, please use .video_urls"
+ )
def parse_links(self) -> List[str]: # pragma: no cover
""" Deprecated function for returning list of URLs
@@ -64,7 +76,9 @@ def parse_links(self) -> List[str]: # pragma: no cover
"""
return self.video_urls
- def _paginate(self, until_watch_id: Optional[str] = None) -> Iterable[List[str]]:
+ def _paginate(
+ self, until_watch_id: Optional[str] = None
+ ) -> Iterable[List[str]]:
"""Parse the video links from the page source, yields the /watch?v= part from video link
"""
req = self.html
@@ -94,7 +108,9 @@ def _paginate(self, until_watch_id: Optional[str] = None) -> Iterable[List[str]]
videos_urls = self._extract_videos(html)
if until_watch_id:
try:
- trim_index = videos_urls.index(f"/watch?v={until_watch_id}")
+ trim_index = videos_urls.index(
+ f"/watch?v={until_watch_id}"
+ )
yield videos_urls[:trim_index]
return
except ValueError:
@@ -132,7 +148,9 @@ def video_urls(self) -> List[str]:
:returns: List of video URLs
"""
return [
- self._video_url(video) for page in list(self._paginate()) for video in page
+ self._video_url(video)
+ for page in list(self._paginate())
+ for video in page
]
@property
diff --git a/pytube/exceptions.py b/pytube/exceptions.py
index 134cf34a2..8a4818d47 100644
--- a/pytube/exceptions.py
+++ b/pytube/exceptions.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-
"""Library specific exception definitions."""
-from typing import Union, Pattern
+from typing import Pattern
+from typing import Union
class PytubeError(Exception):
diff --git a/pytube/extract.py b/pytube/extract.py
index 43fe7f821..d1b122df1 100644
--- a/pytube/extract.py
+++ b/pytube/extract.py
@@ -5,12 +5,21 @@
import re
from collections import OrderedDict
from html.parser import HTMLParser
-from typing import Any, Optional, Tuple, List, Dict
-from urllib.parse import quote, parse_qs, unquote, parse_qsl
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+from urllib.parse import parse_qs
+from urllib.parse import parse_qsl
+from urllib.parse import quote
+from urllib.parse import unquote
from urllib.parse import urlencode
from pytube.cipher import Cipher
-from pytube.exceptions import RegexMatchError, HTMLParseError, LiveStreamError
+from pytube.exceptions import HTMLParseError
+from pytube.exceptions import LiveStreamError
+from pytube.exceptions import RegexMatchError
from pytube.helpers import regex_search
logger = logging.getLogger(__name__)
@@ -123,7 +132,9 @@ def video_info_url_age_restricted(video_id: str, embed_html: str) -> str:
# Here we use ``OrderedDict`` so that the output is consistent between
# Python 2.7+.
eurl = f"https://youtube.googleapis.com/v/{video_id}"
- params = OrderedDict([("video_id", video_id), ("eurl", eurl), ("sts", sts),])
+ params = OrderedDict(
+ [("video_id", video_id), ("eurl", eurl), ("sts", sts),]
+ )
return _video_info_url(params)
@@ -199,7 +210,9 @@ def get_ytplayer_config(html: str) -> Any:
yt_player_config = function_match.group(1)
return json.loads(yt_player_config)
- raise RegexMatchError(caller="get_ytplayer_config", pattern="config_patterns")
+ raise RegexMatchError(
+ caller="get_ytplayer_config", pattern="config_patterns"
+ )
def _get_vid_descr(html: Optional[str]) -> str:
@@ -248,7 +261,9 @@ def apply_signature(config_args: Dict, fmt: str, js: str) -> None:
signature = cipher.get_signature(ciphered_signature=stream["s"])
- logger.debug("finished descrambling signature for itag=%s", stream["itag"])
+ logger.debug(
+ "finished descrambling signature for itag=%s", stream["itag"]
+ )
# 403 forbidden fix
stream_manifest[i]["url"] = url + "&sig=" + signature
@@ -278,7 +293,9 @@ def apply_descrambler(stream_data: Dict, key: str) -> None:
if key == "url_encoded_fmt_stream_map" and not stream_data.get(
"url_encoded_fmt_stream_map"
):
- formats = json.loads(stream_data["player_response"])["streamingData"]["formats"]
+ formats = json.loads(stream_data["player_response"])["streamingData"][
+ "formats"
+ ]
formats.extend(
json.loads(stream_data["player_response"])["streamingData"][
"adaptiveFormats"
@@ -298,7 +315,8 @@ def apply_descrambler(stream_data: Dict, key: str) -> None:
]
except KeyError:
cipher_url = [
- parse_qs(formats[i]["cipher"]) for i, data in enumerate(formats)
+ parse_qs(formats[i]["cipher"])
+ for i, data in enumerate(formats)
]
stream_data[key] = [
{
diff --git a/pytube/helpers.py b/pytube/helpers.py
index 6bd15f89e..b09ea099e 100644
--- a/pytube/helpers.py
+++ b/pytube/helpers.py
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
-
"""Various helper functions implemented by pytube."""
import functools
import logging
import os
import re
import warnings
-from typing import TypeVar, Callable, Optional, Dict, List, Any
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import TypeVar
from urllib import request
from pytube.exceptions import RegexMatchError
diff --git a/pytube/monostate.py b/pytube/monostate.py
index 8d09cec61..10a07e1ab 100644
--- a/pytube/monostate.py
+++ b/pytube/monostate.py
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
+from typing import Any
+from typing import Optional
-from typing import Any, Optional
from typing_extensions import Protocol
class OnProgress(Protocol):
- def __call__(self, stream: Any, chunk: bytes, bytes_remaining: int) -> None:
+ def __call__(
+ self, stream: Any, chunk: bytes, bytes_remaining: int
+ ) -> None:
"""On download progress callback function.
:param stream:
diff --git a/pytube/query.py b/pytube/query.py
index 1e7819dd1..1c53efaaa 100644
--- a/pytube/query.py
+++ b/pytube/query.py
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
-
"""This module provides a query interface for media streams and captions."""
-from typing import Callable, List, Optional, Union
-from collections.abc import Mapping, Sequence
-
-from pytube import Stream, Caption
+from collections.abc import Mapping
+from collections.abc import Sequence
+from typing import Callable
+from typing import List
+from typing import Optional
+from typing import Union
+
+from pytube import Caption
+from pytube import Stream
from pytube.helpers import deprecated
@@ -150,12 +154,16 @@ def filter(
if only_audio:
filters.append(
- lambda s: (s.includes_audio_track and not s.includes_video_track),
+ lambda s: (
+ s.includes_audio_track and not s.includes_video_track
+ ),
)
if only_video:
filters.append(
- lambda s: (s.includes_video_track and not s.includes_audio_track),
+ lambda s: (
+ s.includes_video_track and not s.includes_audio_track
+ ),
)
if progressive:
@@ -185,10 +193,14 @@ def order_by(self, attribute_name: str) -> "StreamQuery":
The name of the attribute to sort by.
"""
has_attribute = [
- s for s in self.fmt_streams if getattr(s, attribute_name) is not None
+ s
+ for s in self.fmt_streams
+ if getattr(s, attribute_name) is not None
]
# Check that the attributes have string values.
- if has_attribute and isinstance(getattr(has_attribute[0], attribute_name), str):
+ if has_attribute and isinstance(
+ getattr(has_attribute[0], attribute_name), str
+ ):
# Try to return a StreamQuery sorted by the integer representations
# of the values.
try:
@@ -196,7 +208,9 @@ def order_by(self, attribute_name: str) -> "StreamQuery":
sorted(
has_attribute,
key=lambda s: int(
- "".join(filter(str.isdigit, getattr(s, attribute_name)))
+ "".join(
+ filter(str.isdigit, getattr(s, attribute_name))
+ )
), # type: ignore # noqa: E501
)
)
@@ -263,7 +277,9 @@ def get_lowest_resolution(self) -> Optional[Stream]:
"""
return (
- self.filter(progressive=True, subtype="mp4").order_by("resolution").first()
+ self.filter(progressive=True, subtype="mp4")
+ .order_by("resolution")
+ .first()
)
def get_highest_resolution(self) -> Optional[Stream]:
@@ -287,7 +303,11 @@ def get_audio_only(self, subtype: str = "mp4") -> Optional[Stream]:
The :class:`Stream ` matching the given itag or None if
not found.
"""
- return self.filter(only_audio=True, subtype=subtype).order_by("abr").last()
+ return (
+ self.filter(only_audio=True, subtype=subtype)
+ .order_by("abr")
+ .last()
+ )
def otf(self, is_otf: bool = False) -> "StreamQuery":
"""Filter stream by OTF, useful if some streams have 404 URLs
@@ -368,7 +388,9 @@ def __init__(self, captions: List[Caption]):
"""
self.lang_code_index = {c.code: c for c in captions}
- @deprecated("This object can be treated as a dictionary, i.e. captions['en']")
+ @deprecated(
+ "This object can be treated as a dictionary, i.e. captions['en']"
+ )
def get_by_language_code(
self, lang_code: str
) -> Optional[Caption]: # pragma: no cover
diff --git a/pytube/request.py b/pytube/request.py
index 436bfbfb5..ab0b03dcd 100644
--- a/pytube/request.py
+++ b/pytube/request.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
-
"""Implements a simple wrapper around urlopen."""
import logging
from functools import lru_cache
from http.client import HTTPResponse
-from typing import Iterable, Dict, Optional
+from typing import Dict
+from typing import Iterable
+from typing import Optional
from urllib.request import Request
from urllib.request import urlopen
@@ -12,7 +13,9 @@
def _execute_request(
- url: str, method: Optional[str] = None, headers: Optional[Dict[str, str]] = None
+ url: str,
+ method: Optional[str] = None,
+ headers: Optional[Dict[str, str]] = None,
) -> HTTPResponse:
base_headers = {"User-Agent": "Mozilla/5.0"}
if headers:
@@ -50,7 +53,9 @@ def stream(
while downloaded < file_size:
stop_pos = min(downloaded + range_size, file_size) - 1
range_header = f"bytes={downloaded}-{stop_pos}"
- response = _execute_request(url, method="GET", headers={"Range": range_header})
+ response = _execute_request(
+ url, method="GET", headers={"Range": range_header}
+ )
if file_size == range_size:
try:
content_range = response.info()["Content-Range"]
diff --git a/pytube/streams.py b/pytube/streams.py
index 27671fa10..f0c0bdadf 100644
--- a/pytube/streams.py
+++ b/pytube/streams.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-
"""
This module contains a container for stream manifest data.
@@ -8,16 +7,19 @@
has been renamed to accommodate DASH (which serves the audio and video
separately).
"""
-
-from datetime import datetime
import logging
import os
-from typing import Dict, Tuple, Optional, BinaryIO
+from datetime import datetime
+from typing import BinaryIO
+from typing import Dict
+from typing import Optional
+from typing import Tuple
from urllib.parse import parse_qs
from pytube import extract
from pytube import request
-from pytube.helpers import safe_filename, target_directory
+from pytube.helpers import safe_filename
+from pytube.helpers import target_directory
from pytube.itags import get_format_profile
from pytube.monostate import Monostate
@@ -27,7 +29,9 @@
class Stream:
"""Container for stream manifest data."""
- def __init__(self, stream: Dict, player_config_args: Dict, monostate: Monostate):
+ def __init__(
+ self, stream: Dict, player_config_args: Dict, monostate: Monostate
+ ):
"""Construct a :class:`Stream `.
:param dict stream:
@@ -44,7 +48,9 @@ def __init__(self, stream: Dict, player_config_args: Dict, monostate: Monostate)
self._monostate = monostate
self.url = stream["url"] # signed download url
- self.itag = int(stream["itag"]) # stream format id (youtube nomenclature)
+ self.itag = int(
+ stream["itag"]
+ ) # stream format id (youtube nomenclature)
# set type and codec info
@@ -68,8 +74,12 @@ def __init__(self, stream: Dict, player_config_args: Dict, monostate: Monostate)
itag_profile = get_format_profile(self.itag)
self.is_dash = itag_profile["is_dash"]
self.abr = itag_profile["abr"] # average bitrate (audio streams only)
- self.fps = itag_profile["fps"] # frames per second (video streams only)
- self.resolution = itag_profile["resolution"] # resolution (e.g.: "480p")
+ self.fps = itag_profile[
+ "fps"
+ ] # frames per second (video streams only)
+ self.resolution = itag_profile[
+ "resolution"
+ ] # resolution (e.g.: "480p")
self.is_3d = itag_profile["is_3d"]
self.is_hdr = itag_profile["is_hdr"]
self.is_live = itag_profile["is_live"]
@@ -167,7 +177,9 @@ def filesize_approx(self) -> int:
"""
if self._monostate.duration and self.bitrate:
bits_in_byte = 8
- return int((self._monostate.duration * self.bitrate) / bits_in_byte)
+ return int(
+ (self._monostate.duration * self.bitrate) / bits_in_byte
+ )
return self.filesize
@@ -220,7 +232,9 @@ def download(
"""
file_path = self.get_file_path(
- filename=filename, output_path=output_path, filename_prefix=filename_prefix
+ filename=filename,
+ output_path=output_path,
+ filename_prefix=filename_prefix,
)
if skip_existing and self.exists_at_path(file_path):
@@ -230,7 +244,9 @@ def download(
bytes_remaining = self.filesize
logger.debug(
- "downloading (%s total bytes) file to %s", self.filesize, file_path,
+ "downloading (%s total bytes) file to %s",
+ self.filesize,
+ file_path,
)
with open(file_path, "wb") as fh:
@@ -257,7 +273,10 @@ def get_file_path(
return os.path.join(target_directory(output_path), filename)
def exists_at_path(self, file_path: str) -> bool:
- return os.path.isfile(file_path) and os.path.getsize(file_path) == self.filesize
+ return (
+ os.path.isfile(file_path)
+ and os.path.getsize(file_path) == self.filesize
+ )
def stream_to_buffer(self, buffer: BinaryIO) -> None:
"""Write the media stream to buffer
@@ -276,7 +295,9 @@ def stream_to_buffer(self, buffer: BinaryIO) -> None:
self.on_progress(chunk, buffer, bytes_remaining)
self.on_complete(None)
- def on_progress(self, chunk: bytes, file_handler: BinaryIO, bytes_remaining: int):
+ def on_progress(
+ self, chunk: bytes, file_handler: BinaryIO, bytes_remaining: int
+ ):
"""On progress callback function.
This function writes the binary data to the file, then checks if an
diff --git a/setup.py b/setup.py
index aef97fe8f..ffba30fbb 100644
--- a/setup.py
+++ b/setup.py
@@ -3,6 +3,7 @@
"""This module contains setup instructions for pytube3."""
import codecs
import os
+
from setuptools import setup
here = os.path.abspath(os.path.dirname(__file__))
diff --git a/tests/conftest.py b/tests/conftest.py
index 86de07259..bb72f1b3b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
"""Reusable dependency injected testing components."""
-
import gzip
import json
import os
@@ -57,7 +56,9 @@ def playlist_html():
"""Youtube playlist HTML loaded on 2020-01-25 from
https://www.youtube.com/playlist?list=PLzMcBGfZo4-mP7qA9cagf68V06sko5otr"""
file_path = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "mocks", "playlist.html.gz"
+ os.path.dirname(os.path.realpath(__file__)),
+ "mocks",
+ "playlist.html.gz",
)
with gzip.open(file_path, "rb") as f:
return f.read().decode("utf-8")
@@ -68,7 +69,9 @@ def playlist_long_html():
"""Youtube playlist HTML loaded on 2020-01-25 from
https://www.youtube.com/playlist?list=PLzMcBGfZo4-mP7qA9cagf68V06sko5otr"""
file_path = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "mocks", "playlist_long.html.gz"
+ os.path.dirname(os.path.realpath(__file__)),
+ "mocks",
+ "playlist_long.html.gz",
)
with gzip.open(file_path, "rb") as f:
return f.read().decode("utf-8")
diff --git a/tests/contrib/test_playlist.py b/tests/contrib/test_playlist.py
index 820acb982..5f467ed7c 100644
--- a/tests/contrib/test_playlist.py
+++ b/tests/contrib/test_playlist.py
@@ -15,7 +15,10 @@ def test_title(request_get):
url = "https://www.fakeurl.com/playlist?list=PLS1QulWo1RIaJECMeUT4LFwJ-ghgoSH6n"
pl = Playlist(url)
pl_title = pl.title()
- assert pl_title == "(149) Python Tutorial for Beginners (For Absolute Beginners)"
+ assert (
+ pl_title
+ == "(149) Python Tutorial for Beginners (For Absolute Beginners)"
+ )
@mock.patch("pytube.contrib.playlist.request.get")
@@ -208,7 +211,9 @@ def test_trimmed_pagination(request_get, playlist_html, playlist_long_html):
@mock.patch("pytube.contrib.playlist.request.get")
-def test_trimmed_pagination_not_found(request_get, playlist_html, playlist_long_html):
+def test_trimmed_pagination_not_found(
+ request_get, playlist_html, playlist_long_html
+):
url = "https://www.fakeurl.com/playlist?list=whatever"
request_get.side_effect = [
playlist_long_html,
diff --git a/tests/generate_fixture.py b/tests/generate_fixture.py
index 844223acc..311c9f967 100755
--- a/tests/generate_fixture.py
+++ b/tests/generate_fixture.py
@@ -1,16 +1,15 @@
#!/usr/bin/env python3
-
# flake8: noqa: E402
-
-from os import path
-import sys
import json
+import sys
+from os import path
+
+from pytube import YouTube
currentdir = path.dirname(path.realpath(__file__))
parentdir = path.dirname(currentdir)
sys.path.append(parentdir)
-from pytube import YouTube
yt = YouTube(sys.argv[1], defer_prefetch_init=True)
yt.prefetch()
diff --git a/tests/test_captions.py b/tests/test_captions.py
index 3aff89125..72e51feb0 100644
--- a/tests/test_captions.py
+++ b/tests/test_captions.py
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
from unittest import mock
-from unittest.mock import patch, mock_open, MagicMock
+from unittest.mock import MagicMock
+from unittest.mock import mock_open
+from unittest.mock import patch
import pytest
-from pytube import Caption, CaptionQuery, captions
+from pytube import Caption
+from pytube import CaptionQuery
+from pytube import captions
def test_float_to_srt_time_format():
@@ -60,10 +64,17 @@ def test_download(srt):
with patch("builtins.open", open_mock):
srt.return_value = ""
caption = Caption(
- {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en"}
+ {
+ "url": "url1",
+ "name": {"simpleText": "name1"},
+ "languageCode": "en",
+ }
)
caption.download("title")
- assert open_mock.call_args_list[0][0][0].split("/")[-1] == "title (en).srt"
+ assert (
+ open_mock.call_args_list[0][0][0].split("/")[-1]
+ == "title (en).srt"
+ )
@mock.patch("pytube.captions.Caption.generate_srt_captions")
@@ -72,10 +83,17 @@ def test_download_with_prefix(srt):
with patch("builtins.open", open_mock):
srt.return_value = ""
caption = Caption(
- {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en"}
+ {
+ "url": "url1",
+ "name": {"simpleText": "name1"},
+ "languageCode": "en",
+ }
)
caption.download("title", filename_prefix="1 ")
- assert open_mock.call_args_list[0][0][0].split("/")[-1] == "1 title (en).srt"
+ assert (
+ open_mock.call_args_list[0][0][0].split("/")[-1]
+ == "1 title (en).srt"
+ )
@mock.patch("pytube.captions.Caption.generate_srt_captions")
@@ -85,7 +103,11 @@ def test_download_with_output_path(srt):
with patch("builtins.open", open_mock):
srt.return_value = ""
caption = Caption(
- {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en"}
+ {
+ "url": "url1",
+ "name": {"simpleText": "name1"},
+ "languageCode": "en",
+ }
)
file_path = caption.download("title", output_path="blah")
assert file_path == "/target/title (en).srt"
@@ -98,10 +120,17 @@ def test_download_xml_and_trim_extension(xml):
with patch("builtins.open", open_mock):
xml.return_value = ""
caption = Caption(
- {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en"}
+ {
+ "url": "url1",
+ "name": {"simpleText": "name1"},
+ "languageCode": "en",
+ }
)
caption.download("title.xml", srt=False)
- assert open_mock.call_args_list[0][0][0].split("/")[-1] == "title (en).xml"
+ assert (
+ open_mock.call_args_list[0][0][0].split("/")[-1]
+ == "title (en).xml"
+ )
def test_repr():
diff --git a/tests/test_cli.py b/tests/test_cli.py
index bdcf0758c..66f117ed3 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
import argparse
from unittest import mock
-from unittest.mock import MagicMock, patch
+from unittest.mock import MagicMock
+from unittest.mock import patch
import pytest
-from pytube import cli, StreamQuery, Caption, CaptionQuery
+from pytube import Caption
+from pytube import CaptionQuery
+from pytube import cli
+from pytube import StreamQuery
from pytube.exceptions import PytubeError
parse_args = cli._parse_args
@@ -179,7 +183,9 @@ def test_main_logging_setup(setup_logger):
@mock.patch("pytube.cli.YouTube", return_value=None)
def test_main_download_by_itag(youtube):
parser = argparse.ArgumentParser()
- args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "--itag=10"])
+ args = parse_args(
+ parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "--itag=10"]
+ )
cli._parse_args = MagicMock(return_value=args)
cli.download_by_itag = MagicMock()
cli.main()
@@ -191,7 +197,8 @@ def test_main_download_by_itag(youtube):
def test_main_build_playback_report(youtube):
parser = argparse.ArgumentParser()
args = parse_args(
- parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "--build-playback-report"]
+ parser,
+ ["http://youtube.com/watch?v=9bZkp7q19f0", "--build-playback-report"],
)
cli._parse_args = MagicMock(return_value=args)
cli.build_playback_report = MagicMock()
@@ -226,7 +233,9 @@ def test_main_download_caption(youtube):
@mock.patch("pytube.cli.download_by_resolution")
def test_download_by_resolution_flag(youtube, download_by_resolution):
parser = argparse.ArgumentParser()
- args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-r", "320p"])
+ args = parse_args(
+ parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-r", "320p"]
+ )
cli._parse_args = MagicMock(return_value=args)
cli.main()
youtube.assert_called()
@@ -284,7 +293,9 @@ def test_download_by_resolution(download, stream, stream_query, youtube):
stream_query.get_by_resolution.return_value = stream
youtube.streams = stream_query
# When
- cli.download_by_resolution(youtube=youtube, resolution="320p", target="test_target")
+ cli.download_by_resolution(
+ youtube=youtube, resolution="320p", target="test_target"
+ )
# Then
download.assert_called_with(stream, target="test_target")
@@ -319,12 +330,16 @@ def test_download_stream_file_exists(stream, capsys):
def test_perform_args_should_ffmpeg_process(ffmpeg_process, youtube):
# Given
parser = argparse.ArgumentParser()
- args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-f", "best"])
+ args = parse_args(
+ parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-f", "best"]
+ )
cli._parse_args = MagicMock(return_value=args)
# When
cli._perform_args_on_youtube(youtube, args)
# Then
- ffmpeg_process.assert_called_with(youtube=youtube, resolution="best", target=None)
+ ffmpeg_process.assert_called_with(
+ youtube=youtube, resolution="best", target=None
+ )
@mock.patch("pytube.cli.YouTube")
@@ -335,7 +350,9 @@ def test_ffmpeg_process_best_should_download(_ffmpeg_downloader, youtube):
streams = MagicMock()
youtube.streams = streams
video_stream = MagicMock()
- streams.filter.return_value.order_by.return_value.last.return_value = video_stream
+ streams.filter.return_value.order_by.return_value.last.return_value = (
+ video_stream
+ )
audio_stream = MagicMock()
streams.get_audio_only.return_value = audio_stream
# When
@@ -367,7 +384,9 @@ def test_ffmpeg_process_res_should_download(_ffmpeg_downloader, youtube):
@mock.patch("pytube.cli.YouTube")
@mock.patch("pytube.cli._ffmpeg_downloader")
-def test_ffmpeg_process_res_none_should_not_download(_ffmpeg_downloader, youtube):
+def test_ffmpeg_process_res_none_should_not_download(
+ _ffmpeg_downloader, youtube
+):
# Given
target = "/target"
streams = MagicMock()
@@ -392,7 +411,9 @@ def test_ffmpeg_process_audio_none_should_fallback_download(
streams = MagicMock()
youtube.streams = streams
stream = MagicMock()
- streams.filter.return_value.order_by.return_value.last.return_value = stream
+ streams.filter.return_value.order_by.return_value.last.return_value = (
+ stream
+ )
streams.get_audio_only.return_value = None
# When
cli.ffmpeg_process(youtube, "best", target)
@@ -404,7 +425,9 @@ def test_ffmpeg_process_audio_none_should_fallback_download(
@mock.patch("pytube.cli.YouTube")
@mock.patch("pytube.cli._ffmpeg_downloader")
-def test_ffmpeg_process_audio_fallback_none_should_exit(_ffmpeg_downloader, youtube):
+def test_ffmpeg_process_audio_fallback_none_should_exit(
+ _ffmpeg_downloader, youtube
+):
# Given
target = "/target"
streams = MagicMock()
@@ -463,7 +486,9 @@ def test_ffmpeg_downloader(unique_name, download, run, unlink):
def test_download_audio_args(youtube, download_audio):
# Given
parser = argparse.ArgumentParser()
- args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-a", "mp4"])
+ args = parse_args(
+ parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-a", "mp4"]
+ )
cli._parse_args = MagicMock(return_value=args)
# When
cli.main()
@@ -515,10 +540,16 @@ def test_perform_args_on_youtube(youtube):
@mock.patch("pytube.cli.os.path.exists", return_value=False)
def test_unique_name(path_exists):
- assert cli._unique_name("base", "subtype", "video", "target") == "base_video_0"
+ assert (
+ cli._unique_name("base", "subtype", "video", "target")
+ == "base_video_0"
+ )
@mock.patch("pytube.cli.os.path.exists")
def test_unique_name_counter(path_exists):
path_exists.side_effect = [True, False]
- assert cli._unique_name("base", "subtype", "video", "target") == "base_video_1"
+ assert (
+ cli._unique_name("base", "subtype", "video", "target")
+ == "base_video_1"
+ )
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index b56e2ff83..3568de53d 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
-from pytube.exceptions import VideoUnavailable, RegexMatchError, LiveStreamError
+from pytube.exceptions import LiveStreamError
+from pytube.exceptions import RegexMatchError
+from pytube.exceptions import VideoUnavailable
def test_video_unavailable():
diff --git a/tests/test_extract.py b/tests/test_extract.py
index 6f507097a..24ca4bb06 100644
--- a/tests/test_extract.py
+++ b/tests/test_extract.py
@@ -2,8 +2,8 @@
"""Unit tests for the :module:`extract ` module."""
import pytest
-from pytube.exceptions import RegexMatchError
from pytube import extract
+from pytube.exceptions import RegexMatchError
def test_extract_video_id():
@@ -25,7 +25,8 @@ def test_info_url(age_restricted):
def test_info_url_age_restricted(cipher_signature):
video_info_url = extract.video_info_url(
- video_id=cipher_signature.video_id, watch_url=cipher_signature.watch_url
+ video_id=cipher_signature.video_id,
+ watch_url=cipher_signature.watch_url,
)
expected = (
"https://youtube.com/get_video_info?video_id=9bZkp7q19f0&el=%24el"
@@ -36,7 +37,9 @@ def test_info_url_age_restricted(cipher_signature):
def test_js_url(cipher_signature):
- expected = "https://youtube.com/yts/jsbin/player_ias-vflWQEEag/en_US/base.js"
+ expected = (
+ "https://youtube.com/yts/jsbin/player_ias-vflWQEEag/en_US/base.js"
+ )
result = extract.js_url(cipher_signature.watch_html)
assert expected == result
@@ -69,7 +72,9 @@ def test_get_vid_desc(cipher_signature):
def test_mime_type_codec():
- mime_type, mime_subtype = extract.mime_type_codec('audio/webm; codecs="opus"')
+ mime_type, mime_subtype = extract.mime_type_codec(
+ 'audio/webm; codecs="opus"'
+ )
assert mime_type == "audio/webm"
assert mime_subtype == ["opus"]
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index a7e934df8..2bcbb4879 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -5,7 +5,10 @@
from pytube import helpers
from pytube.exceptions import RegexMatchError
-from pytube.helpers import deprecated, cache, target_directory, setup_logger
+from pytube.helpers import cache
+from pytube.helpers import deprecated
+from pytube.helpers import setup_logger
+from pytube.helpers import target_directory
def test_regex_search_no_match():
diff --git a/tests/test_query.py b/tests/test_query.py
index dc4cec836..eb30b1e49 100644
--- a/tests/test_query.py
+++ b/tests/test_query.py
@@ -59,7 +59,8 @@ def test_order_by(cipher_signature):
:class:`Stream ` instances in the expected order.
"""
itags = [
- s.itag for s in cipher_signature.streams.filter(type="audio").order_by("itag")
+ s.itag
+ for s in cipher_signature.streams.filter(type="audio").order_by("itag")
]
assert itags == [140, 249, 250, 251]
@@ -71,7 +72,9 @@ def test_order_by_descending(cipher_signature):
# numerical values
itags = [
s.itag
- for s in cipher_signature.streams.filter(type="audio").order_by("itag").desc()
+ for s in cipher_signature.streams.filter(type="audio")
+ .order_by("itag")
+ .desc()
]
assert itags == [251, 250, 249, 140]
@@ -93,7 +96,9 @@ def test_order_by_ascending(cipher_signature):
# numerical values
itags = [
s.itag
- for s in cipher_signature.streams.filter(type="audio").order_by("itag").asc()
+ for s in cipher_signature.streams.filter(type="audio")
+ .order_by("itag")
+ .asc()
]
assert itags == [140, 249, 250, 251]
@@ -101,7 +106,9 @@ def test_order_by_ascending(cipher_signature):
def test_order_by_non_numerical_ascending(cipher_signature):
mime_types = [
s.mime_type
- for s in cipher_signature.streams.filter(res="360p").order_by("mime_type").asc()
+ for s in cipher_signature.streams.filter(res="360p")
+ .order_by("mime_type")
+ .asc()
]
assert mime_types == ["video/mp4", "video/mp4", "video/webm"]
diff --git a/tests/test_streams.py b/tests/test_streams.py
index 989bc6ea7..92d79e756 100644
--- a/tests/test_streams.py
+++ b/tests/test_streams.py
@@ -6,7 +6,8 @@
from unittest.mock import MagicMock
from pytube import request
-from pytube import Stream, streams
+from pytube import Stream
+from pytube import streams
@mock.patch("pytube.streams.request")
@@ -61,7 +62,9 @@ def test_title(cipher_signature):
def test_expiration(cipher_signature):
- assert cipher_signature.streams[0].expiration == datetime(2020, 1, 16, 5, 12, 5)
+ assert cipher_signature.streams[0].expiration == datetime(
+ 2020, 1, 16, 5, 12, 5
+ )
def test_caption_tracks(presigned_video):