From 250438f287220bfb91241d58f30d2a7a05b6974a Mon Sep 17 00:00:00 2001 From: Maarten den Braber Date: Thu, 30 Jul 2026 15:09:33 +0200 Subject: [PATCH] fix(extractors): detect livestreams from the thumbnail time-status overlay `VideoRendererParser` decides `liveNow` solely from a "LIVE" entry in `videoRenderer.badges`. YouTube no longer always sends one: on the trending feed `badges` is absent entirely, and the only live marker is the thumbnail's time-status overlay, which carries `{"style": "LIVE", "text": "LIVE"}` where a duration would otherwise be. The result is that the list and detail endpoints contradict each other for the same video id: `/api/v1/trending` reports `liveNow: false` while `/api/v1/videos/` reports `liveNow: true`. This is especially visible because trending *is* the livestreams feed now (`fetch_trending` browses the livestreams channel since YouTube removed the aggregated trending page, #5397), so nearly every trending item is affected. Measured 14 of 15 before, 0 of 15 after. Read the overlay as a second source for the LiveNow badge, keeping the existing `badges` parsing unchanged. `length_seconds` deliberately needs no equivalent change: the overlay text is "LIVE", which `decode_length_seconds` already reduces to 0, correct for a stream with no fixed duration. AI disclosure, per AI_POLICY.md: this patch was written with AI assistance. Exact model: Claude Opus 5, model ID `claude-opus-5[1m]`. Tool used to interact with it: Claude Code, Anthropic's agentic CLI. The change was verified against a live patched instance (see the PR for the measurements) and is running in production on the submitter's instance. Submitted by and the responsibility of @mdbraber. --- src/invidious/yt_backend/extractors.cr | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index b2226e74..55668183 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -156,6 +156,22 @@ private module Parsers end end + # A livestream no longer necessarily carries a "LIVE" entry in `badges`. + # On the trending feed — which is the livestreams feed since YouTube + # removed the aggregated trending page — `badges` is absent altogether and + # the only marker is the thumbnail's time-status overlay, which reads + # `{"style": "LIVE", "text": "LIVE"}` in place of a duration. + # + # Without this, every item on that feed is serialised as + # `liveNow: false`, directly contradicting `/api/v1/videos` for the same + # video id. (`length_seconds` needs no equivalent fix: the overlay text is + # "LIVE", which `decode_length_seconds` already reduces to 0 — correct for + # a stream with no fixed duration.) + is_live_overlay = item_contents["thumbnailOverlays"]?.try &.as_a.any? do |overlay| + overlay.dig?("thumbnailOverlayTimeStatusRenderer", "style").try &.as_s == "LIVE" + end + badges |= VideoBadges::LiveNow if is_live_overlay + SearchVideo.new({ title: title, id: video_id,