fixes length and thumbnail properties
Browse files- pytube/__main__.py +18 -6
- pytube/helpers.py +1 -1
- pytube/query.py +3 -3
- pytube/streams.py +9 -6
- tests/test_cli.py +1 -1
pytube/__main__.py
CHANGED
@@ -252,7 +252,17 @@ class YouTube(object):
|
|
252 |
:rtype: str
|
253 |
|
254 |
"""
|
255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
|
257 |
@property
|
258 |
def title(self) -> str:
|
@@ -273,10 +283,10 @@ class YouTube(object):
|
|
273 |
return self.vid_descr
|
274 |
|
275 |
@property
|
276 |
-
def rating(self):
|
277 |
"""Get the video average rating.
|
278 |
|
279 |
-
:rtype:
|
280 |
|
281 |
"""
|
282 |
return (
|
@@ -286,16 +296,18 @@ class YouTube(object):
|
|
286 |
)
|
287 |
|
288 |
@property
|
289 |
-
def length(self):
|
290 |
"""Get the video length in seconds.
|
291 |
|
292 |
:rtype: str
|
293 |
|
294 |
"""
|
295 |
-
return self.player_config_args["
|
|
|
|
|
296 |
|
297 |
@property
|
298 |
-
def views(self):
|
299 |
"""Get the number of the times the video has been viewed.
|
300 |
|
301 |
:rtype: str
|
|
|
252 |
:rtype: str
|
253 |
|
254 |
"""
|
255 |
+
player_response = self.player_config_args.get("player_response", {})
|
256 |
+
thumbnail_details = (
|
257 |
+
player_response.get("videoDetails", {})
|
258 |
+
.get("thumbnail", {})
|
259 |
+
.get("thumbnails")
|
260 |
+
)
|
261 |
+
if thumbnail_details:
|
262 |
+
thumbnail_details = thumbnail_details[-1] # last item has max size
|
263 |
+
return thumbnail_details["url"]
|
264 |
+
|
265 |
+
return "https://img.youtube.com/vi/" + self.video_id + "/maxresdefault.jpg"
|
266 |
|
267 |
@property
|
268 |
def title(self) -> str:
|
|
|
283 |
return self.vid_descr
|
284 |
|
285 |
@property
|
286 |
+
def rating(self) -> float:
|
287 |
"""Get the video average rating.
|
288 |
|
289 |
+
:rtype: float
|
290 |
|
291 |
"""
|
292 |
return (
|
|
|
296 |
)
|
297 |
|
298 |
@property
|
299 |
+
def length(self) -> str:
|
300 |
"""Get the video length in seconds.
|
301 |
|
302 |
:rtype: str
|
303 |
|
304 |
"""
|
305 |
+
return self.player_config_args["player_response"]["videoDetails"][
|
306 |
+
"lengthSeconds"
|
307 |
+
]
|
308 |
|
309 |
@property
|
310 |
+
def views(self) -> str:
|
311 |
"""Get the number of the times the video has been viewed.
|
312 |
|
313 |
:rtype: str
|
pytube/helpers.py
CHANGED
@@ -135,7 +135,7 @@ def safe_filename(s: str, max_length: int = 255) -> str:
|
|
135 |
return filename[:max_length].rsplit(" ", 0)[0]
|
136 |
|
137 |
|
138 |
-
def create_logger(level:int = logging.ERROR) -> logging.Logger:
|
139 |
"""Create a configured instance of logger.
|
140 |
|
141 |
:param int level:
|
|
|
135 |
return filename[:max_length].rsplit(" ", 0)[0]
|
136 |
|
137 |
|
138 |
+
def create_logger(level: int = logging.ERROR) -> logging.Logger:
|
139 |
"""Create a configured instance of logger.
|
140 |
|
141 |
:param int level:
|
pytube/query.py
CHANGED
@@ -167,7 +167,7 @@ class StreamQuery:
|
|
167 |
return StreamQuery(fmt_streams)
|
168 |
|
169 |
def order_by(self, attribute_name: str) -> "StreamQuery":
|
170 |
-
"""Apply a sort order to a resultset.
|
171 |
|
172 |
:param str attribute_name:
|
173 |
The name of the attribute to sort by.
|
@@ -195,10 +195,10 @@ class StreamQuery:
|
|
195 |
|
196 |
# lookup integer values if we have them
|
197 |
if integer_attr_repr is not None:
|
198 |
-
return StreamQuery(
|
199 |
sorted(
|
200 |
has_attribute,
|
201 |
-
key=lambda s: integer_attr_repr[getattr(s, attribute_name)], # type: ignore
|
202 |
)
|
203 |
)
|
204 |
else:
|
|
|
167 |
return StreamQuery(fmt_streams)
|
168 |
|
169 |
def order_by(self, attribute_name: str) -> "StreamQuery":
|
170 |
+
"""Apply a sort order to a resultset. Filters out stream the do not have the attribute.
|
171 |
|
172 |
:param str attribute_name:
|
173 |
The name of the attribute to sort by.
|
|
|
195 |
|
196 |
# lookup integer values if we have them
|
197 |
if integer_attr_repr is not None:
|
198 |
+
return StreamQuery(
|
199 |
sorted(
|
200 |
has_attribute,
|
201 |
+
key=lambda s: integer_attr_repr[getattr(s, attribute_name)], # type: ignore # noqa: E501
|
202 |
)
|
203 |
)
|
204 |
else:
|
pytube/streams.py
CHANGED
@@ -12,7 +12,7 @@ import io
|
|
12 |
import logging
|
13 |
import os
|
14 |
import pprint
|
15 |
-
from typing import Dict, Tuple, Optional
|
16 |
|
17 |
from pytube import extract
|
18 |
from pytube import request
|
@@ -42,18 +42,21 @@ class Stream(object):
|
|
42 |
# (Borg pattern).
|
43 |
self._monostate = monostate
|
44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
self.abr = None # average bitrate (audio streams only)
|
46 |
self.fps = None # frames per second (video streams only)
|
47 |
-
self.itag = None # stream format id (youtube nomenclature)
|
48 |
self.res = None # resolution (e.g.: 480p, 720p, 1080p)
|
49 |
-
self.url = None # signed download url
|
50 |
|
51 |
self._filesize: Optional[int] = None # filesize in bytes
|
52 |
self.mime_type = None # content identifier (e.g.: video/mp4)
|
53 |
-
self.type = None # the part of the mime before the slash
|
54 |
self.subtype = None # the part of the mime after the slash
|
55 |
|
56 |
-
self.codecs = [] # audio/video encoders (e.g.: vp8, mp4a)
|
57 |
self.audio_codec = None # audio codec of the stream (e.g.: vorbis)
|
58 |
self.video_codec = None # video codec of the stream (e.g.: vp8)
|
59 |
|
@@ -63,7 +66,7 @@ class Stream(object):
|
|
63 |
|
64 |
# Additional information about the stream format, such as resolution,
|
65 |
# frame rate, and whether the stream is live (HLS) or 3D.
|
66 |
-
self.fmt_profile = get_format_profile(self.itag)
|
67 |
|
68 |
# Same as above, except for the format profile attributes.
|
69 |
self.set_attributes_from_dict(self.fmt_profile)
|
|
|
12 |
import logging
|
13 |
import os
|
14 |
import pprint
|
15 |
+
from typing import Dict, Tuple, Optional, List
|
16 |
|
17 |
from pytube import extract
|
18 |
from pytube import request
|
|
|
42 |
# (Borg pattern).
|
43 |
self._monostate = monostate
|
44 |
|
45 |
+
self.url = stream["url"] # signed download url
|
46 |
+
self.itag = int(stream["itag"]) # stream format id (youtube nomenclature)
|
47 |
+
self.type = stream[
|
48 |
+
"type"
|
49 |
+
] # the part of the mime before the slash, overwritten later
|
50 |
+
|
51 |
self.abr = None # average bitrate (audio streams only)
|
52 |
self.fps = None # frames per second (video streams only)
|
|
|
53 |
self.res = None # resolution (e.g.: 480p, 720p, 1080p)
|
|
|
54 |
|
55 |
self._filesize: Optional[int] = None # filesize in bytes
|
56 |
self.mime_type = None # content identifier (e.g.: video/mp4)
|
|
|
57 |
self.subtype = None # the part of the mime after the slash
|
58 |
|
59 |
+
self.codecs: List[str] = [] # audio/video encoders (e.g.: vp8, mp4a)
|
60 |
self.audio_codec = None # audio codec of the stream (e.g.: vorbis)
|
61 |
self.video_codec = None # video codec of the stream (e.g.: vp8)
|
62 |
|
|
|
66 |
|
67 |
# Additional information about the stream format, such as resolution,
|
68 |
# frame rate, and whether the stream is live (HLS) or 3D.
|
69 |
+
self.fmt_profile: Dict = get_format_profile(self.itag)
|
70 |
|
71 |
# Same as above, except for the format profile attributes.
|
72 |
self.set_attributes_from_dict(self.fmt_profile)
|
tests/test_cli.py
CHANGED
@@ -10,4 +10,4 @@ def test_download(MockYouTube, mock_sys):
|
|
10 |
instance = MockYouTube.return_value
|
11 |
instance.prefetch_descramble.return_value = None
|
12 |
instance.streams = mock.Mock()
|
13 |
-
cli.download("asdf",
|
|
|
10 |
instance = MockYouTube.return_value
|
11 |
instance.prefetch_descramble.return_value = None
|
12 |
instance.streams = mock.Mock()
|
13 |
+
cli.download("asdf", 123)
|