diff --git a/internal/adapters/youtube/captions.go b/internal/adapters/youtube/captions.go index 5797bdc..de87d88 100644 --- a/internal/adapters/youtube/captions.go +++ b/internal/adapters/youtube/captions.go @@ -21,6 +21,11 @@ const defaultPlayerBaseURL = "https://www.youtube.com" // returns caption baseUrls without a PoToken requirement (ADR-010). const androidUserAgent = "com.google.android.youtube/20.10.38 (Linux; U; Android 11) gzip" +// browserUserAgent is required for the watch-page scrape: with an app UA YouTube +// serves a page WITHOUT the embedded ytInitialPlayerResponse, so captionTracks +// come back empty. A desktop-browser UA returns the player JSON with captions. +const browserUserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" + // maxCaptionBytes bounds a single timedtext/player body read. const maxCaptionBytes = 16 << 20 // 16 MiB @@ -134,7 +139,7 @@ const ytInitialMarker = "ytInitialPlayerResponse" // embedded ytInitialPlayerResponse JSON. Any failure degrades to no tracks. func (a *Adapter) scrapeCaptionTracks(ctx context.Context, client *http.Client, videoID string) ([]captionTrack, error) { body, status, err := a.httpGet(ctx, client, a.playerBaseURL+"/watch?v="+videoID, map[string]string{ - "User-Agent": androidUserAgent, + "User-Agent": browserUserAgent, }) if err != nil { return nil, err