Merge a03ef74cab4720db25cd7b8e15ab6881ff6b0af8 into d10f2a48021f1768f2d6ff3bd1b9f3de4e0b2d22

This commit is contained in:
Maarten den Braber 2026-08-15 10:25:09 +08:00 committed by GitHub
commit dd1b3ea88e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 2 deletions

View File

@ -9,6 +9,9 @@ enum VideoBadges
VR180
VR360
ClosedCaptions
# Appended rather than inserted: these are @[Flags] bit values, and reordering
# would silently change the meaning of any already-stored badge set.
Shorts
end
struct SearchVideo
@ -133,6 +136,11 @@ struct SearchVideo
json.field "isVr360", self.badges.vr360?
json.field "is3d", self.badges.three_d?
json.field "hasCaptions", self.badges.closed_captions?
# Whether YouTube served this as a Short. Worth stating outright: the
# duration cannot be used to infer it, because YouTube no longer reports a
# real one for Shorts and the parsers substitute an approximate 60s — so a
# genuine 60-second upload is otherwise indistinguishable from a Short.
json.field "isShort", self.badges.shorts?
end
end

View File

@ -156,6 +156,18 @@ private module Parsers
end
end
# A Short is marked by the thumbnail's time-status overlay, which carries
# the literal text "SHORTS" where a duration would be. The length branch
# above already special-cases it — approximating 60s, because YouTube no
# longer reports a real duration for Shorts — but then discards the *fact*,
# leaving clients to re-derive it from that approximated length or from a
# "#shorts" title tag. Neither works: a genuine 60-second upload looks
# identical, and the tag is only a convention.
is_shorts_overlay = item_contents["thumbnailOverlays"]?.try &.as_a.any? do |overlay|
overlay.dig?("thumbnailOverlayTimeStatusRenderer", "text", "simpleText").try &.as_s == "SHORTS"
end
badges |= VideoBadges::Shorts if is_shorts_overlay
SearchVideo.new({
title: title,
id: video_id,
@ -609,6 +621,8 @@ private module Parsers
duration = (minutes*60 + seconds)
# Shorts is certain here rather than inferred: this parser only ever runs
# on a reel renderer, which YouTube uses exclusively for Shorts.
SearchVideo.new({
title: title,
id: video_id,
@ -621,7 +635,7 @@ private module Parsers
premiere_timestamp: Time.unix(0),
author_verified: false,
author_thumbnail: nil,
badges: VideoBadges::None,
badges: VideoBadges::Shorts,
})
end
@ -872,6 +886,10 @@ private module Parsers
# TODO: Maybe use -1 as an error value and handle that on the frontend?
duration = 60_i32
# Shorts is certain here rather than inferred: this parser only ever runs
# on a shorts renderer. Without the badge, the approximated 60s above is
# the only hint a client has left, and it cannot be told apart from a real
# 60-second upload.
SearchVideo.new({
title: title,
id: video_id,
@ -884,7 +902,7 @@ private module Parsers
premiere_timestamp: Time.unix(0),
author_verified: false,
author_thumbnail: nil,
badges: VideoBadges::None,
badges: VideoBadges::Shorts,
})
end