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
- func ExtractPlaylistID(input string) (string, error)
- func ExtractVideoID(input string) (string, error)
- func ExtractVideoIDStrict(input string) (string, error)
- func IsWebClient(name string) bool
- type AttemptID
- type Availability
- type ChannelRef
- type Chapter
- type Client
- func (c *Client) Enumerate(ctx context.Context, playlistID string, o EnumOptions) (*Playlist, error)
- func (c *Client) Extract(ctx context.Context, videoID string) (*Extraction, error)
- func (c *Client) ExtractAttempt(ctx context.Context, videoID string, a AttemptID) (*Extraction, error)
- func (c *Client) ExtractExcluding(ctx context.Context, videoID string, skip map[AttemptID]bool) (*Extraction, error)
- func (c *Client) ExtractWebContext(ctx context.Context, videoID string) (*Extraction, error)
- func (c *Client) ForcedSingleClient() (string, bool)
- func (c *Client) Info(ctx context.Context, videoID string) (*Video, error)
- func (c *Client) Resolve(ctx context.Context, ext *Extraction, formatIndex int) (MediaPlan, error)
- func (c *Client) ResolveUploadsPlaylist(ctx context.Context, ref ChannelRef) (uploadsID, channelID string, err error)
- func (c *Client) ResolveWithFailure(ctx context.Context, ext *Extraction, formatIndex int, ...) (MediaPlan, error)
- func (c *Client) WatchPageMetadata(ctx context.Context, videoID string) (WatchPageMeta, error)
- func (c *Client) WebContextConfigured() bool
- type ClientProfile
- type Config
- type EnumOptions
- type Extraction
- type LiveStatus
- type MediaPlan
- type Playlist
- type PlaylistEntry
- type ResolvedStream
- type SABRStream
- type SABRStreamInfo
- type Thumbnail
- type Video
- type WatchPageMeta
Constants ¶
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 ¶
ExtractPlaylistID extracts a playlist ID from a bare ID or a URL carrying a list= parameter.
func ExtractVideoID ¶
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 ¶
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 ¶
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.
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 (*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 ¶
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 ¶
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 ¶
ForcedSingleClient returns the InnerTube name when exactly one client profile is configured. It also returns true for a single profile override.
func (*Client) Resolve ¶
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 ¶
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 ¶
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.