grabinator 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grabinator-0.5.0/LICENSE +21 -0
- grabinator-0.5.0/PKG-INFO +325 -0
- grabinator-0.5.0/README.md +291 -0
- grabinator-0.5.0/pyproject.toml +70 -0
- grabinator-0.5.0/setup.cfg +4 -0
- grabinator-0.5.0/src/grabinator/__init__.py +0 -0
- grabinator-0.5.0/src/grabinator/cli.py +1520 -0
- grabinator-0.5.0/src/grabinator.egg-info/PKG-INFO +325 -0
- grabinator-0.5.0/src/grabinator.egg-info/SOURCES.txt +12 -0
- grabinator-0.5.0/src/grabinator.egg-info/dependency_links.txt +1 -0
- grabinator-0.5.0/src/grabinator.egg-info/entry_points.txt +2 -0
- grabinator-0.5.0/src/grabinator.egg-info/requires.txt +8 -0
- grabinator-0.5.0/src/grabinator.egg-info/top_level.txt +1 -0
- grabinator-0.5.0/tests/test_core.py +402 -0
grabinator-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EagleSquwak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grabinator
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Download from TikTok, YouTube, Dailymotion, SoundCloud, Instagram, X, and Threads with security hardening, quality-aware dedupe, and macOS playback fixes.
|
|
5
|
+
Author: EagleSquwak
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/eaglesquawk/grabinator
|
|
8
|
+
Project-URL: Repository, https://github.com/eaglesquawk/grabinator
|
|
9
|
+
Project-URL: Issues, https://github.com/eaglesquawk/grabinator/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/eaglesquawk/grabinator/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: tiktok,youtube,dailymotion,soundcloud,instagram,twitter,x,threads,downloader,yt-dlp,video,mp3,cli
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Multimedia :: Video
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
22
|
+
Classifier: Topic :: Internet
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: yt-dlp>=2026.1.1
|
|
27
|
+
Requires-Dist: tqdm>=4.66
|
|
28
|
+
Requires-Dist: certifi>=2024.2.2
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pre-commit>=3.7; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# Grabinator
|
|
36
|
+
|
|
37
|
+
[](LICENSE)
|
|
38
|
+

|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
A command-line downloader for **TikTok, YouTube, Dailymotion, SoundCloud, Instagram,
|
|
42
|
+
X, and Threads** — security-hardened, with automatic quality-aware deduplication and
|
|
43
|
+
playback fixes for native macOS players. Also converts local video files to MP3 with
|
|
44
|
+
`--convert`, no network access required.
|
|
45
|
+
|
|
46
|
+
## Table of contents
|
|
47
|
+
|
|
48
|
+
- [Features](#features)
|
|
49
|
+
- [Supported platforms (OS)](#supported-platforms-os)
|
|
50
|
+
- [Installation](#installation)
|
|
51
|
+
- [Usage](#usage)
|
|
52
|
+
- [Authentication (cookies)](#authentication-cookies)
|
|
53
|
+
- [Configuration](#configuration)
|
|
54
|
+
- [Security measures](#security-measures)
|
|
55
|
+
- [Testing status](#testing-status)
|
|
56
|
+
- [Legal](#legal)
|
|
57
|
+
- [Development](#development)
|
|
58
|
+
- [Author](#author)
|
|
59
|
+
- [License](#license)
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
| | |
|
|
64
|
+
|---|---|
|
|
65
|
+
| **Sources** | TikTok, YouTube, Dailymotion, SoundCloud, Instagram, X, Threads |
|
|
66
|
+
| **Naming** | TikTok/Instagram/X/Threads → `@username_id.mp4` · YouTube/Dailymotion → `title.mp4` |
|
|
67
|
+
| **Organization** | Each platform gets its own subfolder (`TikTok/`, `YouTube/`, etc.) |
|
|
68
|
+
| **Audio** | `--mp3` extracts clean, padding-free audio on any platform |
|
|
69
|
+
| **Local conversion** | `--convert PATH` turns existing video files into MP3s — no downloading |
|
|
70
|
+
| **Captions** | `--captions` downloads subtitles only (converted to `.srt`), for a video or a whole playlist |
|
|
71
|
+
| **Channels** | Point a YouTube channel/handle URL at Grabinator and it downloads every upload, same as a playlist |
|
|
72
|
+
| **Playlists** | Full YouTube/SoundCloud playlist support, with `--range` to grab a specific span |
|
|
73
|
+
| **Quality selection** | Capped one tier above 1080p by default, or pick interactively with `-q` |
|
|
74
|
+
| **Dedupe** | Re-downloading never creates duplicates — a lower/equal-quality repeat is skipped, a genuinely better one replaces the old file |
|
|
75
|
+
| **macOS fix** | Auto re-encodes tracks that play in VLC but are silent/blank in QuickTime, Preview, or Photos |
|
|
76
|
+
| **Proxy** | `--proxy` routes every request (including the connectivity check) through a SOCKS5/HTTP(S) proxy |
|
|
77
|
+
| **Cookies** | `--cookies-from-browser` or `--cookies` unlock private/login-required content |
|
|
78
|
+
| **Size check** | `-c` shows the estimated download size — one total for a whole playlist — before anything downloads |
|
|
79
|
+
|
|
80
|
+
## Supported platforms (OS)
|
|
81
|
+
|
|
82
|
+
Pure Python, using only `pathlib`/`subprocess` for filesystem and process work — it
|
|
83
|
+
runs the same way on:
|
|
84
|
+
|
|
85
|
+
- **Linux** — any distro with Python 3.10+ and `ffmpeg` available.
|
|
86
|
+
- **macOS** — including the playback-compatibility fixes this tool specifically adds.
|
|
87
|
+
- **Windows** — the script forces UTF-8 console output and enables ANSI color
|
|
88
|
+
processing on startup, so the colored status output and emoji indicators render
|
|
89
|
+
correctly instead of crashing or printing garbled escape codes on a legacy `cmd.exe`
|
|
90
|
+
codepage. Works from PowerShell, Windows Terminal, or plain `cmd.exe`.
|
|
91
|
+
|
|
92
|
+
The one external dependency that varies by OS is `ffmpeg` itself — see
|
|
93
|
+
[Installation](#installation) for the install command on each.
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git clone https://github.com/eaglesquawk/grabinator.git
|
|
99
|
+
cd grabinator
|
|
100
|
+
pip install -e .
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This also installs `yt-dlp`, `tqdm`, and `certifi`. Separately, install `ffmpeg`
|
|
104
|
+
(which includes `ffprobe`) via your OS package manager:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
brew install ffmpeg # macOS
|
|
108
|
+
sudo apt install ffmpeg # Debian/Ubuntu
|
|
109
|
+
winget install ffmpeg # Windows
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
### Single items
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
grabinator "https://www.tiktok.com/@user/video/123"
|
|
118
|
+
grabinator "https://www.youtube.com/watch?v=XXXXXXXXXXX"
|
|
119
|
+
grabinator "https://www.dailymotion.com/video/XXXXXXX"
|
|
120
|
+
grabinator "https://soundcloud.com/artist/track-name"
|
|
121
|
+
grabinator "https://www.instagram.com/p/XXXXXXXXXXX/"
|
|
122
|
+
grabinator "https://x.com/user/status/XXXXXXXXXXX"
|
|
123
|
+
grabinator "https://www.threads.net/@user/post/XXXXXXXXXXX"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Playlists and channels
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
grabinator "https://www.youtube.com/playlist?list=XXXXXXXXXXX"
|
|
130
|
+
grabinator "https://soundcloud.com/artist/sets/album-name"
|
|
131
|
+
|
|
132
|
+
# A YouTube channel or handle downloads every upload, the same as a playlist —
|
|
133
|
+
# no special flag needed, Grabinator recognizes the URL shape automatically
|
|
134
|
+
grabinator "https://www.youtube.com/@SomeChannel"
|
|
135
|
+
grabinator "https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx"
|
|
136
|
+
|
|
137
|
+
# Only videos 3 through 7 of a playlist or channel
|
|
138
|
+
grabinator "PLAYLIST_OR_CHANNEL_URL" --range 3-7
|
|
139
|
+
|
|
140
|
+
# Just the first 10
|
|
141
|
+
grabinator "PLAYLIST_OR_CHANNEL_URL" --range 10
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Multiple URLs at once
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
grabinator "URL1,URL2,URL3"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Audio
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Audio only, on any platform — clean MP3, no leading silence
|
|
154
|
+
grabinator "URL" --mp3
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The leading-silence "padding" that naive MP3 extraction leaves in place comes from
|
|
158
|
+
source timestamps that don't start at zero — this isn't a TikTok-specific quirk, it
|
|
159
|
+
affects YouTube (and Dailymotion, SoundCloud, the rest) just as much. `--mp3` runs
|
|
160
|
+
the exact same ffmpeg fix — zeroing negative timestamps and regenerating clean
|
|
161
|
+
presentation timestamps before encoding — on every platform uniformly, YouTube
|
|
162
|
+
included, so the output is padding-free no matter where it came from.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Convert local video files to MP3 instead — no network access at all,
|
|
166
|
+
# originals are never touched. PATH can be a file, a folder, or a .txt
|
|
167
|
+
# manifest listing one path per line.
|
|
168
|
+
grabinator --convert /path/to/videos
|
|
169
|
+
grabinator --convert /path/to/video.mp4
|
|
170
|
+
grabinator --convert /path/to/list.txt
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Captions
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Download only the subtitles for a video, saved as .srt
|
|
177
|
+
grabinator "URL" --captions
|
|
178
|
+
|
|
179
|
+
# Works on a whole playlist or channel too — one .srt per video
|
|
180
|
+
grabinator "PLAYLIST_OR_CHANNEL_URL" --captions
|
|
181
|
+
|
|
182
|
+
# A specific language (default: en)
|
|
183
|
+
grabinator "URL" --captions --caption-lang es
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Whatever subtitle format the source actually provides gets converted to `.srt`, so
|
|
187
|
+
the output is consistent regardless of platform. If a video simply has no captions
|
|
188
|
+
available, Grabinator says so and moves on rather than failing the whole run.
|
|
189
|
+
|
|
190
|
+
### Quality selection
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Interactively pick a resolution from a numbered menu
|
|
194
|
+
grabinator "URL" -q
|
|
195
|
+
|
|
196
|
+
# See the estimated file size and confirm before downloading
|
|
197
|
+
# (one total for an entire playlist, not one prompt per video)
|
|
198
|
+
grabinator "URL" -c
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Proxy
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
grabinator "URL" --proxy socks5://127.0.0.1:9050
|
|
205
|
+
grabinator "URL" --proxy http://user:pass@host:port
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Other flags
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
grabinator "URL" --silent # suppress progress bars and status output
|
|
212
|
+
grabinator "URL" --output-dir /some/path # override the default download folder
|
|
213
|
+
grabinator "URL" --log # also write this run's output to a timestamped .txt file
|
|
214
|
+
grabinator --version # print the installed version
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`--log` writes a full copy of everything printed during that run to
|
|
218
|
+
`<output-dir>/logs/grabinator_<timestamp>.txt`, one file per run, alongside — not
|
|
219
|
+
instead of — the normal console output. Live-updating progress-bar frames aren't
|
|
220
|
+
logged individually (that would just be noise); each bar's final result still is.
|
|
221
|
+
|
|
222
|
+
Run without installing, straight from the source file:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
python src/grabinator/cli.py "URL"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Authentication (cookies)
|
|
229
|
+
|
|
230
|
+
Some content requires being logged in to view at all — a private Instagram account,
|
|
231
|
+
an age-restricted YouTube video, some Threads posts. Grabinator doesn't handle logins
|
|
232
|
+
itself; instead it borrows a session you already have, the same way `yt-dlp` does
|
|
233
|
+
under the hood.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Reuse cookies from a browser you're already logged into
|
|
237
|
+
grabinator "PRIVATE_URL" --cookies-from-browser chrome
|
|
238
|
+
grabinator "PRIVATE_URL" --cookies-from-browser firefox
|
|
239
|
+
|
|
240
|
+
# Or use an exported cookies.txt file instead, without touching a live browser profile
|
|
241
|
+
grabinator "PRIVATE_URL" --cookies /path/to/cookies.txt
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The two are mutually exclusive — pick one. `--cookies-from-browser` reads directly
|
|
245
|
+
from that browser's cookie storage each run; `--cookies` points at a Netscape-format
|
|
246
|
+
file you've exported once (browser extensions like "Get cookies.txt" can produce
|
|
247
|
+
this). Neither option is stored, logged, or written anywhere by Grabinator itself —
|
|
248
|
+
they're passed straight through to `yt-dlp` for that run only.
|
|
249
|
+
|
|
250
|
+
## Configuration
|
|
251
|
+
|
|
252
|
+
The default download folder is set at the top of `src/grabinator/cli.py`:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
OUTPUT_DIR = Path.home() / "Downloads" / "media by Grabinator"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Edit that constant, or pass `--output-dir` on the command line to override it per run.
|
|
259
|
+
Whatever you choose, each platform still gets its own subfolder underneath it.
|
|
260
|
+
|
|
261
|
+
## Security measures
|
|
262
|
+
|
|
263
|
+
- URLs are checked against an explicit host allowlist (TikTok/YouTube/Dailymotion/
|
|
264
|
+
SoundCloud/Instagram/X/Threads domains only) before any network request is made.
|
|
265
|
+
- All subprocess calls (`ffmpeg`, `ffprobe`) use argument lists, never `shell=True`.
|
|
266
|
+
- Filenames derived from remote titles/usernames are sanitized, and every final
|
|
267
|
+
destination path is resolved and verified to stay inside the configured output
|
|
268
|
+
directory before anything is written — blocking path traversal from a malicious or
|
|
269
|
+
unexpected title.
|
|
270
|
+
- Per-run URL and playlist-size caps guard against accidental bulk-scraping.
|
|
271
|
+
- The connectivity check only probes the exact host a given URL points to — never a
|
|
272
|
+
fixed external address unrelated to what you asked to download.
|
|
273
|
+
- `--convert` never opens the network at all, and never modifies or deletes the
|
|
274
|
+
original video files it reads.
|
|
275
|
+
- Cookies passed via `--cookies-from-browser` or `--cookies` are used only for that
|
|
276
|
+
run's requests — Grabinator never writes them to disk, logs them, or includes them
|
|
277
|
+
in the dedupe index.
|
|
278
|
+
|
|
279
|
+
## Testing status
|
|
280
|
+
|
|
281
|
+
The pure logic (URL/platform detection, path safety, filename sanitization, range
|
|
282
|
+
parsing, cookie-options handling, etc.) is covered by an automated test suite and
|
|
283
|
+
verified on every push. A few things are logic-verified but **not yet confirmed
|
|
284
|
+
against real, live usage**:
|
|
285
|
+
|
|
286
|
+
- **Windows.** The UTF-8/ANSI console hardening is implemented and tested for
|
|
287
|
+
correctness, but hasn't been run on an actual Windows machine.
|
|
288
|
+
- **Cookies** (`--cookies-from-browser`, `--cookies`). The options are built and
|
|
289
|
+
passed to `yt-dlp` correctly, but haven't been exercised against a real
|
|
290
|
+
login-gated download.
|
|
291
|
+
- **Channel downloads.** URL detection and normalization to a channel's "Videos"
|
|
292
|
+
tab are tested, but a real channel hasn't been downloaded end-to-end yet.
|
|
293
|
+
- **Captions.** The download and `.srt` conversion path hasn't been run against a
|
|
294
|
+
real video with real subtitles.
|
|
295
|
+
|
|
296
|
+
If you hit something that doesn't work as documented in one of these areas, that's
|
|
297
|
+
useful to know about.
|
|
298
|
+
|
|
299
|
+
## Legal
|
|
300
|
+
|
|
301
|
+
Built on [yt-dlp](https://github.com/yt-dlp/yt-dlp) and `ffmpeg`. Use it for content
|
|
302
|
+
you have the right to download — your own uploads, permissively licensed content, or
|
|
303
|
+
personal archival use consistent with each platform's Terms of Service and applicable
|
|
304
|
+
copyright law. You're responsible for how you use it.
|
|
305
|
+
|
|
306
|
+
## Development
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
pip install -e ".[dev]"
|
|
310
|
+
pre-commit install
|
|
311
|
+
pytest
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Linting/formatting runs automatically on commit via `pre-commit` + `ruff`. See
|
|
315
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for how to add a new platform, what's expected
|
|
316
|
+
of a pull request, and the release process. See [CHANGELOG.md](CHANGELOG.md) for the
|
|
317
|
+
project's history.
|
|
318
|
+
|
|
319
|
+
## Author
|
|
320
|
+
|
|
321
|
+
EagleSquwak — September 2026
|
|
322
|
+
|
|
323
|
+
## License
|
|
324
|
+
|
|
325
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Grabinator
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
A command-line downloader for **TikTok, YouTube, Dailymotion, SoundCloud, Instagram,
|
|
8
|
+
X, and Threads** — security-hardened, with automatic quality-aware deduplication and
|
|
9
|
+
playback fixes for native macOS players. Also converts local video files to MP3 with
|
|
10
|
+
`--convert`, no network access required.
|
|
11
|
+
|
|
12
|
+
## Table of contents
|
|
13
|
+
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Supported platforms (OS)](#supported-platforms-os)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Authentication (cookies)](#authentication-cookies)
|
|
19
|
+
- [Configuration](#configuration)
|
|
20
|
+
- [Security measures](#security-measures)
|
|
21
|
+
- [Testing status](#testing-status)
|
|
22
|
+
- [Legal](#legal)
|
|
23
|
+
- [Development](#development)
|
|
24
|
+
- [Author](#author)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
| | |
|
|
30
|
+
|---|---|
|
|
31
|
+
| **Sources** | TikTok, YouTube, Dailymotion, SoundCloud, Instagram, X, Threads |
|
|
32
|
+
| **Naming** | TikTok/Instagram/X/Threads → `@username_id.mp4` · YouTube/Dailymotion → `title.mp4` |
|
|
33
|
+
| **Organization** | Each platform gets its own subfolder (`TikTok/`, `YouTube/`, etc.) |
|
|
34
|
+
| **Audio** | `--mp3` extracts clean, padding-free audio on any platform |
|
|
35
|
+
| **Local conversion** | `--convert PATH` turns existing video files into MP3s — no downloading |
|
|
36
|
+
| **Captions** | `--captions` downloads subtitles only (converted to `.srt`), for a video or a whole playlist |
|
|
37
|
+
| **Channels** | Point a YouTube channel/handle URL at Grabinator and it downloads every upload, same as a playlist |
|
|
38
|
+
| **Playlists** | Full YouTube/SoundCloud playlist support, with `--range` to grab a specific span |
|
|
39
|
+
| **Quality selection** | Capped one tier above 1080p by default, or pick interactively with `-q` |
|
|
40
|
+
| **Dedupe** | Re-downloading never creates duplicates — a lower/equal-quality repeat is skipped, a genuinely better one replaces the old file |
|
|
41
|
+
| **macOS fix** | Auto re-encodes tracks that play in VLC but are silent/blank in QuickTime, Preview, or Photos |
|
|
42
|
+
| **Proxy** | `--proxy` routes every request (including the connectivity check) through a SOCKS5/HTTP(S) proxy |
|
|
43
|
+
| **Cookies** | `--cookies-from-browser` or `--cookies` unlock private/login-required content |
|
|
44
|
+
| **Size check** | `-c` shows the estimated download size — one total for a whole playlist — before anything downloads |
|
|
45
|
+
|
|
46
|
+
## Supported platforms (OS)
|
|
47
|
+
|
|
48
|
+
Pure Python, using only `pathlib`/`subprocess` for filesystem and process work — it
|
|
49
|
+
runs the same way on:
|
|
50
|
+
|
|
51
|
+
- **Linux** — any distro with Python 3.10+ and `ffmpeg` available.
|
|
52
|
+
- **macOS** — including the playback-compatibility fixes this tool specifically adds.
|
|
53
|
+
- **Windows** — the script forces UTF-8 console output and enables ANSI color
|
|
54
|
+
processing on startup, so the colored status output and emoji indicators render
|
|
55
|
+
correctly instead of crashing or printing garbled escape codes on a legacy `cmd.exe`
|
|
56
|
+
codepage. Works from PowerShell, Windows Terminal, or plain `cmd.exe`.
|
|
57
|
+
|
|
58
|
+
The one external dependency that varies by OS is `ffmpeg` itself — see
|
|
59
|
+
[Installation](#installation) for the install command on each.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/eaglesquawk/grabinator.git
|
|
65
|
+
cd grabinator
|
|
66
|
+
pip install -e .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This also installs `yt-dlp`, `tqdm`, and `certifi`. Separately, install `ffmpeg`
|
|
70
|
+
(which includes `ffprobe`) via your OS package manager:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
brew install ffmpeg # macOS
|
|
74
|
+
sudo apt install ffmpeg # Debian/Ubuntu
|
|
75
|
+
winget install ffmpeg # Windows
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Single items
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
grabinator "https://www.tiktok.com/@user/video/123"
|
|
84
|
+
grabinator "https://www.youtube.com/watch?v=XXXXXXXXXXX"
|
|
85
|
+
grabinator "https://www.dailymotion.com/video/XXXXXXX"
|
|
86
|
+
grabinator "https://soundcloud.com/artist/track-name"
|
|
87
|
+
grabinator "https://www.instagram.com/p/XXXXXXXXXXX/"
|
|
88
|
+
grabinator "https://x.com/user/status/XXXXXXXXXXX"
|
|
89
|
+
grabinator "https://www.threads.net/@user/post/XXXXXXXXXXX"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Playlists and channels
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
grabinator "https://www.youtube.com/playlist?list=XXXXXXXXXXX"
|
|
96
|
+
grabinator "https://soundcloud.com/artist/sets/album-name"
|
|
97
|
+
|
|
98
|
+
# A YouTube channel or handle downloads every upload, the same as a playlist —
|
|
99
|
+
# no special flag needed, Grabinator recognizes the URL shape automatically
|
|
100
|
+
grabinator "https://www.youtube.com/@SomeChannel"
|
|
101
|
+
grabinator "https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx"
|
|
102
|
+
|
|
103
|
+
# Only videos 3 through 7 of a playlist or channel
|
|
104
|
+
grabinator "PLAYLIST_OR_CHANNEL_URL" --range 3-7
|
|
105
|
+
|
|
106
|
+
# Just the first 10
|
|
107
|
+
grabinator "PLAYLIST_OR_CHANNEL_URL" --range 10
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Multiple URLs at once
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
grabinator "URL1,URL2,URL3"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Audio
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Audio only, on any platform — clean MP3, no leading silence
|
|
120
|
+
grabinator "URL" --mp3
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The leading-silence "padding" that naive MP3 extraction leaves in place comes from
|
|
124
|
+
source timestamps that don't start at zero — this isn't a TikTok-specific quirk, it
|
|
125
|
+
affects YouTube (and Dailymotion, SoundCloud, the rest) just as much. `--mp3` runs
|
|
126
|
+
the exact same ffmpeg fix — zeroing negative timestamps and regenerating clean
|
|
127
|
+
presentation timestamps before encoding — on every platform uniformly, YouTube
|
|
128
|
+
included, so the output is padding-free no matter where it came from.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Convert local video files to MP3 instead — no network access at all,
|
|
132
|
+
# originals are never touched. PATH can be a file, a folder, or a .txt
|
|
133
|
+
# manifest listing one path per line.
|
|
134
|
+
grabinator --convert /path/to/videos
|
|
135
|
+
grabinator --convert /path/to/video.mp4
|
|
136
|
+
grabinator --convert /path/to/list.txt
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Captions
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Download only the subtitles for a video, saved as .srt
|
|
143
|
+
grabinator "URL" --captions
|
|
144
|
+
|
|
145
|
+
# Works on a whole playlist or channel too — one .srt per video
|
|
146
|
+
grabinator "PLAYLIST_OR_CHANNEL_URL" --captions
|
|
147
|
+
|
|
148
|
+
# A specific language (default: en)
|
|
149
|
+
grabinator "URL" --captions --caption-lang es
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Whatever subtitle format the source actually provides gets converted to `.srt`, so
|
|
153
|
+
the output is consistent regardless of platform. If a video simply has no captions
|
|
154
|
+
available, Grabinator says so and moves on rather than failing the whole run.
|
|
155
|
+
|
|
156
|
+
### Quality selection
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Interactively pick a resolution from a numbered menu
|
|
160
|
+
grabinator "URL" -q
|
|
161
|
+
|
|
162
|
+
# See the estimated file size and confirm before downloading
|
|
163
|
+
# (one total for an entire playlist, not one prompt per video)
|
|
164
|
+
grabinator "URL" -c
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Proxy
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
grabinator "URL" --proxy socks5://127.0.0.1:9050
|
|
171
|
+
grabinator "URL" --proxy http://user:pass@host:port
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Other flags
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
grabinator "URL" --silent # suppress progress bars and status output
|
|
178
|
+
grabinator "URL" --output-dir /some/path # override the default download folder
|
|
179
|
+
grabinator "URL" --log # also write this run's output to a timestamped .txt file
|
|
180
|
+
grabinator --version # print the installed version
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`--log` writes a full copy of everything printed during that run to
|
|
184
|
+
`<output-dir>/logs/grabinator_<timestamp>.txt`, one file per run, alongside — not
|
|
185
|
+
instead of — the normal console output. Live-updating progress-bar frames aren't
|
|
186
|
+
logged individually (that would just be noise); each bar's final result still is.
|
|
187
|
+
|
|
188
|
+
Run without installing, straight from the source file:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
python src/grabinator/cli.py "URL"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Authentication (cookies)
|
|
195
|
+
|
|
196
|
+
Some content requires being logged in to view at all — a private Instagram account,
|
|
197
|
+
an age-restricted YouTube video, some Threads posts. Grabinator doesn't handle logins
|
|
198
|
+
itself; instead it borrows a session you already have, the same way `yt-dlp` does
|
|
199
|
+
under the hood.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Reuse cookies from a browser you're already logged into
|
|
203
|
+
grabinator "PRIVATE_URL" --cookies-from-browser chrome
|
|
204
|
+
grabinator "PRIVATE_URL" --cookies-from-browser firefox
|
|
205
|
+
|
|
206
|
+
# Or use an exported cookies.txt file instead, without touching a live browser profile
|
|
207
|
+
grabinator "PRIVATE_URL" --cookies /path/to/cookies.txt
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The two are mutually exclusive — pick one. `--cookies-from-browser` reads directly
|
|
211
|
+
from that browser's cookie storage each run; `--cookies` points at a Netscape-format
|
|
212
|
+
file you've exported once (browser extensions like "Get cookies.txt" can produce
|
|
213
|
+
this). Neither option is stored, logged, or written anywhere by Grabinator itself —
|
|
214
|
+
they're passed straight through to `yt-dlp` for that run only.
|
|
215
|
+
|
|
216
|
+
## Configuration
|
|
217
|
+
|
|
218
|
+
The default download folder is set at the top of `src/grabinator/cli.py`:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
OUTPUT_DIR = Path.home() / "Downloads" / "media by Grabinator"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Edit that constant, or pass `--output-dir` on the command line to override it per run.
|
|
225
|
+
Whatever you choose, each platform still gets its own subfolder underneath it.
|
|
226
|
+
|
|
227
|
+
## Security measures
|
|
228
|
+
|
|
229
|
+
- URLs are checked against an explicit host allowlist (TikTok/YouTube/Dailymotion/
|
|
230
|
+
SoundCloud/Instagram/X/Threads domains only) before any network request is made.
|
|
231
|
+
- All subprocess calls (`ffmpeg`, `ffprobe`) use argument lists, never `shell=True`.
|
|
232
|
+
- Filenames derived from remote titles/usernames are sanitized, and every final
|
|
233
|
+
destination path is resolved and verified to stay inside the configured output
|
|
234
|
+
directory before anything is written — blocking path traversal from a malicious or
|
|
235
|
+
unexpected title.
|
|
236
|
+
- Per-run URL and playlist-size caps guard against accidental bulk-scraping.
|
|
237
|
+
- The connectivity check only probes the exact host a given URL points to — never a
|
|
238
|
+
fixed external address unrelated to what you asked to download.
|
|
239
|
+
- `--convert` never opens the network at all, and never modifies or deletes the
|
|
240
|
+
original video files it reads.
|
|
241
|
+
- Cookies passed via `--cookies-from-browser` or `--cookies` are used only for that
|
|
242
|
+
run's requests — Grabinator never writes them to disk, logs them, or includes them
|
|
243
|
+
in the dedupe index.
|
|
244
|
+
|
|
245
|
+
## Testing status
|
|
246
|
+
|
|
247
|
+
The pure logic (URL/platform detection, path safety, filename sanitization, range
|
|
248
|
+
parsing, cookie-options handling, etc.) is covered by an automated test suite and
|
|
249
|
+
verified on every push. A few things are logic-verified but **not yet confirmed
|
|
250
|
+
against real, live usage**:
|
|
251
|
+
|
|
252
|
+
- **Windows.** The UTF-8/ANSI console hardening is implemented and tested for
|
|
253
|
+
correctness, but hasn't been run on an actual Windows machine.
|
|
254
|
+
- **Cookies** (`--cookies-from-browser`, `--cookies`). The options are built and
|
|
255
|
+
passed to `yt-dlp` correctly, but haven't been exercised against a real
|
|
256
|
+
login-gated download.
|
|
257
|
+
- **Channel downloads.** URL detection and normalization to a channel's "Videos"
|
|
258
|
+
tab are tested, but a real channel hasn't been downloaded end-to-end yet.
|
|
259
|
+
- **Captions.** The download and `.srt` conversion path hasn't been run against a
|
|
260
|
+
real video with real subtitles.
|
|
261
|
+
|
|
262
|
+
If you hit something that doesn't work as documented in one of these areas, that's
|
|
263
|
+
useful to know about.
|
|
264
|
+
|
|
265
|
+
## Legal
|
|
266
|
+
|
|
267
|
+
Built on [yt-dlp](https://github.com/yt-dlp/yt-dlp) and `ffmpeg`. Use it for content
|
|
268
|
+
you have the right to download — your own uploads, permissively licensed content, or
|
|
269
|
+
personal archival use consistent with each platform's Terms of Service and applicable
|
|
270
|
+
copyright law. You're responsible for how you use it.
|
|
271
|
+
|
|
272
|
+
## Development
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
pip install -e ".[dev]"
|
|
276
|
+
pre-commit install
|
|
277
|
+
pytest
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Linting/formatting runs automatically on commit via `pre-commit` + `ruff`. See
|
|
281
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for how to add a new platform, what's expected
|
|
282
|
+
of a pull request, and the release process. See [CHANGELOG.md](CHANGELOG.md) for the
|
|
283
|
+
project's history.
|
|
284
|
+
|
|
285
|
+
## Author
|
|
286
|
+
|
|
287
|
+
EagleSquwak — September 2026
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
[MIT](LICENSE)
|