waxtap

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 28 Imported by: 0

README

WaxTap

WaxTap is an audio-focused YouTube downloader and audio processor for Go. It is available as both a reusable library and the waxtap CLI, with both surfaces built on the same core.

WaxTap can download the best available YouTube audio or process an existing local file. Processing stages are opt-in: transcode, cut explicit time ranges, remove SponsorBlock segments, measure loudness, and normalize loudness. A plain download keeps the selected source stream and does not re-encode.

WaxTap targets public videos. Private, age-restricted, and login-gated videos are expected failures, not bypass targets. YouTube changes its player and anti-bot behavior without notice; see MAINTENANCE.md for the recovery runbook and runtime knobs.

Design at a glance

  • Library and CLI over one core. github.com/colespringer/waxtap is the stable facade; cmd/waxtap is a real CLI built on the same packages.
  • Pure-Go extraction (InnerTube + goja for the cipher). No yt-dlp.
  • Volatile surfaces are isolated behind small interfaces (youtube, youtube/internal/resolver) so a YouTube change touches few, marked files.
  • Server-friendly: concurrency-safe, context-cancelable, bounded memory, per-operation timeouts (never a single global cap), atomic temp-file output.
  • Clear about quality: YouTube audio is lossy; FLAC/ALAC/WAV are lossless re-encodes of a lossy source. Only copy/remux avoids re-encoding.

Package layout

Package Role
waxtap (root) Stable facade: Client, Request/Result, Options.
cmd/waxtap The CLI (cobra): download, info, cut, normalize, doctor, …
format Audio-first Format model and selectors.
download Resilient ranged/streaming download (parallel chunks, expiry refresh).
transcode ffmpeg/ffprobe execution home (codecs, probing).
cut Time-range cut + SponsorBlock bridge (composes transcode).
normalize Loudness measure/normalize (EBU R128; track and album).
sponsorblock SponsorBlock client + category vocabulary.
potoken PO-token provider contract (caller-supplied).
waxerr The domain error taxonomy (one errors.Is source of truth).
youtube YouTube extraction (volatile; exported for the facade, may churn).
youtube/internal/resolver Cipher / base.js / stream-URL resolution.
internal/pipeline Fused probe → cut → loudness → encode pipeline.
internal/httpx HTTP client: retry, backoff, Retry-After, per-host limiter.
internal/cache In-memory LRU+TTL+singleflight cache.
internal/diskcache On-disk, size-capped, schema-versioned player-JS cache.
internal/tempfile Atomic temp-output staging + cleanup contract.

Requirements

  • Go 1.26+
  • ffmpeg / ffprobe on PATH (for transcode/cut/normalize; not needed for plain metadata or best-source downloads).

Install

Install either the CLI or the Go package. The CLI is meant to run from a shell; the release archives do not install a desktop app. See Using the prebuilt binaries for platform notes.

With Go:

go install github.com/colespringer/waxtap/cmd/waxtap@latest

This installs waxtap into $(go env GOBIN) (or $(go env GOPATH)/bin); make sure that directory is on your PATH.

Prebuilt binaries: tagged releases include Linux, macOS, and Windows archives (amd64/arm64) on the GitHub Releases page.

Library:

go get github.com/colespringer/waxtap
Using the prebuilt binaries

Each release archive contains the waxtap executable and documentation. Extract the archive for your platform, put the executable somewhere on your PATH, then run it from a terminal like any other CLI.

Linux / macOS:

# 1. Extract the archive for your platform
tar -xzf waxtap_*_linux_amd64.tar.gz      # or _darwin_arm64, etc.

# 2. Move it to a directory on your PATH
sudo mv waxtap /usr/local/bin/            # or ~/.local/bin, ~/bin, etc.

# 3. Run it from a terminal
waxtap --help

The archive preserves the executable bit; a standalone downloaded binary may need chmod +x waxtap first.

macOS Gatekeeper: the binaries are not code-signed, so macOS may block the first launch ("cannot be opened because Apple cannot check it for malware" / "developer cannot be verified"). Clear the quarantine flag for the installed binary:

xattr -d com.apple.quarantine /usr/local/bin/waxtap

You can also right-click the binary in Finder and choose Open the first time.

Windows:

  1. Unzip waxtap_*_windows_amd64.zip.
  2. Move waxtap.exe into a folder on your PATH (or add its folder to PATH).
  3. Open PowerShell or Command Prompt and run waxtap --help.

SmartScreen: because the .exe is not signed, Windows may show "Windows protected your PC". Choose More info > Run anyway on first launch.

Usage

CLI

Media commands accept a YouTube URL or bare video/playlist ID and support --json for a stable scriptable contract. cut, transcode, and normalize also accept a local file, so no network is needed for local processing. Every command has --help.

# Inspect audio formats (no download)
waxtap info <video-url>
waxtap formats <video-url>

# Download the best audio with no re-encode (the default, keep-source)
waxtap download <video-url> -o track

# Download and transcode to FLAC in a single ffmpeg pass
waxtap download <video-url> --transcode flac -o track.flac

# Remove SponsorBlock non-music segments and normalize loudness in one pass
waxtap download <video-url> --cut-sponsorblock --transcode mp3 --normalize --loudness-target -14 -o track.mp3

# Process a LOCAL file (no network)
waxtap transcode song.flac song.mp3
waxtap normalize song.wav --apply --target -14 --transcode flac -o song.flac

# Download a whole playlist into a directory, skipping already-fetched IDs
waxtap download <playlist-url> -d ./music --download-archive seen.txt

# Check extraction health
waxtap doctor
Library
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
	"github.com/colespringer/waxtap/sponsorblock"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	// Download the best audio, remove SponsorBlock "music_offtopic" segments, and
	// transcode to FLAC in one ffmpeg pass.
	res, err := client.Download(context.Background(), waxtap.Request{
		URL: "https://youtu.be/VIDEO_ID_01",
		ProcessSpec: waxtap.ProcessSpec{
			Transcode: &waxtap.TranscodeSpec{Format: waxtap.FormatFLAC},
			Cut: &waxtap.CutSpec{
				SponsorBlock: []sponsorblock.Category{sponsorblock.CategoryMusicOffTopic},
				OnError:      waxtap.ProceedUncut,
			},
			Output: waxtap.ToFile("track.flac"),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s -> %s (%d bytes)\n", res.VideoID, res.OutputPath, res.OutputBytes)
}

The default Download does no re-encode; all processing is opt-in. See example_test.go for Stream, Process (local files), Enumerate (playlists), MeasureAlbum, and Info.

Configuration

CLI configuration is resolved in this order: explicit flag, WAXTAP_* environment variable, JSON config file, built-in default. The default config file is config.json under os.UserConfigDir()/waxtap; override it with --config or WAXTAP_CONFIG.

Useful operational knobs:

  • Cache: waxtap cache dir, waxtap cache clean, --cache-dir, WAXTAP_CACHE_DIR, --no-cache, WAXTAP_NO_CACHE.
  • Runtime client refresh: --profile-override, WAXTAP_PROFILE_OVERRIDE, or profileOverridePath in config JSON.
  • Network posture: --proxy, --qps, --hl, --gl, and their documented WAXTAP_* equivalents.
  • Diagnostics: set WAXTAP_DUMP_DIR to write unusable YouTube responses on extraction failures.

ffmpeg and ffprobe are required only for processing or probing. Plain metadata, stream resolution, and keep-source downloads do not need them.

Maintenance

YouTube's player and anti-bot surfaces change without notice. WaxTap isolates that volatility behind a few marked files and ships operational controls: client-profile override files, env-gated artifact dumps, a persistent player cache, and a doctor health check suitable for cron or container health probes. See MAINTENANCE.md for the full breakage-response runbook.

Acknowledgements

WaxTap was heavily influenced by kkdai/youtube and yt-dlp.

WaxTap is an independent implementation: it ships no code from either project and does not invoke yt-dlp at runtime.

Disclaimer

WaxTap is for personal and otherwise authorized use only. You are responsible for complying with YouTube's Terms of Service and applicable law. WaxTap is public-video only: private, age-restricted, and login-gated videos are expected failures, not something WaxTap promises to bypass.

License

MIT © Cole Springer.

Documentation

Overview

Package waxtap provides the public WaxTap API for acquiring and processing audio.

WaxTap can take audio from YouTube or from a local file. Processing stages such as transcoding, cutting, SponsorBlock removal, loudness measurement, and loudness normalization are opt-in. By default, downloads keep the selected source stream and do not re-encode it.

This top-level package is the stable pre-1.0 surface. The youtube package and packages below it are YouTube-specific implementation surfaces; they are exported where the facade needs them, but external callers should prefer this package.

Type ownership

To keep the dependency graph acyclic, contract types are defined in the package that owns the behavior and re-exported here for convenience:

  • audio formats and selectors: package format
  • the PO-token provider contract: package potoken
  • the SponsorBlock category vocabulary: package sponsorblock
  • the error taxonomy: package waxerr
  • extraction models (Video, Playlist): package youtube

Callers can work entirely through waxtap, using names such as BestAudio, ErrVideoUnavailable, and POTokenProvider.

Index

Examples

Constants

View Source
const (
	Unknown = format.Unknown
	Yes     = format.Yes
	No      = format.No
)

Tri values.

Variables

View Source
var (
	// YouTube extraction maintenance signals.
	ErrExtractionFailed = waxerr.ErrExtractionFailed
	ErrCipherSolve      = waxerr.ErrCipherSolve
	ErrNeedsPOToken     = waxerr.ErrNeedsPOToken
	ErrURLExpired       = waxerr.ErrURLExpired

	// Availability failures.
	ErrVideoUnavailable = waxerr.ErrVideoUnavailable
	ErrVideoRestricted  = waxerr.ErrVideoRestricted
	ErrLoginRequired    = waxerr.ErrLoginRequired
	ErrLiveContent      = waxerr.ErrLiveContent
	ErrNoAudioFormats   = waxerr.ErrNoAudioFormats

	// Throttling.
	ErrRateLimited = waxerr.ErrRateLimited

	// Input / routing.
	ErrIsPlaylist        = waxerr.ErrIsPlaylist
	ErrInvalidVideoID    = waxerr.ErrInvalidVideoID
	ErrInvalidPlaylistID = waxerr.ErrInvalidPlaylistID

	// Processing / local files.
	ErrIncompatibleSpec = waxerr.ErrIncompatibleSpec
	ErrUnsupportedInput = waxerr.ErrUnsupportedInput
	ErrFFmpegNotFound   = waxerr.ErrFFmpegNotFound
)

Re-exported sentinel errors. The canonical definitions live in package waxerr; match them with errors.Is.

View Source
var ErrNoMatch = format.ErrNoMatch

ErrNoMatch reports that audio selection found no candidate satisfying the request. Download/Process translate it to ErrNoAudioFormats; it is re-exported for callers using BestForTarget directly.

View Source
var ErrNotImplemented = errors.New("waxtap: not implemented")

ErrNotImplemented marks facade methods that are declared but not implemented yet.

Functions

func BestForTarget

func BestForTarget(candidates []Format, policy SourcePolicy, target Target) (int, error)

BestForTarget chooses the best source audio index for a transcode target under a SourcePolicy. It is the selection BestAudio uses; exposed for callers that resolve formats themselves.

Types

type AlbumLoudnessResult

type AlbumLoudnessResult struct {
	Album    LoudnessInfo
	PerTrack []LoudnessInfo
}

AlbumLoudnessResult reports a group loudness measurement plus per-track measurements, in input order. The album value is a true group EBU R128 measurement, not a mean of the per-track LUFS.

type AlbumProcessResult

type AlbumProcessResult struct {
	Album    LoudnessInfo
	GainDB   float64 // Target - album integrated LUFS, applied to every track (0 for a silent album)
	PerTrack []LoudnessInfo
	Outputs  []string
}

AlbumProcessResult reports the album loudness, the gain applied to every track, the input measurements, and the output paths.

type AlbumTrack

type AlbumTrack struct {
	Input  string
	Output string
}

AlbumTrack names one album input and where its processed output should be written.

type AudioSelector

type AudioSelector = format.AudioSelector

Audio format model and selectors (package format).

func BestAudio

func BestAudio() AudioSelector

BestAudio selects the best audio stream, preferring the original track, non-DRC audio, then higher effective bitrate.

func Codec

func Codec(codec string) AudioSelector

Codec selects the best stream whose codec matches (e.g. "opus", "aac").

func Itag

func Itag(itag int) AudioSelector

Itag selects the stream with the exact itag.

type AudioTrack

type AudioTrack = format.AudioTrack

Audio format model and selectors (package format).

type Chapter

type Chapter = youtube.Chapter

Extraction models (package youtube). Part of the volatile surface; may evolve pre-1.0.

type Client

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

Client is the main WaxTap entry point for library callers and the CLI. It is safe for concurrent use after construction.

The same httpx.Client backs extraction, media download, and SponsorBlock. Its per-host limiter is shared by all request paths, while each host keeps its own schedule. The ffmpeg Runner is created lazily on first use, so metadata calls and keep-source downloads work without ffmpeg installed.

func New

func New(opts Options) (*Client, error)

New constructs a Client from Options, applying defaults for unset fields.

func (*Client) Download

func (c *Client) Download(ctx context.Context, req Request) (res *Result, err error)

Download acquires and processes a single YouTube video to the configured sink. It is strictly single-video: a playlist URL returns ErrIsPlaylist (use Enumerate and loop).

When no processing is requested it downloads straight to the sink with no temp file. When a cut, transcode, or loudness stage is requested it stages the source to a temp file, runs the fused pipeline, and finalizes to the sink.

Example

ExampleClient_Download downloads the best audio stream to a file. With no processing requested, WaxTap keeps the source encoding.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	res, err := client.Download(context.Background(), waxtap.Request{
		URL: "https://www.youtube.com/watch?v=VIDEO_ID_01",
		ProcessSpec: waxtap.ProcessSpec{
			Output: waxtap.ToFile("track.opus"),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s -> %s (%d bytes)\n", res.VideoID, res.OutputPath, res.OutputBytes)
}
Example (TranscodeAndSponsorBlock)

ExampleClient_Download_transcodeAndSponsorBlock downloads a video, removes SponsorBlock "music_offtopic" segments, and transcodes to FLAC in one ffmpeg pass. SourcePolicy defaults to MinimizeLoss.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
	"github.com/colespringer/waxtap/sponsorblock"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	res, err := client.Download(context.Background(), waxtap.Request{
		URL: "https://www.youtube.com/watch?v=VIDEO_ID_01",
		ProcessSpec: waxtap.ProcessSpec{
			Transcode: &waxtap.TranscodeSpec{Format: waxtap.FormatFLAC},
			Cut: &waxtap.CutSpec{
				SponsorBlock: []sponsorblock.Category{sponsorblock.CategoryMusicOffTopic},
				OnError:      waxtap.ProceedUncut,
			},
			Output: waxtap.ToFile("track.flac"),
			Events: func(e waxtap.Event) {
				if e.Stage == waxtap.StageWarning && e.Warning != nil {
					log.Printf("warning: %s", e.Warning.Detail)
				}
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("cut=%v transcoded=%v\n", res.CutApplied, res.Transcoded)
}

func (*Client) Enumerate

func (c *Client) Enumerate(ctx context.Context, url string, opts EnumerateOptions) (*Playlist, error)

Enumerate expands a playlist URL into entries without downloading media. EnumerateOptions.MaxItems caps the listing. With Enrich set, entries are refreshed with bounded-parallel InfoBasic calls; item-level failures are kept on Playlist.Errors.

Example

ExampleClient_Enumerate lists a playlist without downloading any audio.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	pl, err := client.Enumerate(context.Background(),
		"https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI",
		waxtap.EnumerateOptions{MaxItems: 50},
	)
	if err != nil {
		log.Fatal(err)
	}
	for _, entry := range pl.Entries {
		// The video ID is the stable key WaxTap uses for deduplication.
		fmt.Printf("%d. %s (%s)\n", entry.Index, entry.Title, entry.VideoID)
	}
}

func (*Client) Info

func (c *Client) Info(ctx context.Context, url string, depth InfoDepth) (*Video, error)

Info returns video metadata and candidate audio formats at the requested depth, without downloading.

InfoBasic returns extracted metadata and candidate formats. InfoResolved additionally resolves the best-audio format, surfacing resolution errors (such as ErrNeedsPOToken) and filling in its content length. InfoProbe additionally runs ffprobe on that resolved stream and fills its authoritative sample rate, channel count, bitrate, and duration (network-expensive, and requires ffmpeg). The signed stream URLs themselves are not returned through Video; use Download or Stream to fetch bytes.

Example

ExampleClient_Info fetches metadata and candidate audio formats without downloading.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	video, err := client.Info(context.Background(), "VIDEO_ID_01", waxtap.InfoBasic)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s by %s (%d formats)\n", video.Title, video.Author, len(video.Formats))
}

func (*Client) MeasureAlbum

func (c *Client) MeasureAlbum(ctx context.Context, paths []string) (*AlbumLoudnessResult, error)

MeasureAlbum measures local audio files as one album and also returns each track's loudness. It does not write output files; callers can use the album value for ReplayGain tags or playback gain.

It requires ffmpeg. Use normalize.AlbumGainFilter to bake the same album gain into each track.

Example

ExampleClient_MeasureAlbum measures several files as one album, useful for ReplayGain-style album tags without rewriting the files.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	album, err := client.MeasureAlbum(context.Background(), []string{
		"01.flac", "02.flac", "03.flac",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("album: %.2f LUFS\n", album.Album.IntegratedLUFS)
	for i, tr := range album.PerTrack {
		fmt.Printf("track %d: %.2f LUFS\n", i+1, tr.IntegratedLUFS)
	}
}

func (*Client) Process

func (c *Client) Process(ctx context.Context, req ProcessRequest) (res *Result, err error)

Process runs the transcode/cut/normalize pipeline on a local file, with no YouTube access, through the same source-agnostic pipeline as Download. SponsorBlock is not used here: it is keyed by video ID, which a local file does not have, so only explicit Cut.Ranges apply.

The input is validated up front (ffprobe); a corrupt or non-audio file fails with ErrUnsupportedInput. Writing the output over the input is rejected unless the caller targets a different path.

Example

ExampleClient_Process transcodes a local file and normalizes its loudness to -14 LUFS, fused into the same encode. No YouTube access occurs.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/colespringer/waxtap"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	res, err := client.Process(context.Background(), waxtap.ProcessRequest{
		Input: "song.wav",
		ProcessSpec: waxtap.ProcessSpec{
			Transcode: &waxtap.TranscodeSpec{Format: waxtap.FormatMP3},
			Loudness:  &waxtap.LoudnessSpec{Mode: waxtap.LoudnessApply, Target: -14},
			Output:    waxtap.ToFile("song.mp3"),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	if res.Loudness != nil && res.Loudness.Output != nil {
		fmt.Printf("normalized to %.1f LUFS\n", res.Loudness.Output.IntegratedLUFS)
	}
}

func (*Client) ProcessAlbum

func (c *Client) ProcessAlbum(ctx context.Context, tracks []AlbumTrack, target float64, spec TranscodeSpec) (*AlbumProcessResult, error)

ProcessAlbum measures local files as one album, then bakes the same gain into every track. The shared offset preserves track-to-track loudness differences; per-track normalization would flatten them.

Album processing requires ffmpeg and a non-copy transcode format. A silent album bakes a no-op gain, leaving each track unchanged apart from re-encoding.

func (*Client) Resolve

func (c *Client) Resolve(ctx context.Context, url string, sel AudioSelector) (ResolvedStream, error)

Resolve selects and resolves an audio stream without downloading it. The zero AudioSelector means best audio. The returned ResolvedStream contains a temporary googlevideo URL, its expiry, content length, and request headers.

It is exposed for diagnostics: the CLI's info --show-urls and doctor. Most callers use Download or Stream, which never expose the raw URL.

func (*Client) SponsorBlockSegments

func (c *Client) SponsorBlockSegments(ctx context.Context, videoURL string, categories []sponsorblock.Category) ([]sponsorblock.Segment, error)

SponsorBlockSegments returns skip segments for videoURL using the client's SponsorBlock settings and shared HTTP client. It does not cut or download media.

func (*Client) Stream

func (c *Client) Stream(ctx context.Context, req Request) (rc io.ReadCloser, info StreamInfo, err error)

Stream acquires a single YouTube video and returns a reader for source-style delivery (pipe to disk or object storage). When processing is requested it stages and processes to a temp file first, then streams the result. Final byte counts are known only after the reader is drained and closed.

Example

ExampleClient_Stream pipes the audio to an arbitrary writer (here a file) without staging to a temp file when no processing is requested.

package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/colespringer/waxtap"
)

func main() {
	client, err := waxtap.New(waxtap.Options{})
	if err != nil {
		log.Fatal(err)
	}

	rc, info, err := client.Stream(context.Background(), waxtap.Request{
		URL: "https://youtu.be/VIDEO_ID_01",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer rc.Close()

	out, err := os.Create("track" + "." + info.Format.Extension)
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()
	if _, err := io.Copy(out, rc); err != nil {
		log.Fatal(err)
	}
}

type Concurrency

type Concurrency struct {
	// Downloads is the max simultaneous downloads (e.g. across a playlist run).
	Downloads int
	// Chunks is the max parallel ranged chunks within a single download. Kept
	// low by default, especially for CLI playlist runs.
	Chunks int
	// FFmpeg limits concurrent ffmpeg/ffprobe processes, guarding local CPU
	// independently from network parallelism. Zero selects a conservative default
	// (GOMAXPROCS); a negative value disables the limit.
	FFmpeg int
}

Concurrency bounds parallel work. Zero values select conservative defaults at New time.

type CutMode

type CutMode uint8

CutMode selects how cuts are rendered.

const (
	// CutSmart copies when cutting alone (lossless, frame-boundary) and fuses the
	// cut into the transcode when one is requested. It avoids cut-then-transcode
	// workflows that would encode twice.
	CutSmart CutMode = iota
	// CutCopy forces stream-copy; it errors with ErrIncompatibleSpec when copy is
	// unsafe for the codec/container.
	CutCopy
	// CutAccurate decodes, cuts sample-exactly, and re-encodes.
	CutAccurate
)

type CutSpec

type CutSpec struct {
	// Ranges are explicit [Start, End) removals (optional).
	Ranges []TimeRange
	// SponsorBlock lists categories to fetch and remove. Nil disables the SB
	// fetch entirely; an empty-but-non-nil slice falls back to
	// sponsorblock.DefaultCategories.
	SponsorBlock []sponsorblock.Category
	// Mode selects copy/accurate/smart rendering.
	Mode CutMode
	// Crossfade, when > 0, applies a click-free crossfade at splice points. It
	// is OFF by default and orthogonal to Mode (accurate does not imply it).
	Crossfade time.Duration
	// OnError governs the SponsorBlock fetch only.
	OnError SponsorBlockErrorPolicy
	// Timeout is a strict cap on the SponsorBlock fetch.
	Timeout time.Duration
}

CutSpec describes time-range removal and/or SponsorBlock-driven cuts.

type EnumerateOptions

type EnumerateOptions struct {
	// MaxItems caps the number of entries returned (0 = all).
	MaxItems int
	// Enrich is reserved for a future full-metadata pass. Current enumeration
	// returns lightweight entries regardless of this value.
	Enrich bool
}

EnumerateOptions tunes playlist enumeration. Enumeration never downloads.

type Event

type Event struct {
	Stage   Stage
	VideoID string

	// Downloading progress.
	Bytes int64
	Total int64 // 0 if unknown

	// CLI playlist expansion.
	ItemIndex int
	ItemCount int

	Warning *Warning // set when Stage == StageWarning
	Err     error    // set when Stage == StageFailed
	Message string
}

Event is a best-effort progress signal. Callbacks are invoked synchronously from the worker and are panic-recovered. A terminal event always fires: StageDone on success or StageFailed with Err. For Stream, the terminal event is emitted when the returned reader is closed.

type ExtractionError

type ExtractionError = waxerr.ExtractionError

Re-exported structured error types. Use errors.As to inspect them.

type Format

type Format = format.Format

Audio format model and selectors (package format).

type HTTPStatusError

type HTTPStatusError = waxerr.HTTPStatusError

Re-exported structured error types. Use errors.As to inspect them.

type InfoDepth

type InfoDepth uint8

InfoDepth selects how much work Info does. Callers do not pay for what they do not request.

const (
	// InfoBasic returns metadata and candidate formats (the default).
	InfoBasic InfoDepth = iota
	// InfoResolved additionally resolves the best-audio stream URL and expiry.
	// These signed googlevideo URLs are temporary and sensitive; the CLI omits
	// them from human output unless --show-urls is given.
	InfoResolved
	// InfoProbe additionally runs ffprobe on the selected format only. This is
	// network-expensive (it reads the remote signed URL) and is never run on
	// every candidate.
	InfoProbe
)

type Locale

type Locale struct {
	HL string // host language, e.g. "en", "de", "ja"
	GL string // content region, e.g. "US", "DE", "JP"
}

Locale sets InnerTube localization hints. The zero value uses en / US.

HL affects localized UI text YouTube returns, including some error reasons. GL is a content-region hint; it does not change the request IP or bypass geo restrictions. Titles and descriptions are usually returned as-authored.

type LoudnessInfo

type LoudnessInfo struct {
	IntegratedLUFS float64 // integrated loudness, LUFS
	TruePeakDBTP   float64 // true peak, dBTP
	LRA            float64 // loudness range, LU
	Threshold      float64 // relative gating threshold, LUFS
}

LoudnessInfo holds an EBU R128 measurement.

type LoudnessMode

type LoudnessMode uint8

LoudnessMode selects measurement vs. normalization.

const (
	// LoudnessMeasureOnly returns measurements without altering the audio.
	LoudnessMeasureOnly LoudnessMode = iota
	// LoudnessApply normalizes to Target, fused into the transcode pass. It
	// requires an encode, so it is rejected with FormatCopy or no transcode unless
	// an explicit output codec is given (ErrIncompatibleSpec).
	LoudnessApply
)

type LoudnessResult

type LoudnessResult struct {
	Input  *LoudnessInfo // measured input loudness (post-cut)
	Output *LoudnessInfo // post-apply loudness; set only when Mode == LoudnessApply
	Target float64
}

LoudnessResult reports loudness measurements. WaxTap returns LUFS/true-peak measurements, not ReplayGain tag values.

type LoudnessSpec

type LoudnessSpec struct {
	Mode LoudnessMode
	// Target is the target integrated loudness in LUFS for Apply (e.g. -14). The
	// value is the caller's policy; WaxTap does not impose one.
	Target float64
}

LoudnessSpec requests loudness measurement or normalization (EBU R128).

type Options

type Options struct {
	// HTTPClient is used for all requests. It should set a DialContext and a
	// conservative Timeout, or rely on the per-operation context deadlines
	// WaxTap applies (see Timeouts). A no-timeout client on a dead proxy cannot
	// leak goroutines because WaxTap still bounds each operation by context. If
	// nil, a default client is used.
	HTTPClient *http.Client

	// Logger receives structured logs. If nil, logging is discarded; the CLI
	// installs its own handler.
	Logger *slog.Logger

	// Locale sets the InnerTube host language (hl) and content region (gl). The
	// zero value defaults to en / US.
	Locale Locale

	// CacheDir is the base directory for the on-disk player cache. Empty selects
	// os.UserCacheDir()/waxtap.
	CacheDir string
	// DisableDiskCache turns off on-disk player cache reads and writes.
	DisableDiskCache bool

	// TempDir is where intermediate/staging files are written; empty uses the OS
	// temp dir. MaxTempBytes optionally guards total staging size (0 =
	// unlimited). This is useful in constrained containers.
	TempDir      string
	MaxTempBytes int64

	Concurrency Concurrency
	Timeouts    Timeouts
	Retry       RetryPolicy
	Politeness  Politeness

	// ProfileOverridePath points at a strict JSON file that replaces the built-in
	// YouTube client profile chain at startup. Use it to refresh client versions,
	// user agents, or device fingerprints without rebuilding.
	ProfileOverridePath string

	SponsorBlock SponsorBlockOptions

	// POTokenProvider supplies PO tokens for profiles that require them. When a
	// download refresh follows a 403, WaxTap passes the failure details in the
	// request. Nil means no provider is configured.
	POTokenProvider POTokenProvider
}

Options configures a Client. The zero value is usable; New fills in defaults for timeouts, retry policy, and limits. All fields are read once during New.

type Output

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

Output is a delivery sink: either a file path or a writer. The zero value is unset. Construct it with ToFile or ToWriter.

The library writes the exact path given to ToFile (only a temp suffix and an atomic rename); filename templating, sanitization, and collision handling are the CLI's job, not the library's.

func ToFile

func ToFile(path string) Output

ToFile delivers to an exact file path (written atomically via a temp + rename).

func ToWriter

func ToWriter(w io.Writer) Output

ToWriter delivers to a caller-provided writer (bounded memory, no atomicity guarantee).

type POTokenProvider

type POTokenProvider = potoken.Provider

PO-token provider contract (package potoken).

type POTokenRequest

type POTokenRequest = potoken.Request

PO-token provider contract (package potoken).

type POTokenResponse

type POTokenResponse = potoken.Response

PO-token provider contract (package potoken).

type POTokenScope

type POTokenScope = potoken.Scope

PO-token provider contract (package potoken).

type PlayabilityError

type PlayabilityError = waxerr.PlayabilityError

Re-exported structured error types. Use errors.As to inspect them.

type Playlist

type Playlist = youtube.Playlist

Extraction models (package youtube). Part of the volatile surface; may evolve pre-1.0.

type PlaylistEntry

type PlaylistEntry = youtube.PlaylistEntry

Extraction models (package youtube). Part of the volatile surface; may evolve pre-1.0.

type Politeness

type Politeness struct {
	// PerHostQPS throttles requests per host (0 = unlimited). youtube.com and
	// googlevideo.com are limited independently.
	PerHostQPS float64
	// Cooldown pauses a host's queue after a 429/403 burst.
	Cooldown time.Duration
	// MaxDownloadsPerRun caps a single playlist run (0 = unlimited).
	MaxDownloadsPerRun int
}

Politeness governs request volume and backoff. The posture is to be a well-behaved client (reduce load, honor backoff, stop when limited), not to evade detection.

type ProcessRequest

type ProcessRequest struct {
	// Input is the local file path. Reader-based inputs will use a separate
	// request type; non-seekable inputs are staged before processing.
	Input string

	ProcessSpec
}

ProcessRequest processes a local audio file through the same pipeline as a YouTube download (transcode/cut/normalize), with no YouTube access.

type ProcessSpec

type ProcessSpec struct {
	Transcode *TranscodeSpec // nil = keep source, no re-encode
	Cut       *CutSpec       // nil = no cut
	Loudness  *LoudnessSpec  // nil = no loudness work

	// Output is the sink. For source-style delivery (an io.ReadCloser to pipe
	// elsewhere) use Client.Stream instead of setting Output.
	Output Output

	// Events receives best-effort, synchronous, panic-recovered stage events.
	// It may be nil. A slow callback backpressures the worker, so keep it fast.
	Events func(Event)

	// SkipIfExists skips work when the exact output path already exists. This is
	// only a path check; callers remain responsible for library-level deduping.
	SkipIfExists bool
}

ProcessSpec is the processing pipeline shared by YouTube and local-file requests. Each stage is opt-in: a nil pointer means that stage is skipped, so the default path keeps the selected source stream unchanged.

type RateLimitError

type RateLimitError = waxerr.RateLimitError

Re-exported structured error types. Use errors.As to inspect them.

type Request

type Request struct {
	URL string

	// Audio selects which audio stream to take. The zero value is BestAudio.
	Audio AudioSelector
	// SourcePolicy controls the source tradeoff when transcoding. The zero value
	// is MinimizeLoss.
	SourcePolicy SourcePolicy

	ProcessSpec
}

Request is a YouTube acquisition + processing request.

type ResolvedStream

type ResolvedStream = youtube.ResolvedStream

Extraction models (package youtube). Part of the volatile surface; may evolve pre-1.0.

type Result

type Result struct {
	SourceKind SourceKind
	VideoID    string // empty for local files
	Title      string // empty for local files
	InputPath  string // set for local files
	OutputPath string // empty for ToWriter delivery

	SourceFormat Format // input/source format
	OutputFormat Format // after transcode (== source when copy/keep)

	SourceBytes int64
	OutputBytes int64

	Transcoded          bool
	CutApplied          bool
	SponsorBlockApplied bool
	LoudnessMeasured    bool // measured != normalized
	LoudnessApplied     bool
	Loudness            *LoudnessResult // nil unless measured

	Warnings []Warning
}

Result reports the outcome of a Download or Process. Boolean flags describe completed effects, not requested work: an empty SponsorBlock match leaves SponsorBlockApplied false, and ranges that vanish after clamping leave CutApplied false.

type RetryPolicy

type RetryPolicy struct {
	MaxRetries  int           // additional attempts after the first
	BaseBackoff time.Duration // base of the exponential backoff
	MaxBackoff  time.Duration // cap on a single backoff sleep

	// MaxRetryWait caps an honored Retry-After. Beyond it WaxTap fails fast with
	// a *RateLimitError instead of sleeping a goroutine. Some Retry-After values
	// can be hours long.
	MaxRetryWait time.Duration
}

RetryPolicy tunes HTTP retry/backoff.

type SourceKind

type SourceKind uint8

SourceKind distinguishes a YouTube download from local-file processing.

const (
	SourceYouTube SourceKind = iota
	SourceLocalFile
)

func (SourceKind) String

func (k SourceKind) String() string

type SourcePolicy

type SourcePolicy = format.SourcePolicy

Audio format model and selectors (package format).

func BestNative

func BestNative() SourcePolicy

BestNative ignores target codec matching and uses normal best-audio ranking.

func MinimizeLoss

func MinimizeLoss() SourcePolicy

MinimizeLoss prefers a source in the target codec family, avoiding a cross-codec transcode when possible.

func PreferCodec

func PreferCodec(codec string) SourcePolicy

PreferCodec prefers a source in the named codec family when policy is active.

type SponsorBlockErrorPolicy

type SponsorBlockErrorPolicy uint8

SponsorBlockErrorPolicy governs SponsorBlock fetch failures only (ffmpeg cut/transcode failures are always hard errors).

const (
	// ProceedUncut logs a warning and delivers the full, uncut audio when the
	// SponsorBlock fetch fails or times out (the default).
	ProceedUncut SponsorBlockErrorPolicy = iota
	// FailDownload fails the whole request when the SponsorBlock fetch fails.
	FailDownload
)

type SponsorBlockOptions

type SponsorBlockOptions struct {
	// BaseURL overrides the SponsorBlock API base URL (empty = public default).
	BaseURL string
	// Timeout is a strict per-fetch timeout; if set it takes precedence over
	// Timeouts.SponsorBlock.
	Timeout time.Duration
}

SponsorBlockOptions configures the SponsorBlock client.

type Stage

type Stage uint8

Stage identifies a pipeline stage in an Event.

const (
	StageExtracting Stage = iota
	StageResolving
	StageDownloading
	StageStaging
	StageProbing
	StageAnalyzing
	StageCutting
	StageNormalizing
	StageTranscoding
	StageFinalizing
	StageSkipped
	StageWarning
	StageDone
	StageFailed
)

func (Stage) String

func (s Stage) String() string

type StreamInfo

type StreamInfo struct {
	VideoID       string
	Title         string
	Format        Format
	ContentLength int64 // 0 if unknown
}

StreamInfo is the initial metadata returned by Client.Stream alongside the stream reader. Final byte counts are known only after read-to-EOF/Close.

type Target

type Target = format.Target

Target describes a transcode output for source selection. The facade maps a TranscodeSpec onto it; most callers do not construct one directly.

type Thumbnail

type Thumbnail = youtube.Thumbnail

Extraction models (package youtube). Part of the volatile surface; may evolve pre-1.0.

type TimeRange

type TimeRange struct {
	Start time.Duration
	End   time.Duration
}

TimeRange is a half-open [Start, End) span. End must be greater than Start.

type Timeouts

type Timeouts struct {
	Extraction     time.Duration // player-response fetch + parse
	Resolve        time.Duration // stream-URL resolution (incl. cipher JS)
	SponsorBlock   time.Duration // SponsorBlock fetch (see also SponsorBlock.Timeout)
	ChunkRetry     time.Duration // per-chunk deadline for ranged downloads
	FFmpegShutdown time.Duration // grace period before killing ffmpeg on cancel
}

Timeouts are per-operation deadlines applied through context. There is no single global download cap; each operation gets its own budget. A zero field means WaxTap adds no extra deadline for that operation.

type TranscodeFormat

type TranscodeFormat uint8

TranscodeFormat names an output preset. FormatCopy is the only no-re-encode path. FLAC, ALAC, and WAV preserve the decoded samples, but they are still decode-and-encode passes when the source is YouTube audio.

const (
	FormatCopy TranscodeFormat = iota // remux / stream-copy (no re-encode)
	FormatFLAC
	FormatALAC
	FormatWAV
	FormatMP3
	FormatAAC // delivered in an .m4a container
	FormatOpus
	FormatVorbis
)

type TranscodeSpec

type TranscodeSpec struct {
	Format TranscodeFormat
	// Bitrate is the target bits per second for lossy presets (e.g. 256000).
	// Zero selects the preset default. Ignored by lossless presets.
	Bitrate int
}

TranscodeSpec requests ffmpeg processing. An explicit FormatCopy stream-copies through ffmpeg to remux into the destination container; a nil TranscodeSpec keeps the selected source bytes untouched.

type Tri

type Tri = format.Tri

Audio format model and selectors (package format).

type Video

type Video = youtube.Video

Extraction models (package youtube). Part of the volatile surface; may evolve pre-1.0.

type Warning

type Warning struct {
	Code   WarningCode
	Detail string // human-readable context
}

Warning is a typed, non-fatal signal. It is both delivered as a StageWarning Event and accumulated in Result.Warnings.

type WarningCode

type WarningCode uint8

WarningCode is a stable, machine-actionable warning identifier. Warning.Detail is human-only.

const (
	WarnProceedUncut        WarningCode = iota // SponsorBlock fetch failed; delivered uncut
	WarnFallbackProfile                        // a fallback client profile was used
	WarnURLReResolved                          // an expired stream URL was re-resolved
	WarnPlaylistEntryFailed                    // one playlist entry failed (others returned)
	WarnRateLimitedRetried                     // a request was retried after a 429
	WarnSponsorBlockEmpty                      // SponsorBlock matched no segments
	WarnRangesEmpty                            // cut ranges were empty after clamp/merge
	WarnThrottled                              // a limiter/cooldown is active
)

func (WarningCode) String

func (w WarningCode) String() string

Directories

Path Synopsis
cmd
waxtap command
Command waxtap provides the WaxTap CLI for YouTube audio downloads and local audio processing.
Command waxtap provides the WaxTap CLI for YouTube audio downloads and local audio processing.
Package cut removes time ranges from an audio file.
Package cut removes time ranges from an audio file.
Package download transfers resolved media streams to files, writers, or callers that want an io.ReadCloser.
Package download transfers resolved media streams to files, writers, or callers that want an io.ReadCloser.
Package format defines WaxTap's stream-format model and the rules for picking an audio source from a candidate list.
Package format defines WaxTap's stream-format model and the rules for picking an audio source from a candidate list.
internal
cache
Package cache provides a thread-safe LRU cache with TTL expiry, schema versioning, and singleflight de-duplication of concurrent loads.
Package cache provides a thread-safe LRU cache with TTL expiry, schema versioning, and singleflight de-duplication of concurrent loads.
diskcache
Package diskcache provides a small on-disk blob cache for data that is expensive to fetch and safe to lose.
Package diskcache provides a small on-disk blob cache for data that is expensive to fetch and safe to lose.
httpx
Package httpx is WaxTap's internal HTTP client wrapper.
Package httpx is WaxTap's internal HTTP client wrapper.
pipeline
Package pipeline runs WaxTap's source-agnostic audio processing on a staged local file: it cuts time ranges, normalizes loudness, and transcodes, fusing whatever is requested into a single ffmpeg encode.
Package pipeline runs WaxTap's source-agnostic audio processing on a staged local file: it cuts time ranges, normalizes loudness, and transcodes, fusing whatever is requested into a single ffmpeg encode.
tempfile
Package tempfile stages output in the destination directory and publishes it with an atomic rename.
Package tempfile stages output in the destination directory and publishes it with an atomic rename.
Package normalize measures EBU R128 loudness with ffmpeg's loudnorm filter and builds the matching apply filter for a later encode.
Package normalize measures EBU R128 loudness with ffmpeg's loudnorm filter and builds the matching apply filter for a later encode.
Package potoken defines the PO-token provider contract.
Package potoken defines the PO-token provider contract.
Package sponsorblock defines the SponsorBlock category vocabulary used by WaxTap cut requests.
Package sponsorblock defines the SponsorBlock category vocabulary used by WaxTap cut requests.
Package transcode wraps ffmpeg and ffprobe for local audio files.
Package transcode wraps ffmpeg and ffprobe for local audio files.
Package waxerr defines WaxTap's domain error taxonomy: a flat set of sentinel errors plus a few structured error types that carry diagnostic context.
Package waxerr defines WaxTap's domain error taxonomy: a flat set of sentinel errors plus a few structured error types that carry diagnostic context.
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.
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.
internal/resolver
Package resolver isolates YouTube's most volatile surface: discovering the player JS (base.js), solving the signature and n-parameter transforms, and building a playable, signed stream URL.
Package resolver isolates YouTube's most volatile surface: discovering the player JS (base.js), solving the signature and n-parameter transforms, and building a playable, signed stream URL.

Jump to

Keyboard shortcuts

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