diff --git a/api/video/poster.py b/api/video/poster.py
index 90f14f97..d82ada3f 100644
--- a/api/video/poster.py
+++ b/api/video/poster.py
@@ -133,7 +133,13 @@ def register_routes(bp):
abort(404)
try:
import requests
- upstream = requests.get(url, timeout=15, stream=True)
+ # A browser UA — Google's image CDN (yt3/googleusercontent) 403s some
+ # avatars when fetched with no User-Agent, which blanked search results.
+ upstream = requests.get(url, timeout=15, stream=True, headers={
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
+ "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
+ })
if upstream.status_code != 200:
abort(404)
resp = Response(upstream.iter_content(8192),
diff --git a/webui/static/video/video-youtube.js b/webui/static/video/video-youtube.js
index 87f8dcef..4234469b 100644
--- a/webui/static/video/video-youtube.js
+++ b/webui/static/video/video-youtube.js
@@ -37,18 +37,21 @@
// policy can't blank them out. Non-YouTube / already-proxied urls pass through.
function img(url) {
if (!url) return url;
- if (/^https:\/\/([\w-]+\.)?(ytimg\.com|ggpht\.com|googleusercontent\.com)\//i.test(url))
+ if (url.indexOf('//') === 0) url = 'https:' + url; // protocol-relative
+ if (/^https:\/\/([\w-]+\.)*(ytimg\.com|ggpht\.com|googleusercontent\.com)\//i.test(url))
return '/api/video/img?u=' + encodeURIComponent(url);
return url;
}
function avatar(ch, cls) {
var url = ch && (ch.poster_url || ch.avatar_url);
+ var ini = esc(initials(ch && ch.title));
if (url) {
- return '
';
+ // If the image can't load, swap to the initials chip (never an empty circle).
+ return '
' + ini + '\'">';
}
- return '' + esc(initials(ch && ch.title)) + '';
+ return '' + ini + '';
}
// A single wished video tile (thumbnail, title, date, remove).