Nami
An open-source CLI media downloader for Instagram, TikTok, Facebook, and X.
Nami coordinates gallery-dl and yt-dlp through a pure functional planning core and deterministic execution shell. It provides deduplicated archive indexing, multi-attempt failure classification, process isolation, and both interactive Rich terminal UI and scriptable JSON CLI modes.
Table of Contents
- Supported Platforms
- Architecture & Design Principles
- Installation
- Quickstart
- Workspace Layout
- CLI Reference
- Authentication
- Failure Classification & Retry Policy
- Exit Codes
- Development & Testing
- License
Supported Platforms
| Platform | Photos / Posts | Videos / Reels | Stories | Highlights | Authentication | Primary Engine |
|---|---|---|---|---|---|---|
| Supported | Supported | Supported | Supported | Netscape Cookie / Anonymous | gallery-dl (Photos/Stories) / yt-dlp (Video Fallback) |
|
| TikTok | Limited by upstream | Supported | Unsupported | Unsupported | Browser DB / Netscape Cookie | yt-dlp / gallery-dl |
| Limited by upstream | Supported | Unsupported | Unsupported | Netscape Cookie / Anonymous | gallery-dl / yt-dlp |
|
| X (Twitter) | Limited by upstream | Supported | Unsupported | Unsupported | Netscape Cookie / Anonymous | gallery-dl / yt-dlp |
Unsupported media/platform combinations return structured Outcome.UNSUPPORTED (Exit Code 3) rather than silent failure or fake success.
Architecture & Design Principles
- Pure Core, Imperative Shell:
targets.py: URL parsing, sanitization, canonical host mapping, and media endpoint expansion.planner.py: Pure mapping fromTarget+MediaKindto deterministicPlanStepsequences without filesystem I/O.retry.py: Stateless retry decision engine with exponential jittered backoff.service.py: Orchestrator executing plan steps behind atomic file locks (archive.lock).
- Dual-Engine Routing (ADR-0001):
- Photos, Instagram stories, and highlights route to
gallery-dl. - Videos route to
gallery-dlwith automatic fallback toyt-dlpuponFailureKind.EXTRACTOR. - Network errors, rate limits, and authentication rejections retry on the same engine without inappropriate cross-engine churn.
- Photos, Instagram stories, and highlights route to
- No Shell Invocations (
shell=False):- All subprocess arguments are passed as discrete string arrays (
argv) to prevent shell injection. - Process trees are tracked and terminated via process group signals (
SIGTERM/SIGKILLon POSIX,taskkill /PID <PID> /T /Fon Windows).
- All subprocess arguments are passed as discrete string arrays (
- Strict Path Containment:
- Every destination folder is validated against
base_dirusingsafe_target_dir()to prevent path traversal attacks.
- Every destination folder is validated against
Installation
Requires Python 3.10 or newer.
python -m pip install nami
Upgrade to latest release:
python -m pip install --upgrade nami
Quickstart
1. Interactive Terminal UI
Run nami without arguments:
nami
┌──────────────────────── Nami ────────────────────────┐
│ What do you want to download? │
│ │
│ 1 Photos only │
│ 2 Videos only │
│ 3 Stories only │
│ 4 Highlights only │
│ 5 Photos + Videos │
│ 6 Stories + Highlights │
│ 7 All │
│ 8 Settings │
│ 0 Exit │
│ │
│ Save: ~/Nami/downloads │
└──────────────────────────────────────────────────────┘
On first launch without configuration, Nami interactively guides setup and workspace initialization.
2. Command-Line Direct Invocations
# Download direct posts
nami download https://www.instagram.com/p/C_EXAMPLE/ https://x.com/OpenAI/status/1234567890
# Filter specific media kinds
nami download https://www.instagram.com/natgeo/ --media photos,videos
# Batch download profiles from configured text files
nami download --profiles --platform instagram --media stories,highlights
Workspace Layout
Nami/
├── downloads/
│ ├── instagram/
│ │ └── natgeo/
│ │ ├── Photos/
│ │ │ └── archive.txt
│ │ ├── Videos/
│ │ │ └── archive.txt
│ │ └── Stories/
│ │ └── archive.txt
│ └── tiktok/
│ └── example_user/
│ └── Videos/
├── cookies/
│ ├── instagram_cookies.txt
│ ├── tiktok_cookies.txt
│ ├── facebook_cookies.txt
│ └── x_cookies.txt
└── profiles/
├── instagram_profiles.txt
├── tiktok_profiles.txt
├── facebook_profiles.txt
└── x_profiles.txt
CLI Reference
nami setup
Initializes configuration and workspace directories:
# Initialize workspace under specific root
nami setup --root /path/to/parent
# Create empty Netscape cookie template files
nami setup --root . --cookie-templates
# Machine-readable JSON output
nami setup --root . --json
nami download
Executes download planning and execution for URLs and profile lists:
nami download [URL ...] [OPTIONS]
| Flag | Type | Description |
|---|---|---|
URL ... |
Positional | One or more direct content or profile URLs |
--profiles |
Flag | Read target URLs from profiles_dir/<platform>_profiles.txt |
--platform |
Option | Restrict or disambiguate platform (instagram, tiktok, facebook, x) |
--media |
Option | Comma-separated media kinds (photos, videos, stories, highlights, all) |
--json |
Flag | Output structured JSON result for automated pipelines |
nami doctor
Runs local, read-only diagnostic checks without network I/O:
nami doctor
nami doctor --json
Checks executed:
- Config JSON structure and permissions
- Python version (>= 3.10)
- Core module imports (
rich,gallery_dl,yt_dlp) - Read/write access on
base_dir,cookies_dir, andprofiles_dir - Browser installation and process lock check (
brave,chrome,edge,firefox) - Netscape cookie file syntax validation (minimum 7 valid columns)
- Profile file accessibility and syntax validation
- Namespace conflicts (
urllib3_future,niquests) - Stale
archive.lockfiles (> 1 hour old)
nami config
Inspects and updates persistent configuration (~/.nami/nami_config.json):
# Display all configuration
nami config show
# Get specific key
nami config get base_dir
nami config get browser
# Set key
nami config set browser chrome
nami config set timeout_seconds 3600
# Reset key to default
nami config unset browser
# JSON mode
nami config show --json
nami archive reset
Manages download tracking records (archive.txt) safely:
# Dry run preview
nami archive reset --all --dry-run
# Back up archives for target (creates .bak files)
nami archive reset --platform instagram --target natgeo --yes
# Reset specific media kind
nami archive reset --platform instagram --target natgeo --media stories --yes
# Delete matching archives permanently
nami archive reset --platform tiktok --target creator --delete --yes
Authentication
Netscape Cookie Files
Export cookies from your browser using a Netscape-compatible extension and save them into cookies_dir:
instagram_cookies.txtorinstagram.com_cookies.txttiktok_cookies.txtortiktok.com_cookies.txtfacebook_cookies.txtorfacebook.com_cookies.txtx_cookies.txt,x.com_cookies.txt, ortwitter_cookies.txt
Nami validates that cookie files contain valid 7-column rows before passing them to engines.
Browser Cookie DB
When downloading from TikTok without an explicit cookie file, Nami attempts direct cookie extraction from the configured browser (brave, chrome, edge, firefox).
Failure Classification & Retry Policy
failures.py maps error logs to typed FailureKind categories:
| Failure Kind | Description | Retry Action |
|---|---|---|
FailureKind.AUTH |
HTTP 401, login required | 1 anonymous retry if credentials were supplied |
FailureKind.COOKIE |
Cookie decryption or file error | 1 anonymous retry if credentials were supplied |
FailureKind.RATE_LIMIT |
HTTP 429, too many requests | Stop immediately (no retry) |
FailureKind.NETWORK |
DNS reset, SSL, connection timeout | Up to 3 attempts with exponential jittered backoff |
FailureKind.EXTRACTOR |
Unsupported route, broken extractor | 1 retry on alternate engine (yt-dlp) |
FailureKind.NOT_FOUND |
HTTP 404, account deleted | Stop immediately (Outcome.NO_RESULTS) |
FailureKind.DEPENDENCY |
Missing binary or module | Stop immediately |
FailureKind.TIMEOUT |
Subprocess deadline exceeded | Up to 3 attempts with backoff |
FailureKind.LOCKED |
Checkpoint or archive lock busy | Stop immediately |
Exit Codes
| Code | Outcome | Description |
|---|---|---|
0 |
SUCCESS |
All items downloaded or already up to date |
1 |
FAILED |
Unrecoverable failure across attempts |
2 |
INVALID |
Malformed CLI arguments, invalid target URLs, or corrupt config |
3 |
PARTIAL / WARN |
Mixed batch outcomes, unsupported operations, or doctor warnings |
4 |
NO_RESULTS |
Extractor ran cleanly but found zero downloadable items |
130 |
CANCELLED |
Execution interrupted via SIGINT (Ctrl+C) |
Development & Testing
Setup Environment
git clone https://github.com/OpenSelena/nami.git
cd nami
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
Running Test Suite
# Run pytest
python -m pytest
# Run Ruff linter and formatter
python -m ruff check src tests
python -m ruff format --check src tests
# Build distribution package
python -m build
License
Distributed under the MIT License. Maintained by Igect under OpenSelena.
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 nami-5.0.3.tar.gz.
File metadata
- Download URL: nami-5.0.3.tar.gz
- Upload date:
- Size: 67.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
477b711bb4d63d45f6dd5cd8d2677f7032153d325a5a50a8b6552a5b2b24c643
|
|
| MD5 |
02ecc0271f9a4d6b47619a5a92163fd7
|
|
| BLAKE2b-256 |
4e89476da99e6b4ac95cf68a9564bdbaae06172847a24e1f5323575bcc739065
|
Provenance
The following attestation bundles were made for nami-5.0.3.tar.gz:
Publisher:
publish.yml on OpenSelena/nami
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nami-5.0.3.tar.gz -
Subject digest:
477b711bb4d63d45f6dd5cd8d2677f7032153d325a5a50a8b6552a5b2b24c643 - Sigstore transparency entry: 2568642265
- Sigstore integration time:
-
Permalink:
OpenSelena/nami@93750227fe7df262ea4e9b98445bbd1e27a57500 -
Branch / Tag:
refs/tags/v5.0.3 - Owner: https://github.com/OpenSelena
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@93750227fe7df262ea4e9b98445bbd1e27a57500 -
Trigger Event:
release
-
Statement type:
File details
Details for the file nami-5.0.3-py3-none-any.whl.
File metadata
- Download URL: nami-5.0.3-py3-none-any.whl
- Upload date:
- Size: 53.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1acaa4f0fc49c5986cf3b21cd11c9c6a0d8838b59458c37a7b7cb745e81ac9a6
|
|
| MD5 |
ea945cbfe425e4e25109540121d00dcc
|
|
| BLAKE2b-256 |
a061c89abc5fe55e3abc9d5dbdb80b296f9f24668f0c9d4d8e6d8b38398b6697
|
Provenance
The following attestation bundles were made for nami-5.0.3-py3-none-any.whl:
Publisher:
publish.yml on OpenSelena/nami
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nami-5.0.3-py3-none-any.whl -
Subject digest:
1acaa4f0fc49c5986cf3b21cd11c9c6a0d8838b59458c37a7b7cb745e81ac9a6 - Sigstore transparency entry: 2568642278
- Sigstore integration time:
-
Permalink:
OpenSelena/nami@93750227fe7df262ea4e9b98445bbd1e27a57500 -
Branch / Tag:
refs/tags/v5.0.3 - Owner: https://github.com/OpenSelena
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@93750227fe7df262ea4e9b98445bbd1e27a57500 -
Trigger Event:
release
-
Statement type: