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/<id>` 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.
This commit is contained in:
Maarten den Braber 2026-07-30 15:09:33 +02:00
parent 9d1291a0b8
commit 250438f287

View File

@ -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,