CLI tool to download Zoom cloud recordings and extract audio for transcription
Project description
dlzoom
Download Zoom cloud recordings from the command line.
Simple CLI tool to download audio recordings and metadata from Zoom meetings using meeting IDs.
Features
- 🎵 Download audio recordings (M4A format)
- 📝 Download transcripts, chat logs, and timelines
- 🔄 Automatic audio extraction from video files (MP4 → M4A)
- 🔐 Authentication: Hosted user OAuth (default) or Server-to-Server OAuth
- 📋 JSON output for automation
- 🎯 Support for recurring meetings and PMI
- ⏳ Wait for recording processing with
--wait - 🔍 Check recording availability before downloading
- 🛡️ Secure (credentials never exposed in logs)
- 🔁 Automatic retry with exponential backoff
- 💪 Production-ready with 119 tests
Installation
Choose your preferred method:
🚀 Quick Try (uvx - Instant Run, No Install)
Fastest way to try dlzoom - requires Python 3.11+ and ffmpeg:
# Install uv first (if not installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Run dlzoom instantly (no installation needed!)
uvx dlzoom download 123456789 --check-availability
📦 PyPI Install (Recommended for Regular Use)
# Install with pip
pip install dlzoom
# Or with uv (10-100x faster)
uv pip install dlzoom
# Or with uv tool (isolated installation)
uv tool install dlzoom
Note: Requires Python 3.11+ and ffmpeg (see below).
🐳 Docker (Zero Dependencies - Everything Included!)
Best for: Production, CI/CD, no local dependencies
# Run with Docker (includes Python + ffmpeg)
docker run -it --rm \
-v $(pwd)/recordings:/app/downloads \
-e ZOOM_ACCOUNT_ID="your_account_id" \
-e ZOOM_CLIENT_ID="your_client_id" \
-e ZOOM_CLIENT_SECRET="your_secret" \
yanivgolan1/dlzoom:latest \
download 123456789
Or use GitHub Container Registry:
docker run -it --rm \
-v $(pwd)/recordings:/app/downloads \
-e ZOOM_ACCOUNT_ID="your_account_id" \
-e ZOOM_CLIENT_ID="your_client_id" \
-e ZOOM_CLIENT_SECRET="your_secret" \
ghcr.io/yaniv-golan/dlzoom:latest \
download 123456789
🔧 From Source (Development)
# Clone the repository
git clone https://github.com/yaniv-golan/dlzoom.git
cd dlzoom
# Create virtual environment (recommended)
python3.11 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install
pip install -e .
Prerequisites (for non-Docker installations)
- Python 3.11+ (required)
- ffmpeg (required for audio extraction from video files)
Install ffmpeg:
# macOS
brew install ffmpeg
# Ubuntu/Debian
sudo apt install ffmpeg
# Windows (via Chocolatey)
choco install ffmpeg
# Windows (via winget)
winget install ffmpeg
Docker users: No need to install Python or ffmpeg - everything is included!
Quick Start
1. Sign In (Recommended)
Use our hosted authentication service to connect your Zoom account (no secrets required):
dlzoom login
This opens your browser to approve access and stores a short‑lived token locally (refreshed automatically).
Alternatively, organizational users can configure Server‑to‑Server (S2S) OAuth using environment variables or a config file.
Host your own broker (optional)
- The CLI uses a hosted sign‑in broker by default. To self‑host, deploy the worker under
zoom-broker/(see that README), then point dlzoom to your URL via the--auth-urloption or environment variable when logging in.
2. Configure S2S Credentials (Optional)
Create a .env file in your working directory:
ZOOM_ACCOUNT_ID=your_account_id_here
ZOOM_CLIENT_ID=your_client_id_here
ZOOM_CLIENT_SECRET=your_client_secret_here
⚠️ Security Warning: If your project directory is in a cloud-synced folder (iCloud, Dropbox, Google Drive, etc.), your
.envfile containing credentials may be uploaded to cloud storage. Consider using environment variables instead, moving the project to a non-synced directory, or using a config file outside the project directory with--config.
Or set environment variables:
export ZOOM_ACCOUNT_ID="your_account_id"
export ZOOM_CLIENT_ID="your_client_id"
export ZOOM_CLIENT_SECRET="your_client_secret"
Or use a config file:
# config.yaml
zoom_account_id: "your_account_id"
zoom_client_id: "your_client_id"
zoom_client_secret: "your_client_secret"
log_level: "INFO"
Automatic .env loading and opt-out
- dlzoom automatically loads environment variables from a
.envfile at startup. It searches from your current working directory upwards (likegit), and loads the first.envit finds. - Loading uses
override=False, so variables already present in your shell/environment take precedence over values in.env. - To disable auto-loading (e.g., for CI or scripts that must be fully deterministic), set:
export DLZOOM_NO_DOTENV=1
Tips and caveats:
- Prefer explicit environment variables for automation where you don’t want a parent directory
.envto influence behavior. - If your project lives in a cloud‑synced folder (Dropbox, iCloud, Google Drive), treat
.envas sensitive; consider using environment variables or a config file stored outside that folder.
3. Browse and Download
Browse your recordings by date:
# Last 7 days
dlzoom recordings --range last-7-days
# Specific window
dlzoom recordings --from-date 2025-01-01 --to-date 2025-01-31
# Filter by topic (user-wide mode)
dlzoom recordings --range today --topic "standup"
Inspect instances for a specific meeting (replaces the old download --list):
dlzoom recordings --meeting-id 123456789
Download a recording (audio + transcript + chat + timeline):
dlzoom download 123456789
Tip: You can paste meeting IDs directly from Zoom. Spaces are removed automatically:
dlzoom download "882 9060 9309" # Works! Spaces are removed automatically
dlzoom download 88290609309 # Also works
Usage
Basic Commands
Check if recording is available:
dlzoom download 123456789 --check-availability
Use dlzoom recordings --meeting-id 123456789 instead of the removed download --list.
Download recording (audio + transcript + chat + timeline):
dlzoom download 123456789
Download with custom output name:
dlzoom download 123456789 --output-name "my_meeting"
Download to specific directory:
dlzoom download 123456789 --output-dir ~/Downloads/zoom
Wait for recording to finish processing:
dlzoom download 123456789 --wait 30
Advanced Options
Use config file:
dlzoom download 123456789 --config config.yaml
Verbose output (see detailed logs):
dlzoom download 123456789 --verbose
Debug mode (full API responses):
dlzoom download 123456789 --debug
JSON output (for automation):
dlzoom download 123456789 --json
Other Commands
- Show current account:
dlzoom whoami
- Sign out and remove local tokens:
dlzoom logout
Optional Permissions (Advanced)
dlzoom works with minimal permissions by default. You can optionally add these to improve fidelity:
Required Scopes (Minimum):
cloud_recording:read:list_user_recordings- List your cloud recordingscloud_recording:read:list_recording_files- Access recording file details for download
Optional Scopes (Enhanced Features):
meeting:read:meeting- Definitively mark recurring meetings by checking meeting type; without it, recurrence is inferred only within the fetched date rangeuser:read:user- Show your name/email inwhoamiwhen using user tokens
All scopes use Zoom's granular scope naming (user-managed OAuth). Behavior degrades gracefully if optional scopes are not enabled.
**Dry run (see what would be downloaded):**
```bash
dlzoom download 123456789 --dry-run
Custom filename template:
dlzoom download 123456789\
--filename-template "{topic}_{start_time:%Y%m%d}"
Custom folder structure:
dlzoom download 123456789\
--folder-template "{start_time:%Y}/{start_time:%m}"
Select specific recording instance (for recurring meetings):
dlzoom download 123456789 --recording-id "abc123def456"
Skip Downloads
Skip transcript download:
dlzoom download 123456789 --skip-transcript
Skip chat log download:
dlzoom download 123456789 --skip-chat
Skip timeline download:
dlzoom download 123456789 --skip-timeline
Download Options
dlzoom download [OPTIONS] MEETING_ID
Options:
--output-dir, -o PATH Output directory (default: current directory)
--output-name, -n TEXT Base filename (default: meeting_id)
--verbose, -v Show detailed operation information
--debug, -d Show full API responses and trace
--json, -j JSON output mode (machine-readable)
--check-availability, -c Check if recording is ready
--recording-id TEXT Select specific recording by UUID
--wait MINUTES Wait for recording processing (timeout)
--skip-transcript Skip transcript download
--skip-chat Skip chat log download
--skip-timeline Skip timeline download
--dry-run Show what would be downloaded
--log-file PATH Write structured log (JSONL format)
--config PATH Path to config file (JSON/YAML)
--filename-template TEXT Custom filename template
--folder-template TEXT Custom folder structure template
--help Show this message and exit
--version Show version and exit
Recordings Options
dlzoom recordings [OPTIONS]
User-wide mode (default):
--from-date TEXT Start date (YYYY-MM-DD)
--to-date TEXT End date (YYYY-MM-DD)
--range [today|yesterday|last-7-days|last-30-days]
Quick date range (exclusive with --from-date/--to-date)
--topic TEXT Substring filter on topic
--limit INTEGER Max results (0 = unlimited) [default: 1000]
--page-size INTEGER [Advanced] Results per API request (Zoom max 300) [default: 300]
Meeting-scoped mode (replaces `download --list`):
--meeting-id TEXT Exact meeting ID or UUID to list instances
Common options:
--json, -j JSON output mode (silent)
--verbose, -v Verbose human output
--debug, -d Debug logging
--config PATH Path to config file
--help Show this message and exit
Template Variables
Use in --filename-template and --folder-template:
{topic}- Meeting topic{meeting_id}- Meeting ID{host_email}- Host email address{start_time:%Y%m%d}- Start time (format with strftime codes){duration}- Meeting duration
Examples:
# Date-based filename
--filename-template "{start_time:%Y%m%d}_{topic}"
# Organized by date
--folder-template "{start_time:%Y}/{start_time:%m}"
# Include host
--filename-template "{host_email}_{topic}_{start_time:%Y%m%d}"
Common Use Cases
Download Latest Recording from Recurring Meeting
dlzoom download 123456789 --verbose
Download Specific Instance
# List all instances first (meeting-scoped view)
dlzoom recordings --meeting-id 123456789
# Download a specific instance by UUID
dlzoom download 123456789 --recording-id "abc123def456"
Automated Pipeline (JSON Output)
dlzoom download 123456789 --json > recording.json
Batch Processing
#!/bin/bash
for meeting_id in 111111111 222222222 333333333; do
dlzoom download $meeting_id --output-dir ./recordings
done
Wait for Recording to Process
# Wait up to 60 minutes for processing
dlzoom download 123456789 --wait 60
Troubleshooting
Authentication Failed
Error: Authentication failed
Solution: Check your credentials in .env or environment variables.
Meeting ID Format Invalid
Error: Invalid meeting ID format
Solution: Meeting IDs must be:
- 9-12 digit numbers (e.g.,
123456789) - Or UUID format (e.g.,
abc123XYZ+/=_-)
Recording Not Found
Error: Recording not found
Possible causes:
- Meeting wasn't recorded
- Recording not yet processed (use
--wait) - No permission to access recording
- Wrong meeting ID
Check availability first:
dlzoom download 123456789 --check-availability
ffmpeg Not Found
Error: ffmpeg not found in PATH
Solution: Install ffmpeg:
# macOS
brew install ffmpeg
# Ubuntu/Debian
sudo apt install ffmpeg
Rate Limit Exceeded
Error: Rate limit exceeded
Solution: Wait a few minutes and try again. The tool automatically retries with exponential backoff.
Insufficient Disk Space
Error: Insufficient disk space
Solution: Free up space or use --output-dir to save to a different location.
Configuration Files
JSON Config
{
"zoom_account_id": "your_account_id",
"zoom_client_id": "your_client_id",
"zoom_client_secret": "your_client_secret",
"output_dir": "./recordings",
"log_level": "INFO"
}
YAML Config
zoom_account_id: "your_account_id"
zoom_client_id: "your_client_id"
zoom_client_secret: "your_client_secret"
output_dir: "./recordings"
log_level: "INFO"
Use with:
dlzoom download 123456789 --config config.yaml
Output Files
Audio file:
- Format: M4A (AAC audio)
- Naming:
{meeting_id}.m4aor custom via--output-name
Transcript file:
- Format: VTT (WebVTT)
- Naming:
{meeting_id}_transcript.vtt
Chat log:
- Format: TXT
- Naming:
{meeting_id}_chat.txt
Timeline:
- Format: JSON
- Naming:
{meeting_id}_timeline.json - Contains: Meeting events (joins, leaves, screen shares, etc.)
Metadata:
- Format: JSON
- Naming:
{meeting_id}_metadata.json - Contains: Meeting info, participants, recording details
Requirements
- Python 3.11 or higher
- ffmpeg (for audio extraction)
- Zoom account (User OAuth via
dlzoom login) or S2S OAuth app (optional)
Security note (tokens): On Windows, file permission enforcement for tokens.json is best‑effort. Treat your token file as sensitive and ensure your user profile is protected.
Broker Origin Restriction (Optional)
For tighter security on the hosted auth service, you can restrict which origin is allowed to call the token endpoints:
- Set the
ALLOWED_ORIGINenvironment variable on your Cloudflare Worker (e.g., your CLI’s origin or a specific domain). When set, the broker sendsAccess-Control-Allow-Origin: <value>andVary: Origininstead of*. - See
zoom-broker/README.mdfor details. If you don’t set it, the broker defaults to*— acceptable for CLI usage but less restrictive.
Development
# Clone repository
git clone <repo-url>
cd dlzoom
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
# Install in development mode
pip install -e .
# Run tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=src/dlzoom --cov-report=term-missing
Version
See CHANGELOG.md for detailed release notes. Current version: 0.2.0.
Roadmap
Planned for future releases:
- 🎨 More output formats (TSV)
- 🔐 Token encryption via system keychain
- 📱 Multiple profiles support
- 📦 Optional SBOM generation in CI
Known Limitations
Feature Limitations
- Audio quality parameter not exposed via CLI (internal only)
License
MIT License - see LICENSE file for details.
Contributing
We welcome contributions! Here's how you can help:
Quick Start for Contributors
-
Fork and clone the repository
-
Set up development environment:
python3.11 -m venv .venv source .venv/bin/activate pip install -e ".[dev]"
-
Run tests:
pytest tests/ -v
-
Make your changes and ensure tests pass
-
Submit a pull request
Guidelines
- Follow Conventional Commits for commit messages
- Add tests for new features
- Update documentation as needed
- Ensure all CI checks pass
Commit Message Format
<type>(<scope>): <subject>
Examples:
feat(cli): add support for CSV export
fix(auth): handle expired OAuth tokens
docs: update installation instructions
Types:
feat- New featurefix- Bug fixdocs- Documentationtest- Testsrefactor- Code refactoringchore- Maintenance
For detailed guidelines, see CONTRIBUTING.md.
Code of Conduct
- See CODE_OF_CONDUCT.md.
Security
Security is important to us. If you discover a security vulnerability:
- Do not open a public issue
- Email yaniv@golan.name with details
- See SECURITY.md for our security policy
Security Features
- ✅ Credentials never logged or exposed
- ✅ Input validation (prevents injection attacks)
- ✅ Atomic file operations
- ✅ Automatic security scanning (Trivy)
- ✅ Docker images run as non-root user
Support
For issues and questions:
Credits
Built with:
- Click / Rich-Click - CLI framework
- Rich - Terminal output
- Requests - HTTP client
- pytest - Testing framework
Acknowledgments
Thanks to all contributors and the open source community.
Architecture
This is a monorepo containing two independently deployable components:
Repository Structure
dlzoom/
├── src/dlzoom/ # Python CLI (primary deliverable)
├── tests/ # Python test suite
├── zoom-broker/ # Cloudflare Worker (OAuth broker)
├── docs/ # Documentation
│ ├── architecture.md # Detailed architecture overview
│ └── zoom-app/ # Zoom App marketplace docs
└── pyproject.toml # Python package configuration
Components:
- Python CLI (
src/dlzoom/): Command-line tool to browse and download Zoom cloud recordings. Published to PyPI asdlzoom. - OAuth Broker (
zoom-broker/): Cloudflare Worker that performs OAuth code exchange and token refresh on behalf of the CLI. Optional component for user OAuth mode.
Why a monorepo?
- Simplified development workflow (single repo to clone, single issue tracker)
- Shared documentation and versioning strategy
- Python CLI is the primary deliverable; broker is a supporting service
- Each component remains independently deployable
📖 See docs/architecture.md for detailed architecture, data flows, security model, and deployment options.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dlzoom-0.2.0.tar.gz.
File metadata
- Download URL: dlzoom-0.2.0.tar.gz
- Upload date:
- Size: 67.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c15c020c276d1b33b77efa000c7ad28341dd82e9710c1e48035c80e382197f9f
|
|
| MD5 |
fdf1bee4106dfb3467b0767525e2ce39
|
|
| BLAKE2b-256 |
86cf9d5cdb57e4a6b567a96a081cbae352d0b24aac8d8f673c7309ec52e464e2
|
Provenance
The following attestation bundles were made for dlzoom-0.2.0.tar.gz:
Publisher:
release.yml on yaniv-golan/dlzoom
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dlzoom-0.2.0.tar.gz -
Subject digest:
c15c020c276d1b33b77efa000c7ad28341dd82e9710c1e48035c80e382197f9f - Sigstore transparency entry: 698646559
- Sigstore integration time:
-
Permalink:
yaniv-golan/dlzoom@eda07d967dfc4f63423b70355b313ec4d2604c36 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/yaniv-golan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eda07d967dfc4f63423b70355b313ec4d2604c36 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dlzoom-0.2.0-py3-none-any.whl.
File metadata
- Download URL: dlzoom-0.2.0-py3-none-any.whl
- Upload date:
- Size: 54.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7b2abf05240c273d8a75171adf3a44fccf84817fa4010073135d4a4507d3c45
|
|
| MD5 |
cc0d3eb35e3db893e88cbb1593ef5d7d
|
|
| BLAKE2b-256 |
9fa9e68f75fe731548ac98fd5d9ef89e1d899ed88d83902bc406852350c22687
|
Provenance
The following attestation bundles were made for dlzoom-0.2.0-py3-none-any.whl:
Publisher:
release.yml on yaniv-golan/dlzoom
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dlzoom-0.2.0-py3-none-any.whl -
Subject digest:
f7b2abf05240c273d8a75171adf3a44fccf84817fa4010073135d4a4507d3c45 - Sigstore transparency entry: 698646565
- Sigstore integration time:
-
Permalink:
yaniv-golan/dlzoom@eda07d967dfc4f63423b70355b313ec4d2604c36 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/yaniv-golan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eda07d967dfc4f63423b70355b313ec4d2604c36 -
Trigger Event:
push
-
Statement type: