A Python CLI tool to download all galleries (albums) from a SmugMug account using the SmugMug API v2. Features persistent download tracking so interrupted downloads can be resumed automatically.
- Parallel downloads — utilizes concurrent thread workers (
-w/--workers) for high-speed file transfers. - Integrity verification — automatically validates downloaded files using MD5 checksums (with self-healing retries on mismatches).
- Advanced global progress — displays real-time backup metrics via
rich, including overall transfer speed (MB/s), total backup size, aggregate ETA, and transient sub-task download bars. - OAuth 1.0a authentication — one-time browser-based authorization; OAuth access tokens are cached in
.smugmug_tokens.jsonfor future runs. - Full account download — discovers and downloads every album tied to the user's account.
- Album filtering — download specific albums by name matching (
-a/--album). - Resume support — tracks per-image download state in a JSON file (
.smugmug_download_state.json); interrupted downloads resume seamlessly. - Retry with exponential backoff — automatically retries on rate limits (HTTP 429), server errors (HTTP 500/503), and connection failures.
- Original quality — downloads full-resolution archived photos and videos.
- Hardened Security & Path Traversal Protection — protects against API parameter and file path traversal, SSRF via endpoint and redirect validation, terminal injection via Rich tag escaping, stack trace/credential leakage, and enforces secure token file permissions.
The project uses Behavior-Driven Development (BDD) with Gherkin feature files stored in features/ and implemented via pytest-bdd.
Smugmug_Downloader/
├── main.py # Top-level executable script entry point
├── requirements.txt # Python runtime and testing dependencies
├── setup.py # Package installation script (defines `smd` and `smugmug-download`)
├── pyproject.toml # Project metadata and setuptools configuration
├── .env.example # API credential template
├── features/ # Gherkin BDD Feature Specification Files
│ ├── api_resilience.feature # Backoff retries for HTTP 429/500/503 errors
│ ├── authentication.feature # OAuth 1.0a flow, token caching, credential resolution
│ ├── cli_workflows.feature # Listing, album filtering, status, and reset flags
│ ├── concurrency.feature # Parallel thread worker scaling
│ ├── download_tracking.feature # Persistent state tracking and resume capabilities
│ └── integrity.feature # MD5 checksum verification and corruption self-healing
├── src/ # Core Python implementation package
│ ├── __init__.py # Version declaration (v1.0.0)
│ ├── config.py # Credential resolution (Env vars > Static constants > CLI prompt)
│ ├── auth.py # OAuth 1.0a authentication & token persistence
│ ├── api_client.py # SmugMug API wrapper (pagination, backoff, download stream)
│ ├── tracker.py # JSON state tracking engine (.smugmug_download_state.json)
│ ├── downloader.py # Concurrent download engine and progress reporting
│ └── cli.py # Command line interface parsing & command dispatch
└── tests/ # Test suite
├── step_defs/ # BDD step definition files for pytest-bdd
│ ├── test_api_steps.py
│ ├── test_authentication_steps.py
│ ├── test_cli_steps.py
│ ├── test_concurrency_steps.py
│ ├── test_integrity_steps.py
│ └── test_tracking_steps.py
├── test_tracker.py # Unit tests for DownloadTracker
├── test_api_client.py # Unit tests for SmugMugClient
├── test_downloader.py # Unit tests for download orchestration
└── test_security_fix.py # Unit tests for terminal injection and security fixes
pip3 install -e .This installs the project locally and makes the smd and smugmug-download commands available in your terminal.
To uninstall the tool later, run:
pip3 uninstall smugmug-downloaderApply for an API key at https://api.smugmug.com/api/developer/apply.
Set your credentials via one of these methods:
| Method | How |
|---|---|
| Environment variables | export SMUGMUG_API_KEY=... SMUGMUG_API_SECRET=... |
| Static constants | Edit API_KEY and API_SECRET in src/config.py |
| Interactive prompt | Run the application — it will prompt for missing keys |
smd
# or
smugmug-downloadOn first run, the app will:
- Prompt for API credentials (if not already configured)
- Prompt for destination folder (default:
./smugmug_downloads) - Open a browser window to authorize access via OAuth 1.0a
- Save access tokens to
.smugmug_tokens.jsonand start downloading all albums
To speed up downloads, adjust the worker thread count (default: 3):
smd -w 5smd -o ~/SmugMug_Backupsmd -a "Vacation 2024"smd --list-albumssmd --status -o ~/SmugMug_Backupsmd --reset -o ~/SmugMug_BackupSimply re-run your download command — already-downloaded and verified files are automatically skipped:
smd -o ~/SmugMug_BackupThe test suite covers unit tests and BDD feature specification scenarios.
pip3 install -e ".[dev]"
python3 -m pytest tests/ -vTo run only the BDD feature tests in features/:
python3 -m pytest tests/step_defs/ -v