2020-08-07 12:22:48 +00:00
|
|
|
import cherrypy
|
|
|
|
import json
|
|
|
|
import youtube_dl
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
ytdl_opts = {
|
|
|
|
"quiet": True,
|
|
|
|
"dump_single_json": True,
|
|
|
|
"playlist_items": "1-100",
|
|
|
|
"extract_flat": "in_playlist"
|
|
|
|
}
|
|
|
|
ytdl = youtube_dl.YoutubeDL(ytdl_opts)
|
|
|
|
|
|
|
|
class HelloWorld(object):
|
|
|
|
def _cp_dispatch(self, vpath):
|
|
|
|
if vpath[:2] == ["api", "v1"] and len(vpath) >= 4:
|
|
|
|
endpoints = ["channels", "videos"]
|
|
|
|
for e in endpoints:
|
|
|
|
if vpath[2] == e:
|
|
|
|
vpath[:3] = [e]
|
|
|
|
return self
|
|
|
|
|
|
|
|
return vpath
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def videos(self, id):
|
|
|
|
try:
|
|
|
|
info = ytdl.extract_info(id, download=False)
|
|
|
|
|
|
|
|
year = int(info["upload_date"][:4])
|
|
|
|
month = int(info["upload_date"][4:6])
|
|
|
|
day = int(info["upload_date"][6:8])
|
|
|
|
|
|
|
|
def format_is_adaptive(format):
|
|
|
|
return format["acodec"] == "none" or format["vcodec"] == "none"
|
|
|
|
|
|
|
|
def format_type(format):
|
|
|
|
sense = "audio"
|
|
|
|
codecs = []
|
|
|
|
if format["vcodec"] != "none":
|
|
|
|
sense = "video"
|
|
|
|
codecs.append(format["vcodec"])
|
|
|
|
if format["acodec"] != "none":
|
|
|
|
codecs.append(format["acodec"])
|
|
|
|
return '{}/{}; codecs="{}"'.format(sense, format["ext"], ", ".join(codecs))
|
|
|
|
|
|
|
|
return {
|
|
|
|
"type": "video",
|
|
|
|
"title": info["title"],
|
|
|
|
"videoId": info["id"],
|
|
|
|
"videoThumbnails": None,
|
|
|
|
"storyboards": None,
|
|
|
|
"description": info["description"],
|
|
|
|
"descriptionHtml": None,
|
|
|
|
"published": int(datetime.datetime(year, month, day).timestamp()),
|
|
|
|
"publishedText": None,
|
|
|
|
"keywords": None,
|
|
|
|
"viewCount": info["view_count"],
|
|
|
|
"likeCount": info["like_count"],
|
|
|
|
"dislikeCount": info["dislike_count"],
|
|
|
|
"paid": None,
|
|
|
|
"premium": None,
|
|
|
|
"isFamilyFriendly": None,
|
|
|
|
"allowedRegions": [],
|
|
|
|
"genre": None,
|
|
|
|
"genreUrl": None,
|
|
|
|
"author": info["uploader"],
|
|
|
|
"authorId": info["channel_id"],
|
|
|
|
"authorUrl": info["channel_url"],
|
|
|
|
"second__uploaderId": info["uploader_id"],
|
|
|
|
"second__uploaderUrl": info["uploader_url"],
|
|
|
|
"authorThumbnails": [],
|
|
|
|
"subCountText": None,
|
|
|
|
"lengthSeconds": info["duration"],
|
|
|
|
"allowRatings": None,
|
|
|
|
"rating": info["average_rating"],
|
|
|
|
"isListed": None,
|
|
|
|
"liveNow": None,
|
|
|
|
"isUpcoming": None,
|
|
|
|
"dashUrl": None,
|
|
|
|
"adaptiveFormats": list({
|
|
|
|
"index": None,
|
|
|
|
"bitrate": str(int(format["tbr"]*1000)),
|
|
|
|
"init": None,
|
|
|
|
"url": format["url"],
|
|
|
|
"itag": format["format_id"],
|
|
|
|
"type": format_type(format),
|
|
|
|
"clen": str(format["filesize"]),
|
|
|
|
"lmt": None,
|
|
|
|
"projectionType": None,
|
|
|
|
"fps": format["fps"],
|
|
|
|
"container": format["ext"],
|
|
|
|
"encoding": None,
|
|
|
|
"resolution": format["format_note"],
|
|
|
|
"qualityLabel": format["format_note"],
|
|
|
|
"second__width": format["width"],
|
|
|
|
"second__height": format["height"]
|
|
|
|
} for format in info["formats"] if format_is_adaptive(format)),
|
|
|
|
"formatStreams": list({
|
|
|
|
"url": format["url"],
|
|
|
|
"itag": format["format_id"],
|
|
|
|
"type": format_type(format),
|
|
|
|
"quality": None,
|
|
|
|
"fps": format["fps"],
|
|
|
|
"container": format["ext"],
|
|
|
|
"encoding": None,
|
|
|
|
"resolution": format["format_note"],
|
|
|
|
"qualityLabel": format["format_note"],
|
|
|
|
"size": "{}x{}".format(format["width"], format["height"]),
|
|
|
|
"second__width": format["width"],
|
|
|
|
"second__height": format["height"]
|
|
|
|
} for format in info["formats"] if not format_is_adaptive(format)),
|
|
|
|
"captions": [],
|
|
|
|
"recommendedVideos": []
|
|
|
|
}
|
|
|
|
|
|
|
|
except youtube_dl.DownloadError:
|
|
|
|
return {
|
|
|
|
"error": "Video unavailable",
|
|
|
|
"identifier": "VIDEO_DOES_NOT_EXIST"
|
|
|
|
}
|
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
@cherrypy.tools.json_out()
|
|
|
|
def channels(self, *suffix):
|
|
|
|
ucid = ""
|
|
|
|
part = ""
|
|
|
|
if len(suffix) == 1:
|
|
|
|
ucid = suffix[0]
|
|
|
|
else: # len(suffix) >= 2
|
|
|
|
if suffix[0] == "videos" or suffix[0] == "latest":
|
|
|
|
[part, ucid] = suffix
|
|
|
|
else:
|
|
|
|
[ucid, part] = suffix
|
|
|
|
|
|
|
|
try:
|
|
|
|
info = ytdl.extract_info("https://www.youtube.com/channel/{}".format(ucid), download=False)
|
|
|
|
|
|
|
|
response = {
|
|
|
|
"author": info["uploader"],
|
|
|
|
"authorId": info["uploader_id"],
|
|
|
|
"authorUrl": info["uploader_url"],
|
|
|
|
"authorBanners": [],
|
|
|
|
"authorThumbnails": [],
|
|
|
|
"subCount": None,
|
|
|
|
"totalViews": None,
|
|
|
|
"joined": None,
|
|
|
|
"paid": None,
|
|
|
|
"autoGenerated": None,
|
|
|
|
"isFamilyFriendly": None,
|
|
|
|
"description": None,
|
|
|
|
"descriptionHtml": None,
|
|
|
|
"allowedRegions": [],
|
|
|
|
"latestVideos": list({
|
|
|
|
"type": "video",
|
|
|
|
"title": video["title"],
|
|
|
|
"videoId": video["id"],
|
|
|
|
"author": info["uploader"],
|
|
|
|
"authorId": info["uploader_id"],
|
|
|
|
"authorUrl": info["uploader_url"],
|
|
|
|
"videoThumbnails": [],
|
|
|
|
"description": None,
|
|
|
|
"descriptionHtml": None,
|
|
|
|
"viewCount": None,
|
|
|
|
"published": None,
|
|
|
|
"publishedText": None,
|
|
|
|
"lengthSeconds": None,
|
|
|
|
"liveNow": None,
|
|
|
|
"paid": None,
|
|
|
|
"premium": None,
|
|
|
|
"isUpcoming": None
|
|
|
|
} for video in info["entries"]),
|
|
|
|
"relatedChannels": []
|
|
|
|
}
|
|
|
|
|
|
|
|
if part == "videos" or part == "latest":
|
|
|
|
return response["latestVideos"]
|
|
|
|
else:
|
|
|
|
return response
|
|
|
|
|
|
|
|
except youtube_dl.DownloadError:
|
|
|
|
return {
|
|
|
|
"error": "This channel does not exist.",
|
|
|
|
"identifier": "CHANNEL_DOES_NOT_EXIST"
|
|
|
|
}
|
|
|
|
|
2020-08-07 12:42:56 +00:00
|
|
|
cherrypy.config.update({"server.socket_port": 3000})
|
2020-08-07 12:22:48 +00:00
|
|
|
cherrypy.quickstart(HelloWorld())
|