is_dash property, more tests
Browse files- pytube/contrib/playlist.py +3 -3
- pytube/itags.py +3 -2
- pytube/query.py +7 -0
- pytube/streams.py +1 -0
- tests/contrib/test_playlist.py +12 -0
- tests/test_query.py +8 -0
pytube/contrib/playlist.py
CHANGED
|
@@ -6,6 +6,7 @@ import logging
|
|
| 6 |
import re
|
| 7 |
from collections import OrderedDict
|
| 8 |
from typing import List, Optional
|
|
|
|
| 9 |
|
| 10 |
from pytube import request
|
| 11 |
from pytube.__main__ import YouTube
|
|
@@ -25,9 +26,8 @@ class Playlist:
|
|
| 25 |
|
| 26 |
if "watch?v=" in url:
|
| 27 |
base_url = "https://www.youtube.com/playlist?list="
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
self.playlist_url = base_url + playlist_code
|
| 31 |
|
| 32 |
@staticmethod
|
| 33 |
def _find_load_more_url(req: str) -> Optional[str]:
|
|
|
|
| 6 |
import re
|
| 7 |
from collections import OrderedDict
|
| 8 |
from typing import List, Optional
|
| 9 |
+
from urllib.parse import parse_qs
|
| 10 |
|
| 11 |
from pytube import request
|
| 12 |
from pytube.__main__ import YouTube
|
|
|
|
| 26 |
|
| 27 |
if "watch?v=" in url:
|
| 28 |
base_url = "https://www.youtube.com/playlist?list="
|
| 29 |
+
query_parameters = parse_qs(url.split("?")[1])
|
| 30 |
+
self.playlist_url = base_url + query_parameters["list"][0]
|
|
|
|
| 31 |
|
| 32 |
@staticmethod
|
| 33 |
def _find_load_more_url(req: str) -> Optional[str]:
|
pytube/itags.py
CHANGED
|
@@ -98,7 +98,8 @@ _3D = [82, 83, 84, 85, 100, 101, 102]
|
|
| 98 |
LIVE = [91, 92, 93, 94, 95, 96, 132, 151]
|
| 99 |
DASH_MP4_VIDEO = [133, 134, 135, 136, 137, 138, 160, 212, 264, 266, 298, 299]
|
| 100 |
DASH_MP4_AUDIO = [139, 140, 141, 256, 258, 325, 328]
|
| 101 |
-
|
|
|
|
| 102 |
|
| 103 |
def get_format_profile(itag: int) -> Dict:
|
| 104 |
"""Get additional format information for a given itag.
|
|
@@ -118,5 +119,5 @@ def get_format_profile(itag: int) -> Dict:
|
|
| 118 |
"is_3d": itag in _3D,
|
| 119 |
"is_hdr": itag in HDR,
|
| 120 |
"fps": 60 if itag in _60FPS else 30,
|
| 121 |
-
"is_dash": itag in DASH_MP4_VIDEO or itag in DASH_MP4_AUDIO,
|
| 122 |
}
|
|
|
|
| 98 |
LIVE = [91, 92, 93, 94, 95, 96, 132, 151]
|
| 99 |
DASH_MP4_VIDEO = [133, 134, 135, 136, 137, 138, 160, 212, 264, 266, 298, 299]
|
| 100 |
DASH_MP4_AUDIO = [139, 140, 141, 256, 258, 325, 328]
|
| 101 |
+
DASH_WEBM_VIDEO = [167, 168, 169, 170, 218, 219, 278, 242, 243, 244, 245, 246, 247, 248, 271, 272, 302, 303, 308, 313, 315]
|
| 102 |
+
DASH_WEBM_AUDIO = [171, 172, 249, 250, 251]
|
| 103 |
|
| 104 |
def get_format_profile(itag: int) -> Dict:
|
| 105 |
"""Get additional format information for a given itag.
|
|
|
|
| 119 |
"is_3d": itag in _3D,
|
| 120 |
"is_hdr": itag in HDR,
|
| 121 |
"fps": 60 if itag in _60FPS else 30,
|
| 122 |
+
"is_dash": itag in DASH_MP4_VIDEO or itag in DASH_MP4_AUDIO or itag in DASH_WEBM_VIDEO or itag in DASH_WEBM_AUDIO,
|
| 123 |
}
|
pytube/query.py
CHANGED
|
@@ -34,6 +34,7 @@ class StreamQuery:
|
|
| 34 |
only_video=None,
|
| 35 |
progressive=None,
|
| 36 |
adaptive=None,
|
|
|
|
| 37 |
custom_filter_functions=None,
|
| 38 |
):
|
| 39 |
"""Apply the given filtering criterion.
|
|
@@ -103,6 +104,9 @@ class StreamQuery:
|
|
| 103 |
Excludes progressive streams (audio and video are on separate
|
| 104 |
tracks).
|
| 105 |
|
|
|
|
|
|
|
|
|
|
| 106 |
:param bool only_audio:
|
| 107 |
Excludes streams with video tracks.
|
| 108 |
|
|
@@ -161,6 +165,9 @@ class StreamQuery:
|
|
| 161 |
for fn in custom_filter_functions:
|
| 162 |
filters.append(fn)
|
| 163 |
|
|
|
|
|
|
|
|
|
|
| 164 |
fmt_streams = self.fmt_streams
|
| 165 |
for fn in filters:
|
| 166 |
fmt_streams = list(filter(fn, fmt_streams))
|
|
|
|
| 34 |
only_video=None,
|
| 35 |
progressive=None,
|
| 36 |
adaptive=None,
|
| 37 |
+
is_dash=None,
|
| 38 |
custom_filter_functions=None,
|
| 39 |
):
|
| 40 |
"""Apply the given filtering criterion.
|
|
|
|
| 104 |
Excludes progressive streams (audio and video are on separate
|
| 105 |
tracks).
|
| 106 |
|
| 107 |
+
:param bool is_dash:
|
| 108 |
+
Include/exclude dash streams.
|
| 109 |
+
|
| 110 |
:param bool only_audio:
|
| 111 |
Excludes streams with video tracks.
|
| 112 |
|
|
|
|
| 165 |
for fn in custom_filter_functions:
|
| 166 |
filters.append(fn)
|
| 167 |
|
| 168 |
+
if is_dash is not None:
|
| 169 |
+
filters.append(lambda s: s.is_dash == is_dash)
|
| 170 |
+
|
| 171 |
fmt_streams = self.fmt_streams
|
| 172 |
for fn in filters:
|
| 173 |
fmt_streams = list(filter(fn, fmt_streams))
|
pytube/streams.py
CHANGED
|
@@ -59,6 +59,7 @@ class Stream:
|
|
| 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 |
|
| 63 |
# Iterates over the key/values of stream and sets them as class
|
| 64 |
# attributes. This is an anti-pattern and should be removed.
|
|
|
|
| 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 |
+
self.is_dash: Optional[bool] = None
|
| 63 |
|
| 64 |
# Iterates over the key/values of stream and sets them as class
|
| 65 |
# attributes. This is an anti-pattern and should be removed.
|
tests/contrib/test_playlist.py
CHANGED
|
@@ -14,3 +14,15 @@ def test_title(request_get):
|
|
| 14 |
pl = Playlist(url)
|
| 15 |
pl_title = pl.title()
|
| 16 |
assert pl_title == "(149) Python Tutorial for Beginners (For Absolute Beginners)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
pl = Playlist(url)
|
| 15 |
pl_title = pl.title()
|
| 16 |
assert pl_title == "(149) Python Tutorial for Beginners (For Absolute Beginners)"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_init_with_playlist_url():
|
| 20 |
+
url = "https://www.youtube.com/playlist?list=PLynhp4cZEpTbRs_PYISQ8v_uwO0_mDg_X"
|
| 21 |
+
playlist = Playlist(url)
|
| 22 |
+
assert playlist.playlist_url == url
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_init_with_watch_url():
|
| 26 |
+
url = "https://www.youtube.com/watch?v=1KeYzjILqDo&list=PLynhp4cZEpTbRs_PYISQ8v_uwO0_mDg_X&index=2&t=661s"
|
| 27 |
+
playlist = Playlist(url)
|
| 28 |
+
assert playlist.playlist_url == "https://www.youtube.com/playlist?list=PLynhp4cZEpTbRs_PYISQ8v_uwO0_mDg_X"
|
tests/test_query.py
CHANGED
|
@@ -137,3 +137,11 @@ def test_get_by_itag(cipher_signature):
|
|
| 137 |
|
| 138 |
def test_get_by_non_existent_itag(cipher_signature):
|
| 139 |
assert not cipher_signature.streams.get_by_itag(22983)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
def test_get_by_non_existent_itag(cipher_signature):
|
| 139 |
assert not cipher_signature.streams.get_by_itag(22983)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def test_get_by_resolution(cipher_signature):
|
| 143 |
+
assert cipher_signature.streams.get_by_resolution("360p").itag == 18
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def test_get_lowest_resolution(cipher_signature):
|
| 147 |
+
assert cipher_signature.streams.get_lowest_resolution().itag == 18
|