replace .format calls with f-strings
Browse files- pytube/captions.py +3 -5
- pytube/cipher.py +1 -3
- pytube/cli.py +8 -20
- pytube/exceptions.py +2 -6
- pytube/extract.py +3 -3
- pytube/mixins.py +9 -9
- pytube/monostate.py +29 -31
- pytube/streams.py +4 -8
pytube/captions.py
CHANGED
@@ -49,7 +49,7 @@ class Caption:
|
|
49 |
"""
|
50 |
fraction, whole = math.modf(d)
|
51 |
time_fmt = time.strftime("%H:%M:%S,", time.gmtime(whole))
|
52 |
-
ms = "{:.3f}".
|
53 |
return time_fmt + ms
|
54 |
|
55 |
def xml_caption_to_srt(self, xml_captions: str) -> str:
|
@@ -113,13 +113,11 @@ class Caption:
|
|
113 |
filename = title
|
114 |
|
115 |
if filename_prefix:
|
116 |
-
filename = "{
|
117 |
-
prefix=safe_filename(filename_prefix), filename=filename,
|
118 |
-
)
|
119 |
|
120 |
filename = safe_filename(filename)
|
121 |
|
122 |
-
filename += " ({
|
123 |
|
124 |
if srt:
|
125 |
filename += ".srt"
|
|
|
49 |
"""
|
50 |
fraction, whole = math.modf(d)
|
51 |
time_fmt = time.strftime("%H:%M:%S,", time.gmtime(whole))
|
52 |
+
ms = f"{fraction:.3f}".replace("0.", "")
|
53 |
return time_fmt + ms
|
54 |
|
55 |
def xml_caption_to_srt(self, xml_captions: str) -> str:
|
|
|
113 |
filename = title
|
114 |
|
115 |
if filename_prefix:
|
116 |
+
filename = f"{safe_filename(filename_prefix)}{filename}"
|
|
|
|
|
117 |
|
118 |
filename = safe_filename(filename)
|
119 |
|
120 |
+
filename += f" ({self.code})"
|
121 |
|
122 |
if srt:
|
123 |
filename += ".srt"
|
pytube/cipher.py
CHANGED
@@ -54,9 +54,7 @@ def get_initial_function_name(js: str) -> str:
|
|
54 |
regex = re.compile(pattern)
|
55 |
results = regex.search(js)
|
56 |
if results:
|
57 |
-
logger.debug(
|
58 |
-
"finished regex search, matched: {pattern}".format(pattern=pattern)
|
59 |
-
)
|
60 |
return results.group(1)
|
61 |
|
62 |
raise RegexMatchError(caller="get_initial_function_name", pattern="multiple")
|
|
|
54 |
regex = re.compile(pattern)
|
55 |
results = regex.search(js)
|
56 |
if results:
|
57 |
+
logger.debug(f"finished regex search, matched: {pattern}")
|
|
|
|
|
58 |
return results.group(1)
|
59 |
|
60 |
raise RegexMatchError(caller="get_initial_function_name", pattern="multiple")
|
pytube/cli.py
CHANGED
@@ -130,9 +130,7 @@ def build_playback_report(youtube: YouTube) -> None:
|
|
130 |
A YouTube object.
|
131 |
"""
|
132 |
ts = int(dt.datetime.utcnow().timestamp())
|
133 |
-
fp = os.path.join(
|
134 |
-
os.getcwd(), "yt-video-{yt.video_id}-{ts}.json.gz".format(yt=youtube, ts=ts),
|
135 |
-
)
|
136 |
|
137 |
js = youtube.js
|
138 |
watch_html = youtube.watch_html
|
@@ -179,9 +177,7 @@ def display_progress_bar(
|
|
179 |
remaining = max_width - filled
|
180 |
progress_bar = ch * filled + " " * remaining
|
181 |
percent = round(100.0 * bytes_received / float(filesize), 1)
|
182 |
-
text = " ↳ |{progress_bar}| {percent}%\r"
|
183 |
-
progress_bar=progress_bar, percent=percent
|
184 |
-
)
|
185 |
sys.stdout.write(text)
|
186 |
sys.stdout.flush()
|
187 |
|
@@ -197,7 +193,7 @@ def on_progress(
|
|
197 |
|
198 |
def _download(stream: Stream, target: Optional[str] = None) -> None:
|
199 |
filesize_megabytes = stream.filesize // 1048576
|
200 |
-
print("{
|
201 |
stream.download(output_path=target)
|
202 |
sys.stdout.write("\n")
|
203 |
|
@@ -214,7 +210,7 @@ def download_by_itag(youtube: YouTube, itag: int, target: Optional[str] = None)
|
|
214 |
"""
|
215 |
stream = youtube.streams.get_by_itag(itag)
|
216 |
if stream is None:
|
217 |
-
print("Could not find a stream with itag: {itag}"
|
218 |
print("Try one of these:")
|
219 |
display_streams(youtube)
|
220 |
sys.exit()
|
@@ -242,11 +238,7 @@ def download_by_resolution(
|
|
242 |
# TODO(nficano): allow dash itags to be selected
|
243 |
stream = youtube.streams.get_by_resolution(resolution)
|
244 |
if stream is None:
|
245 |
-
print(
|
246 |
-
"Could not find a stream with resolution: {resolution}".format(
|
247 |
-
resolution=resolution
|
248 |
-
)
|
249 |
-
)
|
250 |
print("Try one of these:")
|
251 |
display_streams(youtube)
|
252 |
sys.exit()
|
@@ -271,11 +263,7 @@ def display_streams(youtube: YouTube) -> None:
|
|
271 |
|
272 |
|
273 |
def _print_available_captions(captions: CaptionQuery) -> None:
|
274 |
-
print(
|
275 |
-
"Available caption codes are: {}".format(
|
276 |
-
", ".join(c.code for c in captions.all())
|
277 |
-
)
|
278 |
-
)
|
279 |
|
280 |
|
281 |
def download_caption(
|
@@ -299,9 +287,9 @@ def download_caption(
|
|
299 |
caption = youtube.captions.get_by_language_code(lang_code=lang_code)
|
300 |
if caption:
|
301 |
downloaded_path = caption.download(title=youtube.title, output_path=target)
|
302 |
-
print("Saved caption file to: {}"
|
303 |
else:
|
304 |
-
print("Unable to find caption with code: {}"
|
305 |
_print_available_captions(youtube.captions)
|
306 |
|
307 |
|
|
|
130 |
A YouTube object.
|
131 |
"""
|
132 |
ts = int(dt.datetime.utcnow().timestamp())
|
133 |
+
fp = os.path.join(os.getcwd(), f"yt-video-{youtube.video_id}-{ts}.json.gz")
|
|
|
|
|
134 |
|
135 |
js = youtube.js
|
136 |
watch_html = youtube.watch_html
|
|
|
177 |
remaining = max_width - filled
|
178 |
progress_bar = ch * filled + " " * remaining
|
179 |
percent = round(100.0 * bytes_received / float(filesize), 1)
|
180 |
+
text = f" ↳ |{progress_bar}| {percent}%\r"
|
|
|
|
|
181 |
sys.stdout.write(text)
|
182 |
sys.stdout.flush()
|
183 |
|
|
|
193 |
|
194 |
def _download(stream: Stream, target: Optional[str] = None) -> None:
|
195 |
filesize_megabytes = stream.filesize // 1048576
|
196 |
+
print(f"{stream.default_filename} | {filesize_megabytes} MB")
|
197 |
stream.download(output_path=target)
|
198 |
sys.stdout.write("\n")
|
199 |
|
|
|
210 |
"""
|
211 |
stream = youtube.streams.get_by_itag(itag)
|
212 |
if stream is None:
|
213 |
+
print(f"Could not find a stream with itag: {itag}")
|
214 |
print("Try one of these:")
|
215 |
display_streams(youtube)
|
216 |
sys.exit()
|
|
|
238 |
# TODO(nficano): allow dash itags to be selected
|
239 |
stream = youtube.streams.get_by_resolution(resolution)
|
240 |
if stream is None:
|
241 |
+
print(f"Could not find a stream with resolution: {resolution}")
|
|
|
|
|
|
|
|
|
242 |
print("Try one of these:")
|
243 |
display_streams(youtube)
|
244 |
sys.exit()
|
|
|
263 |
|
264 |
|
265 |
def _print_available_captions(captions: CaptionQuery) -> None:
|
266 |
+
print(f"Available caption codes are: {', '.join(c.code for c in captions.all())}")
|
|
|
|
|
|
|
|
|
267 |
|
268 |
|
269 |
def download_caption(
|
|
|
287 |
caption = youtube.captions.get_by_language_code(lang_code=lang_code)
|
288 |
if caption:
|
289 |
downloaded_path = caption.download(title=youtube.title, output_path=target)
|
290 |
+
print(f"Saved caption file to: {downloaded_path}")
|
291 |
else:
|
292 |
+
print(f"Unable to find caption with code: {lang_code}")
|
293 |
_print_available_captions(youtube.captions)
|
294 |
|
295 |
|
pytube/exceptions.py
CHANGED
@@ -26,11 +26,7 @@ class RegexMatchError(ExtractError):
|
|
26 |
:param str pattern:
|
27 |
Pattern that failed to match
|
28 |
"""
|
29 |
-
super().__init__(
|
30 |
-
"{caller}: could not find match for {pattern}".format(
|
31 |
-
caller=caller, pattern=pattern
|
32 |
-
)
|
33 |
-
)
|
34 |
self.caller = caller
|
35 |
self.pattern = pattern
|
36 |
|
@@ -47,7 +43,7 @@ class VideoUnavailable(PytubeError):
|
|
47 |
:param str video_id:
|
48 |
A YouTube video identifier.
|
49 |
"""
|
50 |
-
super().__init__("{video_id} is unavailable"
|
51 |
|
52 |
self.video_id = video_id
|
53 |
|
|
|
26 |
:param str pattern:
|
27 |
Pattern that failed to match
|
28 |
"""
|
29 |
+
super().__init__(f"{caller}: could not find match for {pattern}")
|
|
|
|
|
|
|
|
|
30 |
self.caller = caller
|
31 |
self.pattern = pattern
|
32 |
|
|
|
43 |
:param str video_id:
|
44 |
A YouTube video identifier.
|
45 |
"""
|
46 |
+
super().__init__(f"{video_id} is unavailable")
|
47 |
|
48 |
self.video_id = video_id
|
49 |
|
pytube/extract.py
CHANGED
@@ -33,7 +33,7 @@ class PytubeHTMLParser(HTMLParser):
|
|
33 |
|
34 |
def handle_data(self, data):
|
35 |
if self.in_vid_descr_br:
|
36 |
-
self.vid_descr += "\n{}"
|
37 |
self.in_vid_descr_br = False
|
38 |
elif self.in_vid_descr:
|
39 |
self.vid_descr += data
|
@@ -89,11 +89,11 @@ def watch_url(video_id: str) -> str:
|
|
89 |
|
90 |
|
91 |
def embed_url(video_id: str) -> str:
|
92 |
-
return "https://www.youtube.com/embed/{}"
|
93 |
|
94 |
|
95 |
def eurl(video_id: str) -> str:
|
96 |
-
return "https://youtube.googleapis.com/v/{}"
|
97 |
|
98 |
|
99 |
def video_info_url(
|
|
|
33 |
|
34 |
def handle_data(self, data):
|
35 |
if self.in_vid_descr_br:
|
36 |
+
self.vid_descr += f"\n{data}"
|
37 |
self.in_vid_descr_br = False
|
38 |
elif self.in_vid_descr:
|
39 |
self.vid_descr += data
|
|
|
89 |
|
90 |
|
91 |
def embed_url(video_id: str) -> str:
|
92 |
+
return f"https://www.youtube.com/embed/{video_id}"
|
93 |
|
94 |
|
95 |
def eurl(video_id: str) -> str:
|
96 |
+
return f"https://youtube.googleapis.com/v/{video_id}"
|
97 |
|
98 |
|
99 |
def video_info_url(
|
pytube/mixins.py
CHANGED
@@ -100,10 +100,10 @@ def apply_descrambler(stream_data: Dict, key: str) -> None:
|
|
100 |
try:
|
101 |
stream_data[key] = [
|
102 |
{
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
}
|
108 |
for format_item in formats
|
109 |
]
|
@@ -113,11 +113,11 @@ def apply_descrambler(stream_data: Dict, key: str) -> None:
|
|
113 |
]
|
114 |
stream_data[key] = [
|
115 |
{
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
}
|
122 |
for i, format_item in enumerate(formats)
|
123 |
]
|
|
|
100 |
try:
|
101 |
stream_data[key] = [
|
102 |
{
|
103 |
+
"url": format_item["url"],
|
104 |
+
"type": format_item["mimeType"],
|
105 |
+
"quality": format_item["quality"],
|
106 |
+
"itag": format_item["itag"],
|
107 |
}
|
108 |
for format_item in formats
|
109 |
]
|
|
|
113 |
]
|
114 |
stream_data[key] = [
|
115 |
{
|
116 |
+
"url": cipher_url[i]["url"][0],
|
117 |
+
"s": cipher_url[i]["s"][0],
|
118 |
+
"type": format_item["mimeType"],
|
119 |
+
"quality": format_item["quality"],
|
120 |
+
"itag": format_item["itag"],
|
121 |
}
|
122 |
for i, format_item in enumerate(formats)
|
123 |
]
|
pytube/monostate.py
CHANGED
@@ -13,44 +13,42 @@ class OnProgress(Protocol):
|
|
13 |
file_handler: io.BufferedWriter,
|
14 |
bytes_remaining: int,
|
15 |
) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
...
|
17 |
|
18 |
-
"""On download progress callback function.
|
19 |
-
|
20 |
-
:param stream:
|
21 |
-
An instance of :class:`Stream <Stream>` being downloaded.
|
22 |
-
:type stream:
|
23 |
-
:py:class:`pytube.Stream`
|
24 |
-
:param str chunk:
|
25 |
-
Segment of media file binary data, not yet written to disk.
|
26 |
-
:param file_handler:
|
27 |
-
The file handle where the media is being written to.
|
28 |
-
:type file_handler:
|
29 |
-
:py:class:`io.BufferedWriter`
|
30 |
-
:param int bytes_remaining:
|
31 |
-
How many bytes have been downloaded.
|
32 |
-
|
33 |
-
"""
|
34 |
-
|
35 |
|
36 |
class OnComplete(Protocol):
|
37 |
def __call__(self, stream: Any, file_handler: io.BufferedWriter) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
...
|
39 |
|
40 |
-
"""On download complete handler function.
|
41 |
-
|
42 |
-
:param stream:
|
43 |
-
An instance of :class:`Stream <Stream>` being downloaded.
|
44 |
-
:type stream:
|
45 |
-
:py:class:`pytube.Stream`
|
46 |
-
:param file_handler:
|
47 |
-
The file handle where the media is being written to.
|
48 |
-
:type file_handler:
|
49 |
-
:py:class:`io.BufferedWriter`
|
50 |
-
|
51 |
-
:rtype: None
|
52 |
-
"""
|
53 |
-
|
54 |
|
55 |
class Monostate:
|
56 |
def __init__(
|
|
|
13 |
file_handler: io.BufferedWriter,
|
14 |
bytes_remaining: int,
|
15 |
) -> None:
|
16 |
+
"""On download progress callback function.
|
17 |
+
|
18 |
+
:param stream:
|
19 |
+
An instance of :class:`Stream <Stream>` being downloaded.
|
20 |
+
:type stream:
|
21 |
+
:py:class:`pytube.Stream`
|
22 |
+
:param str chunk:
|
23 |
+
Segment of media file binary data, not yet written to disk.
|
24 |
+
:param file_handler:
|
25 |
+
The file handle where the media is being written to.
|
26 |
+
:type file_handler:
|
27 |
+
:py:class:`io.BufferedWriter`
|
28 |
+
:param int bytes_remaining:
|
29 |
+
How many bytes have been downloaded.
|
30 |
+
|
31 |
+
"""
|
32 |
...
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
class OnComplete(Protocol):
|
36 |
def __call__(self, stream: Any, file_handler: io.BufferedWriter) -> None:
|
37 |
+
"""On download complete handler function.
|
38 |
+
|
39 |
+
:param stream:
|
40 |
+
An instance of :class:`Stream <Stream>` being downloaded.
|
41 |
+
:type stream:
|
42 |
+
:py:class:`pytube.Stream`
|
43 |
+
:param file_handler:
|
44 |
+
The file handle where the media is being written to.
|
45 |
+
:type file_handler:
|
46 |
+
:py:class:`io.BufferedWriter`
|
47 |
+
|
48 |
+
:rtype: None
|
49 |
+
"""
|
50 |
...
|
51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
class Monostate:
|
54 |
def __init__(
|
pytube/streams.py
CHANGED
@@ -191,7 +191,7 @@ class Stream:
|
|
191 |
"""
|
192 |
|
193 |
filename = safe_filename(self.title)
|
194 |
-
return "{filename}.{
|
195 |
|
196 |
def download(
|
197 |
self,
|
@@ -226,16 +226,12 @@ class Stream:
|
|
226 |
|
227 |
"""
|
228 |
if filename:
|
229 |
-
filename = "{filename}.{
|
230 |
-
filename=safe_filename(filename), s=self
|
231 |
-
)
|
232 |
else:
|
233 |
filename = self.default_filename
|
234 |
|
235 |
if filename_prefix:
|
236 |
-
filename = "{
|
237 |
-
prefix=safe_filename(filename_prefix), filename=filename,
|
238 |
-
)
|
239 |
|
240 |
file_path = os.path.join(target_directory(output_path), filename)
|
241 |
|
@@ -351,4 +347,4 @@ class Stream:
|
|
351 |
else:
|
352 |
parts.extend(['abr="{s.abr}"', 'acodec="{s.audio_codec}"'])
|
353 |
parts.extend(['progressive="{s.is_progressive}"', 'type="{s.type}"'])
|
354 |
-
return "<Stream: {
|
|
|
191 |
"""
|
192 |
|
193 |
filename = safe_filename(self.title)
|
194 |
+
return f"{filename}.{self.subtype}"
|
195 |
|
196 |
def download(
|
197 |
self,
|
|
|
226 |
|
227 |
"""
|
228 |
if filename:
|
229 |
+
filename = f"{safe_filename(filename)}.{self.subtype}"
|
|
|
|
|
230 |
else:
|
231 |
filename = self.default_filename
|
232 |
|
233 |
if filename_prefix:
|
234 |
+
filename = f"{safe_filename(filename_prefix)}{filename}"
|
|
|
|
|
235 |
|
236 |
file_path = os.path.join(target_directory(output_path), filename)
|
237 |
|
|
|
347 |
else:
|
348 |
parts.extend(['abr="{s.abr}"', 'acodec="{s.audio_codec}"'])
|
349 |
parts.extend(['progressive="{s.is_progressive}"', 'type="{s.type}"'])
|
350 |
+
return f"<Stream: {' '.join(parts).format(s=self)}>"
|