subxx 0.3.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.
- subxx-0.3.0/!README.md +853 -0
- subxx-0.3.0/.gitattributes +27 -0
- subxx-0.3.0/.gitignore +98 -0
- subxx-0.3.0/.subxx.toml.example +88 -0
- subxx-0.3.0/Justfile +118 -0
- subxx-0.3.0/LICENSE +190 -0
- subxx-0.3.0/Makefile +168 -0
- subxx-0.3.0/PKG-INFO +896 -0
- subxx-0.3.0/PLAN.md +837 -0
- subxx-0.3.0/README.md +853 -0
- subxx-0.3.0/__main__.py +642 -0
- subxx-0.3.0/build-package.ps1 +125 -0
- subxx-0.3.0/conftest.py +18 -0
- subxx-0.3.0/pyproject.toml +84 -0
- subxx-0.3.0/subxx.py +1691 -0
- subxx-0.3.0/test_subxx.py +1376 -0
- subxx-0.3.0/uv.lock +2031 -0
subxx-0.3.0/!README.md
ADDED
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
# subxx
|
|
2
|
+
|
|
3
|
+
**YouTube transcript / subtitle fetching toolkit for Python** - Download, extract, and process subtitles from video URLs with a simple CLI or HTTP API.
|
|
4
|
+
|
|
5
|
+
[](https://gist.github.com/cprima/1ec077cb315295e349ee61dccf13f6b2)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://creativecommons.org/licenses/by/4.0/)
|
|
8
|
+
[](https://github.com/cprima/subxx)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Download YouTube subtitles** from videos and channels (powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp))
|
|
15
|
+
- **Multiple output formats**: SRT, VTT, TXT, Markdown, PDF
|
|
16
|
+
- **Text extraction** with automatic subtitle cleanup and optional timestamp markers
|
|
17
|
+
- **Language selection**: Download specific languages or all available subtitles
|
|
18
|
+
- **Batch processing**: Process multiple URLs from a file
|
|
19
|
+
- **Configuration files**: Project and global settings via TOML
|
|
20
|
+
- **HTTP API**: Optional FastAPI server for programmatic access
|
|
21
|
+
- **Dry-run mode**: Preview operations without downloading
|
|
22
|
+
- **Filename sanitization**: Safe, nospace, or slugify modes
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Table of Contents
|
|
27
|
+
|
|
28
|
+
- [Installation](#installation)
|
|
29
|
+
- [Quick Start](#quick-start)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [List Available Subtitles](#list-available-subtitles)
|
|
32
|
+
- [Download Subtitles](#download-subtitles)
|
|
33
|
+
- [Text Extraction](#text-extraction)
|
|
34
|
+
- [Batch Processing](#batch-processing)
|
|
35
|
+
- [Extract from Files](#extract-from-files)
|
|
36
|
+
- [Configuration](#configuration)
|
|
37
|
+
- [Makefile Shortcuts](#makefile-shortcuts)
|
|
38
|
+
- [HTTP API](#http-api)
|
|
39
|
+
- [Development](#development)
|
|
40
|
+
- [Testing](#testing)
|
|
41
|
+
- [License](#license)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Requirements
|
|
48
|
+
|
|
49
|
+
- Python 3.9 or higher
|
|
50
|
+
- [uv](https://github.com/astral-sh/uv) package manager (recommended)
|
|
51
|
+
|
|
52
|
+
### Install with uv (recommended)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Clone or download the project
|
|
56
|
+
git clone https://gist.github.com/cprima/subxx
|
|
57
|
+
cd subxx
|
|
58
|
+
|
|
59
|
+
# Install core dependencies
|
|
60
|
+
uv sync
|
|
61
|
+
|
|
62
|
+
# Install with optional features
|
|
63
|
+
uv sync --extra extract # Text extraction (txt/md/pdf)
|
|
64
|
+
uv sync --extra api # HTTP API server
|
|
65
|
+
uv sync --extra dev # Development tools (pytest)
|
|
66
|
+
|
|
67
|
+
# Install all features
|
|
68
|
+
uv sync --extra extract --extra api --extra dev
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Using Make (Windows)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
make install # Core dependencies
|
|
75
|
+
make install-all # All dependencies (extract + api + dev)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
### Basic Usage
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# List available subtitles
|
|
86
|
+
uv run python __main__.py list https://youtu.be/VIDEO_ID
|
|
87
|
+
|
|
88
|
+
# Download English subtitle (SRT format, default)
|
|
89
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID
|
|
90
|
+
|
|
91
|
+
# Extract to plain text
|
|
92
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID --txt
|
|
93
|
+
|
|
94
|
+
# Extract to Markdown with 5-minute timestamps
|
|
95
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID --md -t 300
|
|
96
|
+
|
|
97
|
+
# Extract to PDF
|
|
98
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID --pdf
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### With Makefile
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Quick Markdown extraction (just paste video ID)
|
|
105
|
+
make md VIDEO_ID=dQw4w9WgXcQ
|
|
106
|
+
|
|
107
|
+
# With timestamps
|
|
108
|
+
make md VIDEO_ID=dQw4w9WgXcQ TIMESTAMPS=300
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Usage
|
|
114
|
+
|
|
115
|
+
### List Available Subtitles
|
|
116
|
+
|
|
117
|
+
Preview available subtitle languages without downloading:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run python __main__.py list https://youtu.be/VIDEO_ID
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Output:**
|
|
124
|
+
```
|
|
125
|
+
📹 Video: Example Video Title
|
|
126
|
+
🕒 Duration: 12:34
|
|
127
|
+
|
|
128
|
+
✅ Manual subtitles:
|
|
129
|
+
- en
|
|
130
|
+
- es
|
|
131
|
+
|
|
132
|
+
🤖 Auto-generated subtitles:
|
|
133
|
+
- en, de, fr, ja, ko, pt, ru, zh-Hans, ...
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Options:**
|
|
137
|
+
- `-v, --verbose` - Debug output
|
|
138
|
+
- `-q, --quiet` - Errors only
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Download Subtitles
|
|
143
|
+
|
|
144
|
+
#### Format Selection
|
|
145
|
+
|
|
146
|
+
Download subtitle files in SRT or VTT format:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Download SRT (default)
|
|
150
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID
|
|
151
|
+
|
|
152
|
+
# Download VTT
|
|
153
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID --vtt
|
|
154
|
+
|
|
155
|
+
# Using --fmt flag
|
|
156
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID -f srt
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Behavior**: Subtitle files (SRT/VTT) are downloaded and kept on disk.
|
|
160
|
+
|
|
161
|
+
#### Language Selection
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Download English (default)
|
|
165
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID
|
|
166
|
+
|
|
167
|
+
# Download specific language
|
|
168
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID -l de
|
|
169
|
+
|
|
170
|
+
# Download multiple languages
|
|
171
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID -l "en,de,fr"
|
|
172
|
+
|
|
173
|
+
# Download all available languages
|
|
174
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID -l all
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Output Directory
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Save to specific directory
|
|
181
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID -o ~/Downloads/subs
|
|
182
|
+
|
|
183
|
+
# Use current directory (default)
|
|
184
|
+
uv run python __main__.py subs https://youtu.be/VIDEO_ID -o .
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Filename Sanitization
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Safe mode: Remove unsafe characters, keep spaces (default)
|
|
191
|
+
uv run python __main__.py subs URL --sanitize safe
|
|
192
|
+
|
|
193
|
+
# No spaces: Replace spaces with underscores
|
|
194
|
+
uv run python __main__.py subs URL --sanitize nospaces
|
|
195
|
+
|
|
196
|
+
# Slugify: Lowercase, hyphens, URL-safe
|
|
197
|
+
uv run python __main__.py subs URL --sanitize slugify
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Examples:**
|
|
201
|
+
- `safe`: `"My Video Title.srt"` → `"My Video Title.srt"`
|
|
202
|
+
- `nospaces`: `"My Video Title.srt"` → `"My_Video_Title.srt"`
|
|
203
|
+
- `slugify`: `"My Video Title.srt"` → `"my-video-title.srt"`
|
|
204
|
+
|
|
205
|
+
#### Overwrite Handling
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Prompt before overwriting (default)
|
|
209
|
+
uv run python __main__.py subs URL
|
|
210
|
+
|
|
211
|
+
# Force overwrite without prompting
|
|
212
|
+
uv run python __main__.py subs URL --force
|
|
213
|
+
|
|
214
|
+
# Skip existing files
|
|
215
|
+
uv run python __main__.py subs URL --skip-existing
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Auto-Generated Subtitles
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Include auto-generated subtitles (default)
|
|
222
|
+
uv run python __main__.py subs URL --auto
|
|
223
|
+
|
|
224
|
+
# Only manual subtitles
|
|
225
|
+
uv run python __main__.py subs URL --no-auto
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### Dry Run
|
|
229
|
+
|
|
230
|
+
Preview what would be downloaded without actually downloading:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
uv run python __main__.py subs URL --dry-run
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Output:**
|
|
237
|
+
```
|
|
238
|
+
[DRY RUN] Would download subtitle: en
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### Text Extraction
|
|
244
|
+
|
|
245
|
+
Extract clean, readable text from subtitles by automatically removing timestamps and formatting.
|
|
246
|
+
|
|
247
|
+
**Key behavior**: When using text formats (txt/md/pdf), subxx:
|
|
248
|
+
1. Downloads the subtitle as SRT
|
|
249
|
+
2. Extracts the text content
|
|
250
|
+
3. **Automatically deletes the SRT file**
|
|
251
|
+
|
|
252
|
+
#### Plain Text
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Extract to plain text
|
|
256
|
+
uv run python __main__.py subs URL --txt
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Output**: `Video_Title.VIDEO_ID.en.txt`
|
|
260
|
+
|
|
261
|
+
**Example content:**
|
|
262
|
+
```
|
|
263
|
+
Hello world.
|
|
264
|
+
This is a subtitle.
|
|
265
|
+
Welcome to the video.
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### Markdown
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Extract to Markdown
|
|
272
|
+
uv run python __main__.py subs URL --md
|
|
273
|
+
|
|
274
|
+
# Markdown with timestamp markers every 5 minutes
|
|
275
|
+
uv run python __main__.py subs URL --md -t 300
|
|
276
|
+
|
|
277
|
+
# Markdown with timestamp markers every 30 seconds
|
|
278
|
+
uv run python __main__.py subs URL --md -t 30
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Output**: `Video_Title.VIDEO_ID.en.md`
|
|
282
|
+
|
|
283
|
+
**Example content (with timestamps):**
|
|
284
|
+
```markdown
|
|
285
|
+
## [0:00]
|
|
286
|
+
|
|
287
|
+
Hello world.
|
|
288
|
+
This is a subtitle.
|
|
289
|
+
|
|
290
|
+
## [5:00]
|
|
291
|
+
|
|
292
|
+
Welcome to the next section.
|
|
293
|
+
More content here.
|
|
294
|
+
|
|
295
|
+
## [10:00]
|
|
296
|
+
|
|
297
|
+
Final section of the video.
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### PDF
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Extract to PDF
|
|
304
|
+
uv run python __main__.py subs URL --pdf
|
|
305
|
+
|
|
306
|
+
# PDF with timestamp markers
|
|
307
|
+
uv run python __main__.py subs URL --pdf -t 300
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Output**: `Video_Title.VIDEO_ID.en.pdf`
|
|
311
|
+
|
|
312
|
+
**Requirements**: Install extraction dependencies:
|
|
313
|
+
```bash
|
|
314
|
+
uv sync --extra extract
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### Timestamp Intervals
|
|
318
|
+
|
|
319
|
+
Add timestamp markers at regular intervals for long-form content:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Every 5 minutes (300 seconds)
|
|
323
|
+
uv run python __main__.py subs URL --md -t 300
|
|
324
|
+
|
|
325
|
+
# Every 30 seconds
|
|
326
|
+
uv run python __main__.py subs URL --txt -t 30
|
|
327
|
+
|
|
328
|
+
# Every 10 minutes
|
|
329
|
+
uv run python __main__.py subs URL --pdf -t 600
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Format**: Timestamps appear as `## [0:00]`, `## [5:00]`, `## [10:00]`, etc.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
### Batch Processing
|
|
337
|
+
|
|
338
|
+
Download subtitles for multiple URLs from a file:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Create URLs file (one URL per line)
|
|
342
|
+
cat > urls.txt << EOF
|
|
343
|
+
https://youtu.be/VIDEO_ID_1
|
|
344
|
+
https://youtu.be/VIDEO_ID_2
|
|
345
|
+
# This is a comment
|
|
346
|
+
https://youtu.be/VIDEO_ID_3
|
|
347
|
+
EOF
|
|
348
|
+
|
|
349
|
+
# Process all URLs
|
|
350
|
+
uv run python __main__.py batch urls.txt
|
|
351
|
+
|
|
352
|
+
# With options
|
|
353
|
+
uv run python __main__.py batch urls.txt -l "en,de" -f srt -o ~/subs
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Options:**
|
|
357
|
+
- `-l, --langs` - Language codes (default: en)
|
|
358
|
+
- `-f, --fmt` - Output format (default: srt)
|
|
359
|
+
- `-o, --output-dir` - Output directory (default: .)
|
|
360
|
+
- `--sanitize` - Filename sanitization mode (default: safe)
|
|
361
|
+
- `-v, --verbose` - Verbose output
|
|
362
|
+
- `-q, --quiet` - Quiet mode
|
|
363
|
+
|
|
364
|
+
**URL File Format** (yt-dlp standard):
|
|
365
|
+
- One URL per line
|
|
366
|
+
- Lines starting with `#` are comments
|
|
367
|
+
- Empty lines are ignored
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Extract from Files
|
|
372
|
+
|
|
373
|
+
Extract text from existing subtitle files:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Extract SRT to plain text
|
|
377
|
+
uv run python __main__.py extract video.srt
|
|
378
|
+
|
|
379
|
+
# Extract to Markdown
|
|
380
|
+
uv run python __main__.py extract video.srt -f md
|
|
381
|
+
|
|
382
|
+
# Extract to PDF
|
|
383
|
+
uv run python __main__.py extract video.srt -f pdf
|
|
384
|
+
|
|
385
|
+
# With timestamp markers every 5 minutes
|
|
386
|
+
uv run python __main__.py extract video.srt -f md -t 300
|
|
387
|
+
|
|
388
|
+
# Specify output file
|
|
389
|
+
uv run python __main__.py extract video.srt -o output.txt
|
|
390
|
+
|
|
391
|
+
# Force overwrite
|
|
392
|
+
uv run python __main__.py extract video.srt --force
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Supported input formats**: SRT, VTT
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Configuration
|
|
400
|
+
|
|
401
|
+
### Config File Locations
|
|
402
|
+
|
|
403
|
+
Configuration files are loaded in priority order:
|
|
404
|
+
|
|
405
|
+
1. `./.subxx.toml` (project-specific, current directory)
|
|
406
|
+
2. `~/.subxx.toml` (user global, home directory)
|
|
407
|
+
|
|
408
|
+
### Priority Chain
|
|
409
|
+
|
|
410
|
+
Settings are resolved in this order (highest to lowest):
|
|
411
|
+
|
|
412
|
+
1. **CLI flags** (e.g., `--langs en`, `--fmt srt`)
|
|
413
|
+
2. **Config file** (`.subxx.toml`)
|
|
414
|
+
3. **Hardcoded defaults**
|
|
415
|
+
|
|
416
|
+
### Example Configuration
|
|
417
|
+
|
|
418
|
+
Copy `.subxx.toml.example` to `.subxx.toml` or `~/.subxx.toml`:
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
cp .subxx.toml.example ~/.subxx.toml
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Example config:**
|
|
425
|
+
|
|
426
|
+
```toml
|
|
427
|
+
[defaults]
|
|
428
|
+
# Language codes (comma-separated or "all")
|
|
429
|
+
langs = "en"
|
|
430
|
+
|
|
431
|
+
# Output format: srt, vtt, txt, md, pdf
|
|
432
|
+
fmt = "md"
|
|
433
|
+
|
|
434
|
+
# Include auto-generated subtitles
|
|
435
|
+
auto = true
|
|
436
|
+
|
|
437
|
+
# Output directory (supports ~)
|
|
438
|
+
output_dir = "~/Downloads/subtitles"
|
|
439
|
+
|
|
440
|
+
# Filename sanitization: safe, nospaces, slugify
|
|
441
|
+
sanitize = "safe"
|
|
442
|
+
|
|
443
|
+
# Timestamp interval (seconds) for txt/md/pdf
|
|
444
|
+
timestamps = 300 # 5-minute intervals
|
|
445
|
+
|
|
446
|
+
[logging]
|
|
447
|
+
# Log level: DEBUG, INFO, WARNING, ERROR
|
|
448
|
+
level = "INFO"
|
|
449
|
+
|
|
450
|
+
# Log file (optional)
|
|
451
|
+
log_file = "~/.subxx/subxx.log"
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Use Case Configurations
|
|
455
|
+
|
|
456
|
+
**Configuration 1: Download SRT files to dedicated directory**
|
|
457
|
+
```toml
|
|
458
|
+
[defaults]
|
|
459
|
+
langs = "en"
|
|
460
|
+
fmt = "srt"
|
|
461
|
+
output_dir = "~/Downloads/subtitles"
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Configuration 2: Auto-extract to Markdown with timestamps**
|
|
465
|
+
```toml
|
|
466
|
+
[defaults]
|
|
467
|
+
langs = "en"
|
|
468
|
+
fmt = "md"
|
|
469
|
+
timestamps = 300
|
|
470
|
+
output_dir = "~/Documents/transcripts"
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Configuration 3: Multiple languages, plain text**
|
|
474
|
+
```toml
|
|
475
|
+
[defaults]
|
|
476
|
+
langs = "en,de,fr"
|
|
477
|
+
fmt = "txt"
|
|
478
|
+
sanitize = "slugify"
|
|
479
|
+
output_dir = "./subtitles"
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Makefile Shortcuts
|
|
485
|
+
|
|
486
|
+
### Available Targets
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
# Installation
|
|
490
|
+
make install # Core dependencies
|
|
491
|
+
make install-all # All dependencies (extract + api + dev)
|
|
492
|
+
|
|
493
|
+
# Testing
|
|
494
|
+
make test # Run all tests
|
|
495
|
+
make test-unit # Unit tests only
|
|
496
|
+
make test-integration # Integration tests only
|
|
497
|
+
make test-coverage # Tests with coverage report
|
|
498
|
+
|
|
499
|
+
# Usage
|
|
500
|
+
make list VIDEO_URL=https://youtu.be/VIDEO_ID
|
|
501
|
+
make subs VIDEO_URL=https://youtu.be/VIDEO_ID
|
|
502
|
+
make md VIDEO_ID=VIDEO_ID # Quick Markdown extraction
|
|
503
|
+
make md VIDEO_ID=VIDEO_ID TIMESTAMPS=300 # With timestamps
|
|
504
|
+
|
|
505
|
+
# Utilities
|
|
506
|
+
make version # Show version
|
|
507
|
+
make clean # Clean cache files
|
|
508
|
+
make clean-all # Clean everything including .venv
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Examples
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
# Quick Markdown extraction (just paste video ID)
|
|
515
|
+
make md VIDEO_ID=dQw4w9WgXcQ
|
|
516
|
+
|
|
517
|
+
# With 5-minute timestamps
|
|
518
|
+
make md VIDEO_ID=lHuxDMMkGJ8 TIMESTAMPS=300
|
|
519
|
+
|
|
520
|
+
# List subtitles
|
|
521
|
+
make list VIDEO_URL=https://youtu.be/dQw4w9WgXcQ
|
|
522
|
+
|
|
523
|
+
# Download with languages
|
|
524
|
+
make subs VIDEO_URL=https://youtu.be/dQw4w9WgXcQ LANGS=en,de
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## HTTP API
|
|
530
|
+
|
|
531
|
+
Start an HTTP API server for programmatic access (requires API dependencies):
|
|
532
|
+
|
|
533
|
+
### Installation
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
# Install API dependencies
|
|
537
|
+
uv sync --extra api
|
|
538
|
+
|
|
539
|
+
# Or with Make
|
|
540
|
+
make install-api
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Start Server
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
# Start on localhost:8000 (default)
|
|
547
|
+
uv run python __main__.py serve
|
|
548
|
+
|
|
549
|
+
# Custom host/port
|
|
550
|
+
uv run python __main__.py serve --host 127.0.0.1 --port 8080
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**Security Warning**: The API has NO authentication and should ONLY run on localhost (127.0.0.1).
|
|
554
|
+
|
|
555
|
+
### API Endpoints
|
|
556
|
+
|
|
557
|
+
#### POST /subs
|
|
558
|
+
|
|
559
|
+
Fetch subtitles and return content directly.
|
|
560
|
+
|
|
561
|
+
**Request:**
|
|
562
|
+
```json
|
|
563
|
+
{
|
|
564
|
+
"url": "https://youtu.be/VIDEO_ID",
|
|
565
|
+
"langs": "en",
|
|
566
|
+
"fmt": "srt",
|
|
567
|
+
"auto": true,
|
|
568
|
+
"sanitize": "safe"
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Response:** Subtitle file content as plain text.
|
|
573
|
+
|
|
574
|
+
**Example:**
|
|
575
|
+
```bash
|
|
576
|
+
curl -X POST http://127.0.0.1:8000/subs \
|
|
577
|
+
-H "Content-Type: application/json" \
|
|
578
|
+
-d '{
|
|
579
|
+
"url": "https://youtu.be/dQw4w9WgXcQ",
|
|
580
|
+
"langs": "en",
|
|
581
|
+
"fmt": "srt"
|
|
582
|
+
}'
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
#### GET /health
|
|
586
|
+
|
|
587
|
+
Health check endpoint.
|
|
588
|
+
|
|
589
|
+
**Response:**
|
|
590
|
+
```json
|
|
591
|
+
{
|
|
592
|
+
"status": "ok",
|
|
593
|
+
"service": "subxx"
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### API Documentation
|
|
598
|
+
|
|
599
|
+
Interactive API docs available at:
|
|
600
|
+
- Swagger UI: `http://127.0.0.1:8000/docs`
|
|
601
|
+
- ReDoc: `http://127.0.0.1:8000/redoc`
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Development
|
|
606
|
+
|
|
607
|
+
### Setup Development Environment
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
# Clone repository
|
|
611
|
+
git clone https://gist.github.com/cprima/subxx
|
|
612
|
+
cd subxx
|
|
613
|
+
|
|
614
|
+
# Install all dependencies (core + extract + api + dev)
|
|
615
|
+
uv sync --extra extract --extra api --extra dev
|
|
616
|
+
|
|
617
|
+
# Or with Make
|
|
618
|
+
make install-all
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Project Structure
|
|
622
|
+
|
|
623
|
+
```
|
|
624
|
+
subxx/
|
|
625
|
+
├── __main__.py # CLI entry point (Typer commands)
|
|
626
|
+
├── subxx.py # Core library functions
|
|
627
|
+
├── test_subxx.py # Test suite (pytest)
|
|
628
|
+
├── conftest.py # Pytest configuration
|
|
629
|
+
├── pyproject.toml # Project metadata and dependencies
|
|
630
|
+
├── Makefile # Build and test automation
|
|
631
|
+
├── .subxx.toml.example # Example configuration file
|
|
632
|
+
└── !README.md # This file
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Key Components
|
|
636
|
+
|
|
637
|
+
- **`subxx.py`**: Core library
|
|
638
|
+
- `fetch_subs()` - Download subtitles
|
|
639
|
+
- `extract_text()` - Extract text from subtitles
|
|
640
|
+
- `load_config()` - Configuration management
|
|
641
|
+
- Helper functions for parsing, sanitization, logging
|
|
642
|
+
|
|
643
|
+
- **`__main__.py`**: CLI application
|
|
644
|
+
- `list` - List available subtitles
|
|
645
|
+
- `subs` - Download subtitles
|
|
646
|
+
- `batch` - Batch processing
|
|
647
|
+
- `extract` - Extract from files
|
|
648
|
+
- `serve` - HTTP API server
|
|
649
|
+
- `version` - Version information
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## Testing
|
|
654
|
+
|
|
655
|
+
### Run Tests
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
# All tests
|
|
659
|
+
make test
|
|
660
|
+
|
|
661
|
+
# Unit tests only (fast, no network)
|
|
662
|
+
make test-unit
|
|
663
|
+
|
|
664
|
+
# Integration tests only
|
|
665
|
+
make test-integration
|
|
666
|
+
|
|
667
|
+
# With coverage report
|
|
668
|
+
make test-coverage
|
|
669
|
+
|
|
670
|
+
# Verbose output
|
|
671
|
+
make test-verbose
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Test Categories
|
|
675
|
+
|
|
676
|
+
- **Unit tests** (`@pytest.mark.unit`): No external dependencies, mocked I/O
|
|
677
|
+
- **Integration tests** (`@pytest.mark.integration`): May use files/network
|
|
678
|
+
- **E2E tests** (`@pytest.mark.e2e`): Real YouTube API, requires internet
|
|
679
|
+
- **Slow tests** (`@pytest.mark.slow`): Network I/O, real downloads
|
|
680
|
+
|
|
681
|
+
### Running Specific Test Categories
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
# Run all tests except e2e (fast, for CI)
|
|
685
|
+
pytest -m "not e2e"
|
|
686
|
+
|
|
687
|
+
# Run only e2e tests (slow, requires internet)
|
|
688
|
+
pytest -m e2e
|
|
689
|
+
|
|
690
|
+
# Run unit tests only
|
|
691
|
+
pytest -m unit
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Test Coverage
|
|
695
|
+
|
|
696
|
+
Current coverage: **~50 tests** (unit, integration, and e2e)
|
|
697
|
+
|
|
698
|
+
Key areas tested:
|
|
699
|
+
- Configuration loading and defaults
|
|
700
|
+
- Language parsing
|
|
701
|
+
- Filename sanitization
|
|
702
|
+
- Text extraction (txt/md/pdf)
|
|
703
|
+
- Timestamp markers
|
|
704
|
+
- CLI commands
|
|
705
|
+
- Overwrite protection
|
|
706
|
+
- Real YouTube subtitle download (e2e)
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## Exit Codes
|
|
711
|
+
|
|
712
|
+
- `0` - Success
|
|
713
|
+
- `1` - User cancelled
|
|
714
|
+
- `2` - No subtitles available
|
|
715
|
+
- `3` - Network error
|
|
716
|
+
- `4` - Invalid URL
|
|
717
|
+
- `5` - Configuration error
|
|
718
|
+
- `6` - File error
|
|
719
|
+
|
|
720
|
+
---
|
|
721
|
+
|
|
722
|
+
## Troubleshooting
|
|
723
|
+
|
|
724
|
+
### Missing Dependencies for Text Extraction
|
|
725
|
+
|
|
726
|
+
**Error:**
|
|
727
|
+
```
|
|
728
|
+
❌ Error: Missing dependencies for text extraction
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Solution:**
|
|
732
|
+
```bash
|
|
733
|
+
uv sync --extra extract
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Missing Dependencies for API
|
|
737
|
+
|
|
738
|
+
**Error:**
|
|
739
|
+
```
|
|
740
|
+
❌ Error: API dependencies not installed
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
**Solution:**
|
|
744
|
+
```bash
|
|
745
|
+
uv sync --extra api
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### Windows Console Encoding Issues
|
|
749
|
+
|
|
750
|
+
If you see encoding errors on Windows, the tool automatically attempts to reconfigure stdout/stderr to UTF-8. If issues persist, use:
|
|
751
|
+
|
|
752
|
+
```bash
|
|
753
|
+
# Set console to UTF-8
|
|
754
|
+
chcp 65001
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### yt-dlp Network Errors
|
|
758
|
+
|
|
759
|
+
If downloads fail with network errors:
|
|
760
|
+
|
|
761
|
+
1. Update yt-dlp:
|
|
762
|
+
```bash
|
|
763
|
+
uv sync --upgrade
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
2. Check firewall/proxy settings
|
|
767
|
+
|
|
768
|
+
3. Try with `--verbose` for debug output:
|
|
769
|
+
```bash
|
|
770
|
+
uv run python __main__.py subs URL --verbose
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
## Roadmap
|
|
776
|
+
|
|
777
|
+
Future enhancements planned:
|
|
778
|
+
|
|
779
|
+
- [ ] Progress bars for downloads
|
|
780
|
+
- [ ] Retry logic for network failures
|
|
781
|
+
- [ ] Subtitle merging/combining
|
|
782
|
+
- [ ] Translation support
|
|
783
|
+
- [ ] Docker container
|
|
784
|
+
- [ ] GitHub Actions CI/CD
|
|
785
|
+
- [ ] Published package on PyPI
|
|
786
|
+
- [ ] SRT/VTT format conversion
|
|
787
|
+
- [ ] Subtitle editing/manipulation
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
## Contributing
|
|
792
|
+
|
|
793
|
+
Contributions welcome! This is an alpha project under active development.
|
|
794
|
+
|
|
795
|
+
### How to Contribute
|
|
796
|
+
|
|
797
|
+
1. Fork the repository
|
|
798
|
+
2. Create a feature branch
|
|
799
|
+
3. Make your changes
|
|
800
|
+
4. Add tests for new functionality
|
|
801
|
+
5. Ensure all tests pass: `make test`
|
|
802
|
+
6. Submit a pull request
|
|
803
|
+
|
|
804
|
+
### Guidelines
|
|
805
|
+
|
|
806
|
+
- Follow existing code style
|
|
807
|
+
- Add docstrings for new functions
|
|
808
|
+
- Update tests for changes
|
|
809
|
+
- Update README for new features
|
|
810
|
+
- Keep commits focused and atomic
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## License
|
|
815
|
+
|
|
816
|
+
This project is licensed under **CC BY 4.0** (Creative Commons Attribution 4.0 International).
|
|
817
|
+
|
|
818
|
+
You are free to:
|
|
819
|
+
- **Share** - Copy and redistribute the material
|
|
820
|
+
- **Adapt** - Remix, transform, and build upon the material
|
|
821
|
+
|
|
822
|
+
Under the following terms:
|
|
823
|
+
- **Attribution** - You must give appropriate credit
|
|
824
|
+
|
|
825
|
+
See [LICENSE](https://creativecommons.org/licenses/by/4.0/) for full details.
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## Credits
|
|
830
|
+
|
|
831
|
+
- Built with [yt-dlp](https://github.com/yt-dlp/yt-dlp) for video subtitle extraction
|
|
832
|
+
- CLI powered by [Typer](https://typer.tiangolo.com/)
|
|
833
|
+
- API built with [FastAPI](https://fastapi.tiangolo.com/)
|
|
834
|
+
- Text extraction using [srt](https://github.com/cdown/srt) and [fpdf2](https://github.com/py-pdf/fpdf2)
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
## Author
|
|
839
|
+
|
|
840
|
+
**Christian Prior-Mamulyan**
|
|
841
|
+
- Email: cprior@gmail.com
|
|
842
|
+
- GitHub: [@cprima](https://github.com/cprima)
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
## Support
|
|
847
|
+
|
|
848
|
+
- Report issues: [GitHub Issues](https://gist.github.com/cprima/subxx/issues)
|
|
849
|
+
- Documentation: [GitHub Gist](https://gist.github.com/cprima/subxx)
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
**subxx** - Simple, powerful YouTube transcript / subtitle fetching for Python.
|