Initial Commit

This commit is contained in:
cere 2024-01-16 05:08:13 -05:00
commit 0543b89d8f
12 changed files with 1256 additions and 0 deletions

View file

@ -0,0 +1,15 @@
import typing as t
class BasePipedModel:
"""
Base class for all Piped models.
"""
def __init__(self, data: t.Dict[str, t.Any]) -> None:
"""
### Parameters:
- `data` - The JSON (`dict`) data to initialize the model with.
"""
self.data = data

View file

@ -0,0 +1,104 @@
import typing as t
from . import BasePipedModel
from .videos import Video
class NextPageChannel(BasePipedModel):
"""
Represents a channel obtained via the `nextpage` endpoint.
This model contains only `nextpage` and `relatedStreams`. It's a parent for `Channel`.
"""
@property
def nextpage(self) -> str:
"""
A JSON encoded string to be passed to the `'nextpage'` endpoint(s) when
obtaining paginated data.
"""
return self.data['nextpage']
@property
def uploaded_videos(self) -> t.List[Video.RelatedStream]:
"""
List of uploaded videos from the current fetched data
There are max. 30 videos per page
"""
return [Video.RelatedStream(video_data) for video_data in self.data['relatedStreams']]
class Channel(NextPageChannel):
"""
Represents a YouTube channel.
Contains properties of `NextPageChannel`.
"""
@property
def id(self) -> str:
"""
The channel's ID
"""
return self.data['id']
@property
def name(self) -> str:
"""
The channel's name
"""
return self.data['name']
@property
def avatar_url(self) -> str:
"""
The channel's avatar URL
"""
return self.data['avatarUrl']
@property
def banner_url(self) -> str:
"""
The channel's banner URL
"""
return self.data['bannerUrl']
@property
def description(self) -> str:
"""
The channel's description
"""
return self.data['description']
@property
def subscriber_count(self) -> int:
"""
The number of subscribers the channel has
"""
return self.data['subscriberCount']
@property
def verified(self) -> bool:
"""
Whether or not the channel is verified by YouTube (has a badge)
"""
return self.data['verified']

View file

@ -0,0 +1,163 @@
import typing as t
from . import BasePipedModel
class Comments(BasePipedModel):
class Comment(BasePipedModel):
@property
def author(self) -> str:
"""
The name of the author of the comment
"""
return self.data['author']
@property
def comment_id(self) -> str:
"""
The comment ID
"""
return self.data['commentId']
@property
def comment_text(self) -> str:
"""
The text of the comment
"""
return self.data['commentText']
@property
def commented_time(self) -> str:
"""
The time the comment was made (format: `'x y ago'`).
### Note:
The raw time from API also includes the `'(edited)'` suffix to mark comment as edited (if it was).
By accessing this property, the suffix is automatically removed.
If you for whatever reason want to keep the suffix, access this property directly via `Comment.data['commentedTime']`
"""
time: str = self.data['commentedTime']
return time.removesuffix(' (edited)')
@property
def is_edited(self) -> bool:
"""
Whether or not the comment is edited.
### Note:
This property checks whether there is `'(edited)'` in the `commentedTime` property, because that's where you get that from.
See `Comments.Comment.commented_time`
"""
time: str = self.data['commentedTime']
return time.endswith('(edited)')
@property
def commentor_url(self) -> str:
"""
The URL of the channel that made the comment
"""
return self.data['commentorUrl']
@property
def replies_page(self) -> t.Optional[str]:
"""
Same as `Comments.nextpage`, but to load replies.
`None` means that there are no replies.
"""
return self.data['repliesPage']
@property
def hearted(self) -> bool:
"""
Whether or not the comment has been hearted
"""
return self.data['hearted']
@property
def like_count(self) -> int:
"""
The number of likes the comment has
"""
return self.data['likeCount']
@property
def pinned(self) -> bool:
"""
Whether or not the comment is pinned
"""
return self.data['pinned']
@property
def thumbnail(self) -> str:
"""
The thumbnail of the commentor's channel
"""
return self.data['thumbnail']
@property
def verified(self) -> bool:
"""
Whether or not the author of the comment is verified
"""
return self.data['verified']
def get_comments(self) -> t.List[Comment]:
"""
Obtain a list of comments
"""
return [self.Comment(comment_json) for comment_json in self.data['comments']]
def __iter__(self) -> t.Iterator[Comment]:
iter(self.get_comments())
@property
def disabled(self) -> bool:
"""
Whether or not the comments are disabled
"""
return self.data['disabled']
@property
def nextpage(self) -> t.Optional[str]:
"""
A JSON encoded page, which is used for the nextpage endpoint.
If there is no nextpage data, this returns `None`.
"""
return self.data['nextpage']

View file

@ -0,0 +1,559 @@
import typing as t
from datetime import datetime, date, timedelta
from . import BasePipedModel
class Video(BasePipedModel):
@property
def title(self) -> str:
"""
The title/name of the video
"""
return self.data['title']
@property
def description(self) -> str:
"""
The description of the video
"""
return self.data['description']
@property
def upload_date(self) -> date:
"""
The date the video was uploaded at.
### Note:
Use `Video.data['uploadDate']` to get the raw string that was obtained from the API - this package
automatically converts the raw string to a `datetime.date` object.
"""
raw = self.data['uploadDate']
return datetime.strptime(raw, r"%Y-%m-%d").date()
@property
def uploader(self) -> str:
"""
The channel name of the author of the video
"""
return self.data['uploader']
@property
def uploader_url(self) -> str:
"""
The URI to the author's channel
"""
return self.data['uploaderUrl']
@property
def uploader_avatar(self) -> str:
"""
The URL to the video author's avatar image
"""
return self.data['uploaderAvatar']
@property
def thumbnail_url(self) -> str:
"""
The URL to the video's thumbnail image
"""
return self.data['thumbnail']
@property
def hls(self) -> t.Optional[str]:
"""
The hls manifest URL, to be used for Livestreams
"""
return self.data['hls']
@property
def dash(self) -> t.Optional[str]:
"""
The dash manifest URL for OTF streams
"""
return self.data['dash']
@property
def lbry_id(self) -> str:
"""
The lbry id of the video, if available. I assume this has something to do with https://lbry.com/
"""
return self.data['lbryId']
@property
def uploader_verified(self) -> str:
"""
Whether or not the channel that uploaded the video is verified by YouTube (badge)
"""
return self.data['uploaderVerified']
@property
def duration(self) -> timedelta:
"""
The duration of the video.
### Note:
The original value from the API was in seconds (`Video.data['duration']`), but this package
converts it to a `datetime.timedelta` object.
"""
return timedelta(seconds=self.data['duration'])
@property
def views(self) -> int:
"""
The number of views the video has received
"""
return self.data['views']
@property
def likes(self) -> int:
"""
The amount of likes the video has received. `-1` if hidden
"""
return self.data['likes']
@property
def dislikes(self) -> int:
"""
The amount of dislikes the video has received. `-1` if hidden
### Note:
This is obsolete since YouTube did a tiny gigantical little big whoopsie with their like system and screwed it all up
You can use awesome user-made projects such as https://returnyoutubedislike.com to obtain the dislike count
"""
return self.data['dislikes']
class Stream(BasePipedModel):
"""
Either an audio or video stream of a video
"""
@property
def url(self) -> str:
"""
The URL of the stream
"""
return self.data['url']
@property
def format(self) -> str:
"""
The format of the stream (`'M4A' or 'WEBMA_OPUS' or 'MPEG_4' or 'WEBM' or 'v3GPP'`
No, I don't know how many are there or what does each mean
"""
return self.data['format']
@property
def quality(self) -> str:
"""
The standard quality we all know and love (e. g.: `'240p'` for video or `'128k'` for audio)
"""
return self.data['quality']
@property
def mime_type(self) -> str:
"""
If you come from web development (or other invidious area that works with these French mimes),
then you already know what this is. If not, consider [checking the Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
"""
return self.data['mimeType']
@property
def codec(self) -> str:
"""
What is this? I don't know. A codec?
"""
return self.data['codec']
@property
def video_only(self) -> bool:
"""
Whether or not the stream is video only (AKA. muted video)
"""
return self.data['videoOnly']
@property
def bitrate(self) -> int:
"""
The bitrate of the stream
"""
return self.data['bitrate']
@property
def init_start(self) -> int:
"""
Not sure what this does, but it seems to be useful for creating dash streams
"""
return self.data['initStart']
@property
def init_end(self) -> int:
"""
Not sure what this does, but it seems to be useful for creating dash streams
"""
return self.data['initEnd']
@property
def index_start(self) -> int:
"""
Not sure what this does, but it seems to be useful for creating dash streams
"""
return self.data['indexStart']
@property
def index_end(self) -> int:
"""
Not sure what this does, but it seems to be useful for creating dash streams
"""
return self.data['indexEnd']
@property
def width(self) -> int:
"""
The width of the stream. `'0'` for audio streams (makes sense)
"""
return self.data['width']
@property
def height(self) -> int:
"""
The height of the stream. `'0'` for audio streams (makes sense)
"""
return self.data['width']
@property
def fps(self) -> int:
"""
Frames Per Second. This is usually `'0'` for audio and `'30'` or `'60'` for video
"""
return self.data['fps']
def get_streams(self, type: t.Literal['video', 'audio']='video') -> t.List[Stream]:
"""
Get the streams of a video.
### Parameters:
- `type` - The type of stream to get. Either `'video'` or `'audio'`
"""
if type == 'video' or type == 'audio':
return [self.Stream(stream_data) for stream_data in self.data[f"{type}Streams"]]
raise ValueError('Invalid stream type. Must be either `video` or `audio`')
class RelatedStream(BasePipedModel):
"""
A related stream (e. g.: related video to the current one from the right sidebar, video related to/uploaded by a channel and trending video).
"""
@property
def url(self) -> str:
"""
The URL to the related video
"""
return self.data['url']
@property
def title(self) -> str:
"""
The title of the related video
"""
return self.data['title']
@property
def thumbnail(self) -> str:
"""
The thumbnail URL of the related video
"""
return self.data['thumbnail']
@property
def uploader_name(self) -> str:
"""
The name of the channel that uploaded the related video
"""
return self.data['uploaderName']
@property
def uploader_url(self) -> str:
"""
The URL of the channel that uploaded the related video
"""
return self.data['uploaderUrl']
@property
def uploader_avatar(self) -> str:
"""
The URL of the channel's avatar
"""
return self.data['uploaderAvatar']
@property
def uploaded_date(self) -> str:
"""
The date the related video was uploaded (format: `'x y ago'`)
"""
return self.data['uploadedDate']
@property
def short_description(self) -> t.Optional[str]:
"""
The short description of the related video. As far as I know, this is always `None` - perhaps some unused YouTube feature?
"""
return self.data['shortDescription']
@property
def duration(self) -> timedelta:
"""
The duration of the related video.
### Note:
The original value from the API was in seconds (`Video.data['duration']`), but this package
converts it to a `datetime.timedelta` object.
"""
return timedelta(seconds=self.data['duration'])
@property
def views(self) -> str:
"""
The amount of views the related video has received
"""
return self.data['views']
@property
def uploaded(self) -> datetime:
"""
The date the related video was uploaded (as a `datetime.datetime` object).
### Note:
The original value was in milliseconds since epoch (`Video.data['uploaded']`), but this package converts it to a `datetime.datetime` object.
"""
return datetime.fromtimestamp(self.data['uploaded'] / 1000)
@property
def uploader_verified(self) -> bool:
"""
Whether or not the channel that uploaded the related video is verified by YouTube (e. g.: has badge)
"""
return self.data['uploaderVerified']
@property
def related_videos(self) -> t.List[RelatedStream]:
"""
List of related streams
"""
return [self.RelatedStream(video_data) for video_data in self.data['relatedStreams']]
class Subtitle(BasePipedModel):
@property
def url(self) -> str:
"""
The URL to the subtitle
"""
return self.data['url']
@property
def mime_type(self) -> str:
"""
If you come from web development (or other invidious area that works with these French mimes),
then you already know what this is. If not, consider [checking the Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
"""
return self.data['mimeType']
@property
def name(self) -> str:
"""
The name of the language the captions are in
"""
return self.data['name']
@property
def code(self) -> str:
"""
The country code for the captions
"""
return self.data['code']
@property
def auto_generated(self) -> bool:
"""
Whether or not the captions are auto-generated by YouTube
"""
return self.data['autoGenerated']
@property
def subtitles(self) -> t.List[Subtitle]:
"""
A list of captions for the video
"""
return [self.Subtitle(subtitle_data) for subtitle_data in self.data['subtitles']]
@property
def livestream(self) -> bool:
"""
Whether or not the video is a livestream
"""
return self.data['livestream']
@property
def proxy_url(self) -> str:
"""
The base URL for Piped proxy
"""
return self.data['proxyUrl']
class Chapter(BasePipedModel):
"""
A video chapter (or "section").
YouTube displays a list of chapters, if there are timestamps in the description.
"""
@property
def title(self) -> str:
"""
The title of the chapter
"""
return self.data['title']
@property
def image(self) -> str:
"""
The image URL for the chapter
"""
return self.data['image']
@property
def start(self) -> timedelta:
"""
The start time of the chapter
### Note:
The original value from the API was in seconds, this package automatically converts it to `datetime.timedelta`
"""
return timedelta(seconds=self.data['start'])
@property
def chapters(self) -> t.List[Chapter]:
"""
A list of chapters for the video
"""
return [self.Chapter(chapter_data) for chapter_data in self.data['chapters']]