Compare commits
6 Commits
ef177f7200
...
c3fae689e1
| Author | SHA1 | Date | |
|---|---|---|---|
| c3fae689e1 | |||
| 4cfb1db7d0 | |||
| 5a1e772909 | |||
| 7c4991cea7 | |||
| 5f88d6f096 | |||
| eaaa14c4d8 |
4
views.py
4
views.py
@@ -36,6 +36,10 @@ def ggphtProxy(received_request):
|
|||||||
|
|
||||||
prefix = "https://yt3.ggpht.com/"
|
prefix = "https://yt3.ggpht.com/"
|
||||||
|
|
||||||
|
# fix for how materialious fetches avatars
|
||||||
|
if received_request.startswith("guc/"):
|
||||||
|
return gucProxy(received_request.removeprefix("guc/"))
|
||||||
|
|
||||||
ggpht = requests.get(prefix + received_request, headers=ythdd_globals.getHeaders(caller='proxy'), stream=True)
|
ggpht = requests.get(prefix + received_request, headers=ythdd_globals.getHeaders(caller='proxy'), stream=True)
|
||||||
ggpht.raw.decode_content = True
|
ggpht.raw.decode_content = True
|
||||||
response = Response(ggpht.raw, mimetype=ggpht.headers['content-type'], status=ggpht.status_code)
|
response = Response(ggpht.raw, mimetype=ggpht.headers['content-type'], status=ggpht.status_code)
|
||||||
|
|||||||
@@ -276,8 +276,8 @@ def generateChannelAvatarsFromUrl(url: str, proxied: bool = True) -> list:
|
|||||||
# Generates channel avatars at default sizes.
|
# Generates channel avatars at default sizes.
|
||||||
|
|
||||||
# avatar urls for channels in search results start with //yt3.ggpht.com/
|
# avatar urls for channels in search results start with //yt3.ggpht.com/
|
||||||
if url.startswith("//yt3.ggpht.com/"):
|
if url.startswith("//"):
|
||||||
url = url.replace("//yt3.ggpht.com/", "https://yt3.ggpht.com/")
|
url = "https:" + url
|
||||||
|
|
||||||
avatars = []
|
avatars = []
|
||||||
if not url.startswith("https://yt3.ggpht.com/") and not url.startswith("https://yt3.googleusercontent.com/"):
|
if not url.startswith("https://yt3.ggpht.com/") and not url.startswith("https://yt3.googleusercontent.com/"):
|
||||||
@@ -308,7 +308,7 @@ def isVerified(response_json: dict) -> bool:
|
|||||||
|
|
||||||
match safeTraverse(list(response_json.keys()), [0], default=""):
|
match safeTraverse(list(response_json.keys()), [0], default=""):
|
||||||
case "metadataBadgeRenderer": # channels in search results
|
case "metadataBadgeRenderer": # channels in search results
|
||||||
verified = safeTraverse(response_json, ["metadataBadgeRenderer", "tooltip"], default="") in ("Verified") # room for support of artist channels
|
verified = safeTraverse(response_json, ["metadataBadgeRenderer", "tooltip"], default="") in ("Verified", "Official Artist Channel") # perhaps look for badge styles?
|
||||||
return verified
|
return verified
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -23,13 +23,16 @@ import ythdd_struct_parser
|
|||||||
# [✓] /vi/videoIdXXXX/maxresdefault.jpg (todo: add a fallback for 404s)
|
# [✓] /vi/videoIdXXXX/maxresdefault.jpg (todo: add a fallback for 404s)
|
||||||
# [✓] /api/v1/search?q=... (videos and playlists)
|
# [✓] /api/v1/search?q=... (videos and playlists)
|
||||||
# [✓] /api/v1/search/suggestions?q=...&pq=...
|
# [✓] /api/v1/search/suggestions?q=...&pq=...
|
||||||
# [✓] /api/v1/channels/id
|
# [✓] /api/v1/channels/:ucid
|
||||||
# [✓] /api/v1/channels/videos, shorts, playlists
|
# [✓] /api/v1/channels/:ucid/videos, shorts, playlists
|
||||||
|
# [✓] /api/v1/comments/:videoid?continuation=...
|
||||||
|
# [✓] /api/v1/videos/videoIdXXXX
|
||||||
|
# [X] /api/v1/channels/:ucid/streams
|
||||||
# [X] /api/v1/playlists/:plid
|
# [X] /api/v1/playlists/:plid
|
||||||
|
# [X] /api/v1/storyboards/:videoIdXXXX
|
||||||
# [*] /api/v1/auth/subscriptions (stub? db?)
|
# [*] /api/v1/auth/subscriptions (stub? db?)
|
||||||
# [*] /api/v1/auth/feed?page=1 (stub? db?)
|
# [*] /api/v1/auth/feed?page=1 (stub? db?)
|
||||||
# [*] /api/v1/auth/playlists (stub? db?)
|
# [*] /api/v1/auth/playlists (stub? db?)
|
||||||
# [*] /api/v1/videos/videoIdXXXX
|
|
||||||
|
|
||||||
DEFAULT_AVATAR = "https://yt3.ggpht.com/a/default-user=s176-c-k-c0x00ffffff-no-rj"
|
DEFAULT_AVATAR = "https://yt3.ggpht.com/a/default-user=s176-c-k-c0x00ffffff-no-rj"
|
||||||
|
|
||||||
@@ -434,10 +437,16 @@ def videos(data):
|
|||||||
|
|
||||||
format_streams = []
|
format_streams = []
|
||||||
# adaptive_formats, format_streams = rebuildFormats(adaptive_formats)
|
# adaptive_formats, format_streams = rebuildFormats(adaptive_formats)
|
||||||
adaptive_formats, format_streams = rebuildFormatsFromYtdlpApi(ydata)
|
if not live_now:
|
||||||
|
adaptive_formats, format_streams = rebuildFormatsFromYtdlpApi(ydata)
|
||||||
|
hls_url = None
|
||||||
|
else:
|
||||||
|
adaptive_formats, format_streams = [{"url": f"http://a/?expire={int(time_start + 5.9 * 60 * 60)}", "itag": "18", "type": "", "clen": "0", "lmt": "", "projectionType": "RECTANGULAR"}], [] # freetube/clipious shenanigans, see: https://github.com/FreeTubeApp/FreeTube/pull/5997 and https://github.com/lamarios/clipious/blob/b9e7885/lib/videos/models/adaptive_format.g.dart
|
||||||
|
hls_url = safeTraverse(ydata, ["url"], default="ythdd: unable to retrieve stream url")
|
||||||
|
|
||||||
if live_now:
|
if live_now:
|
||||||
video_type = "livestream"
|
video_type = "livestream"
|
||||||
|
premiere_timestamp = published # ??? that works i guess
|
||||||
elif premiere_timestamp:
|
elif premiere_timestamp:
|
||||||
video_type = "scheduled"
|
video_type = "scheduled"
|
||||||
published = dateToEpoch(premiere_timestamp) if premiere_timestamp else int(time())
|
published = dateToEpoch(premiere_timestamp) if premiere_timestamp else int(time())
|
||||||
@@ -493,7 +502,7 @@ def videos(data):
|
|||||||
"isUpcoming": is_upcoming,
|
"isUpcoming": is_upcoming,
|
||||||
"dashUrl": ythdd_globals.config['general']['public_facing_url'] + "api/invidious/api/v1/manifest/" + video_id, # not implemented
|
"dashUrl": ythdd_globals.config['general']['public_facing_url'] + "api/invidious/api/v1/manifest/" + video_id, # not implemented
|
||||||
"premiereTimestamp": premiere_timestamp,
|
"premiereTimestamp": premiere_timestamp,
|
||||||
#"hlsUrl": hls_url, # broken after a change in iOS player, only usable for livestreams
|
"hlsUrl": hls_url, # broken after a change in iOS player, only usable for livestreams
|
||||||
"adaptiveFormats": adaptive_formats, # same as hlsUrl
|
"adaptiveFormats": adaptive_formats, # same as hlsUrl
|
||||||
"formatStreams": format_streams,
|
"formatStreams": format_streams,
|
||||||
"captions": [], # not implemented
|
"captions": [], # not implemented
|
||||||
@@ -740,7 +749,8 @@ def ensure_comment_continuation(video_id: str, wdata = None):
|
|||||||
if comment_continuation is not None:
|
if comment_continuation is not None:
|
||||||
ythdd_globals.general_cache["continuations"]["comments"][video_id].append(comment_continuation)
|
ythdd_globals.general_cache["continuations"]["comments"][video_id].append(comment_continuation)
|
||||||
else:
|
else:
|
||||||
print(f"error: couldn't extract comment continuation token from video page ({video_id})")
|
print(f"error: couldn't extract comment continuation token from video page ({video_id}). this video likely has comments disabled.")
|
||||||
|
ythdd_globals.general_cache["continuations"]["comments"][video_id].append("")
|
||||||
|
|
||||||
def channels(data, req, only_json: bool = False):
|
def channels(data, req, only_json: bool = False):
|
||||||
|
|
||||||
@@ -767,7 +777,7 @@ def channels(data, req, only_json: bool = False):
|
|||||||
verified = False # to be replaced later with ythdd_extractor.isVerified(...)
|
verified = False # to be replaced later with ythdd_extractor.isVerified(...)
|
||||||
|
|
||||||
author_name = safeTraverse(channel_meta, ["title"], default="Unknown Channel")
|
author_name = safeTraverse(channel_meta, ["title"], default="Unknown Channel")
|
||||||
author_ucid = safeTraverse(channel_meta, ["externalId"], default="UNKNOWNCHANNELID")
|
author_ucid = safeTraverse(channel_meta, ["externalId"], default=data[3]) # prevent recursion with fallback to provided ucid
|
||||||
|
|
||||||
ythdd_globals.general_cache["continuations"]["channels"][author_ucid] = {
|
ythdd_globals.general_cache["continuations"]["channels"][author_ucid] = {
|
||||||
"avatar": avatar,
|
"avatar": avatar,
|
||||||
|
|||||||
@@ -98,6 +98,20 @@ def parseRenderers(entry: dict, context: dict = {}) -> dict:
|
|||||||
else:
|
else:
|
||||||
avatar_url = safeTraverse(entry, ["videoRenderer", "avatar", "decoratedAvatarViewModel", "avatar", "avatarViewModel", "image", "sources", 0, "url"], default="unknown")
|
avatar_url = safeTraverse(entry, ["videoRenderer", "avatar", "decoratedAvatarViewModel", "avatar", "avatarViewModel", "image", "sources", 0, "url"], default="unknown")
|
||||||
|
|
||||||
|
views_or_viewers_model = safeTraverse(entry, ["videoRenderer", "viewCountText"])
|
||||||
|
if "simpleText" in views_or_viewers_model:
|
||||||
|
# means this is a video with X views
|
||||||
|
view_count = parseViewsFromViewText(entry["videoRenderer"]["viewCountText"]["simpleText"])
|
||||||
|
view_count_text = entry["videoRenderer"]["viewCountText"]["simpleText"]
|
||||||
|
elif "runs" in views_or_viewers_model:
|
||||||
|
# means this is a livestream with X concurrent viewers
|
||||||
|
view_count = parseViewsFromViewText(entry["videoRenderer"]["viewCountText"]["runs"][0]["text"] + " watching")
|
||||||
|
view_count_text = entry["videoRenderer"]["viewCountText"]["runs"][0]["text"] + " watching"
|
||||||
|
else:
|
||||||
|
# unknown model, assume no views
|
||||||
|
view_count = 0
|
||||||
|
view_count_text = "Unknown amount of views"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": "video",
|
"type": "video",
|
||||||
"title": safeTraverse(entry, ["videoRenderer", "title", "runs", 0, "text"]),
|
"title": safeTraverse(entry, ["videoRenderer", "title", "runs", 0, "text"]),
|
||||||
@@ -110,8 +124,8 @@ def parseRenderers(entry: dict, context: dict = {}) -> dict:
|
|||||||
"videoThumbnails": genThumbs(safeTraverse(entry, ["videoRenderer", "videoId"], default="unknown")),
|
"videoThumbnails": genThumbs(safeTraverse(entry, ["videoRenderer", "videoId"], default="unknown")),
|
||||||
"description": description,
|
"description": description,
|
||||||
"descriptionHtml": description_html,
|
"descriptionHtml": description_html,
|
||||||
"viewCount": parseViewsFromViewText(safeTraverse(entry, ["videoRenderer", "viewCountText", "simpleText"], default="No views")),
|
"viewCount": view_count,
|
||||||
"viewCountText": safeTraverse(entry, ["videoRenderer", "viewCountText", "simpleText"], default="Unknown amount of views"),
|
"viewCountText": view_count_text,
|
||||||
"published": int(dateparser.parse(published_date).timestamp()), # sadly best we can do, invidious does this too
|
"published": int(dateparser.parse(published_date).timestamp()), # sadly best we can do, invidious does this too
|
||||||
"publishedText": published_date,
|
"publishedText": published_date,
|
||||||
"lengthSeconds": parseLengthFromTimeBadge(safeTraverse(entry, ["videoRenderer", "lengthText", "simpleText"], default="0:0")),
|
"lengthSeconds": parseLengthFromTimeBadge(safeTraverse(entry, ["videoRenderer", "lengthText", "simpleText"], default="0:0")),
|
||||||
|
|||||||
Reference in New Issue
Block a user