diff options
Diffstat (limited to 'youtube')
-rw-r--r-- | youtube/static/js/plyr-start.js | 4 | ||||
-rw-r--r-- | youtube/templates/watch.html | 1 | ||||
-rw-r--r-- | youtube/watch.py | 64 | ||||
-rw-r--r-- | youtube/yt_data_extract/watch_extraction.py | 2 |
4 files changed, 71 insertions, 0 deletions
diff --git a/youtube/static/js/plyr-start.js b/youtube/static/js/plyr-start.js index 86840e9..7b67185 100644 --- a/youtube/static/js/plyr-start.js +++ b/youtube/static/js/plyr-start.js @@ -116,6 +116,10 @@ } }, }, + previewThumbnails: { + enabled: true, + src: [storyboard_url], + }, settings: ['captions', 'quality', 'speed', 'loop'], }); }()); diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 7c8f5cf..b4f9adf 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -239,6 +239,7 @@ <script src="/youtube.com/static/js/av-merge.js"></script> <script src="/youtube.com/static/js/watch.js"></script> + <script> let storyboard_url = {{ storyboard_url | tojson }} </script> <script src="/youtube.com/static/js/common.js"></script> <script src="/youtube.com/static/js/transcript-table.js"></script> {% if settings.use_video_player == 2 %} diff --git a/youtube/watch.py b/youtube/watch.py index 85b4bd9..a592385 100644 --- a/youtube/watch.py +++ b/youtube/watch.py @@ -15,6 +15,9 @@ import traceback import urllib import re import urllib3.exceptions +from urllib.parse import parse_qs, urlencode +from types import SimpleNamespace +from math import ceil try: with open(os.path.join(settings.data_dir, 'decrypt_function_cache.json'), 'r') as f: @@ -507,6 +510,65 @@ def format_bytes(bytes): return '%.2f%s' % (converted, suffix) +@yt_app.route('/ytl-api/storyboard.vtt') +def get_storyboard_vtt(): + """ + See: + https://github.com/iv-org/invidious/blob/9a8b81fcbe49ff8d88f197b7f731d6bf79fc8087/src/invidious.cr#L3603 + https://github.com/iv-org/invidious/blob/3bb7fbb2f119790ee6675076b31cd990f75f64bb/src/invidious/videos.cr#L623 + """ + + spec_url = request.args.get('spec_url') + url, *l = spec_url.split('|') + url1, q = url.split('?') + q = parse_qs(q) + + storyboard = None + wanted_height = 90 + + for i, board in enumerate(l): + *t, _, sigh = board.split("#") + width, height, count, width_cnt, height_cnt, interval = map(int, t) + if height != wanted_height: continue + q['sigh'] = [sigh] + url = f"{url1}?{urlencode(q, doseq=True)}" + storyboard = SimpleNamespace( + url = url.replace("$L", str(i)).replace("$N", "M$M"), + width = width, + height = height, + interval = interval, + width_cnt = width_cnt, + height_cnt = height_cnt, + storyboard_count = ceil(count / (width_cnt * height_cnt)) + ) + + if not storyboard: + flask.abort(404) + + def to_ts(ms): + s, ms = divmod(ms, 1000) + h, s = divmod(s, 3600) + m, s = divmod(s, 60) + return f"{h:02}:{m:02}:{s:02}.{ms:03}" + + r = "WEBVTT" + ts = 0 + + for i in range(storyboard.storyboard_count): + url = '/' + storyboard.url.replace("$M", str(i)) + interval = storyboard.interval + w, h = storyboard.width, storyboard.height + w_cnt, h_cnt = storyboard.width_cnt, storyboard.height_cnt + + for j in range(h_cnt): + for k in range(w_cnt): + r += f"{to_ts(ts)} --> {to_ts(ts+interval)}\n" + r += f"{url}#xywh={w * k},{h * j},{w},{h}\n\n" + ts += interval + + return flask.Response(r, mimetype='text/vtt') + + time_table = {'h': 3600, 'm': 60, 's': 1} @@ -726,6 +788,8 @@ def get_watch_page(video_id=None): invidious_reload_button = info['invidious_reload_button'], video_url = util.URL_ORIGIN + '/watch?v=' + video_id, video_id = video_id, + storyboard_url = (util.URL_ORIGIN + '/ytl-api/storyboard.vtt?' + + urlencode([('spec_url', info['storyboard_spec_url'])])), js_data = { 'video_id': info['id'], diff --git a/youtube/yt_data_extract/watch_extraction.py b/youtube/yt_data_extract/watch_extraction.py index e7e0d74..309c85c 100644 --- a/youtube/yt_data_extract/watch_extraction.py +++ b/youtube/yt_data_extract/watch_extraction.py @@ -650,6 +650,8 @@ def extract_watch_info(polymer_json): # other stuff info['author_url'] = 'https://www.youtube.com/channel/' + info['author_id'] if info['author_id'] else None + info['storyboard_spec_url'] = player_response['storyboards']['playerStoryboardSpecRenderer']['spec'] + return info single_char_codes = { |