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.
@@ -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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
39
+ ![Platforms](https://img.shields.io/badge/platform-Linux%20%7C%20Windows%20%7C%20macOS-lightgrey)
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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
5
+ ![Platforms](https://img.shields.io/badge/platform-Linux%20%7C%20Windows%20%7C%20macOS-lightgrey)
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)