YouTube: fix missing channel poster on the wishlist orb
Flat channel listing doesn't always surface the avatar, so it could be stored null → the orb fell back to plain initials (looked like a missing poster). Two fixes: - Orb falls back to the channel's newest video thumbnail when the avatar is absent, so it's never blank. - Opening the channel page (which resolves the real avatar) now backfills it onto every wished row via set_wishlist_channel_poster — so the actual channel avatar appears on the wishlist orb thereafter. 6 tests green.
This commit is contained in:
parent
591138a8a1
commit
b3fd6037bb
4 changed files with 41 additions and 3 deletions
|
|
@ -147,6 +147,12 @@ def register_routes(bp):
|
|||
return jsonify({"success": False, "error": "Channel not found"}), 404
|
||||
cid = channel["youtube_id"]
|
||||
following = bool(db.channel_watch_state([cid]))
|
||||
# backfill the real avatar onto wished rows (flat listing often omits it)
|
||||
if channel.get("avatar_url"):
|
||||
try:
|
||||
db.set_wishlist_channel_poster(cid, channel["avatar_url"])
|
||||
except Exception:
|
||||
pass
|
||||
wished = db.youtube_video_wish_state([v.get("youtube_id") for v in channel.get("videos") or []])
|
||||
for v in channel.get("videos") or []:
|
||||
v["wished"] = v.get("youtube_id") in wished
|
||||
|
|
|
|||
|
|
@ -1975,6 +1975,22 @@ class VideoDatabase:
|
|||
finally:
|
||||
conn.close()
|
||||
|
||||
def set_wishlist_channel_poster(self, channel_id, poster_url) -> int:
|
||||
"""Refresh the channel avatar on all of a channel's wished video rows — used
|
||||
to backfill avatars that flat listing didn't surface (channel page resolves
|
||||
the real avatar). Returns rows updated."""
|
||||
if not poster_url or not channel_id:
|
||||
return 0
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"UPDATE video_wishlist SET poster_url=? WHERE kind='video' AND parent_source_id=?",
|
||||
(poster_url, str(channel_id)))
|
||||
conn.commit()
|
||||
return cur.rowcount
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def youtube_wishlist_counts(self) -> dict:
|
||||
"""{'channel': n distinct channels, 'video': n videos} in the wishlist."""
|
||||
conn = self._get_connection()
|
||||
|
|
|
|||
|
|
@ -1144,3 +1144,16 @@ def test_upgrade_from_pre_source_schema(tmp_path):
|
|||
assert db.add_videos_to_wishlist({"youtube_id": "UCx", "title": "Chan"},
|
||||
[{"youtube_id": "x1", "title": "X1"}]) == 1
|
||||
assert db.youtube_wishlist_counts() == {"channel": 1, "video": 1}
|
||||
|
||||
|
||||
def test_set_wishlist_channel_poster_backfills_avatar(db):
|
||||
# videos added without an avatar (flat listing omitted it) → poster_url null
|
||||
db.add_videos_to_wishlist({"youtube_id": "UCx", "title": "X"},
|
||||
[{"youtube_id": "a", "title": "A", "published_at": "2024-01-01"},
|
||||
{"youtube_id": "b", "title": "B", "published_at": "2024-02-01"}])
|
||||
grp = db.query_youtube_wishlist()["items"][0]
|
||||
assert not grp["poster_url"]
|
||||
# backfilling the resolved avatar fills every row → the orb gets its poster
|
||||
assert db.set_wishlist_channel_poster("UCx", "http://yt/avatar.jpg") == 2
|
||||
grp = db.query_youtube_wishlist()["items"][0]
|
||||
assert grp["poster_url"] == "http://yt/avatar.jpg"
|
||||
|
|
|
|||
|
|
@ -82,8 +82,11 @@
|
|||
: 'data-vwsh-open-show data-vwsh-src="' + src + '" data-vwsh-id="' + esc(openId) + '"';
|
||||
var hue = hueOf(sh.title);
|
||||
var total = sh.wanted || 0;
|
||||
var img = sh.poster_url
|
||||
? '<img class="wl-orb-img" src="' + esc(sh.poster_url) + '" alt="" ' +
|
||||
// Channel avatar can be missing (flat listing doesn't always surface it) —
|
||||
// fall back to the newest video's thumbnail so the orb is never blank.
|
||||
var poster = sh.poster_url || (yt && (sh.seasons || [])[0] ? sh.seasons[0].poster_url : null);
|
||||
var img = poster
|
||||
? '<img class="wl-orb-img" src="' + esc(poster) + '" alt="" ' +
|
||||
'onerror="this.outerHTML=\'<div class="wl-orb-initials">' + esc(initials(sh.title)) + '</div>\'">'
|
||||
: '<div class="wl-orb-initials">' + esc(initials(sh.title)) + '</div>';
|
||||
// Episodes are shown grouped under a clickable season header (header →
|
||||
|
|
@ -113,7 +116,7 @@
|
|||
// --orb-hue on the GROUP so the music orb styles + my cinematic-expand
|
||||
// backdrop (--vwsh-poster) both resolve; poster bleeds in only when expanded.
|
||||
var gstyle = 'animation-delay:' + Math.min(idx * 45, 700) + 'ms;--orb-hue:' + hue +
|
||||
(sh.poster_url ? ";--vwsh-poster:url('" + esc(sh.poster_url) + "')" : '');
|
||||
(poster ? ";--vwsh-poster:url('" + esc(poster) + "')" : '');
|
||||
var prog = total ? Math.max(0, Math.min(1, (sh.done || 0) / total)) : 0; // #4 acquisition progress
|
||||
// Header is a 3-column row that FLANKS the poster: synopsis (left) · poster
|
||||
// (middle) · cast (right). When collapsed (or no data) the side columns are
|
||||
|
|
|
|||
Loading…
Reference in a new issue