Metadata-Version: 2.4
Name: TermFace
Version: 3.0.0
Summary: Secure terminal video call — TCP media, Opus audio, libsodium E2E encryption, session strings
Author: Yash12b
License-Expression: MIT
Project-URL: Homepage, https://github.com/Yash12b/TermFace
Project-URL: Repository, https://github.com/Yash12b/TermFace
Project-URL: Issues, https://github.com/Yash12b/TermFace/issues
Keywords: ascii,webcam,terminal,videocall,encryption,opus,color,art
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Communications
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: opencv-python>=4.8
Requires-Dist: numpy>=1.24
Requires-Dist: PyNaCl>=1.5.0
Provides-Extra: audio
Requires-Dist: PyAudio>=0.2.14; extra == "audio"
Requires-Dist: opuslib>=3.0.1; extra == "audio"
Provides-Extra: screen
Requires-Dist: mss>=9.0; extra == "screen"
Provides-Extra: compression
Requires-Dist: zstandard>=0.20.0; extra == "compression"
Provides-Extra: webrtc
Requires-Dist: aiortc<2,>=1.15.0; extra == "webrtc"
Provides-Extra: full
Requires-Dist: PyAudio>=0.2.14; extra == "full"
Requires-Dist: opuslib>=3.0.1; extra == "full"
Requires-Dist: mss>=9.0; extra == "full"
Requires-Dist: zstandard>=0.20.0; extra == "full"
Requires-Dist: aiortc<2,>=1.15.0; extra == "full"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# 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

```bash
pip install TermFace
```

If you use `pipx`, install and update it with:

```bash
pipx install TermFace
pipx upgrade TermFace
```

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

```bash
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:

```bash
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

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

## Quick Start

### ASCII Webcam (local)

```bash
ascii-cam
# or
termface ascii
```

### Video Call

```bash
# 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

```bash
# 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

```bash
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:

```bash
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:

```bash
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`](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:

```bash
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

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

## License

MIT
