npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hoangquyet/ytdown

v1.0.2

Published

Zero-dependency YouTube downloader built on a reverse-engineered InnerTube WEB client. Download by quality or itag, read metadata as JSON.

Readme

@hoangquyet/ytdown

A YouTube downloader built on a reverse-engineered InnerTube WEB client.

Zero runtime dependencies — no ytdl-core, no yt-dlp, no youtubei.js, no protobuf library. The protocol work is done in this package: the protobuf codec, the UMP demuxer, the SABR request loop, and the player-script analysis are all part of the source tree.

import YTdownload from '@hoangquyet/ytdown';

const video = await YTdownload.info('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
console.log(video.title);

const result = await YTdownload.down('dQw4w9WgXcQ', { quality: '1080' });
console.log(result.path); // → /full/path/to/video.mp4

Install

npm install @hoangquyet/ytdown

Requires Node.js 18 or newer. ffmpeg on PATH is needed only to mux separate video and audio tracks together — short videos that offer a muxed format, and audio-only downloads, need nothing else.

Quick start

import YTdownload from '@hoangquyet/ytdown';

// Metadata
const info = await YTdownload.info('https://youtu.be/dQw4w9WgXcQ');

// Best quality, muxed into one file
const { path } = await YTdownload.down('https://youtu.be/dQw4w9WgXcQ');

// Cap the resolution
await YTdownload.down('dQw4w9WgXcQ', { quality: '720' });

// Audio only
await YTdownload.down('dQw4w9WgXcQ', { audioOnly: true });

// Audio only, transcoded to mp3
await YTdownload.down('dQw4w9WgXcQ', { audioOnly: true, mp3: true });

// A specific format, by itag
await YTdownload.down('dQw4w9WgXcQ', { itag: 137 });

// Everything as JSON, with n-transformed media URLs
const json = await YTdownload.json('dQw4w9WgXcQ');

API

The default export is an object:

import YTdownload from '@hoangquyet/ytdown';
// YTdownload.down · YTdownload.info · YTdownload.formats · YTdownload.json ·
// YTdownload.describe · YTdownload.session

Named exports are available too:

import { down, info, formats, json, describe, session } from '@hoangquyet/ytdown';

Every method accepts anything you are likely to paste: a watch URL, a youtu.be link, a shorts link, an embed, or a bare eleven-character id.

YTdownload.down(input, options?)

Download a video. Resolves to { path, info, tracks }, where path is the absolute path of the saved file. When ffmpeg is unavailable and the tracks cannot be muxed, path is null and videoPath/audioPath are set instead.

| option | type | default | meaning | |---|---|---|---| | quality | 'best' \| 'audio' \| string | 'best' | 'best', 'audio', or a max height such as '1080' or '720' | | itag | number | — | download a specific format by itag, overriding quality | | audioOnly | boolean | false | only the audio track | | videoOnly | boolean | false | only the video track | | mp3 | boolean | false | with audioOnly, transcode to mp3 | | outputDir | string | process.cwd() | output directory (output is accepted as an alias) | | outputPath | string | — | exact output file path (file is accepted as an alias) | | container | 'mp4' \| 'webm' \| 'mkv' | auto | force the output container | | keepTracks | boolean | false | also keep the separate video/audio files | | transport | 'auto' \| 'progressive' \| 'direct' \| 'session' \| 'playback' | 'auto' | how to fetch the media | | rate | number | 16 | playback-capture speed multiplier | | headless | boolean | false | run the session browser without a window | | attest | boolean | true | mint a PO token | | ffmpegPath | string | — | path to the ffmpeg binary | | onProgress | function | — | ({ bytes, bufferedMs, durationMs }) => void |

YTdownload.info(input)

Resolves to { videoId, title, author, channelId, durationSeconds, viewCount, publishDate, isLive, thumbnails, formats, bestVideo, bestAudio }.

YTdownload.formats(input)

Resolves to the normalised list of formats. Each entry carries itag, kind ('video' | 'audio'), muxed, height, qualityLabel, codecs, bitrate, contentLength, and more — everything you need to pick an itag for down().

YTdownload.json(input) / YTdownload.describe(input)

The full resolved payload: metadata plus every media URL, already n-transformed so each is directly usable by curl, ffmpeg, or a player. Each format carries a reach field:

| reach | meaning | |---|---| | whole | the URL streams the entire file — muxed formats only | | partial | adaptive; the URL stops short and cannot be resumed | | none | no direct URL, so only SABR can fetch it |

recommended.muxed is the one to reach for when you just want a working link.

YTdownload.session(input, options?)

Exports a reusable session as JSON. Downloading against an exported session (YTW_SESSION) needs no browser at all, so a credential minted on one machine can be used on a headless one:

node -e "import('@hoangquyet/ytdown').then(m => m.session('dQw4w9WgXcQ').then(s => console.log(JSON.stringify(s))))" > session.json
YTW_SESSION=session.json node src/cli.js download dQw4w9WgXcQ

CLI

The package also ships a ytw command:

npx ytw <url|id>                    # best quality
npx ytw info <url|id>               # metadata
npx ytw formats <url|id>            # every offered format
npx ytw json <url|id>               # metadata + media urls as JSON
npx ytw download <url|id> -q 1080   # cap the resolution
npx ytw download <url|id> --audio-only
Options
  -o, --output <dir>       output directory (default: current directory)
  -f, --file <path>        exact output file path
  -q, --quality <spec>     best | audio | a max height such as 1080
      --audio-only         download only the audio track
      --mp3                with --audio-only: transcode to mp3
      --video-only         download only the video track
      --container <ext>    force the output container (mp4 | webm | mkv)
      --keep-tracks        also keep the separate video and audio files
      --transport <mode>   auto | progressive | direct | session | playback
      --rate <n>           playback capture speed multiplier (default: 16)
      --headless           run the session browser without a window
      --urls-only          with json: emit only formats that carry a url
      --compact            with json: no indentation
      --no-attest          skip PO token minting
      --ffmpeg <path>      path to the ffmpeg binary
  -v, --verbose            verbose logging
      --quiet              errors only
  -h, --help               show this help

Transports

| transport | behaviour | |---|---| | progressive | plain media URLs, no browser at any point | | direct | SABR over plain HTTP, also browserless; a cold session is capped at roughly a minute | | session | downloads in Node against a trusted session (YTW_SESSION, cache, or a browser opened once) | | playback | plays the video in a browser and keeps what its player fetches — a last resort | | auto | progressive when plain URLs are available, direct for short videos, session for anything longer |

Only browser surfaces are impersonated — the desktop client and, when it helps, the mobile web client. No ANDROID / iOS / TV / visionOS client is used anywhere.

Requirements

  • Node.js 18 or newer
  • ffmpeg on PATH (or --ffmpeg <path>) for muxing video and audio together
  • Google Chrome or Microsoft Edge — only as a fallback, for when the mobile surface declines and the video is longer than the plain-SABR limit

How it works

  1. Bootstrap. Load the watch page for cookies, INNERTUBE_API_KEY, the client version, visitorData, and the player's base.js URL.
  2. Player analysis. Every media URL carries a throttling-deterrent n parameter, and SABR endpoints reject an untransformed one with a bare 403 — so this step is mandatory. The transform is located structurally and driven inside a locked-down node:vm.
  3. Player response. POST /youtubei/v1/player with the WEB context yields the format ladder and serverAbrStreamingUrl.
  4. SABR loop. Build a VideoPlaybackAbrRequest, POST it, demux the UMP part stream, reassemble segments, and report progress so the server's serving window slides forward.
  5. Mux. Concatenate each track's segments and stream-copy them into one container with ffmpeg. No re-encoding. The container is chosen by codec, not mime label.

Tests

npm test

60 tests covering the protobuf codec, UMP framing, format selection, URL parsing, SABR message encoding, and the n transform against a value captured from a real session.

License

MIT