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

video2bunny

v1.1.0

Published

CLI tool to migrate videos from Vimeo, AWS S3, Wistia, Mux, Cloudflare Stream, JW Player, or Brightcove to bunny.net Stream

Downloads

285

Readme

video2bunny

Migrate videos from Vimeo, AWS S3, Wistia, Mux, Cloudflare Stream, JW Player, or Brightcove to bunny.net Stream.

Videos transfer directly from the source platform to Bunny via URL fetch — nothing is downloaded to your machine.

Quick Start

npm install -g video2bunny

video2bunny config      # select source, enter credentials
video2bunny migrate     # run the migration

Or from source:

git clone https://github.com/nathanzbunny/video2bunny.git
cd video2bunny
npm install
npm run build
npm link

Setup

1. Configure your source and Bunny credentials

video2bunny config

The interactive wizard prompts you to choose a source, enter source-specific credentials, then your Bunny Stream library ID and API key.

Every prompt has an environment variable equivalent for non-interactive / CI use (see platform sections below).

2. Bunny Stream credentials (required for all sources)

Get these from your Bunny Stream library: Stream Library → API.

| Setting | Env var | Description | |---|---|---| | Library ID | BUNNY_LIBRARY_ID | Numeric ID from the library URL or API page | | Library API Key | BUNNY_LIBRARY_API_KEY | The per-library key (not your account API key) |


Supported Sources

📹 Vimeo

Downloads are accessed via the Vimeo API. Requires a Standard plan or above for download links.

Credentials:

| Setting | Env var | How to get it | |---|---|---| | Access Token | VIMEO_ACCESS_TOKEN | developer.vimeo.com/apps → Create App → Generate Token |

Required token scopes: public, private, video_files.

Folder mapping: Vimeo Projects → Bunny Collections.

Usage:

video2bunny config                        # select "Vimeo", paste token
video2bunny list --source vimeo
video2bunny migrate --source vimeo --dry-run
video2bunny migrate --source vimeo
video2bunny migrate --source vimeo --folder <project_id>

Dedup tag: vimeoId — the numeric Vimeo video ID is written to each Bunny video as a metaTag so re-runs skip already-migrated videos.


☁️ AWS S3

Uses S3 pre-signed URLs. Bunny fetches directly from S3 — no data passes through your machine.

Credentials:

| Setting | Env var | How to get it | |---|---|---| | Access Key ID | AWS_ACCESS_KEY_ID | IAM console → Users → Security credentials | | Secret Access Key | AWS_SECRET_ACCESS_KEY | Generated alongside the access key | | Region | AWS_REGION | The bucket's region (e.g. us-east-1) | | Session Token (optional) | AWS_SESSION_TOKEN | For temporary/assumed-role credentials | | Bucket | S3_BUCKET | The source bucket name | | Prefix (optional) | S3_PREFIX | Scope to a key prefix (e.g. videos/) | | URL TTL | S3_PRESIGNED_URL_TTL | Seconds (default 21600 = 6h, max 604800 = 7d) |

Alternatively, leave credentials blank during video2bunny config to use the default AWS credential chain (~/.aws/credentials, instance profile, etc.).

Required IAM permissions: s3:ListBucket on the bucket, s3:GetObject on the objects.

Folder mapping: First-level subprefixes below the configured root → Bunny Collections. Root-level objects become uncategorized.

Usage:

video2bunny config                        # select "AWS S3"
video2bunny list --source s3
video2bunny list --source s3 --bucket other-bucket --prefix raw/
video2bunny migrate --source s3 --dry-run
video2bunny migrate --source s3 --concurrency 5
video2bunny migrate --source s3 --url-ttl 86400

Pre-signed URL generation (S3 only):

video2bunny presign                       # print URLs to terminal
video2bunny presign --format json -o urls.json
video2bunny presign --format csv
video2bunny presign --bucket b --prefix p/ --url-ttl 3600

Dedup tag: s3Source — the value bucket/key is written to each Bunny video.

Recognised video extensions: .mp4, .m4v, .mov, .mkv, .webm, .avi, .wmv, .flv, .mpg, .mpeg, .ts, .mts, .m2ts, .3gp. All other S3 objects are ignored.


📊 Wistia

Downloads are accessed via asset URLs retrieved through the Wistia Data API. The tool picks the best available MP4 rendition (HD → MD → SD → Original).

Credentials:

| Setting | Env var | How to get it | |---|---|---| | API Token | WISTIA_ACCESS_TOKEN | Wistia account → Settings → API Access |

Folder mapping: Wistia Projects → Bunny Collections.

Usage:

video2bunny config                        # select "Wistia", paste token
video2bunny list --source wistia
video2bunny migrate --source wistia --dry-run
video2bunny migrate --source wistia
video2bunny migrate --source wistia --folder <project_hashed_id>

Dedup tag: wistiaId — the media hashed_id.

Note: Wistia asset URL access bypasses their analytics and counts toward your bandwidth. See Wistia's asset URL docs for details.


⚡ Mux

Two download mechanisms: static renditions (MP4 at low/medium/high quality) and master access (original mezzanine file, 24h expiry). The tool prefers static renditions when available.

Credentials:

| Setting | Env var | How to get it | |---|---|---| | Token ID | MUX_TOKEN_ID | dashboard.mux.com/settings/api-keys | | Token Secret | MUX_TOKEN_SECRET | Generated alongside the token ID |

Auth is HTTP Basic (token_id:token_secret).

Folder mapping: Mux has no folder concept. All assets migrate as uncategorized. Use --folder is not applicable.

Prerequisites: For static rendition downloads, assets must have mp4_support set to "standard" or "capped-1080p". You can enable this per-asset via the Mux API or dashboard before running the migration. If static renditions aren't available, the tool falls back to master access if enabled.

Usage:

video2bunny config                        # select "Mux", enter token ID + secret
video2bunny list --source mux
video2bunny migrate --source mux --dry-run
video2bunny migrate --source mux
video2bunny migrate --source mux --concurrency 5

Dedup tag: muxAssetId — the Mux asset ID.


🌐 Cloudflare Stream

Downloads are generated on-demand via the /downloads API endpoint. The tool triggers MP4 generation, polls until the download is ready, then hands the URL to Bunny. Private videos require a token with Stream read access.

Credentials:

| Setting | Env var | How to get it | |---|---|---| | API Token | CLOUDFLARE_API_TOKEN | dash.cloudflare.com/profile/api-tokens → Create Token | | Account ID | CLOUDFLARE_ACCOUNT_ID | Dashboard URL: dash.cloudflare.com/<account_id> or Account → Overview sidebar |

Required token permission: Stream:Read (and Stream:Edit if downloads haven't been pre-generated).

Folder mapping: Cloudflare Stream has no folder concept. All videos migrate as uncategorized.

Usage:

video2bunny config                        # select "Cloudflare Stream"
video2bunny list --source cloudflare
video2bunny migrate --source cloudflare --dry-run
video2bunny migrate --source cloudflare
video2bunny migrate --source cloudflare --concurrency 3

Dedup tag: cfStreamId — the Cloudflare Stream video UID.

Note: MP4 download generation may take a few minutes per video. The tool polls automatically. Downloads count toward your Cloudflare Stream encoding minutes and storage.


🎯 JW Player (JWX)

Media is retrieved via the JW Player Management API v2. Download URLs are sourced from the Delivery API media endpoint, picking the highest-quality MP4 rendition.

Credentials:

| Setting | Env var | How to get it | |---|---|---| | API Key (v2) | JWPLAYER_API_KEY | dashboard.jwplayer.com → Properties → API Credentials | | Site ID | JWPLAYER_SITE_ID | Dashboard → Properties → your property's 8-character ID |

Folder mapping: JW Player has no folder concept. All media migrate as uncategorized.

Usage:

video2bunny config                        # select "JW Player (JWX)"
video2bunny list --source jwplayer
video2bunny migrate --source jwplayer --dry-run
video2bunny migrate --source jwplayer
video2bunny migrate --source jwplayer --concurrency 5

Dedup tag: jwPlayerId — the JW Player media ID.


📡 Brightcove

Uses the CMS API with OAuth 2.0 client credentials authentication. The tool fetches the highest-resolution MP4 source for each video. Brightcove has native folders, which map directly to Bunny collections.

Credentials:

| Setting | Env var | How to get it | |---|---|---| | Client ID | BRIGHTCOVE_CLIENT_ID | studio.brightcove.com/admin/oauthclient → Register New Application | | Client Secret | BRIGHTCOVE_CLIENT_SECRET | Generated when registering the OAuth client | | Account ID | BRIGHTCOVE_ACCOUNT_ID | Studio → Admin → Account Information, or from your Studio URL |

Required OAuth permissions: CMS → Video Read (at minimum). CMS → Video Read/Write if you want to access the digital master.

The tool handles token refresh automatically — OAuth access tokens are short-lived (~5 minutes) and re-requested as needed.

Folder mapping: Brightcove Folders → Bunny Collections. Videos not in a folder migrate as uncategorized.

Usage:

video2bunny config                        # select "Brightcove"
video2bunny list --source brightcove
video2bunny list --source brightcove --folders
video2bunny migrate --source brightcove --dry-run
video2bunny migrate --source brightcove
video2bunny migrate --source brightcove --folder <folder_id>
video2bunny migrate --source brightcove --concurrency 5

Dedup tag: brightcoveId — the Brightcove video ID.


Common Commands

All commands work identically across every source. The --source flag overrides whatever was set during video2bunny config.

List content

video2bunny list                          # uses configured source
video2bunny list --source <source>        # override source
video2bunny list --folders                # folders/projects only
video2bunny list --videos                 # videos only

Migrate videos

video2bunny migrate                       # uses configured source
video2bunny migrate --source <source>     # override source
video2bunny migrate --dry-run             # preview without changes
video2bunny migrate --folder <id>         # single folder/project only
video2bunny migrate --concurrency 5       # parallel transfers (default 3, max 20)
video2bunny migrate --resume              # retry failed / continue interrupted
video2bunny migrate --verbose             # show already-migrated videos
video2bunny migrate --processing-timeout 120  # Bunny encode timeout in minutes
video2bunny migrate --request-timeout 60  # HTTP timeout in seconds

Check migration status

video2bunny status

Reset configuration

video2bunny config:reset

How It Works

  1. Discover — The tool lists all videos and folders/projects from the source platform's API.
  2. Map folders — Source folders (Vimeo Projects, Wistia Projects, S3 subprefixes, Brightcove Folders) become Bunny collections with matching names. Platforms without folders (Mux, Cloudflare, JW Player) skip this step.
  3. Get download URL — Each platform provides the URL differently:

| Source | Download mechanism | URL lifetime | |---|---|---| | Vimeo | API download links | ~24 hours | | S3 | Pre-signed GetObject URL | Configurable (1m–7d) | | Wistia | Asset URL from Data API | Persistent (token-scoped) | | Mux | Static rendition URL or master access | Persistent / 24h | | Cloudflare | On-demand /downloads endpoint | Persistent once generated | | JW Player | Delivery API source URL | Persistent (key-scoped) | | Brightcove | CMS API video sources | Token-scoped (~5 min, auto-refreshed) |

  1. Transfer — The URL is sent to Bunny's videos/fetch endpoint. Bunny downloads the file directly from the source platform — nothing passes through your machine.
  2. Metadata — Title, description, and tags are copied where the source API provides them.
  3. Track — A dedup metaTag is written to each Bunny video so re-runs and --resume skip already-migrated content.

Dedup tags by source

| Source | Bunny metaTag | Value | |---|---|---| | Vimeo | vimeoId | Numeric video ID | | S3 | s3Source | bucket/key | | Wistia | wistiaId | Media hashed ID | | Mux | muxAssetId | Asset ID | | Cloudflare | cfStreamId | Video UID | | JW Player | jwPlayerId | Media ID | | Brightcove | brightcoveId | Video ID |


Environment Variables Reference

All credentials can be set via environment variables instead of (or in addition to) the interactive config command. Environment variables take priority over stored configuration.

# ── Bunny Stream (always required) ──────────────────
BUNNY_LIBRARY_ID=...
BUNNY_LIBRARY_API_KEY=...

# ── Vimeo ────────────────────────────────────────────
VIMEO_ACCESS_TOKEN=...

# ── AWS S3 ───────────────────────────────────────────
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1
AWS_SESSION_TOKEN=...              # optional, for temp creds
S3_BUCKET=my-bucket
S3_PREFIX=videos/                  # optional
S3_PRESIGNED_URL_TTL=21600         # optional, seconds

# ── Wistia ───────────────────────────────────────────
WISTIA_ACCESS_TOKEN=...

# ── Mux ──────────────────────────────────────────────
MUX_TOKEN_ID=...
MUX_TOKEN_SECRET=...

# ── Cloudflare Stream ────────────────────────────────
CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ACCOUNT_ID=...

# ── JW Player ────────────────────────────────────────
JWPLAYER_API_KEY=...
JWPLAYER_SITE_ID=...

# ── Brightcove ───────────────────────────────────────
BRIGHTCOVE_CLIENT_ID=...
BRIGHTCOVE_CLIENT_SECRET=...
BRIGHTCOVE_ACCOUNT_ID=...

Credential Storage

Credentials are stored securely using the system keychain when available (macOS Keychain, Windows Credential Vault, Linux libsecret). If the keychain isn't available (e.g. headless servers, CI), credentials fall back to a JSON config file at ~/.config/video2bunny-nodejs/config.json.

Migration state is saved to ~/.video2bunny/migration-state.json with 0600 permissions so interrupted migrations can be resumed with --resume.


Troubleshooting

"No download link available" — The source platform couldn't provide a download URL. For Vimeo, this means a Standard plan or above is required. For Mux, ensure mp4_support is enabled on the asset. For Cloudflare, the download may still be generating — the tool retries automatically for up to 5 minutes.

"Access denied to bucket" — S3 source: check that IAM permissions include s3:ListBucket and s3:GetObject.

"Bucket exists in a different region" — S3 source: set AWS_REGION to match the bucket's actual region.

"Invalid ... credentials" — Double-check the token/key you entered. For Brightcove, ensure your OAuth client has CMS Video Read permission. For JW Player, confirm you're using a v2 Management API key (not a legacy v1 key).

"Rate limited" — All API clients handle rate limiting automatically with backoff and retry. No action needed.

Migration stalled or interrupted — Run video2bunny migrate --resume to pick up where it left off. The tool skips already-completed videos.


License

MIT