β οΈ Warning: This project was vibe coded and is a work in progress. It's not perfectβplease use at your own risk.
Extract videos from X (formerly Twitter) tweets.
- β Extract videos from public X/Twitter tweets
- β Supports multiple formats (mp4, webm, gif, etc.)
- β Automatic format selection (highest quality)
- β Download videos directly or just get the URL
- β
Clip videos to a specific time range (
--fromand--to) β οΈ Downloading videos from private tweets (experimental alpha features)- β Windows support
One-line installer (macOS or Linux):
curl -fsSL https://github.com/RichardBray/x-dl/releases/latest/download/install.sh | bashThis will:
- Detect your platform (macOS/Linux) and architecture (ARM64/x86_64)
- Download the appropriate binary
- Verify the download with SHA256 checksums
- Install to
~/.local/bin/x-dl - Add to PATH if needed
- Prompt to install Playwright Chromium
After installation:
Reload your shell or run:
source ~/.bashrc # or ~/.zshrcThen run:
x-dl --help
x-dl install # Install Playwright Chromium
x-dl https://x.com/user/status/123456x-dl launches Chromium via Playwright, opens tweet, and looks for media requests to video.twimg.com.
- Primary signal: network responses to
video.twimg.com - Fallbacks: Performance API (
performance.getEntriesByType('resource')) and DOM inspection (<video>/<source>) - Selection:
- filters out audio-only tracks (URLs containing
/aud/ormp4a) - prefers progressive files (mp4/webm) when available
- otherwise returns an HLS playlist (
.m3u8) when that's all X exposes
- filters out audio-only tracks (URLs containing
- Download:
- mp4/webm/gif files: direct download
- HLS (m3u8) playlists: downloads via ffmpeg to produce mp4
- If direct download fails with 401/403 auth errors and
--profileis used, automatically retries using authenticated Playwright requests
- Clipping:
--fromand--to(MM:SS format) trim videos to a specific time range- HLS streams are clipped during download with ffmpeg re-encoding
- MP4 streams download full video, then clip locally
- Clipped files get a
_clipsuffix in the filename
- Auth: with
--profile, Playwright reuses cookies/session from a persistent profile directory - ffmpeg: checked at runtime and auto-installed when possible
Examples:
# Print the best video URL (any supported format)
x-dl --url-only https://x.com/WesRoth/status/2013693268190437410# Log in once (interactive browser), saving cookies to a profile dir (alpha)
x-dl --login --profile ~/.x-dl-profile
# Then extract using the logged-in session
x-dl --profile ~/.x-dl-profile --url-only https://x.com/WesRoth/status/2013693268190437410One-line installer (mecommended):
curl -fsSL https://github.com/RichardBray/x-dl/releases/latest/download/install.sh | bashFrom Source
- Bun (>= 1.0.0)
- Playwright (installed via
bun install) - Chromium for Playwright (installed via
bun install/postinstall) - ffmpeg (for HLS/m3u8 downloads, auto-installed when possible)
cd x-dl
bun installAfter installing the tool, you can install Playwright Chromium:
# Install Playwright Chromium only
x-dl install
# Install Chromium + ffmpeg + Linux system dependencies (may require sudo on Linux)
x-dl install --with-depsThe install command:
- Checks if Playwright Chromium is already installed
- Installs Chromium if needed (no sudo required)
- With
--with-deps, also installs ffmpeg and Linux system dependencies - Works both when running via Bun and when using a compiled single-file binary
Extract and download a video from a tweet:
x-dl https://x.com/Remotion/status/2013626968386765291Install Playwright Chromium:
x-dl installInstall Chromium plus ffmpeg and Linux system dependencies:
x-dl install --with-depsNote: --with-deps may require sudo on Linux to install system packages.
| Option | Description |
|---|---|
--url, -u <url> |
Tweet URL to extract from |
--output, -o <path> |
Output directory or file path (default: ~/Downloads) |
--url-only |
Only print video URL, don't download |
| `--quality <best | worst>` |
--timeout <seconds> |
Page load timeout in seconds (default: 30) |
--headed |
Show browser window for debugging |
--profile [dir] |
Use a persistent browser profile for authenticated extraction (default: ~/.x-dl-profile) |
--login |
Open X in a persistent profile and wait for you to log in |
--from <MM:SS> |
Clip start time in minutes and seconds (e.g. 00:30) |
--to <MM:SS> |
Clip end time in minutes and seconds (e.g. 01:30) |
--help, -h |
Show help message |
Note: The -o option accepts any file extension. If you specify a path with an extension (e.g., video.mp4, video.webm), that format will be used. Otherwise, the format is auto-detected from the extracted video.
Download to default location (~/Downloads):
x-dl https://x.com/Remotion/status/2013626968386765291Download to specific directory:
x-dl -o ~/Downloads https://x.com/user/status/123456Only print video URL:
x-dl --url-only https://x.com/user/status/123456Use headed mode for debugging:
x-dl --headed https://x.com/user/status/123456Login once, then reuse the session (alpha):
# Log in interactively (creates/uses the profile dir)
x-dl --login --profile ~/.x-dl-profile
# Extract using the logged-in session
x-dl --profile ~/.x-dl-profile https://x.com/user/status/123456Custom timeout:
x-dl --timeout 60 https://x.com/user/status/123456Clip a video to a specific time range:
# Download only the 30sβ90s portion of a video
x-dl --from 00:30 --to 01:30 https://x.com/user/status/123456
# Download from 1 minute to the end
x-dl --from 01:00 https://x.com/user/status/123456Clipped files are saved with a _clip suffix, e.g. username_123456_clip.mp4. Both --from and --to are optional β omitting --from starts from the beginning, omitting --to runs to the end.
When extracting a video, the tool will:
- Check that Playwright + Chromium are available
- Check ffmpeg availability (for HLS support)
- Validate tweet URL
- Open tweet in a headless browser
- Extract video URL (preferring progressive formats like mp4/webm)
- Download video:
- For mp4/webm/gif files: direct download with progress reporting
- For HLS (m3u8) playlists: use ffmpeg to download and convert to mp4
- Save it with a filename like
username_tweetid.{ext}(extension based on format)
π¬ x-dl - X/Twitter Video Extractor
π Checking for Playwright (Chromium)...
β
Playwright Chromium is ready
π¬ Extracting video from: https://x.com/Remotion/status/2013626968386765291
π Tweet: @Remotion (ID: 2013626968386765291)
π Opening tweet in browser...
β³ Waiting for page to load...
β
Page loaded
π Looking for video...
π‘ Found video via network monitoring
β
Video extracted: https://video.twimg.com/ext_tw_video/...
π Suggested filename: Remotion_2013626968386765291.mp4
π₯ Downloading video from: https://video.twimg.com/...
π Output path: ~/Downloads/Remotion_2013626968386765291.mp4
π Total size: 15.23 MB
β³ Progress: 100.0% (15.23 MB/15.23 MB)
β
Download completed in 0:45
π¦ Final size: 15.23 MB
β
Video saved to: ~/Downloads/Remotion_2013626968386765291.mp4
π¬ x-dl - X/Twitter Video Extractor
π Checking for Playwright (Chromium)...
β
Playwright Chromium is ready
π Checking for ffmpeg...
β
ffmpeg is ready
π¬ Extracting video from: https://x.com/Remotion/status/2013626968386765291
π Tweet: @Remotion (ID: 2013626968386765291)
π Opening tweet in browser...
β
Page loaded
π Looking for video...
β
Video extracted: https://video.twimg.com/ext_tw_video/...
π Suggested filename: Remotion_2013626968386765291_clip.mp4
π₯ Downloading HLS video via ffmpeg...
β Downloading HLS...
β
HLS download completed
β
Video saved to: ~/Downloads/Remotion_2013626968386765291_clip.mp4
-
Public tweets only: Private or protected tweets cannot be extracted
-
Clipping requires ffmpeg:
--fromand--torequire ffmpeg for processing -
Clipping time format: Times must be in MM:SS format (e.g.,
00:30, not0:30or30) -
Public tweets only: Private or protected tweets cannot be extracted
-
Time-limited URLs: Video URLs may expire after some time
-
Rate limiting: X may rate-limit excessive requests
-
Login walls: Use
--loginand--profileto extract login-walled tweets (alpha)
How to tell if a tweet can be extracted:
- Try opening the tweet in an incognito/private browser window
- If you see a "Sign up" or "Log in" prompt, this tool cannot extract it
- If the content loads without login, extraction should work
Run the test suite:
# Run all tests
bun test
# Run only unit tests
bun test test/unit/
# Run integration tests (requires Playwright Chromium)
bun test test/integration/Integration tests use a mock X/Twitter page and require Playwright Chromium:
bun test test/integration/To test with real tweets, you can run the tool directly:
bun run src/index.ts --headed https://x.com/user/status/123456Use --headed mode to see the browser for debugging.
βββ src/
β βββ index.ts # CLI entry point
β βββ extractor.ts # Video extraction logic
β βββ downloader.ts # Download logic (Bun fetch)
β βββ ffmpeg.ts # HLS download via ffmpeg
β βββ installer.ts # Dependency management (Playwright + ffmpeg)
β βββ types.ts # TypeScript interfaces
β βββ utils.ts # Helper functions
β βββ postinstall.ts # Post-install setup script
βββ test/
β βββ unit/ # Unit tests
β βββ integration/ # Integration tests
β βββ test-utils.ts # Test utilities
βββ bin/
βββ x-dl # Executable
bun run buildIf Chromium is missing, install it with:
bunx playwright install chromiumIf you see this error when trying to download an HLS (m3u8) video, ffmpeg is either not installed or lacks the required features.
Auto-install:
The tool will attempt to auto-install ffmpeg when you run bun install or when needed.
Manual install:
macOS: brew install ffmpeg
Linux: sudo apt-get install ffmpeg # or dnf/yum/pacman equivalentAfter installation, run:
bun run src/index.ts <url>The tool will verify ffmpeg capabilities automatically.
- Run
x-dl --login --profile ~/.x-dl-profileand make sure you can view the tweet in that browser - Then rerun extraction with
--profile ~/.x-dl-profile
Security note: your profile directory contains authentication cookies.
Only public tweets can be extracted. Verify that:
- The account is not private/protected
- You're not trying to access sensitive content
- The tweet is publicly accessible
The tweet may not contain a video. Check the tweet URL and verify it contains video content.
- Increase timeout with
--timeoutoption - Check your internet connection
- Try at a different time (X may be rate-limiting)
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
