Trending videos
This commit is contained in:
parent
d17a0cca73
commit
50cc8eb956
7 changed files with 128 additions and 40 deletions
42
README.md
42
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)
|
[![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
|
## 🎁 Support me
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@ from .client import PipedClient
|
||||||
from .models.comments import Comments
|
from .models.comments import Comments
|
||||||
|
|
||||||
|
|
||||||
|
from setup import __doc__ as __sdoc__
|
||||||
|
__doc__ = __sdoc__
|
||||||
|
|
||||||
|
|
||||||
# Supress unused-import warnings:
|
# Supress unused-import warnings:
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
|
|
@ -75,3 +75,16 @@ class PipedClient:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._get_json(f"/streams/{video_id}", Video)
|
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()})]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from re import S
|
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from datetime import datetime, date, timedelta
|
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
|
@property
|
||||||
|
@ -411,10 +410,10 @@ class Video(BasePipedModel):
|
||||||
The date the related video was uploaded (as a `datetime.datetime` object).
|
The date the related video was uploaded (as a `datetime.datetime` object).
|
||||||
|
|
||||||
### Note:
|
### 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
|
@property
|
||||||
|
@ -428,12 +427,12 @@ class Video(BasePipedModel):
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def related_videos(self) -> t.List[RelatedVideo]:
|
def related_videos(self) -> t.List[RelatedStream]:
|
||||||
"""
|
"""
|
||||||
List of related streams
|
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']]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -31,8 +31,8 @@ __doc__ = __readme__
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name = 'piped_api',
|
name = 'piped-api',
|
||||||
packages = setuptools.find_packages(exclude=('tests',)),
|
packages = setuptools.find_packages(exclude=('tests',), include=('piped_api',)),
|
||||||
|
|
||||||
long_description=__readme__,
|
long_description=__readme__,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
|
|
|
@ -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()
|
|
63
tests/test_videos.py
Normal file
63
tests/test_videos.py
Normal file
|
@ -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()
|
Loading…
Reference in a new issue