Skip to main content

TermFace

Secure terminal video call. Your webcam rendered as color ASCII art, with end-to-end encrypted peer-to-peer calls.

What's New in v2.0

  • Authenticated encryption — XSalsa20-Poly1305 (libsodium), enabled for shared sessions or an explicit key
  • Opus audio codec — 24kHz at 32kbps (10x compression over raw PCM), echo cancellation, noise gate
  • zstd compression — available for frame payloads and diagnostics; JPEG media remains compatible with older TCP peers
  • TCP media transport — reliable delivery with flow control (no more UDP packet drops)
  • Session strings — share happy-sunset-ocean instead of IP addresses
  • Multi-client grid renderer — ready for a future relay-based group-call service
  • Auto-reconnection — exponential backoff with up to 5 retries
  • Call quality stats — real-time RTT, bandwidth, and duration display
  • LAN discovery — automatic peer discovery on local network
  • WebRTC-ready signaling relay — optional TLS signaling for offer/answer/ICE

Install

From PyPI

pip install TermFace

If you use pipx, install and update it with:

pipx install TermFace
pipx upgrade TermFace

WebRTC support is optional and does not change the default TCP transport:

pip install "TermFace[webrtc]"

The extra provides a peer-to-peer aiortc adapter. Add --camera and/or --microphone to termface webrtc (with --camera-device and --microphone-device for hardware selection) to negotiate real media tracks. ICE can be configured with repeated --ice-url options plus --ice-username/--ice-credential for TURN. The same settings are available as TERMFACE_WEBRTC_ICE_URLS (comma-separated), TERMFACE_WEBRTC_ICE_USERNAME, and TERMFACE_WEBRTC_ICE_CREDENTIAL. URLs must use stun://, turn://, or turns://; TURN credentials must be supplied together. Keep credentials out of shell history where possible. The existing TCP media path remains the default. WebRTC media tracks are available in the optional command, but a managed rendezvous service and SFU are not bundled.

After the initial install, update TermFace without Git or a project folder:

termface update
# Check without installing:
termface update --check
# Print the installed version:
termface version

The updater uses the same Python interpreter that launched TermFace, so it updates the correct installation on Windows, macOS, and Linux.

From Source

git clone https://github.com/Yash12b/TermFace.git
cd TermFace
pip install -e ".[dev]"

Quick Start

ASCII Webcam (local)

ascii-cam
# or
termface ascii

Video Call

# Person A (call receiver) — start first, wait for incoming
termface facetime

# Person B (caller) — connect to A's IP
termface facetime --target 192.168.1.100

With Session Strings

# Person A starts with a session string
termface facetime --session happy-sunset-ocean

# Person B joins using the same session string
termface facetime --session happy-sunset-ocean --target 192.168.1.100

All Options

termface facetime --help
termface ascii --help

Diagnose your installation

Run the non-destructive doctor to check Python/platform support, terminal capabilities, camera, optional audio/Opus, encryption, compression, and TCP port binding. Every check reports its error instead of aborting the command:

termface doctor

Controls (during call)

Key Action
t Chat (type message, Enter to send)
f Send file
1-4 Color mode: mono / grayscale / color / invert
e Toggle edge detection
b Toggle background blur
r Toggle recording / background removal
s Screenshot (ASCII .txt + .png)
c Cycle character set
+/- Brightness
[/] Contrast
q Hang up / quit

Architecture

terminal_webcam/
├── protocol.py     # Binary protocol (10-byte header, length-prefixed)
├── crypto.py       # libsodium XSalsa20-Poly1305 encryption
├── audio.py        # Opus codec + echo cancellation + noise gate
├── video.py        # ASCII renderer + zstd compression + background effects
├── network.py      # TCP multiplexed transport + auto-reconnect
├── discovery.py    # LAN discovery + session strings + STUN
├── quality.py      # Adaptive quality + call stats
├── terminal_facetime.py  # Main app
└── webcam_ascii.py       # Local webcam viewer

Security

  • Authenticated encryption using libsodium when a shared session or key is supplied
  • XSalsa20-Poly1305 AEAD cipher
  • Directional key rotation and replay protection every 256 messages
  • Shared session strings should be treated as secrets and exchanged over a trusted channel
  • Calls using --session derive the same encryption key from the shared session string. Direct IP calls can use --encrypt-key; otherwise they run without encryption.

Worldwide calling (first slice)

The practical first step toward worldwide/WebRTC calling is a small signaling-only relay. It forwards WebRTC-compatible offer, answer, and ice JSON messages over TLS; media remains on the existing TCP path, so current calls are unchanged. Run it behind a certificate (Let's Encrypt or a reverse proxy) and an access token:

termface-signaling --cert fullchain.pem --key privkey.pem \
  --token "$TERMFACE_SIGNALING_TOKEN" --port 8443

Rooms allow two signaling participants by default. Each join receives a unique participant_id; the joined response includes metadata for existing participants, and members receive participant_joined/participant_left notifications. offer, answer, ice, and hangup messages may include a to participant ID for direct routing. Omitting to preserves broadcast behavior. Operators can raise this limit with --max-peers N or TERMFACE_SIGNALING_MAX_PEERS; this only permits signaling members to join. Multi-peer media still requires an SFU, which TermFace does not provide.

For a Linux host, examples/termface-signaling.service is a hardened systemd template. Create /etc/termface/signaling.env containing only TERMFACE_SIGNALING_TOKEN=<random-secret>, install the package in the service's Python environment, and place the unit behind a firewall/reverse proxy that exposes only its TLS port.

Native integrations use SignalingClient and these environment variables: TERMFACE_SIGNALING_SERVER=signal.example.com:8443, TERMFACE_SIGNALING_TOKEN, and TERMFACE_SIGNALING_ROOM. Set TERMFACE_SIGNALING_INSECURE=1 only for local development. The relay does not provide NAT traversal, TURN, media forwarding, identity, or browser WebSocket support yet; those are required before replacing TCP media with WebRTC.

For short-lived, room-scoped access, configure the relay with --invite-secret (or TERMFACE_SIGNALING_INVITE_SECRET) instead of, or in addition to, --token. An invite is an HMAC-SHA256 token created by a trusted provisioning script with mint_invite_token(secret, room, ttl) (or create_invite_token(secret, room, expires_at)) and passed as TERMFACE_SIGNALING_TOKEN to the client. Tokens expire at the encoded Unix time, with 30 seconds of clock-skew tolerance by default (adjustable with --clock-skew). This is invite authorization only, not an identity or account system; static tokens remain supported for compatibility.

Self-hosted signaling + TURN bundle

For a free, self-hosted deployment of the existing TLS signaling relay together with coturn, see deployment/README.md. It includes a Docker Compose file, a no-secrets environment template, and firewall guidance. This bundle is only a foundation: TermFace still uses its existing TCP media transport, and the project does not yet provide WebRTC media integration.

Create a short-lived room invitation after setting the deployment secret:

export TERMFACE_SIGNALING_INVITE_SECRET='<the secret from deployment/.env>'
termface invite --room family-call --ttl 3600

Give the printed token to both participants as TERMFACE_SIGNALING_TOKEN. The current desktop client still requires the WebRTC integration to consume these relay invitations; the existing facetime command remains LAN/TCP mode.

Testing

pip install -e ".[dev]"
pytest tests/ -v

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

termface-3.0.0-py3-none-any.whl (59.5 kB view details)

Uploaded Python 3

File details

Details for the file termface-3.0.0-py3-none-any.whl.

File metadata

  • Download URL: termface-3.0.0-py3-none-any.whl
  • Upload date:
  • Size: 59.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for termface-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 180d426d6f8b7b36da3fc373f66cb79a7b74c8db2296e25143de7f4c1b73c7ef
MD5 52d5f5920ec613ad050ed2fba54ed1f7
BLAKE2b-256 54d114d2825682bb90567d42cc8df5e44e493bb8c9624401a6e40e58622e1654

See more details on using hashes here.

Release history Release notifications | RSS feed

3.4.1

2 files

3.4.0

2 files

3.3.0

1 file

3.2.0

1 file

3.1.0

1 file

3.0.1

1 file

This release

3.0.0 This release

1 file

2.0.7

1 file

2.0.6

1 file

2.0.2

1 file

2.0.1

1 file

2.0.0

1 file

1.0.0

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page