vidgrab 0.5.2__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.
- vidgrab-0.5.2/LICENSE +21 -0
- vidgrab-0.5.2/PKG-INFO +649 -0
- vidgrab-0.5.2/README.md +629 -0
- vidgrab-0.5.2/pyproject.toml +78 -0
- vidgrab-0.5.2/vidgrab/__init__.py +3 -0
- vidgrab-0.5.2/vidgrab/__main__.py +5 -0
- vidgrab-0.5.2/vidgrab/cli.py +258 -0
- vidgrab-0.5.2/vidgrab/config.py +24 -0
- vidgrab-0.5.2/vidgrab/downloader.py +483 -0
- vidgrab-0.5.2/vidgrab/exceptions.py +52 -0
- vidgrab-0.5.2/vidgrab/models.py +92 -0
vidgrab-0.5.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gsjonio
|
|
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.
|
vidgrab-0.5.2/PKG-INFO
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vidgrab
|
|
3
|
+
Version: 0.5.2
|
|
4
|
+
Summary: CLI tool to download YouTube videos at maximum quality for video editing
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: gsjonio
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: rich (>=13.7.0)
|
|
16
|
+
Requires-Dist: typer (>=0.12.0)
|
|
17
|
+
Requires-Dist: yt-dlp (>=2024.1.1)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# vidgrab
|
|
21
|
+
|
|
22
|
+
**PT-BR** | [EN](#english)
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
[](https://github.com/gsjonio/video_grabber/actions/workflows/lint.yml)
|
|
28
|
+
[](https://github.com/gsjonio/video_grabber/actions/workflows/codeql.yml)
|
|
29
|
+
[](https://codecov.io/gh/gsjonio/video_grabber)
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
CLI para baixar vídeos do YouTube na **maior qualidade técnica disponível** — streams de vídeo e áudio separados (DASH), mesclados via FFmpeg sem nenhum reencode. Feito para quem usa vídeo como material bruto em edição.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## PT-BR
|
|
37
|
+
|
|
38
|
+
- [Como funciona](#como-funciona)
|
|
39
|
+
- [Funcionalidades](#funcionalidades)
|
|
40
|
+
- [Dependências](#dependências-externas)
|
|
41
|
+
- [Instalação](#instalação)
|
|
42
|
+
- [Uso](#uso)
|
|
43
|
+
- [Config file](#config-file)
|
|
44
|
+
- [Opções](#referência-de-opções)
|
|
45
|
+
- [Qualidade de código](#qualidade-de-código)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### Como funciona
|
|
50
|
+
|
|
51
|
+
A maioria das ferramentas de download aplica reencode para juntar vídeo e áudio — o que degrada a qualidade e desperdiça tempo. O vidgrab faz diferente:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
YouTube → stream de vídeo (H.264 / VP9 / AV1) ─┐
|
|
55
|
+
→ stream de áudio (AAC / Opus) ─┴→ FFmpeg mux → arquivo final
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Os dois streams são baixados separadamente no formato DASH (maior qualidade disponível) e mesclados em **modo cópia** — sem recodificação, sem perda de qualidade.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### Funcionalidades
|
|
63
|
+
|
|
64
|
+
| | Funcionalidade | Detalhe |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| ⚡ | **Downloads paralelos** | Até 8 simultâneos via `--workers` |
|
|
67
|
+
| 🔁 | **Retry inteligente** | Backoff exponencial em rate-limits (até 5 tentativas) |
|
|
68
|
+
| 🔍 | **Dry run** | Veja título, resolução e tamanho antes de baixar |
|
|
69
|
+
| ⏸ | **Resume automático** | Downloads interrompidos são retomados de onde pararam |
|
|
70
|
+
| 📋 | **Batch download** | Arquivo `.txt` com uma URL por linha |
|
|
71
|
+
| 🎬 | **Playlists** | Expande e baixa todos os vídeos de uma playlist |
|
|
72
|
+
| 📁 | **Skip inteligente** | Detecta arquivo existente pelo ID e pula automaticamente |
|
|
73
|
+
| 📄 | **Metadados JSON** | Sidecar `.json` com título, canal, data, tags e mais |
|
|
74
|
+
| 🔒 | **Conteúdo restrito** | Suporte a cookies (Netscape) para vídeos com age-gate |
|
|
75
|
+
| ⚙️ | **Config file** | Defaults pessoais em `~/.config/vidgrab/config.toml` |
|
|
76
|
+
| 🏷 | **Nomes previsíveis** | `{data}-{slug}-{video_id}.{ext}` em todo download |
|
|
77
|
+
| ⚠️ | **Aviso de licença** | Alerta quando o vídeo não é Creative Commons |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Dependências externas
|
|
82
|
+
|
|
83
|
+
| Ferramenta | Para quê | Como instalar |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| **Python 3.11+** | Runtime | [python.org](https://www.python.org/downloads/) |
|
|
86
|
+
| **ffmpeg** | Mesclar streams de vídeo e áudio | Veja abaixo |
|
|
87
|
+
| **yt-dlp** | Engine de download | Instalado automaticamente via Poetry |
|
|
88
|
+
| **Deno** *(opcional)* | Acesso a todos os formatos do YouTube, incluindo 4K/HDR | Veja abaixo |
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary>Instalando o ffmpeg</summary>
|
|
92
|
+
|
|
93
|
+
#### Windows
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
winget install ffmpeg
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### macOS
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
brew install ffmpeg
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Linux (Debian/Ubuntu)
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
sudo apt install ffmpeg
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Ou baixe o executável em <https://ffmpeg.org/download.html> e adicione ao `PATH`.
|
|
112
|
+
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
<details>
|
|
116
|
+
<summary>Instalando o Deno (recomendado para 4K/HDR)</summary>
|
|
117
|
+
|
|
118
|
+
Sem o Deno, o yt-dlp usa um método alternativo que pode não enxergar todos os formatos disponíveis. Com o Deno instalado, a extração é completa.
|
|
119
|
+
|
|
120
|
+
#### Windows
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
winget install DenoLand.Deno
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### macOS
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
brew install deno
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Linux
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
curl -fsSL https://deno.land/install.sh | sh
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
</details>
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Instalação
|
|
143
|
+
|
|
144
|
+
**Via pip (recomendado):**
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install vidgrab
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Via pipx (isolado):**
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pipx install vidgrab
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**From source (desenvolvimento):**
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git clone https://github.com/gsjonio/video_grabber.git
|
|
160
|
+
cd video_grabber
|
|
161
|
+
poetry install
|
|
162
|
+
poetry run vidgrab --help
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Automated installers:**
|
|
166
|
+
|
|
167
|
+
- **Linux/macOS:** `bash install.sh`
|
|
168
|
+
- **Windows:** `install.bat`
|
|
169
|
+
|
|
170
|
+
These scripts check for Python 3.11+ and ffmpeg, then install via pip.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Publicando no PyPI
|
|
175
|
+
|
|
176
|
+
Quer distribuir vidgrab para mais pessoas via `pip install vidgrab`?
|
|
177
|
+
|
|
178
|
+
Veja [PUBLISH.md](PUBLISH.md) para instruções completas:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Build
|
|
182
|
+
make build
|
|
183
|
+
|
|
184
|
+
# Configure token PyPI
|
|
185
|
+
poetry config pypi-token.pypi "seu-token"
|
|
186
|
+
|
|
187
|
+
# Publish
|
|
188
|
+
make publish
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### Uso
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Vídeo único — qualidade máxima
|
|
197
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ
|
|
198
|
+
|
|
199
|
+
# Inspecionar antes de baixar (dry run)
|
|
200
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --dry-run
|
|
201
|
+
|
|
202
|
+
# Limitar a 1080p
|
|
203
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --max-height 1080
|
|
204
|
+
|
|
205
|
+
# Salvar em diretório específico
|
|
206
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --output ~/Videos/raw
|
|
207
|
+
|
|
208
|
+
# Baixar playlist inteira
|
|
209
|
+
vidgrab "https://youtube.com/playlist?list=PLxxxx" --playlist
|
|
210
|
+
|
|
211
|
+
# Múltiplas URLs de um arquivo .txt com 5 workers
|
|
212
|
+
vidgrab --batch urls.txt --workers 5
|
|
213
|
+
|
|
214
|
+
# Forçar re-download mesmo se o arquivo já existir
|
|
215
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --force
|
|
216
|
+
|
|
217
|
+
# Conteúdo com restrição de idade
|
|
218
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --cookies ~/cookies.txt
|
|
219
|
+
|
|
220
|
+
# Salvar metadados em JSON
|
|
221
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --write-json
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### Arquivo `--batch`
|
|
225
|
+
|
|
226
|
+
Uma URL por linha. Linhas com `#` são ignoradas.
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
# Meus vídeos
|
|
230
|
+
https://youtu.be/dQw4w9WgXcQ
|
|
231
|
+
https://youtu.be/VIDEO_ID_2
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### Config file
|
|
237
|
+
|
|
238
|
+
Salve seus defaults pessoais em `~/.config/vidgrab/config.toml` para não precisar repetir as flags:
|
|
239
|
+
|
|
240
|
+
```toml
|
|
241
|
+
output = "~/Videos/raw"
|
|
242
|
+
workers = 5
|
|
243
|
+
max_height = 1080
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Flags passadas na linha de comando sempre têm prioridade sobre o config file.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Nomeação dos arquivos
|
|
251
|
+
|
|
252
|
+
```text
|
|
253
|
+
{data_upload}-{slug-do-titulo}-{video_id}.{ext}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Exemplo: `20240315-never-gonna-give-you-up-dQw4w9WgXcQ.mp4`
|
|
257
|
+
|
|
258
|
+
Com `--write-json`, um sidecar `.json` é criado ao lado do vídeo:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"video_id": "dQw4w9WgXcQ",
|
|
263
|
+
"title": "Rick Astley - Never Gonna Give You Up",
|
|
264
|
+
"channel": "Rick Astley",
|
|
265
|
+
"upload_date": "2009-10-25",
|
|
266
|
+
"duration_seconds": 212,
|
|
267
|
+
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
268
|
+
"description": "...",
|
|
269
|
+
"tags": ["pop", "80s"]
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
### Container e qualidade
|
|
276
|
+
|
|
277
|
+
| Streams disponíveis | Container de saída |
|
|
278
|
+
| --- | --- |
|
|
279
|
+
| H.264 + AAC | `mp4` (sem reencode) |
|
|
280
|
+
| VP9 / AV1 + Opus | `mkv` (sem reencode) |
|
|
281
|
+
|
|
282
|
+
O objetivo é **nunca recodificar** — apenas mesclar os streams.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### Referência de opções
|
|
287
|
+
|
|
288
|
+
| Opção | Atalho | Descrição |
|
|
289
|
+
| --- | --- | --- |
|
|
290
|
+
| `[URLS]...` | | Uma ou mais URLs do YouTube |
|
|
291
|
+
| `--batch FILE` | `-b` | Arquivo `.txt` com uma URL por linha |
|
|
292
|
+
| `--output DIR` | `-o` | Diretório de saída (padrão: `~/Downloads`) |
|
|
293
|
+
| `--max-height INT` | | Limitar resolução vertical (ex.: `1080`) |
|
|
294
|
+
| `--playlist` | | Tratar URLs como playlists |
|
|
295
|
+
| `--force` | `-f` | Re-download mesmo se o arquivo já existe |
|
|
296
|
+
| `--cookies FILE` | | Arquivo de cookies (formato Netscape) |
|
|
297
|
+
| `--write-json` | | Salvar metadados em `.json` ao lado do vídeo |
|
|
298
|
+
| `--workers INT` | `-w` | Downloads paralelos (padrão: `3`, máx: `8`) |
|
|
299
|
+
| `--dry-run` | | Mostrar o que seria baixado, sem baixar |
|
|
300
|
+
| `--quiet` | `-q` | Suprimir toda saída exceto erros (útil para scripts) |
|
|
301
|
+
| `--version` | `-V` | Exibir versão |
|
|
302
|
+
| `--install-completion` | | Instalar autocomplete no shell atual |
|
|
303
|
+
| `--help` | | Exibir ajuda |
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### Qualidade de código
|
|
308
|
+
|
|
309
|
+
O projeto usa uma stack completa de qualidade, integrada ao CI e aos pre-commit hooks:
|
|
310
|
+
|
|
311
|
+
| Ferramenta | Função |
|
|
312
|
+
| --- | --- |
|
|
313
|
+
| **Ruff** | Linter + formatador (9 categorias de regras) |
|
|
314
|
+
| **Pylint** | Análise estática avançada |
|
|
315
|
+
| **Mypy** (strict) | Type checking completo — `dict[str, Any]` em todas as fronteiras com yt-dlp |
|
|
316
|
+
| **pytest + pytest-cov** | 38 testes unitários, cobertura reportada ao Codecov |
|
|
317
|
+
| **pre-commit** | Ruff, Pylint, Mypy e Markdownlint rodando antes de cada commit |
|
|
318
|
+
| **GitHub Actions** | Lint, testes, CodeQL e release automática por tag |
|
|
319
|
+
| **Dependabot** | Atualizações semanais de dependências Python e Actions |
|
|
320
|
+
|
|
321
|
+
<details>
|
|
322
|
+
<summary>Estrutura do projeto</summary>
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
vidgrab/
|
|
326
|
+
├── cli.py # Interface Typer — parsing de argumentos e orquestração
|
|
327
|
+
├── downloader.py # Lógica core — parallelismo, retry, classificação de erros
|
|
328
|
+
├── models.py # VideoMetadata e DownloadResult como dataclasses tipadas
|
|
329
|
+
├── exceptions.py # Hierarquia de exceções (geo-block, age-gate, unavailable…)
|
|
330
|
+
└── config.py # Loader de ~/.config/vidgrab/config.toml via tomllib
|
|
331
|
+
|
|
332
|
+
tests/
|
|
333
|
+
├── test_downloader.py # _slugify, _classify_error, _format_selector, DownloadConfig
|
|
334
|
+
├── test_models.py # VideoMetadata serialization / deserialization
|
|
335
|
+
├── test_cli.py # _collect_urls com batch e URLs posicionais
|
|
336
|
+
└── test_config.py # config.load com TOML válido, inválido e ausente
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
</details>
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## English
|
|
344
|
+
|
|
345
|
+
CLI to download YouTube videos at the **highest technically available quality** — separate DASH video and audio streams, muxed via FFmpeg with no re-encoding. Built for raw footage in video editing workflows.
|
|
346
|
+
|
|
347
|
+
- [How it works](#how-it-works)
|
|
348
|
+
- [Features](#features)
|
|
349
|
+
- [Dependencies](#external-dependencies)
|
|
350
|
+
- [Installation](#installation)
|
|
351
|
+
- [Usage](#usage-1)
|
|
352
|
+
- [Config file](#config-file-1)
|
|
353
|
+
- [Options](#option-reference)
|
|
354
|
+
- [Code quality](#code-quality)
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
### How it works
|
|
359
|
+
|
|
360
|
+
Most download tools re-encode to merge video and audio — degrading quality and wasting time. vidgrab does it differently:
|
|
361
|
+
|
|
362
|
+
```text
|
|
363
|
+
YouTube → video stream (H.264 / VP9 / AV1) ─┐
|
|
364
|
+
→ audio stream (AAC / Opus) ─┴→ FFmpeg mux → final file
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Both streams are downloaded separately in DASH format (highest quality available) and muxed in **copy mode** — no transcoding, no quality loss.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Features
|
|
372
|
+
|
|
373
|
+
| | Feature | Detail |
|
|
374
|
+
| --- | --- | --- |
|
|
375
|
+
| ⚡ | **Parallel downloads** | Up to 8 simultaneous via `--workers` |
|
|
376
|
+
| 🔁 | **Smart retry** | Exponential backoff on rate-limits (up to 5 attempts) |
|
|
377
|
+
| 🔍 | **Dry run** | Preview title, resolution and size before downloading |
|
|
378
|
+
| ⏸ | **Auto resume** | Interrupted downloads pick up where they left off |
|
|
379
|
+
| 📋 | **Batch download** | `.txt` file with one URL per line |
|
|
380
|
+
| 🎬 | **Playlists** | Expands and downloads every video in a playlist |
|
|
381
|
+
| 📁 | **Smart skip** | Detects existing file by video ID and skips automatically |
|
|
382
|
+
| 📄 | **JSON metadata** | Sidecar `.json` with title, channel, date, tags and more |
|
|
383
|
+
| 🔒 | **Restricted content** | Cookie support (Netscape format) for age-gated videos |
|
|
384
|
+
| ⚙️ | **Config file** | Personal defaults at `~/.config/vidgrab/config.toml` |
|
|
385
|
+
| 🏷 | **Predictable names** | `{date}-{slug}-{video_id}.{ext}` on every download |
|
|
386
|
+
| ⚠️ | **License warning** | Alerts when a video is not under a Creative Commons license |
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
### External dependencies
|
|
391
|
+
|
|
392
|
+
| Tool | Purpose | How to install |
|
|
393
|
+
| --- | --- | --- |
|
|
394
|
+
| **Python 3.11+** | Runtime | [python.org](https://www.python.org/downloads/) |
|
|
395
|
+
| **ffmpeg** | Merge video + audio streams | See below |
|
|
396
|
+
| **yt-dlp** | Download engine | Installed automatically via Poetry |
|
|
397
|
+
| **Deno** *(optional)* | Access all YouTube formats including 4K/HDR | See below |
|
|
398
|
+
|
|
399
|
+
<details>
|
|
400
|
+
<summary>Installing ffmpeg</summary>
|
|
401
|
+
|
|
402
|
+
#### Windows
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
winget install ffmpeg
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### macOS
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
brew install ffmpeg
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
#### Linux (Debian/Ubuntu)
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
sudo apt install ffmpeg
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Or grab the binary from <https://ffmpeg.org/download.html> and add it to your `PATH`.
|
|
421
|
+
|
|
422
|
+
</details>
|
|
423
|
+
|
|
424
|
+
<details>
|
|
425
|
+
<summary>Installing Deno (recommended for 4K/HDR)</summary>
|
|
426
|
+
|
|
427
|
+
Without Deno, yt-dlp falls back to an alternative extraction method that may not expose all available formats. With Deno, extraction is complete.
|
|
428
|
+
|
|
429
|
+
#### Windows
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
winget install DenoLand.Deno
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### macOS
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
brew install deno
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### Linux
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
curl -fsSL https://deno.land/install.sh | sh
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
</details>
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### Installation
|
|
452
|
+
|
|
453
|
+
**Via pip (recommended):**
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
pip install vidgrab
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Via pipx (isolated):**
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
pipx install vidgrab
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**From source (development):**
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
git clone https://github.com/gsjonio/video_grabber.git
|
|
469
|
+
cd video_grabber
|
|
470
|
+
poetry install
|
|
471
|
+
poetry run vidgrab --help
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Automated installers:**
|
|
475
|
+
|
|
476
|
+
- **Linux/macOS:** `bash install.sh`
|
|
477
|
+
- **Windows:** `install.bat`
|
|
478
|
+
|
|
479
|
+
These scripts check for Python 3.11+ and ffmpeg, then install via pip.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
### Publishing to PyPI
|
|
484
|
+
|
|
485
|
+
Want to distribute vidgrab via `pip install vidgrab`?
|
|
486
|
+
|
|
487
|
+
See [PUBLISH.md](PUBLISH.md) for complete instructions:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
# Build
|
|
491
|
+
make build
|
|
492
|
+
|
|
493
|
+
# Configure PyPI token
|
|
494
|
+
poetry config pypi-token.pypi "your-token"
|
|
495
|
+
|
|
496
|
+
# Publish
|
|
497
|
+
make publish
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### Usage
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Single video — maximum quality
|
|
506
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ
|
|
507
|
+
|
|
508
|
+
# Inspect before downloading (dry run)
|
|
509
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --dry-run
|
|
510
|
+
|
|
511
|
+
# Cap at 1080p
|
|
512
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --max-height 1080
|
|
513
|
+
|
|
514
|
+
# Save to a specific directory
|
|
515
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --output ~/Videos/raw
|
|
516
|
+
|
|
517
|
+
# Download an entire playlist
|
|
518
|
+
vidgrab "https://youtube.com/playlist?list=PLxxxx" --playlist
|
|
519
|
+
|
|
520
|
+
# Download from a .txt file with 5 parallel workers
|
|
521
|
+
vidgrab --batch urls.txt --workers 5
|
|
522
|
+
|
|
523
|
+
# Force re-download even if the file already exists
|
|
524
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --force
|
|
525
|
+
|
|
526
|
+
# Age-restricted content
|
|
527
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --cookies ~/cookies.txt
|
|
528
|
+
|
|
529
|
+
# Save metadata as JSON
|
|
530
|
+
vidgrab https://youtu.be/dQw4w9WgXcQ --write-json
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### `--batch` file format
|
|
534
|
+
|
|
535
|
+
One URL per line. Lines starting with `#` are ignored.
|
|
536
|
+
|
|
537
|
+
```text
|
|
538
|
+
# My videos
|
|
539
|
+
https://youtu.be/dQw4w9WgXcQ
|
|
540
|
+
https://youtu.be/VIDEO_ID_2
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
### Config file
|
|
546
|
+
|
|
547
|
+
Save your personal defaults at `~/.config/vidgrab/config.toml` to avoid repeating flags:
|
|
548
|
+
|
|
549
|
+
```toml
|
|
550
|
+
output = "~/Videos/raw"
|
|
551
|
+
workers = 5
|
|
552
|
+
max_height = 1080
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Flags passed on the command line always take precedence over the config file.
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
### Output filename pattern
|
|
560
|
+
|
|
561
|
+
```text
|
|
562
|
+
{upload_date}-{title-slug}-{video_id}.{ext}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Example: `20240315-never-gonna-give-you-up-dQw4w9WgXcQ.mp4`
|
|
566
|
+
|
|
567
|
+
With `--write-json`, a sidecar `.json` is saved next to each video:
|
|
568
|
+
|
|
569
|
+
```json
|
|
570
|
+
{
|
|
571
|
+
"video_id": "dQw4w9WgXcQ",
|
|
572
|
+
"title": "Rick Astley - Never Gonna Give You Up",
|
|
573
|
+
"channel": "Rick Astley",
|
|
574
|
+
"upload_date": "2009-10-25",
|
|
575
|
+
"duration_seconds": 212,
|
|
576
|
+
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
577
|
+
"description": "...",
|
|
578
|
+
"tags": ["pop", "80s"]
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
### Container and quality
|
|
585
|
+
|
|
586
|
+
| Available streams | Output container |
|
|
587
|
+
| --- | --- |
|
|
588
|
+
| H.264 + AAC | `mp4` (no re-encode) |
|
|
589
|
+
| VP9 / AV1 + Opus | `mkv` (no re-encode) |
|
|
590
|
+
|
|
591
|
+
The goal is to **never re-encode** — only mux the streams.
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### Option reference
|
|
596
|
+
|
|
597
|
+
| Option | Short | Description |
|
|
598
|
+
| --- | --- | --- |
|
|
599
|
+
| `[URLS]...` | | One or more YouTube URLs |
|
|
600
|
+
| `--batch FILE` | `-b` | `.txt` file with one URL per line |
|
|
601
|
+
| `--output DIR` | `-o` | Output directory (default: `~/Downloads`) |
|
|
602
|
+
| `--max-height INT` | | Cap vertical resolution (e.g. `1080`) |
|
|
603
|
+
| `--playlist` | | Treat URLs as playlists |
|
|
604
|
+
| `--force` | `-f` | Re-download even if the file already exists |
|
|
605
|
+
| `--cookies FILE` | | Cookies file (Netscape format) |
|
|
606
|
+
| `--write-json` | | Save metadata as a `.json` sidecar next to the video |
|
|
607
|
+
| `--workers INT` | `-w` | Parallel downloads (default: `3`, max: `8`) |
|
|
608
|
+
| `--dry-run` | | Show what would be downloaded without downloading |
|
|
609
|
+
| `--quiet` | `-q` | Suppress all output except errors (useful for scripting) |
|
|
610
|
+
| `--version` | `-V` | Show version and exit |
|
|
611
|
+
| `--install-completion` | | Install shell autocomplete for the current shell |
|
|
612
|
+
| `--help` | | Show help |
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
### Code quality
|
|
617
|
+
|
|
618
|
+
The project uses a full quality stack, integrated into CI and pre-commit hooks:
|
|
619
|
+
|
|
620
|
+
| Tool | Role |
|
|
621
|
+
| --- | --- |
|
|
622
|
+
| **Ruff** | Linter + formatter (9 rule categories) |
|
|
623
|
+
| **Pylint** | Advanced static analysis |
|
|
624
|
+
| **Mypy** (strict) | Full type checking — `dict[str, Any]` at every yt-dlp boundary |
|
|
625
|
+
| **pytest + pytest-cov** | 38 unit tests, coverage reported to Codecov |
|
|
626
|
+
| **pre-commit** | Ruff, Pylint, Mypy and Markdownlint run before every commit |
|
|
627
|
+
| **GitHub Actions** | Lint, tests, CodeQL and automatic release on tag push |
|
|
628
|
+
| **Dependabot** | Weekly updates for Python deps and Actions |
|
|
629
|
+
|
|
630
|
+
<details>
|
|
631
|
+
<summary>Project structure</summary>
|
|
632
|
+
|
|
633
|
+
```text
|
|
634
|
+
vidgrab/
|
|
635
|
+
├── cli.py # Typer interface — argument parsing and orchestration
|
|
636
|
+
├── downloader.py # Core logic — parallelism, retry, typed error classification
|
|
637
|
+
├── models.py # VideoMetadata and DownloadResult as typed dataclasses
|
|
638
|
+
├── exceptions.py # Exception hierarchy (geo-block, age-gate, unavailable…)
|
|
639
|
+
└── config.py # ~/.config/vidgrab/config.toml loader via tomllib
|
|
640
|
+
|
|
641
|
+
tests/
|
|
642
|
+
├── test_downloader.py # _slugify, _classify_error, _format_selector, DownloadConfig
|
|
643
|
+
├── test_models.py # VideoMetadata serialization / deserialization
|
|
644
|
+
├── test_cli.py # _collect_urls with batch and positional URLs
|
|
645
|
+
└── test_config.py # config.load with valid, invalid and missing TOML
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
</details>
|
|
649
|
+
|