163 lines
4.9 KiB
Python
163 lines
4.9 KiB
Python
from typing import Any
|
|
from typing import Optional
|
|
from urllib.parse import parse_qs
|
|
from urllib.parse import urlparse
|
|
|
|
from ytdl_sub.utils.exceptions import ValidationException
|
|
from ytdl_sub.validators.validators import StringValidator
|
|
|
|
|
|
class YoutubeVideoUrlValidator(StringValidator):
|
|
|
|
_expected_value_type_name = "Youtube video url"
|
|
|
|
@classmethod
|
|
def _get_video_id(cls, url: str) -> Optional[str]:
|
|
"""
|
|
Examples:
|
|
- https://youtu.be/SA2iWivDJiE
|
|
- https://www.youtube.com/watch?v=_oPAwA_Udwc&feature=feedu
|
|
- https://www.youtube.com/embed/SA2iWivDJiE
|
|
- https://www.youtube.com/v/SA2iWivDJiE?version=3&hl=en_US
|
|
"""
|
|
# If the url doesn't contain youtube, assume it is the video id
|
|
if "youtube.com" not in url and "youtu.be" not in url:
|
|
return url
|
|
|
|
# If https:// is not present, urlparse will not work
|
|
if not url.startswith("https://"):
|
|
url = f"https://{url}"
|
|
|
|
query = urlparse(url)
|
|
if query.hostname in ("youtu.be", "www.youtu.be"):
|
|
return query.path[1:]
|
|
if query.hostname in ("youtube.com", "www.youtube.com"):
|
|
if query.path == "/watch":
|
|
parsed_q = parse_qs(query.query)
|
|
return parsed_q["v"][0]
|
|
if query.path[:7] == "/embed/":
|
|
return query.path.split("/")[2]
|
|
if query.path[:3] == "/v/":
|
|
return query.path.split("/")[2]
|
|
|
|
return None
|
|
|
|
def __init__(self, name: str, value: Any):
|
|
super().__init__(name, value)
|
|
|
|
self._video_id = self._get_video_id(value)
|
|
if not self._video_id:
|
|
raise self._validation_exception(f"'{value}' is not a valid Youtube video url or ID.")
|
|
|
|
@property
|
|
def video_id(self) -> str:
|
|
"""
|
|
Returns
|
|
-------
|
|
ID of the video
|
|
"""
|
|
return self._video_id
|
|
|
|
|
|
class YoutubePlaylistUrlValidator(StringValidator):
|
|
|
|
_expected_value_type_name = "Youtube playlist url"
|
|
|
|
@classmethod
|
|
def _get_playlist_id(cls, url: str) -> Optional[str]:
|
|
"""
|
|
Examples:
|
|
- https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc
|
|
"""
|
|
# If the url doesn't contain youtube, assume it is the ID
|
|
if "youtube.com" not in url:
|
|
return url
|
|
|
|
# If https:// is not present, urlparse will not work
|
|
if not url.startswith("https://"):
|
|
url = f"https://{url}"
|
|
|
|
query = urlparse(url)
|
|
if query.hostname in ("youtube.com", "www.youtube.com"):
|
|
if query.path == "/playlist":
|
|
parsed_q = parse_qs(query.query)
|
|
return parsed_q["list"][0]
|
|
|
|
return None
|
|
|
|
def __init__(self, name: str, value: Any):
|
|
super().__init__(name, value)
|
|
|
|
self._playlist_id = self._get_playlist_id(value)
|
|
if not self._playlist_id:
|
|
raise self._validation_exception(f"'{value}' is not a valid Youtube playlist url or ID.")
|
|
|
|
@property
|
|
def playlist_id(self) -> str:
|
|
"""
|
|
Returns
|
|
-------
|
|
ID of the channel
|
|
"""
|
|
return self._playlist_id
|
|
|
|
|
|
class YoutubeChannelUrlValidator(StringValidator):
|
|
|
|
_expected_value_type_name = "Youtube channel url"
|
|
|
|
@classmethod
|
|
def _get_channel_id(cls, url: str) -> Optional[str]:
|
|
"""
|
|
Examples:
|
|
- https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw
|
|
|
|
NOT:
|
|
- https://www.youtube.com/user/a_username_that_can_change
|
|
- https://www.youtube.com/c/a_name_that_can_change
|
|
|
|
Raises
|
|
------
|
|
ValidationException
|
|
If the url is for the user name
|
|
"""
|
|
# If the url doesn't contain youtube, assume it is the ID
|
|
if "youtube.com" not in url:
|
|
return url
|
|
|
|
# If https:// is not present, urlparse will not work
|
|
if not url.startswith("https://"):
|
|
url = f"https://{url}"
|
|
|
|
query = urlparse(url)
|
|
if query.hostname in ("youtube.com", "www.youtube.com"):
|
|
if query.path == "/channel":
|
|
parsed_q = parse_qs(query.query)
|
|
return parsed_q["v"][0]
|
|
if query.path in ("/user", "/c"):
|
|
raise ValidationException('user or c not allowed since it can change')
|
|
|
|
return None
|
|
|
|
def __init__(self, name: str, value: Any):
|
|
super().__init__(name, value)
|
|
|
|
try:
|
|
self._channel_id = self._get_channel_id(value)
|
|
except ValidationException:
|
|
raise self._validation_exception(
|
|
f"{value} uses a deprecated Youtube url that which can change. "
|
|
f"Use the /channel/ url instead."
|
|
)
|
|
|
|
if not self._channel_id:
|
|
raise self._validation_exception(f"'{value}' is not a valid Youtube channel url or ID.")
|
|
|
|
@property
|
|
def channel_id(self) -> str:
|
|
"""
|
|
Returns
|
|
-------
|
|
ID of the channel
|
|
"""
|
|
return self._channel_id
|