Grabinator
A command-line downloader for TikTok, YouTube, Dailymotion, SoundCloud, Instagram,
X, and Threads — security-hardened, with automatic quality-aware deduplication and
playback fixes for native macOS players. Also converts local video files to MP3 with
--convert, no network access required.
Table of contents
- Features
- Supported platforms (OS)
- Installation
- Usage
- Authentication (cookies)
- Configuration
- Security measures
- Testing status
- Legal
- Development
- Author
- License
Features
| Sources | TikTok, YouTube, Dailymotion, SoundCloud, Instagram, X, Threads |
| Naming | TikTok/Instagram/X/Threads → @username_id.mp4 · YouTube/Dailymotion → title.mp4 |
| Organization | Each platform gets its own subfolder (TikTok/, YouTube/, etc.) |
| Audio | --mp3 extracts clean, padding-free audio on any platform |
| Local conversion | --convert PATH turns existing video files into MP3s — no downloading |
| Captions | --captions downloads subtitles only (converted to .srt), for a video or a whole playlist |
| Photo posts | --photos saves a TikTok/Instagram/X/Threads photo-carousel post as individual full-quality images instead of a rendered slideshow video |
| Channels | Point a YouTube channel/handle URL at Grabinator and it downloads every upload, same as a playlist |
| Playlists | Full YouTube/SoundCloud playlist support, with --range to grab a specific span |
| Quality selection | Capped one tier above 1080p by default, or pick interactively with -q |
| Dedupe | Re-downloading never creates duplicates — a lower/equal-quality repeat is skipped, a genuinely better one replaces the old file |
| macOS fix | Auto re-encodes tracks that play in VLC but are silent/blank in QuickTime, Preview, or Photos |
| Proxy | --proxy routes every request (including the connectivity check) through a SOCKS5/HTTP(S) proxy |
| Cookies | --cookies-from-browser or --cookies unlock private/login-required content |
| Size check | -c shows the estimated download size — one total for a whole playlist — before anything downloads |
Supported platforms (OS)
Pure Python, using only pathlib/subprocess for filesystem and process work — it
runs the same way on:
- Linux — any distro with Python 3.10+ and
ffmpegavailable. - macOS — including the playback-compatibility fixes this tool specifically adds.
- Windows — the script forces UTF-8 console output and enables ANSI color
processing on startup, so the colored status output and emoji indicators render
correctly instead of crashing or printing garbled escape codes on a legacy
cmd.execodepage. Works from PowerShell, Windows Terminal, or plaincmd.exe.
The one external dependency that varies by OS is ffmpeg itself — see
Installation for the install command on each.
Installation
git clone https://github.com/eaglesquawk/grabinator.git
cd grabinator
pip install -e .
This also installs yt-dlp, tqdm, and certifi. Separately, install ffmpeg
(which includes ffprobe) via your OS package manager:
brew install ffmpeg # macOS
sudo apt install ffmpeg # Debian/Ubuntu
winget install ffmpeg # Windows
Usage
Single items
grabinator "https://www.tiktok.com/@user/video/123"
grabinator "https://www.youtube.com/watch?v=XXXXXXXXXXX"
grabinator "https://www.dailymotion.com/video/XXXXXXX"
grabinator "https://soundcloud.com/artist/track-name"
grabinator "https://www.instagram.com/p/XXXXXXXXXXX/"
grabinator "https://x.com/user/status/XXXXXXXXXXX"
grabinator "https://www.threads.net/@user/post/XXXXXXXXXXX"
Playlists and channels
grabinator "https://www.youtube.com/playlist?list=XXXXXXXXXXX"
grabinator "https://soundcloud.com/artist/sets/album-name"
# A YouTube channel or handle downloads every upload, the same as a playlist —
# no special flag needed, Grabinator recognizes the URL shape automatically
grabinator "https://www.youtube.com/@SomeChannel"
grabinator "https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx"
# Only videos 3 through 7 of a playlist or channel
grabinator "PLAYLIST_OR_CHANNEL_URL" --range 3-7
# Just the first 10
grabinator "PLAYLIST_OR_CHANNEL_URL" --range 10
Multiple URLs at once
grabinator "URL1,URL2,URL3"
Audio
# Audio only, on any platform — clean MP3, no leading silence
grabinator "URL" --mp3
The leading-silence "padding" that naive MP3 extraction leaves in place comes from
source timestamps that don't start at zero — this isn't a TikTok-specific quirk, it
affects YouTube (and Dailymotion, SoundCloud, the rest) just as much. --mp3 runs
the exact same ffmpeg fix — zeroing negative timestamps and regenerating clean
presentation timestamps before encoding — on every platform uniformly, YouTube
included, so the output is padding-free no matter where it came from.
# Convert local video files to MP3 instead — no network access at all,
# originals are never touched. PATH can be a file, a folder, or a .txt
# manifest listing one path per line.
grabinator --convert /path/to/videos
grabinator --convert /path/to/video.mp4
grabinator --convert /path/to/list.txt
Captions
# Download only the subtitles for a video, saved as .srt
grabinator "URL" --captions
# Works on a whole playlist or channel too — one .srt per video
grabinator "PLAYLIST_OR_CHANNEL_URL" --captions
# A specific language (default: en)
grabinator "URL" --captions --caption-lang es
Whatever subtitle format the source actually provides gets converted to .srt, so
the output is consistent regardless of platform. If a video simply has no captions
available, Grabinator says so and moves on rather than failing the whole run.
Photo posts
# Save a TikTok/Instagram/X/Threads photo-carousel post as individual images,
# instead of the default behavior of rendering it into a slideshow video with audio
grabinator "URL" --photos
By default, a photo-carousel post (no native video track) is reconstructed into an
MP4 slideshow set to its audio track. --photos skips that rendering step entirely
and instead saves each photo exactly as downloaded — no re-encoding, so this is
definitionally the best available quality — into a new folder named after the post
(@username_id/), numbered in post order (1.jpg, 2.jpg, ...). Re-running against
an already-downloaded post is skipped rather than re-downloading into a duplicate
folder. --photos only applies to TikTok/Instagram/X/Threads; on any other platform,
or if the post turns out to be a regular video rather than a photo post, Grabinator
notes that and downloads normally instead. It can't be combined with --mp3,
--captions, -q/--choose-quality, or -c/--check-size.
Quality selection
# Interactively pick a resolution from a numbered menu
grabinator "URL" -q
# See the estimated file size and confirm before downloading
# (one total for an entire playlist, not one prompt per video)
grabinator "URL" -c
Proxy
grabinator "URL" --proxy socks5://127.0.0.1:9050
grabinator "URL" --proxy http://user:pass@host:port
Other flags
grabinator "URL" --silent # suppress progress bars and status output
grabinator "URL" --output-dir /some/path # override the default download folder
grabinator "URL" --log # also write this run's output to a timestamped .txt file
grabinator --version # print the installed version
--log writes a full copy of everything printed during that run to
<output-dir>/logs/grabinator_<timestamp>.txt, one file per run, alongside — not
instead of — the normal console output. Live-updating progress-bar frames aren't
logged individually (that would just be noise); each bar's final result still is.
Run without installing, straight from the source file:
python src/grabinator/cli.py "URL"
Authentication (cookies)
Some content requires being logged in to view at all — a private Instagram account,
an age-restricted YouTube video, some Threads posts. Grabinator doesn't handle logins
itself; instead it borrows a session you already have, the same way yt-dlp does
under the hood.
# Reuse cookies from a browser you're already logged into
grabinator "PRIVATE_URL" --cookies-from-browser chrome
grabinator "PRIVATE_URL" --cookies-from-browser firefox
# Or use an exported cookies.txt file instead, without touching a live browser profile
grabinator "PRIVATE_URL" --cookies /path/to/cookies.txt
The two are mutually exclusive — pick one. --cookies-from-browser reads directly
from that browser's cookie storage each run; --cookies points at a Netscape-format
file you've exported once (browser extensions like "Get cookies.txt" can produce
this). Neither option is stored, logged, or written anywhere by Grabinator itself —
they're passed straight through to yt-dlp for that run only.
Configuration
The default download folder is set at the top of src/grabinator/cli.py:
OUTPUT_DIR = Path.home() / "Downloads" / "media by Grabinator"
Edit that constant, or pass --output-dir on the command line to override it per run.
Whatever you choose, each platform still gets its own subfolder underneath it.
Security measures
- URLs are checked against an explicit host allowlist (TikTok/YouTube/Dailymotion/ SoundCloud/Instagram/X/Threads domains only) before any network request is made.
- All subprocess calls (
ffmpeg,ffprobe) use argument lists, nevershell=True. - Filenames derived from remote titles/usernames are sanitized, and every final destination path is resolved and verified to stay inside the configured output directory before anything is written — blocking path traversal from a malicious or unexpected title.
- Per-run limits guard against accidental bulk-scraping: at most 25 URLs in one invocation, and at most 200 videos pulled from any single playlist or channel.
- The connectivity check only probes the exact host a given URL points to — never a fixed external address unrelated to what you asked to download.
--convertnever opens the network at all, and never modifies or deletes the original video files it reads.- Cookies passed via
--cookies-from-browseror--cookiesare used only for that run's requests — Grabinator never writes them to disk, logs them, or includes them in the dedupe index.
Testing status
The pure logic (URL/platform detection, path safety, filename sanitization, range parsing, cookie-options handling, etc.) is covered by an automated test suite and verified on every push. A few things are logic-verified but not yet confirmed against real, live usage:
- Windows. The UTF-8/ANSI console hardening is implemented and tested for correctness, but hasn't been run on an actual Windows machine.
- Cookies (
--cookies-from-browser,--cookies). The options are built and passed toyt-dlpcorrectly, but haven't been exercised against a real login-gated download. - Channel downloads. URL detection and normalization to a channel's "Videos" tab are tested, but a real channel hasn't been downloaded end-to-end yet.
- Captions. The download and
.srtconversion path hasn't been run against a real video with real subtitles.
If you hit something that doesn't work as documented in one of these areas, that's useful to know about.
Legal
Built on yt-dlp and ffmpeg. Use it for content
you have the right to download — your own uploads, permissively licensed content, or
personal archival use consistent with each platform's Terms of Service and applicable
copyright law. You're responsible for how you use it.
Development
pip install -e ".[dev]"
pre-commit install
pytest
Linting/formatting runs automatically on commit via pre-commit + ruff. See
CONTRIBUTING.md for how to add a new platform, what's expected
of a pull request, and the release process. See CHANGELOG.md for the
project's history.
Author
EagleSquwak — September 2026
License
Metadata
Release files for grabinator 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| grabinator-0.6.0.tar.gz | 35.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grabinator-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.2 kB
Release files / grabinator-0.6.0.tar.gz
| Download URL | grabinator-0.6.0.tar.gz |
|---|---|
| Size | 35.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
312c79bdb5026d040994a8833550c37cb95dbf4d2d9b57e525459fb4132e661c
|
|
BLAKE2b-256 checksum How to use checksums |
0c201105698cd6816b06550580342e3d085cac25442e191050dfde3a950c117f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Release files / grabinator-0.6.0-py3-none-any.whl
| Download URL | grabinator-0.6.0-py3-none-any.whl |
|---|---|
| Size | 27.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f577eac746f2e11d510ffecebd6383cd96e16910cc73d2e6cac46cf5a77e6f8a
|
|
BLAKE2b-256 checksum How to use checksums |
39a0f68a7c2cc27cd6a773bb528eef5505cf6d0ae8868c0ee10158b79f860ab4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|