youtube

package
v3.0.0-...-c9850ed Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Overview

Package youtube performs YouTube extraction: it turns a URL into video metadata and candidate audio formats, and resolves those formats into playable, signed stream URLs.

Stability: this package and everything beneath it, notably the internal resolver, are YouTube-specific implementation surfaces. They are exported for the facade where needed but may change between releases. External users should prefer the top-level waxtap package.

Concurrency and identity: a ClientProfile is immutable, with headers cloned on read. Mutable per-attempt state lives in an unexported session, so concurrent extractions do not share writable client identity.

Index

Constants

View Source
const ClientNameWebContext = "WEB_CONTEXT"

ClientNameWebContext is the profile and Extraction.ClientName value reported for the attested WEB player-context streaming path. It names the magic string in one place; library callers can compare it against a Result's client to detect WEB-context delivery.

Variables

This section is empty.

Functions

func ExtractPlaylistID

func ExtractPlaylistID(input string) (string, error)

ExtractPlaylistID extracts a playlist ID from a bare ID or a URL carrying a list= parameter.

func ExtractVideoID

func ExtractVideoID(input string) (string, error)

ExtractVideoID extracts an 11-character video ID from a bare ID or any common YouTube URL form (watch?v=, youtu.be/, /embed/, /shorts/, /v/, /live/), including scheme-less inputs. It also extracts a bounded 11-character ID token from loose text, such as an ID trailed by "&feature=...", which suits pasted download/info targets.

A playlist-only URL (a list= parameter or /playlist path with no video) returns ErrIsPlaylist so the caller can route it to Enumerate. Inputs that carry a candidate of the wrong shape return ErrInvalidVideoID; an all-ID-character token of the wrong length returns ErrVideoIDTooShort or ErrVideoIDTooLong.

func ExtractVideoIDStrict

func ExtractVideoIDStrict(input string) (string, error)

ExtractVideoIDStrict is ExtractVideoID without the loose substring fallback. It still accepts a clean ID and every recognized URL form, but rejects strings that merely embed an 11-character run, such as "aqz-KE-bpKQ.opus" or "/tmp/x/aqz-KE-bpKQ". The process commands use it so a mistyped local path is reported as a missing file.

func IsWebClient

func IsWebClient(name string) bool

IsWebClient reports whether name is the built-in WEB InnerTube client name. It does not match WEB_EMBEDDED.

Types

type AttemptID

type AttemptID string

AttemptID is a stable identifier for one extraction attempt within a client chain. It allows callers to repeat or skip an attempt without relying on non-unique profile names.

const (
	// AttemptWatchPage is the final watch-page scrape attempt.
	AttemptWatchPage AttemptID = "watch-page"
	// AttemptWebContext is the opt-in attested WEB /player-context attempt.
	AttemptWebContext AttemptID = "web-context"
)

type Availability

type Availability uint8

Availability reports whether a video is publicly listed. It is a tri-state so a caller can distinguish a public video from one whose listing state was never determined (no watch-page pass ran).

const (
	// AvailabilityUnknown means the listing state was not determined.
	AvailabilityUnknown Availability = iota
	// AvailabilityPublic marks a public, listed video.
	AvailabilityPublic
	// AvailabilityUnlisted marks an unlisted (link-only) video.
	AvailabilityUnlisted
)

func AvailabilityFromUnlisted

func AvailabilityFromUnlisted(unlisted bool) Availability

AvailabilityFromUnlisted maps a watch-page microformat isUnlisted flag to the listing state. It is called only after a watch-page pass has run, so the state is determined (Public or Unlisted), never Unknown.

func (Availability) String

func (a Availability) String() string

type ChannelRef

type ChannelRef struct {
	// ID is the UC channel ID when directly available (a bare ID or a /channel/
	// URL); a pure UC-to-UU transform yields the uploads playlist.
	ID string
	// URL is the canonical channel URL to resolve when ID is empty (a handle or a
	// /c/ or /user/ vanity name).
	URL string
}

ChannelRef is a classified channel reference produced by ExtractChannelRef and resolved to an uploads playlist by Client.ResolveUploadsPlaylist. Exactly one of ID or URL is set.

func ExtractChannelRef

func ExtractChannelRef(input string) (ChannelRef, error)

ExtractChannelRef classifies a channel reference: a bare UC channel ID, or a URL of the form /channel/<id>, /@handle, /c/name, or /user/name, each with an optional trailing tab segment such as /videos, /streams, or /shorts, which is stripped. A bare @handle (no host) is also accepted. Anything else returns an error, so a caller can fall through to playlist handling.

type Chapter

type Chapter struct {
	Title string        // chapter title
	Start time.Duration // chapter start offset
	// End is the chapter's end offset, derived from the next chapter's start (or
	// the video duration for the last chapter). It is zero when the chapter is
	// open-ended: the last chapter of a video whose duration is unknown or not
	// past the chapter start.
	End time.Duration
}

Chapter marks a titled section within the video.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client performs YouTube extraction. It holds configuration and injected dependencies, while mutable per-attempt identity lives in session. It is safe for concurrent use after construction.

func New

func New(cfg Config) *Client

New returns a Client, applying defaults for unset Config fields.

func (*Client) Enumerate

func (c *Client) Enumerate(ctx context.Context, playlistID string, o EnumOptions) (*Playlist, error)

Enumerate expands a playlist into lightweight entries without downloading. It pages through continuations until exhausted, MaxItems is reached, or Stop halts paging. Per-page failures after the first page are collected in Playlist.Errors rather than discarding the entries already gathered.

func (*Client) Extract

func (c *Client) Extract(ctx context.Context, videoID string) (*Extraction, error)

Extract fetches metadata and candidate audio formats for videoID, trying the client strategy chain in order until one succeeds. Each attempt uses an immutable profile and a shared per-extraction session; the winning profile and session are carried in the returned Extraction so stream resolution uses the same identity.

func (*Client) ExtractAttempt

func (c *Client) ExtractAttempt(ctx context.Context, videoID string, a AttemptID) (*Extraction, error)

ExtractAttempt repeats one profile or watch-page attempt. Refresh and SABR reload paths use it to remain on the client that produced the original stream. Use ExtractWebContext to repeat the web-context attempt.

func (*Client) ExtractExcluding

func (c *Client) ExtractExcluding(ctx context.Context, videoID string, skip map[AttemptID]bool) (*Extraction, error)

ExtractExcluding behaves like Extract but skips the specified attempts. It returns the highest-precedence error when all remaining attempts fail. Cancellation and rate limiting stop the chain immediately.

func (*Client) ExtractWebContext

func (c *Client) ExtractWebContext(ctx context.Context, videoID string) (*Extraction, error)

ExtractWebContext builds an Extraction from an attested /player streaming context supplied by the configured PlayerContextProvider. The result uses the normal SABR resolution and download path: buildSABRConfig descrambles the URL's n parameter and mints a GVS PO token bound to the context's visitorData.

videoID identifies the video to the provider and is stored on the returned Extraction so a mid-stream RELOAD can re-fetch a fresh context (see SABRStream.reextract).

Config.WebContextTimeout bounds every provider call, including mid-stream refreshes. Provider failures return ProviderError so callers can fall back; cancellation of the caller's own context is propagated unwrapped.

func (*Client) ForcedSingleClient

func (c *Client) ForcedSingleClient() (string, bool)

ForcedSingleClient returns the InnerTube name when exactly one client profile is configured. It also returns true for a single profile override.

func (*Client) Info

func (c *Client) Info(ctx context.Context, videoID string) (*Video, error)

Info returns video metadata and candidate formats.

func (*Client) Resolve

func (c *Client) Resolve(ctx context.Context, ext *Extraction, formatIndex int) (MediaPlan, error)

Resolve builds a direct or SABR MediaPlan for the candidate at formatIndex. It reuses the client profile and session that won extraction so the media is fetched under the same YouTube identity.

Resolution is index-based rather than itag-based because itags can repeat across languages and DRC variants. rawAudio[i] is kept parallel to Video.Formats[i] so the selected public format maps to the right raw format.

func (*Client) ResolveUploadsPlaylist

func (c *Client) ResolveUploadsPlaylist(ctx context.Context, ref ChannelRef) (uploadsID, channelID string, err error)

ResolveUploadsPlaylist resolves a channel reference to its uploads playlist ID (UU) and the channel's canonical UC ID. A direct channel ID is a pure UC-to-UU transform; a handle or vanity name is resolved to the channel ID first, via the InnerTube navigation/resolve_url endpoint with a watch/channel-page scrape fallback.

func (*Client) ResolveWithFailure

func (c *Client) ResolveWithFailure(ctx context.Context, ext *Extraction, formatIndex int, failure *potoken.HTTPFailure) (MediaPlan, error)

ResolveWithFailure resolves the selected format like Resolve and passes the HTTP failure that caused a refresh to the PO-token provider. Initial resolution passes nil. When refreshing an expired signed URL, call this with a fresh Extraction; the old player response still contains the old URL.

func (*Client) WatchPageMetadata

func (c *Client) WatchPageMetadata(ctx context.Context, videoID string) (WatchPageMeta, error)

WatchPageMetadata fetches the watch page for videoID and returns the metadata only the WEB watch page carries: publish date, chapters, and unlisted state. It is the token-free enrichment behind waxtap.WithFullMetadata for extractions that did not already scrape the watch page.

Context cancellation is returned as a fatal error. A fetch or parse failure is also returned, but callers treating enrichment as best-effort should keep the base metadata when the context was not canceled.

func (*Client) WebContextConfigured

func (c *Client) WebContextConfigured() bool

WebContextConfigured reports whether a WEB player-context provider is wired, enabling the opt-in WEB SABR audio path.

type ClientProfile

type ClientProfile struct {
	Name          string // internal name, e.g. "ANDROID_VR"
	InnerTubeName string // InnerTube context clientName, e.g. "ANDROID_VR"
	InnerTubeID   int    // drives X-Youtube-Client-Name (never hardcoded)
	Version       string // InnerTube client version
	APIKey        string // optional InnerTube API key
	UserAgent     string // request User-Agent

	// Device and OS fields sent in the InnerTube client context. Mobile and VR
	// clients need these populated; sparse identities are more likely to hit the
	// bot-check path. Zero values are omitted from the request.
	DeviceMake        string
	DeviceModel       string // client device model
	OSName            string // client operating-system name
	OSVersion         string // client operating-system version
	AndroidSDKVersion int    // Android SDK level, or 0 when not applicable

	// RequiresPOTokens lists the PO-token scopes this client must supply: a
	// player-scope token in the /player body, a GVS token on the stream URL,
	// or both. Empty means none. NewClientProfile clones and canonicalizes
	// the slice.
	RequiresPOTokens  []potoken.Scope
	SupportsCookies   bool // whether cookie-backed sessions are supported
	SupportsPlaylists bool // whether the profile can browse playlists

	// NeedsSignatureTimestamp indicates that /player requests for this profile
	// must include the signature timestamp from base.js. Profiles that return
	// direct URLs should leave it false to avoid loading base.js during extraction.
	NeedsSignatureTimestamp bool

	// EmbedURL is sent as context.thirdParty.embedUrl on /player requests, where
	// WEB_EMBEDDED_PLAYER needs a third-party embed origin (not youtube.com) or it
	// returns a playability ERROR.
	EmbedURL string
	// contains filtered or unexported fields
}

ClientProfile identifies an InnerTube client such as ANDROID_VR, iOS, or WEB. The header map is unexported and every accessor returns a clone, so callers cannot mutate a profile after construction.

func BuildClientChain

func BuildClientChain(name string, chromeMajor int) ([]ClientProfile, error)

BuildClientChain returns a one-element strategy chain for a single built-in client, forcing it as the whole chain instead of the default multi-client fallback. WEB-family clients receive the same User-Agent / Chrome-major treatment buildDefaultProfiles applies; native clients keep their own User-Agent (chromeMajor does not apply to them). An unknown name is an error.

A single forced client is trivially a uniform chain, which is what external session adoption requires.

func BuildProfile

func BuildProfile(base ClientProfile) ClientProfile

BuildProfile derives the static InnerTube request headers from base and returns an isolated profile. Use it for configured profiles so headers such as X-Youtube-Client-Name stay tied to the scalar client identity.

func DefaultProfiles

func DefaultProfiles() []ClientProfile

DefaultProfiles returns the ordered client strategy chain using the default built-in Chrome identity. Token-free clients and clients capable of full-track delivery are tried before clients with observed range or token restrictions.

func NewClientProfile

func NewClientProfile(base ClientProfile, headers map[string]string) ClientProfile

NewClientProfile returns base with headers deep-copied in, yielding a profile that owns an isolated header map callers cannot mutate.

func (ClientProfile) Header

func (p ClientProfile) Header(key string) string

Header returns a single header value.

func (ClientProfile) Headers

func (p ClientProfile) Headers() map[string]string

Headers returns a copy of the profile's headers; mutating the result does not affect the profile.

func (ClientProfile) WithHeader

func (p ClientProfile) WithHeader(key, value string) ClientProfile

WithHeader returns a copy of the profile with key set to value; the receiver is unchanged.

type Config

type Config struct {
	// HTTP is the retrying HTTP client. If nil, a default httpx.Client is used.
	HTTP *httpx.Client
	// Logger receives debug logs. If nil, logging is discarded.
	Logger *slog.Logger
	// Profiles overrides the client strategy chain. If empty, DefaultProfiles().
	Profiles []ClientProfile
	// ChromeMajor overrides the emulated Chrome major for built-in WEB-family
	// identities. It applies to the default profile chain and to built-in WEB
	// requests used for discovery and fallbacks. Zero selects the built-in
	// default. Caller-supplied Profiles are unchanged.
	ChromeMajor int
	// Resolver resolves candidate formats into playable URLs. If nil, New builds
	// a default base.js/goja resolver over the same HTTP client. Metadata
	// extraction does not need one; stream resolution (Resolve) does.
	Resolver resolver.Resolver
	// POTokenProvider supplies PO tokens when a profile requires one. WaxTap may
	// call it during extraction for a player-scope token and during resolution for
	// a GVS-scope stream token. Nil means no provider is configured.
	POTokenProvider potoken.Provider
	// PlayerContextProvider supplies an attested WEB /player streaming context,
	// enabling the opt-in WEB SABR audio path (see Client.ExtractWebContext). Nil
	// leaves WaxTap on its default extraction chain.
	PlayerContextProvider potoken.PlayerContextProvider
	// WebContextTimeout bounds each PlayerContextProvider call, both the initial
	// extraction and a mid-stream reload's re-fetch, so a hung provider cannot
	// hang a download. Zero adds no bound.
	WebContextTimeout time.Duration
	// Session is an externally supplied guest identity the Client adopts verbatim,
	// skipping its own homepage bootstrap. It is pre-resolved: its cookies are
	// seeded at New and its visitorData is used for every extraction. Use it for
	// byte-exact session coherence with a PO-token minter. The caller must select
	// a uniform client chain (the facade enforces this); nil leaves the built-in
	// bootstrap in place.
	Session *potoken.Session
	// SessionProvider resolves an external guest identity lazily, at most once per
	// Client (cached on success). It is the pull-based form of Session. At most one
	// of Session / SessionProvider may be set.
	SessionProvider potoken.SessionProvider
	// ResolveTimeout bounds each cipher JS execution during resolution. Zero
	// uses the resolver default.
	ResolveTimeout time.Duration
	// CacheDir is the base directory for on-disk caches. When set, and when
	// DisableDiskCache is false, the default resolver stores base.js source under
	// CacheDir/players. Empty leaves the resolver memory-only. Ignored when a
	// Resolver is injected.
	CacheDir string
	// DisableDiskCache turns off the on-disk base.js source cache even when
	// CacheDir is set.
	DisableDiskCache bool
	// HL (host language, e.g. "en") and GL (content region, e.g. "US") set the
	// InnerTube locale. Empty defaults to en / US. These are localization hints:
	// geo-restricted availability is still governed by the request IP.
	HL string
	GL string // content-region hint
}

Config configures a Client.

type EnumOptions

type EnumOptions struct {
	// MaxItems caps the returned entries; <= 0 lists every entry.
	MaxItems int
	// OnPage reports the running entry count after each successfully appended page.
	OnPage func(count int)
	// Skip omits matching entries but keeps paging, for an archive cursor over an
	// arbitrary playlist. It is applied before the MaxItems cap so the cap counts
	// unseen entries, and skipped entries still advance the index so
	// PlaylistEntry.Index stays the true playlist position.
	Skip func(id string) bool
	// Stop halts pagination at the first matching entry (excluding it and every
	// entry after it) and leaves Playlist.Continuation empty. It is only correct on
	// an append-only newest-first feed (a channel uploads playlist); a curated PL
	// can insert entries anywhere, so Stop on one would drop items. Use Skip for the
	// general case. Stop is checked before Skip.
	Stop func(id string) bool
	// ChannelID, when set, is stamped onto every entry. A channel-feed enumeration
	// already knows the uploader, so no per-video Enrich is needed.
	ChannelID string
}

EnumOptions configures Client.Enumerate. The facade builds one from its own EnumerateOptions plus any resolved channel ID.

type Extraction

type Extraction struct {
	// contains filtered or unexported fields
}

Extraction contains video metadata plus the client profile and session needed to resolve and download its formats with the same identity.

func (*Extraction) Attempt

func (e *Extraction) Attempt() AttemptID

Attempt returns the chain attempt that produced this Extraction.

func (*Extraction) ClientName

func (e *Extraction) ClientName() string

ClientName returns the winning client's display name. Use Attempt when a unique attempt identifier is required.

func (*Extraction) SubstitutedFrom

func (e *Extraction) SubstitutedFrom() string

SubstitutedFrom returns the name of a forced non-WEB client replaced by the watch-page WEB fallback. It returns an empty string when no substitution occurred.

func (*Extraction) Video

func (e *Extraction) Video() *Video

Video returns the extracted metadata and candidate formats.

type LiveStatus

type LiveStatus uint8

LiveStatus reports a video's live-broadcast state.

const (
	// LiveNone marks a normal, non-live video.
	LiveNone LiveStatus = iota
	// LiveUpcoming marks a scheduled premiere or upcoming stream.
	LiveUpcoming
	// LiveNow marks a currently-live stream.
	LiveNow
	// LiveWasLive marks a completed livestream VOD.
	LiveWasLive
)

func (LiveStatus) String

func (s LiveStatus) String() string

type MediaPlan

type MediaPlan struct {
	Direct *ResolvedStream // direct signed-URL stream
	SABR   *SABRStream     // SABR-backed stream
}

MediaPlan describes how to fetch a selected format. Exactly one of Direct or SABR is non-nil.

func (MediaPlan) Diagnostic

func (m MediaPlan) Diagnostic() ResolvedStream

Diagnostic returns the metadata available without opening the stream.

type Playlist

type Playlist struct {
	ID           string          // YouTube playlist ID
	Title        string          // playlist title
	Author       string          // playlist owner name
	Entries      []PlaylistEntry // entries in playlist order
	Errors       []error         // per-entry failures (partial enumeration is not fatal)
	Continuation string          // opaque token for the next page; "" when exhausted
}

Playlist is the result of enumerating a playlist URL. Enumeration is tolerant: one bad entry is collected in Errors rather than failing the list.

type PlaylistEntry

type PlaylistEntry struct {
	VideoID   string        // YouTube video ID
	Title     string        // entry title
	Author    string        // channel or uploader name
	ChannelID string        // uploader's channel ID (UC), or "" when not resolved at enumerate time
	Duration  time.Duration // video duration, or 0 when unknown
	Index     int           // 0-based position within the playlist
}

PlaylistEntry is a lightweight playlist item. Enumeration does not download media, and full per-video metadata enrichment is opt-in.

type ResolvedStream

type ResolvedStream struct {
	URL           string      // signed media URL; empty for SABR streams
	ExpiresAt     time.Time   // URL expiry, or zero when unknown
	ContentLength int64       // bytes, or 0 when unknown
	Headers       http.Header // headers required when fetching URL
	// IsSABR reports whether the stream must be fetched through SABR.
	IsSABR bool
}

ResolvedStream contains the metadata available after resolution. Direct streams include a URL and request headers; SABR streams set IsSABR and leave URL empty.

func (ResolvedStream) Probeable

func (rs ResolvedStream) Probeable() bool

Probeable reports whether the stream has a direct URL suitable for a local probe.

type SABRStream

type SABRStream struct {
	// contains filtered or unexported fields
}

SABRStream represents a SABR-backed audio stream. SABR formats have no direct media URL; Open fetches their bytes from serverAbrStreamingUrl.

func (*SABRStream) Open

func (s *SABRStream) Open(ctx context.Context, progress func(bytesWritten, total int64)) (io.ReadCloser, SABRStreamInfo, error)

Open starts the SABR stream and returns a reader over the reassembled audio. It sends the first request before returning, so initial protocol and authentication failures are returned by Open. Later failures are returned by Read.

Open retries a bounded number of player reloads and GVS PO-token rejections. progress receives byte counts and may be nil.

func (*SABRStream) PrimeToken

func (s *SABRStream) PrimeToken(ctx context.Context) error

PrimeToken mints the GVS PO token before Open so provider failures surface while the caller can still use a fallback. The first Open consumes the token; later opens, reloads, and refreshes mint a new one. It is a no-op when the selected profile does not require a GVS token.

type SABRStreamInfo

type SABRStreamInfo struct {
	ContentLength int64  // bytes, or 0 when unknown
	ContentType   string // MIME type reported by the SABR response
}

SABRStreamInfo describes an open SABR stream.

type Thumbnail

type Thumbnail struct {
	URL    string // image URL
	Width  int    // pixel width, or 0 when unknown
	Height int    // pixel height, or 0 when unknown
}

Thumbnail is one cover-art candidate.

type Video

type Video struct {
	ID          string        // YouTube video ID (canonical, stable identity anchor)
	URL         string        // canonical watch URL (https://www.youtube.com/watch?v=<ID>)
	Title       string        // video title
	Author      string        // channel / uploader name
	ChannelID   string        // YouTube channel ID (canonical UC identity anchor)
	Duration    time.Duration // video duration, or 0 when unknown
	PublishDate time.Time     // publication date, or zero when unknown
	Description string        // video description

	Thumbnails []Thumbnail // cover-art candidates, largest first
	Chapters   []Chapter   // chapter markers in playback order

	Formats []format.Format // candidate audio (and incidental video) formats

	// LiveStatus reports the broadcast state. On a Video returned by extraction it
	// is LiveNone or LiveWasLive (a completed VOD): currently-live and upcoming
	// videos are rejected earlier and surface as ErrLiveContent / ErrLiveNotStarted
	// sentinels instead of a Video.
	LiveStatus LiveStatus
	// Availability reports whether the video is publicly listed. It is set to
	// AvailabilityPublic or AvailabilityUnlisted only on a watch-page metadata pass
	// (the microformat that carries it is WEB-only); otherwise it is
	// AvailabilityUnknown. Restricted states (private, members-only, geo-blocked,
	// removed, age-gated) surface as availability sentinels, not a Video.
	Availability Availability
}

Video is the extracted metadata model. YouTube exposes channel metadata rather than authoritative artist tags, so WaxTap returns the raw fields and leaves tagging policy to the caller.

type WatchPageMeta

type WatchPageMeta struct {
	PublishDate time.Time // zero when the page carried none
	Chapters    []Chapter // nil when the video has no chapters
	Unlisted    bool      // the video is unlisted (link-only)
}

WatchPageMeta is the metadata a watch-page fetch adds beyond a /player response: the publish date and unlisted state from the WEB microformat, and the chapters from ytInitialData.

Directories

Path Synopsis
internal
resolver
Package resolver isolates YouTube's volatile player JavaScript.
Package resolver isolates YouTube's volatile player JavaScript.
sabr
Package sabr streams YouTube SABR audio over UMP.
Package sabr streams YouTube SABR audio over UMP.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL