make FreeTube work (by using video only streams), fix livestream dates

This commit is contained in:
2024-12-29 04:59:46 +01:00
parent ee31114e51
commit 2837cdf612

View File

@@ -69,6 +69,9 @@ def streams():
def epochToDate(epoch):
return strftime('%Y-%m-%dT%H:%M:%SZ', gmtime(epoch))
def dateToEpoch(date: str):
return datetime.datetime.fromisoformat(date).timestamp()
def trending():
return send(200, [{}])
@@ -105,7 +108,6 @@ def genThumbs(videoId: str):
height = x['height']
quality = x['quality']
url = ythdd_globals.config['general']['public_facing_url'] + 'vi/' + videoId + '/' + x['url'] + '.jpg'
#url = '/vi/' + videoId + '/' + x['url'] + '.jpg'
result.append({'quality': quality, 'url': url, 'width': width, 'height': height})
return result
@@ -113,6 +115,7 @@ def genThumbs(videoId: str):
def rebuildFormats(data):
result = [{} for x in data]
formatStreams = []
best_bitrate = 0
for x in range(len(data)):
@@ -122,6 +125,9 @@ def rebuildFormats(data):
except:
isVideo = 1
if not "initRange" in data[x]: # for livestreams?
continue
result[x]['init'] = str(data[x]['initRange']['start']) + "-" + str(data[x]['initRange']['end'])
result[x]['index'] = str(data[x]['indexRange']['start']) + "-" + str(data[x]['indexRange']['end'])
result[x]['bitrate'] = str(data[x]['averageBitrate'])
@@ -157,8 +163,32 @@ def rebuildFormats(data):
except:
pass
#if data[x]['itag'] <= 80: # won't be triggered for iOS player as it has no progressive streams
# formatStreams.append(result[x])
# we assume here that a stream with the highest bitrate must be a video stream
if data[x]['averageBitrate'] > data[best_bitrate]['averageBitrate']:
best_bitrate = x
# makes FreeTube work, unfortunately it's a video-only stream
formatStreams = [
{
"url": data[best_bitrate]['url'],
"itag": str(data[best_bitrate]['itag']),
"type": data[best_bitrate]['mimeType'],
"quality": data[best_bitrate]['quality'],
"bitrate": str(data[best_bitrate]['averageBitrate']),
"fps": data[best_bitrate]['fps'],
"size": "", # workaround for clipious, which requires ANYTHING to be passed, or else it will throw and error and won't load the video
"resolution": str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height']) + "p",
"qualityLabel": str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height']) + "p",
"container": invidious_formats.FORMATS[data[best_bitrate]['itag']]['ext'],
"encoding": invidious_formats.FORMATS[data[best_bitrate]['itag']]['vcodec']
}
]
# not all itags have width and/or height
try:
formatStreams["size"] = str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['width']) + "x" + str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height'])
except:
pass
return result, formatStreams
@@ -188,7 +218,7 @@ def videos(data):
title = safeTraverse(video_details, ['title'], default=video_id)
views = int(safeTraverse(video_details, ['viewCount'], default=0))
length = int(safeTraverse(video_details, ['lengthSeconds'], default=1))
published = datetime.datetime.fromisoformat(safeTraverse(microformat, ['publishDate'], default="1970-01-02T00:00:00Z")).timestamp() # ISO format to Unix timestamp
published = dateToEpoch(safeTraverse(microformat, ['publishDate'], default="1970-01-02T00:00:00Z")) # ISO format to Unix timestamp
published_date = epochToDate(published)
premiere_timestamp = safeTraverse(microformat, ['liveBroadcastDetails', 'startTimestamp'], default=0) # let's ignore the nitty gritty for the time being
premiere_timestamp = premiere_timestamp if premiere_timestamp else safeTraverse(microformat, ['playabilityStatus', 'liveStreamability', 'liveStreamabilityRenderer', 'offlineSlate', 'liveStreamOfflineSlateRenderer', 'scheduledStartTime'], default=0)
@@ -244,8 +274,8 @@ def videos(data):
for x in magnitude.keys():
if x in likes_text:
likes *= magnitude[x]
description = safeTraverse(microformat, ['description', 'simpleText'], default="\n(ythdd: error ocurred, failed to retrieve description)")
short_description = safeTraverse(wdata, ['ec1', 'videoDetails', 'shortDescription'], default="(ythdd: error occurred, failed to retrieve short description)")
description = safeTraverse(microformat, ['description', 'simpleText'], default="\n(ythdd: failed to retrieve description, perhaps it's empty?)")
short_description = safeTraverse(wdata, ['ec1', 'videoDetails', 'shortDescription'], default="(ythdd: failed to retrieve short description, perhaps it's empty?)")
description_html = "<p>" + description + "</p>" # sorry, not happening right now, TODO: https://github.com/iv-org/invidious/blob/master/src/invidious/videos/parser.cr#L329
metadata = safeTraverse(video_secondary_renderer, ['metadataRowContainer', 'metadataRowContainerRenderer', 'rows'], default={})
@@ -274,13 +304,14 @@ def videos(data):
hls_url = safeTraverse(idata, ['stage1', 'streamingData', 'hlsManifestUrl'], default="")
adaptive_formats = safeTraverse(idata, ['stage1', 'streamingData', 'adaptiveFormats'], default=[])
format_streams = []
adaptive_formats, format_streams = rebuildFormats(adaptive_formats)
if live_now:
video_type = "livestream"
elif premiere_timestamp:
video_type = "scheduled"
published = premiere_timestamp if premiere_timestamp else int(time.time())
published = dateToEpoch(premiere_timestamp) if premiere_timestamp else int(time.time())
else:
video_type = "video"
@@ -289,11 +320,6 @@ def videos(data):
premium = True
# TODO: detect paywalled patron-only videos
if not format_streams:
format_streams = []
# providing format streams breaks Clipious client
#format_streams.append(adaptive_formats[0])
#format_streams.append(adaptive_formats[1])
#'''
response = {
@@ -335,7 +361,7 @@ def videos(data):
"liveNow": live_now,
"isPostLiveDvr": post_live_dvr,
"isUpcoming": is_upcoming,
"dashUrl": "/dash/not/implemented/", # not implemented
"dashUrl": ythdd_globals.config['general']['public_facing_url'] + "/dash/not/implemented/", # not implemented
"premiereTimestamp": premiere_timestamp,
"hlsUrl": hls_url,
@@ -418,10 +444,10 @@ def lookup(data):
case _:
incrementBadRequests()
return notImplemented(data)
elif data[0] == 'ggpht':
elif data[0] in ('ggpht', 'vi'):
# for some reason the Materialous client
# keeps making requests to these
if data[1] == 'ggpht':
# and FreeTube keep making requests to these
if data[1] in ('ggpht', 'vi'):
return redirect('/' + "/".join(data[1:]))
return redirect('/' + "/".join(data[0:]))
else:
@@ -432,7 +458,7 @@ def lookup(data):
return stats()
elif data[0] == "streams":
return streams()
elif data[0] == 'ggpht':
elif data[0] in ('ggpht', 'vi'):
return redirect('/' + "/".join(data[0:]))
else:
incrementBadRequests()