/
youtube_api.py
executable file
·143 lines (132 loc) · 5.61 KB
/
youtube_api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from isodate import parse_duration
from ..constants import YOUTUBE
from ..errors import EmptyBodyError
class YouTubeVideoData:
"""
Obtains YouTube video data and wraps it inside this class. All fields are direct
access available, so it is possible to access them directly:
- title
- id
- thumbnail
- artist
- duration
- views
- likes
- dislikes
"""
def __init__(self, data: dict, ignore_errors: bool = False):
"""
By passing a dict with the YouTube data (YouTube API v3), generate and obtain
the information available from the result.
:param data: a dictionary with the information obtained from YouTube API.
:param ignore_errors: whether to ignore or not errors (do not raise exceptions).
:raises EmptyBodyError when there is no information available and ignored
errors is False.
"""
if not data.get("items"):
raise EmptyBodyError("The data object has no items")
self.id: str = ""
self.title: str = ""
self.thumbnail: str = ""
self.artist: str = ""
self.duration: float = 0.0
self.views: int = 0
self.likes: int = 0
self.dislikes: int = 0
if len(data.get("items")) >= 1:
content = data.get("items")[0]
snippet = content.get("snippet")
details = content.get("contentDetails")
statistics = content.get("statistics")
if not snippet and not ignore_errors:
raise EmptyBodyError("No information available to requested video")
elif not snippet and ignore_errors:
snippet_available = False
else:
snippet_available = True
if not details and not ignore_errors:
raise EmptyBodyError("No video details available")
elif not details and ignore_errors:
details_available = False
else:
details_available = True
if not statistics and not ignore_errors:
raise EmptyBodyError("No statistics available")
elif not statistics and ignore_errors:
statistics_available = False
else:
statistics_available = True
c_id = content.get("id", "")
self.id = c_id.get("videoId", "") if isinstance(c_id, dict) else c_id
if snippet_available:
self.title = snippet["title"]
try:
self.thumbnail = snippet["thumbnails"]["maxres"]["url"]
except KeyError:
try:
self.thumbnail = snippet["thumbnails"]["high"]["url"]
except KeyError:
try:
self.thumbnail = snippet["thumbnails"]["medium"]["url"]
except KeyError:
self.thumbnail = snippet["thumbnails"]["default"]["url"]
self.artist = snippet["channelTitle"]
if details_available:
self.duration = parse_duration(details["duration"]).total_seconds()
if statistics_available:
self.views = int(statistics["viewCount"])
self.likes = int(statistics["likeCount"])
self.dislikes = int(statistics["dislikeCount"])
class YouTubeAPI:
"""
Wrapper for the YouTube API data. Allows the developer searching for videos and,
with a given video ID, obtain its data.
"""
def __init__(self):
from googleapiclient.discovery import build
self.__youtube = build(serviceName=YOUTUBE["api"]["name"],
version=YOUTUBE["api"]["version"],
developerKey=YOUTUBE["key"])
def search(self, term: str) -> dict:
"""
Searchs for a video with the specified term.
:param term: the search term.
:return: dict with YouTube data - can be wrapped inside "YouTubeVideoData" class.
"""
return self.__youtube.search().list(
q=term,
type="video",
part="id,snippet",
maxResults=1
).execute()
@staticmethod
def video_details(video_id: str) -> YouTubeVideoData:
"""
Generates a "YouTubeVideoData" object wrapper with the video ID information.
:param video_id: YouTube video ID.
:return: YouTubeVideoData object with the available metadata.
"""
try:
import ujson as json
except ImportError:
import json
from urllib.request import urlopen
youtube_information = YOUTUBE.copy()
api_url = youtube_information["endpoint"].format(video_id, YOUTUBE["key"])
data = urlopen(url=api_url)
return YouTubeVideoData(data=json.loads(data.read()), ignore_errors=True)