diff --git a/README.md b/README.md index ca92817..91a77fe 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,47 @@ [![Test with pyTest](https://github.com/CWKevo/python-piped-api-client/actions/workflows/pytest.yml/badge.svg?branch=master)](https://github.com/CWKevo/python-piped-api-client/actions/workflows/pytest.yml) -A Python API wrapper for [Piped](https://piped-docs.kavin.rocks/). +A Python API wrapper for [Piped](https://piped-docs.kavin.rocks/). This can essentially be used as an alternative way to access YouTube's API, without needing to use an API key. + +## Installation + +```bash +pip install piped-api +``` + +## Quickstart + +Getting started is very easy: + +```python +from piped_api import PipedClient + +CLIENT = PipedClient() + + +# Print out the first audio stream URL for a video: +video = CLIENT.get_video(video_id) +audio_stream = video.get_streams('audio')[0] + +print(f"Audio stream URL: {audio_stream.url} ({audio_stream.mime_type})") +``` + +## Why? + +This package has allowed me to start creating my open-source project, [ArchiveTube](https://github.com/CWKevo/ArchiveTube) - a scrapper and archive for YouTube content (videos and comments) - to preserve them and make them available to anyone, with ability to search for comments and videos. View hall of fame (most liked comments and videos), bring back dislikes via [ReturnYouTubeDislike.com](https://returnyoutubedislike.com), view deleted content and much more! +Google has showed us that they make YouTube own us by harvesting our data. This is also followed by non-throught out decisions, which their users aren't happy with. Let's do it the other way around this time by reclaiming our content and entertainment back & make YouTube great again! + +The creation of this package was also primarily fueled by the same type of motivation [Piped has](https://piped-docs.kavin.rocks/docs/why/). + +Google's API is not very easy-to-use - you must obtain some JSON thingy to use it, and it is very low-level and not very user-friendly. +On the other hand, this package accessed the [Piped API](https://piped.kavin.rocks/), which has a much more high-level API and doesn't need an account or API keys. + +It is not meant to be a replacement for the official YouTube API, but it can help you to cut the strings that Google attaches to you when using their API. + +## Useful links + +- [Piped's official API documentation](https://piped-docs.kavin.rocks/docs/api-documentation/) +- [Documentation for this package](https://cwkevo.github.io/python-piped-api-client/) ## 🎁 Support me diff --git a/piped_api/__init__.py b/piped_api/__init__.py index 947c45d..5e8c108 100644 --- a/piped_api/__init__.py +++ b/piped_api/__init__.py @@ -6,6 +6,9 @@ from .client import PipedClient from .models.comments import Comments +from setup import __doc__ as __sdoc__ +__doc__ = __sdoc__ + # Supress unused-import warnings: if t.TYPE_CHECKING: diff --git a/piped_api/client.py b/piped_api/client.py index 8106aa8..92407c0 100644 --- a/piped_api/client.py +++ b/piped_api/client.py @@ -75,3 +75,16 @@ class PipedClient: """ return self._get_json(f"/streams/{video_id}", Video) + + + def get_trending(self, country_code: str='US') -> t.List[Video.RelatedStream]: + """ + Obtains trending videos for a specific country. If there are no trending videos (or `country_code` is invalid), + an empty list is returned. + + ### Parameters: + - `country_code` - The country code ([ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)) to get trending videos for. This is automatically capitalized by this package, + since Piped for some reason doesn't accept lowercase country codes. Note: countries such as China or North Korea don't have trending videos, so they will always return an empty list. + """ + + return [Video.RelatedStream(trending_video) for trending_video in self._get_json(f"/trending", params={'region': country_code.upper()})] diff --git a/piped_api/models/videos.py b/piped_api/models/videos.py index 5c1b349..726690d 100644 --- a/piped_api/models/videos.py +++ b/piped_api/models/videos.py @@ -1,4 +1,3 @@ -from re import S import typing as t from datetime import datetime, date, timedelta @@ -306,9 +305,9 @@ class Video(BasePipedModel): - class RelatedVideo(BasePipedModel): + class RelatedStream(BasePipedModel): """ - A related video to the current video (e. g.: from the right sidebar) + 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 @@ -411,10 +410,10 @@ class Video(BasePipedModel): The date the related video was uploaded (as a `datetime.datetime` object). ### Note: - The original value was in POSIX timestamp (`Video.data['uploaded']`), but this package converts it to a `datetime.datetime` object. + 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']) + return datetime.fromtimestamp(self.data['uploaded'] / 1000) @property @@ -428,12 +427,12 @@ class Video(BasePipedModel): @property - def related_videos(self) -> t.List[RelatedVideo]: + def related_videos(self) -> t.List[RelatedStream]: """ List of related streams """ - return [self.RelatedVideo(video_data) for video_data in self.data['relatedVideos']] + return [self.RelatedStream(video_data) for video_data in self.data['relatedVideos']] diff --git a/setup.py b/setup.py index 360a6e4..beef41d 100644 --- a/setup.py +++ b/setup.py @@ -31,8 +31,8 @@ __doc__ = __readme__ setuptools.setup( - name = 'piped_api', - packages = setuptools.find_packages(exclude=('tests',)), + name = 'piped-api', + packages = setuptools.find_packages(exclude=('tests',), include=('piped_api',)), long_description=__readme__, long_description_content_type='text/markdown', diff --git a/tests/test_video.py b/tests/test_video.py deleted file mode 100644 index 4cdd6eb..0000000 --- a/tests/test_video.py +++ /dev/null @@ -1,30 +0,0 @@ -from tests import CLIENT - -from datetime import datetime - - -def test_video(video_id: str='dQw4w9WgXcQ') -> None: - """ - Prints out information about a video. - """ - - video = CLIENT.get_video(video_id) - short_description = video.description[:100].replace('\n', '') - - print(f""" - Video ID: {video_id} - Title: {video.title} - Description: {short_description}... - Views: {video.views} - - Uploaded by: {video.uploader} - Uploaded on: {video.upload_date} ({datetime.now().year - video.upload_date.year} years ago) - - Duration: {video.duration} - FPS: {video.get_streams('video')[0].fps} - """) - - - -if __name__ == '__main__': - test_video() diff --git a/tests/test_videos.py b/tests/test_videos.py new file mode 100644 index 0000000..abb84fc --- /dev/null +++ b/tests/test_videos.py @@ -0,0 +1,63 @@ +import typing as t + +from tests import CLIENT +from datetime import datetime + + +def test_video(video_id: str='dQw4w9WgXcQ') -> None: + """ + Prints out information about a video. + """ + + video = CLIENT.get_video(video_id) + short_description = video.description[:100].replace('\n', '') + + print(f""" + Video ID: {video_id} + Title: {video.title} + Description: {short_description}... + Views: {video.views} + + Uploaded by: {video.uploader} + Uploaded on: {video.upload_date} ({datetime.now().year - video.upload_date.year} years ago) + + Duration: {video.duration} + FPS: {video.get_streams('video')[0].fps} + """) + + + +def test_trending(country_codes: t.List[str]=['US', 'SK', 'CN']) -> None: + """ + Prints out trending videos for a specific country. + """ + + for country_code in country_codes: + videos = CLIENT.get_trending(country_code) + + # Nothing ever trends in China's YouTube: + if country_code == 'CN': + assert len(videos) == 0 + print("\nYes, empty list works.") + + for video in videos: + print(f"{video.uploader_name} >> {video.title} ({video.views} views)") + + + +def test_get_audio(video_id: str='dQw4w9WgXcQ') -> None: + """ + Prints out the first audio stream URL for a video. + """ + + video = CLIENT.get_video(video_id) + audio_stream = video.get_streams('audio')[0] + + print(f"Audio stream URL: {audio_stream.url} ({audio_stream.mime_type})") + + + +if __name__ == '__main__': + test_video() + test_trending() + test_get_audio()