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
-
+
{% for format in download_formats %}
-
@@ -559,6 +568,17 @@
{{ title }}
+ {% if endcard %}
+
+ Endcard
+
+ {% for e in endcard %}
+ {{ common_elements.item(e) }}
+
+ {% endfor %}
+
+
+ {% 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)