From b8715125f870d0fa68e9e6345a6b4f0e27aab7f0 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Sun, 29 May 2022 15:34:18 +0200 Subject: [PATCH] add endcard --- youtube/templates/watch.html | 24 +++++++++++++++-- youtube/watch.py | 1 + youtube/yt_data_extract/common.py | 9 +++++++ youtube/yt_data_extract/watch_extraction.py | 30 ++++++++++++++++++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 69280c8b..89835974 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -150,6 +150,7 @@ "external-player-controls v-checkbox" "v-direct-link v-direct-link" "v-download v-download" + "v-endcard v-endcard" "v-description v-description" "v-music-list v-music-list" "v-more-info v-more-info"; @@ -217,6 +218,13 @@ .video-info > .download-dropdown{ grid-area: v-download; } + .video-info > .endcard-dropdown{ + margin-top: 2px; + grid-area: v-endcard; + } + .video-info > .endcard-dropdown .item-box { + width: 100%; + } .video-info > .description{ background-color:var(--interface-color); margin-top:8px; @@ -343,7 +351,7 @@ width: 120px !important; } - .download-dropdown-content{ + .dropdown-content { background-color: var(--interface-color); padding: 10px; list-style: none; @@ -428,6 +436,7 @@ "external-player-controls v-checkbox" "v-direct-link v-direct-link" "v-download v-download" + "v-endcard v-endcard" "v-description v-description" "v-music-list v-music-list" "v-more-info v-more-info"; @@ -532,7 +541,7 @@

{{ title }}

Download -
+ {% if endcard %} +
+ Endcard + +
+ {% endif %} {{ common_elements.text_runs(description)|escape|urlize|timestamps|safe }}
diff --git a/youtube/watch.py b/youtube/watch.py index c9d75eca..23bb19d4 100644 --- a/youtube/watch.py +++ b/youtube/watch.py @@ -722,6 +722,7 @@ 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, + endcard = info['endcard'], js_data = { 'video_id': info['id'], diff --git a/youtube/yt_data_extract/common.py b/youtube/yt_data_extract/common.py index 4951cd95..2a843373 100644 --- a/youtube/yt_data_extract/common.py +++ b/youtube/yt_data_extract/common.py @@ -570,3 +570,12 @@ def extract_items(response, item_types=_item_types, ctoken = new_ctoken return items, ctoken + +def dbg_assert(val, msg): + import os + if val: return + + if os.getenv('YT_EXTR_DBG'): + raise AssertionError(msg) + else: + print(f"W: {msg}") diff --git a/youtube/yt_data_extract/watch_extraction.py b/youtube/yt_data_extract/watch_extraction.py index e419020c..c4e28179 100644 --- a/youtube/yt_data_extract/watch_extraction.py +++ b/youtube/yt_data_extract/watch_extraction.py @@ -3,7 +3,7 @@ extract_str, extract_formatted_text, extract_int, extract_approx_int, extract_date, check_missing_keys, extract_item_info, extract_items, extract_response, concat_or_none, liberal_dict_update, - conservative_dict_update) + conservative_dict_update, dbg_assert) import json import urllib.parse @@ -752,6 +752,27 @@ def extract_watch_info_from_html(watch_html): def captions_available(info): return bool(info['_captions_base_url']) +def parse_endcard(card): + """ + parses a single endcard into a format that's easier to handle. + from: https://git.gir.st/subscriptionfeed.git/blob/737a2f6f:/app/common/innertube.py#l301 + """ + card = card.get('endscreenElementRenderer', card) #only sometimes nested + ctype = card['style'].lower() + if ctype == "video": + if not 'endpoint' in card: return None # title == "This video is unavailable." + video_id = card['endpoint']['watchEndpoint']['videoId'] + return {'type': ctype, + 'video_id': video_id, + 'title': extract_str(card['title']), + 'approx_view_count': extract_str(card['metadata']), + 'thumbnail': f"/https://i.ytimg.com/vi/{video_id}/default.jpg", + 'duration': extract_str(card["thumbnailOverlays"][0]["thumbnailOverlayTimeStatusRenderer"]["text"]) + # XXX: no channel name + } + else: + dbg_assert(False, f"unknown ctype: {ctype}") + def get_caption_url(info, language, format, automatic=False, translation_language=None): '''Gets the url for captions with the given language and format. If automatic is True, get the automatic captions for that language. If translation_language is given, translate the captions from `language` to `translation_language`. If automatic is true and translation_language is given, the automatic captions will be translated.''' @@ -780,6 +801,13 @@ def update_with_age_restricted_info(info, player_response): info['playability_error'] = ERROR_PREFIX + 'Failed to parse json response' return + info['endcard'] = [] + for e in deep_get(player_response, "endscreen", "endscreenRenderer", "elements", default=[]): + e = parse_endcard(e["endscreenElementRenderer"]) + if not e: continue + info['endcard'].append(e) + print(info['endcard'][-1]) + _extract_formats(info, player_response) _extract_playability_error(info, player_response, error_prefix=ERROR_PREFIX)