subxx
YouTube transcript / subtitle fetching toolkit for Python - Download, extract, and process subtitles from video URLs with a simple CLI or HTTP API.
Features
- Download YouTube subtitles from videos and channels (powered by yt-dlp)
- Multiple output formats: SRT, VTT, TXT, Markdown, PDF
- JSON output: Machine-readable output with
--jsonand--json-fileflags - Importable module: Use as a Python library with dict-based return values
- Text extraction with automatic subtitle cleanup and optional timestamp markers
- Language selection: Download specific languages or all available subtitles
- Batch processing: Process multiple URLs from a file
- Configuration files: Project and global settings via TOML
- HTTP API: Optional FastAPI server for programmatic access
- Dry-run mode: Preview operations without downloading
- Filename sanitization: Safe, nospace, or slugify modes
Table of Contents
- Installation
- Quick Start
- Module Usage (Python Library)
- Usage
- Configuration
- Makefile Shortcuts
- HTTP API
- Development
- Testing
- License
Installation
Requirements
- Python 3.9 or higher
- uv package manager (recommended)
Install with uv (recommended)
# Clone or download the project
git clone https://gist.github.com/cprima/subxx
cd subxx
# Install core dependencies
uv sync
# Install with optional features
uv sync --extra extract # Text extraction (txt/md/pdf)
uv sync --extra api # HTTP API server
uv sync --extra dev # Development tools (pytest)
# Install all features
uv sync --extra extract --extra api --extra dev
Using Make (Windows)
make install # Core dependencies
make install-all # All dependencies (extract + api + dev)
Quick Start
Basic Usage
# List available subtitles
uv run subxx list https://youtu.be/VIDEO_ID
# Download English subtitle (SRT format, default)
uv run subxx subs https://youtu.be/VIDEO_ID
# Extract to plain text
uv run subxx subs https://youtu.be/VIDEO_ID --txt
# Extract to Markdown with 5-minute timestamps
uv run subxx subs https://youtu.be/VIDEO_ID --md -t 300
# Extract to PDF
uv run subxx subs https://youtu.be/VIDEO_ID --pdf
# Get JSON output for automation
uv run subxx list https://youtu.be/VIDEO_ID --json
uv run subxx subs https://youtu.be/VIDEO_ID --json-file output.json
With Makefile
# Quick Markdown extraction (just paste video ID)
make md VIDEO_ID=dQw4w9WgXcQ
# With timestamps
make md VIDEO_ID=dQw4w9WgXcQ TIMESTAMPS=300
Module Usage (Python Library)
subxx can be imported and used as a Python library. Core functions return typed pydantic result models (v0.5.0+; v0.4.x returned dicts, see Migrating to 0.5.0).
Installation
# From test.pypi
pip install -i https://test.pypi.org/simple/ subxx==0.4.1
# Or with uv
uv add subxx==0.4.1 --index https://test.pypi.org/simple/
Basic Example
from subxx import fetch_subs, extract_text
# Download subtitles
result = fetch_subs(
url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
langs="en",
fmt="srt",
output_dir="./subs",
logger=None # Silent mode
)
if result.status == "success":
print(f"Downloaded: {result.video.title}")
for f in result.files:
print(f" {f.language}: {f.path}")
else:
print(f"Error {result.error.code.value}: {result.error.message}")
Result Format
Every function and every CLI command (--json) reports through one of four models sharing an envelope:
SubsResult (fetch_subs, list), VideosResult (list_videos), ThumbnailResult (fetch_thumbnail),
ExtractionResult (extract_text). subxx schema <subs|videos|thumbnail|extraction> prints the JSON Schema.
{
"schema_version": "1",
"kind": "subs",
"status": "success",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"video": {"id": "dQw4w9WgXcQ", "title": "Rick Astley - Never Gonna Give You Up", "duration": 213.0},
"files": [{"path": "subs/video.en.srt", "format": "srt", "size_bytes": 4127, "language": "en", "auto_generated": false}],
"languages": [{"code": "en", "name": "en", "auto": false}],
"download_info": {"requested_languages": "en", "format": "srt", "auto_generated_fallback": true,
"output_directory": "subs", "downloaded_at": "2026-09-26T10:00:00Z", "dry_run": false},
"attempts": []
}
statusissuccess,skippedorerror. On error,erroris{"code", "message", "http_status", "retry_after_seconds"}.result.exit_code(and the CLI exit code) comes from the error code.attemptslists automatic retries (see Automatic Retry); the full yt-dlp metadata is only included asmetadatawithinclude_metadata=True/--full: aVideoInfowith typed fields (title, duration, chapters, channel, ...) plus every other key yt-dlp returned.Nonefields are omitted from JSON.
Error Codes
| Code | Meaning | Retryable | Exit code |
|---|---|---|---|
RATE_LIMITED |
HTTP 429; wait and rerun | yes | 7 |
NETWORK_ERROR |
timeout, 5xx, connection problems | yes | 3 |
BLOCKED |
HTTP 403 or a bot check | no | 3 |
NOT_FOUND |
removed, private or unavailable | no | 3 |
NO_SUBTITLES |
no subtitles for the requested languages | no | 2 |
NO_THUMBNAIL, FILE_EXISTS, FILE_NOT_FOUND, FILE_ERROR, WRITE_ERROR, FORMAT_NOT_SUPPORTED, INVALID_FORMAT |
file and format problems | no | 6 (NO_THUMBNAIL: 3) |
MISSING_DEPENDENCY, UNSUPPORTED |
optional package missing, or the source lacks the capability | no | 5 |
Complete Example
from subxx import fetch_subs, extract_text
# 1. Download subtitle
result = fetch_subs(
url="https://youtube.com/watch?v=...",
langs="en",
fmt="srt",
auto=True,
output_dir="./transcripts",
logger=None
)
if result.status != "success":
print(f"Error: {result.error.message}")
raise SystemExit(result.exit_code)
# 2. Extract to markdown
subtitle_file = result.files[0].path
extract_result = extract_text(
subtitle_file=subtitle_file,
output_format="md",
use_chapters=True,
logger=None
)
if extract_result.status == "success":
print(f"Extracted to: {extract_result.output_files[0].path}")
Available Functions
from subxx import (
fetch_subs, # Download subtitles → SubsResult
fetch_thumbnail, # Download the thumbnail → ThumbnailResult
list_videos, # List a channel/playlist → VideosResult
extract_text, # Extract text from srt → ExtractionResult
get_source, # Build a source (yt-dlp, retry) from config
load_config, # Load .subxx.toml config → dict
get_default, # Get config default value
setup_logging, # Configure logging
ErrorCode, # error codes with .exit_code and .retryable
)
Migrating to 0.5.0
Results are models, not dicts (breaking). Access fields as attributes:
| 0.4.x | 0.5.0 |
|---|---|
result["status"] |
result.status |
result["video_id"], result["video_title"] |
result.video.id, result.video.title |
result["files"][0]["path"] |
result.files[0].path |
result["available_languages"], ["auto"] |
result.languages, .auto |
result["error"], result["error_code"] |
result.error.message, result.error.code |
result["metadata"] (a yt-dlp dict) |
result.metadata (a VideoInfo, only with include_metadata=True; .model_dump() for the dict) |
source.info(url)["title"] (custom sources) |
source.info(url) returns a VideoInfo: .title, .subtitles, .chapters, ... |
list_videos() entries {"id", "title", "url"} |
Video objects: .id, .title, .url |
extract_text(...)["output_files"][0]["path"] |
extract_text(...).output_files[0].path |
result.model_dump() gives a plain dict. JSON output changed shape accordingly (see Result Format).
Usage
List Available Subtitles
Preview available subtitle languages without downloading:
# Traditional output
uv run subxx list https://youtu.be/VIDEO_ID
# JSON output
uv run subxx list https://youtu.be/VIDEO_ID --json
# Save to file
uv run subxx list https://youtu.be/VIDEO_ID --json-file metadata.json
Output:
📹 Video: Example Video Title
🕒 Duration: 12:34
✅ Manual subtitles:
- en
- es
🤖 Auto-generated subtitles:
- en, de, fr, ja, ko, pt, ru, zh-Hans, ...
Options:
-v, --verbose- Debug output-q, --quiet- Errors only
Download Subtitles
Format Selection
Download subtitle files in SRT or VTT format:
# Download SRT (default)
uv run subxx subs https://youtu.be/VIDEO_ID
# Download VTT
uv run subxx subs https://youtu.be/VIDEO_ID --vtt
# Using --fmt flag
uv run subxx subs https://youtu.be/VIDEO_ID -f srt
Behavior: Subtitle files (SRT/VTT) are downloaded and kept on disk.
Language Selection
# Download English (default)
uv run subxx subs https://youtu.be/VIDEO_ID
# Download specific language
uv run subxx subs https://youtu.be/VIDEO_ID -l de
# Download multiple languages
uv run subxx subs https://youtu.be/VIDEO_ID -l "en,de,fr"
# Download all available languages
uv run subxx subs https://youtu.be/VIDEO_ID -l all
Output Directory
# Save to specific directory
uv run python __main__.py subs https://youtu.be/VIDEO_ID -o ~/Downloads/subs
# Use current directory (default)
uv run python __main__.py subs https://youtu.be/VIDEO_ID -o .
Filename Sanitization
# Safe mode: Remove unsafe characters, keep spaces (default)
uv run python __main__.py subs URL --sanitize safe
# No spaces: Replace spaces with underscores
uv run python __main__.py subs URL --sanitize nospaces
# Slugify: Lowercase, hyphens, URL-safe
uv run python __main__.py subs URL --sanitize slugify
Examples:
safe:"My Video Title.srt"→"My Video Title.srt"nospaces:"My Video Title.srt"→"My_Video_Title.srt"slugify:"My Video Title.srt"→"my-video-title.srt"
Overwrite Handling
# Prompt before overwriting (default)
uv run python __main__.py subs URL
# Force overwrite without prompting
uv run python __main__.py subs URL --force
# Skip existing files
uv run python __main__.py subs URL --skip-existing
Auto-Generated Subtitles
# Include auto-generated subtitles (default)
uv run python __main__.py subs URL --auto
# Only manual subtitles
uv run python __main__.py subs URL --no-auto
Dry Run
Preview what would be downloaded without actually downloading:
uv run python __main__.py subs URL --dry-run
Output:
[DRY RUN] Would download subtitle: en
JSON Output
Machine-readable JSON output for automation and scripting. Every result has the same envelope
(schema_version, kind, status, error, attempts); see Result Format, and
subxx schema <kind> for the JSON Schema.
Available Commands with JSON Support
list- List available languagessubs- Download subtitles (--fulladds the complete video metadata asmetadata)videos- List a channel's videos
Output to stdout
# List command with JSON
uv run subxx list "https://youtu.be/dQw4w9WgXcQ" --json
# Subs command with JSON
uv run subxx subs "https://youtu.be/dQw4w9WgXcQ" --json
Example JSON output:
{
"schema_version": "1",
"kind": "subs",
"status": "success",
"url": "https://youtu.be/dQw4w9WgXcQ",
"video": {"id": "dQw4w9WgXcQ", "title": "Rick Astley - Never Gonna Give You Up..."},
"files": [
{
"path": "Rick Astley - Never Gonna Give You Up.dQw4w9WgXcQ.NA.en.srt",
"format": "srt",
"language": "en",
"auto_generated": false
}
],
"languages": [
{"code": "en", "name": "en", "auto": false}
],
"attempts": []
}
Save to file
# Save JSON to file
uv run subxx list URL --json-file metadata.json
uv run subxx subs URL --json-file result.json
# Both stdout and file
uv run subxx subs URL --json --json-file result.json
Use in Scripts
#!/bin/bash
# Get video metadata
metadata=$(uv run subxx list "$VIDEO_URL" --json)
video_title=$(echo "$metadata" | jq -r '.video.title')
echo "Downloading: $video_title"
# Download with JSON output
uv run subxx subs "$VIDEO_URL" --json-file download.json
# Check if successful
if [ "$(jq -r '.status' download.json)" == "success" ]; then
echo "Success! Downloaded $(jq -r '.files | length' download.json) files"
fi
Text Extraction
Extract clean, readable text from subtitles by automatically removing timestamps and formatting.
Key behavior: When using text formats (txt/md/pdf), subxx:
- Downloads the subtitle as SRT
- Extracts the text content
- Automatically deletes the SRT file
Plain Text
# Extract to plain text
uv run python __main__.py subs URL --txt
Output: Video_Title.VIDEO_ID.en.txt
Example content:
Hello world.
This is a subtitle.
Welcome to the video.
Markdown
# Extract to Markdown
uv run python __main__.py subs URL --md
# Markdown with timestamp markers every 5 minutes
uv run python __main__.py subs URL --md -t 300
# Markdown with timestamp markers every 30 seconds
uv run python __main__.py subs URL --md -t 30
Output: Video_Title.VIDEO_ID.en.md
Example content (with timestamps):
## [0:00]
Hello world.
This is a subtitle.
## [5:00]
Welcome to the next section.
More content here.
## [10:00]
Final section of the video.
# Extract to PDF
uv run python __main__.py subs URL --pdf
# PDF with timestamp markers
uv run python __main__.py subs URL --pdf -t 300
Output: Video_Title.VIDEO_ID.en.pdf
Requirements: Install extraction dependencies:
uv sync --extra extract
Timestamp Intervals
Add timestamp markers at regular intervals for long-form content:
# Every 5 minutes (300 seconds)
uv run python __main__.py subs URL --md -t 300
# Every 30 seconds
uv run python __main__.py subs URL --txt -t 30
# Every 10 minutes
uv run python __main__.py subs URL --pdf -t 600
Format: Timestamps appear as ## [0:00], ## [5:00], ## [10:00], etc.
Batch Processing
Download subtitles for multiple URLs from a file:
# Create URLs file (one URL per line)
cat > urls.txt << EOF
https://youtu.be/VIDEO_ID_1
https://youtu.be/VIDEO_ID_2
# This is a comment
https://youtu.be/VIDEO_ID_3
EOF
# Process all URLs
uv run python __main__.py batch urls.txt
# With options
uv run python __main__.py batch urls.txt -l "en,de" -f srt -o ~/subs
Options:
-l, --langs- Language codes (default: en)-f, --fmt- Output format (default: srt)-o, --output-dir- Output directory (default: .)--sanitize- Filename sanitization mode (default: safe)-v, --verbose- Verbose output-q, --quiet- Quiet mode
URL File Format (yt-dlp standard):
- One URL per line
- Lines starting with
#are comments - Empty lines are ignored
Extract from Files
Extract text from existing subtitle files:
# Extract SRT to plain text
uv run python __main__.py extract video.srt
# Extract to Markdown
uv run python __main__.py extract video.srt -f md
# Extract to PDF
uv run python __main__.py extract video.srt -f pdf
# With timestamp markers every 5 minutes
uv run python __main__.py extract video.srt -f md -t 300
# Specify output file
uv run python __main__.py extract video.srt -o output.txt
# Force overwrite
uv run python __main__.py extract video.srt --force
Supported input formats: SRT, VTT
Configuration
Config File Locations
Configuration files are loaded in priority order:
./.subxx.toml(project-specific, current directory)~/.subxx.toml(user global, home directory)
Priority Chain
Settings are resolved in this order (highest to lowest):
- CLI flags (e.g.,
--langs en,--fmt srt) - Config file (
.subxx.toml) - Hardcoded defaults
Example Configuration
Copy .subxx.toml.example to .subxx.toml or ~/.subxx.toml:
cp .subxx.toml.example ~/.subxx.toml
Example config:
[defaults]
# Language codes (comma-separated or "all")
langs = "en"
# Output format: srt, vtt, txt, md, pdf
fmt = "md"
# Include auto-generated subtitles
auto = true
# Output directory (supports ~)
output_dir = "~/Downloads/subtitles"
# Filename sanitization: safe, nospaces, slugify
sanitize = "safe"
# Timestamp interval (seconds) for txt/md/pdf
timestamps = 300 # 5-minute intervals
[logging]
# Log level: DEBUG, INFO, WARNING, ERROR
level = "INFO"
# Log file (optional)
log_file = "~/.subxx/subxx.log"
Subtitle Sources and yt-dlp Options
Subtitles come from a pluggable source (sources.py). The default is yt-dlp; choose another with
[source] backend or --source:
| Source | Extra | Subtitles | List channel videos | Thumbnails | Notes |
|---|---|---|---|---|---|
ytdlp (default) |
none | yes | yes | yes | Full metadata, chapters, channel info |
transcript-api |
subxx[api-source] |
yes | no (UNSUPPORTED) |
no (UNSUPPORTED) |
Uses youtube-transcript-api; text only |
List a channel's videos and download thumbnails (yt-dlp source):
# use a tab URL such as .../videos
uv run subxx videos https://www.youtube.com/channel/CHANNEL_ID/videos --limit 10
uv run subxx videos https://www.youtube.com/channel/CHANNEL_ID/videos --json
# thumbnail next to the subtitles: jpg/png need ffmpeg, "native" keeps the original (webp)
uv run subxx subs <url> --thumbnail jpg # or [defaults] thumbnail = "jpg"
from subxx import list_videos, fetch_thumbnail, get_source, load_config
source = get_source(load_config())
listing = list_videos("https://www.youtube.com/channel/CHANNEL_ID/videos", limit=10, source=source)
# VideosResult: listing.status, listing.videos[i].id/.title/.url, listing.error
thumb = fetch_thumbnail(listing.videos[0].url, fmt="jpg", output_dir="out",
out_template="thumbnail", skip_existing=True, source=source)
yt-dlp options come from the [ytdlp] table and are passed to every yt-dlp request:
[ytdlp]
sleep_interval = 20 # random pause between requests: 20-60 s
max_sleep_interval = 60
sleep_interval_subtitles = 5
retries = 5
socket_timeout = 30
Options subxx derives from its own arguments (writesubtitles, subtitleslangs, outtmpl, ...) are
ignored with a warning; cookie options (cookiefile, cookiesfrombrowser) are not supported.
In Python, build a source from the same config and pass it in:
from subxx import fetch_subs, load_config, get_source
source = get_source(load_config()) # or get_source({}, "transcript-api")
result = fetch_subs(url, langs="en", source=source)
Any object with info(url) and download(...) (see SubtitleSource in sources.py) can be used.
Automatic Retry
Retrying is opt-in: without a [retry] table nothing is repeated, and every value must be set (there is no
built-in schedule).
[retry]
max_attempts = 4 # total tries per operation, the first one included
base_delay = 30 # seconds; doubles after each failure, with jitter (half to full backoff)
max_delay = 600 # cap for one wait
max_total_wait = 1200 # optional: cap for all waits of one operation
- Only retryable errors are repeated (
RATE_LIMITED,NETWORK_ERROR);Retry-Afterfrom the server is honored, and if it exceedsmax_delaysubxx gives up instead of waiting. - Every retry is recorded in
result.attempts; the CLI logsRATE_LIMITED, HTTP 429: retrying in 63s (attempt 2/4). --retries Noverridesmax_attemptsonsubs,list,videosandbatch(the other values still come from[retry]).- This is a second layer on top of yt-dlp's own per-request
[ytdlp]retries and sleeps, which multiply with it;max_total_waitbounds the worst case. - After the retries are used up, a 429 exits with code 7, and
batchstops instead of sending more requests. - Library:
get_source(config)wraps the source when[retry]is present, or useretry.RetryingSource(source, RetryPolicy(...)).
Use Case Configurations
Configuration 1: Download SRT files to dedicated directory
[defaults]
langs = "en"
fmt = "srt"
output_dir = "~/Downloads/subtitles"
Configuration 2: Auto-extract to Markdown with timestamps
[defaults]
langs = "en"
fmt = "md"
timestamps = 300
output_dir = "~/Documents/transcripts"
Configuration 3: Multiple languages, plain text
[defaults]
langs = "en,de,fr"
fmt = "txt"
sanitize = "slugify"
output_dir = "./subtitles"
Makefile Shortcuts
Available Targets
# Installation
make install # Core dependencies
make install-all # All dependencies (extract + api + dev)
# Testing
make test # Run all tests
make test-unit # Unit tests only
make test-integration # Integration tests only
make test-coverage # Tests with coverage report
# Usage
make list VIDEO_URL=https://youtu.be/VIDEO_ID
make subs VIDEO_URL=https://youtu.be/VIDEO_ID
make md VIDEO_ID=VIDEO_ID # Quick Markdown extraction
make md VIDEO_ID=VIDEO_ID TIMESTAMPS=300 # With timestamps
# Utilities
make version # Show version
make clean # Clean cache files
make clean-all # Clean everything including .venv
Examples
# Quick Markdown extraction (just paste video ID)
make md VIDEO_ID=dQw4w9WgXcQ
# With 5-minute timestamps
make md VIDEO_ID=lHuxDMMkGJ8 TIMESTAMPS=300
# List subtitles
make list VIDEO_URL=https://youtu.be/dQw4w9WgXcQ
# Download with languages
make subs VIDEO_URL=https://youtu.be/dQw4w9WgXcQ LANGS=en,de
HTTP API
Start an HTTP API server for programmatic access (requires API dependencies):
Installation
# Install API dependencies
uv sync --extra api
# Or with Make
make install-api
Start Server
# Start on localhost:8000 (default)
uv run python __main__.py serve
# Custom host/port
uv run python __main__.py serve --host 127.0.0.1 --port 8080
Security Warning: The API has NO authentication and should ONLY run on localhost (127.0.0.1).
API Endpoints
POST /subs
Fetch subtitles and return content directly.
Request:
{
"url": "https://youtu.be/VIDEO_ID",
"langs": "en",
"fmt": "srt",
"auto": true,
"sanitize": "safe"
}
Response: Subtitle file content as plain text.
Example:
curl -X POST http://127.0.0.1:8000/subs \
-H "Content-Type: application/json" \
-d '{
"url": "https://youtu.be/dQw4w9WgXcQ",
"langs": "en",
"fmt": "srt"
}'
Errors map from the error code: NO_SUBTITLES/NOT_FOUND → 404, RATE_LIMITED → 429, anything else → 500.
POST /subs/result
Same request, but the response is the typed SubsResult (see Result Format) as JSON, with the
HTTP status mapped from the error code as above. The download is temporary, so files[].path holds file names only.
curl -X POST http://127.0.0.1:8000/subs/result -H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/dQw4w9WgXcQ", "langs": "en"}'
The response schema is in the OpenAPI docs at /docs and via subxx schema subs.
GET /health
Health check endpoint.
Response:
{
"status": "ok",
"service": "subxx"
}
API Documentation
Interactive API docs available at:
- Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
Development
Setup Development Environment
# Clone repository
git clone https://gist.github.com/cprima/subxx
cd subxx
# Install all dependencies (core + extract + api + dev)
uv sync --extra extract --extra api --extra dev
# Or with Make
make install-all
Project Structure
Updated in v0.4.1 - Restructured for Python best practices:
subxx/
├── subxx.py # Core library functions (returns dicts)
├── cli.py # CLI + API implementation (Typer/FastAPI)
├── __main__.py # Minimal entry point (3 lines)
├── test_subxx.py # Test suite (pytest)
├── conftest.py # Pytest configuration
├── pyproject.toml # Project metadata and dependencies
├── Makefile # Build and test automation
├── .subxx.toml.example # Example configuration file
└── !README.md # This file
Key Components
-
subxx.py: Core library (library-first design)fetch_subs()→ dict - Download subtitles, return structured dataextract_text()→ dict - Extract text from subtitles, return structured dataload_config()→ dict - Configuration management- Helper functions for parsing, sanitization, logging
- Importable as Python module
-
cli.py: CLI + API implementation- Typer commands:
list,subs,batch,extract,serve,version - FastAPI HTTP server
- JSON output handling (
--json,--json-file) - Traditional console output with emojis
- Typer commands:
-
__main__.py: Minimal entry point (Python best practice)- 3 lines: import and run CLI
- Enables
python -m subxxusage
Testing
Run Tests
# All tests
make test
# Unit tests only (fast, no network)
make test-unit
# Integration tests only
make test-integration
# With coverage report
make test-coverage
# Verbose output
make test-verbose
Test Categories
- Unit tests (
@pytest.mark.unit): No external dependencies, mocked I/O - Integration tests (
@pytest.mark.integration): May use files/network - E2E tests (
@pytest.mark.e2e): Real YouTube API, requires internet - Slow tests (
@pytest.mark.slow): Network I/O, real downloads
Running Specific Test Categories
# Run all tests except e2e (fast, for CI)
pytest -m "not e2e"
# Run only e2e tests (slow, requires internet)
pytest -m e2e
# Run unit tests only
pytest -m unit
Test Coverage
Current coverage: ~50 tests (unit, integration, and e2e)
Key areas tested:
- Configuration loading and defaults
- Language parsing
- Filename sanitization
- Text extraction (txt/md/pdf)
- Timestamp markers
- CLI commands
- Overwrite protection
- Real YouTube subtitle download (e2e)
Exit Codes
0- Success1- User cancelled2- No subtitles available3- Network error4- Invalid URL5- Configuration error6- File error
Troubleshooting
Missing Dependencies for Text Extraction
Error:
❌ Error: Missing dependencies for text extraction
Solution:
uv sync --extra extract
Missing Dependencies for API
Error:
❌ Error: API dependencies not installed
Solution:
uv sync --extra api
Windows Console Encoding Issues
If you see encoding errors on Windows, the tool automatically attempts to reconfigure stdout/stderr to UTF-8. If issues persist, use:
# Set console to UTF-8
chcp 65001
yt-dlp Network Errors
If downloads fail with network errors:
-
Update yt-dlp:
uv sync --upgrade
-
Check firewall/proxy settings
-
Try with
--verbosefor debug output:uv run python __main__.py subs URL --verbose
Roadmap
Completed (v0.4.x)
- JSON output support (
--json,--json-file) - Importable Python module (library-first architecture)
- Published package on test.pypi.org
- Pythonic project structure (cli.py, minimal main.py)
Future Enhancements
- Publish to PyPI (production)
- Progress bars for downloads
- Retry logic for network failures
- Subtitle merging/combining
- Translation support
- Docker container
- GitHub Actions CI/CD
- SRT/VTT format conversion
- Subtitle editing/manipulation
- Batch command JSON support
- Extract command JSON support
Contributing
Contributions welcome! This is an alpha project under active development.
How to Contribute
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass:
make test - Submit a pull request
Guidelines
- Follow existing code style
- Add docstrings for new functions
- Update tests for changes
- Update README for new features
- Keep commits focused and atomic
License
This project is licensed under CC BY 4.0 (Creative Commons Attribution 4.0 International).
You are free to:
- Share - Copy and redistribute the material
- Adapt - Remix, transform, and build upon the material
Under the following terms:
- Attribution - You must give appropriate credit
See LICENSE for full details.
Credits
- Built with yt-dlp for video subtitle extraction
- CLI powered by Typer
- API built with FastAPI
- Text extraction using srt and fpdf2
Author
Christian Prior-Mamulyan
- Email: cprior@gmail.com
- GitHub: @cprima
Support
- Report issues: GitHub Issues
- Documentation: GitHub Gist
subxx - Simple, powerful YouTube transcript / subtitle fetching for Python.
Release files for subxx 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| subxx-0.5.0.tar.gz | 222.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| subxx-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 275.7 kB
Release files / subxx-0.5.0.tar.gz
| Download URL | subxx-0.5.0.tar.gz |
|---|---|
| Size | 222.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f8266da969189e34d4fa3722bf4b65d32320d287a7555dc4ab61772194ed6cfc
|
|
BLAKE2b-256 checksum How to use checksums |
21dab44a3aabf02a68a2a3f20e87c1c020c76b047bbaf867f13ee247227dcd42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / subxx-0.5.0-py3-none-any.whl
| Download URL | subxx-0.5.0-py3-none-any.whl |
|---|---|
| Size | 53.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d68ad11d8563b8215f792b4160646e348489295d30a186589b043c8983b00fa
|
|
BLAKE2b-256 checksum How to use checksums |
6e1c33ed97b9f0a8ab76c086128afcb2835bd74bad721d7b6a5349c5abd55d54
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|