Metadata-Version: 2.1
Name: bithuman
Version: 2.11.7
Summary: Run a bitHuman avatar on this machine: `bithuman.open(avatar).render(audio)`.
Keywords: bithuman,avatar,essence,lipsync,pybind11
Author-Email: bitHuman <hello@bithuman.ai>
License: Commercial — see LICENSE file
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python
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: Programming Language :: C++
Classifier: Topic :: Multimedia
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Video
Project-URL: Homepage, https://bithuman.ai
Project-URL: Documentation, https://docs.bithuman.ai
Requires-Python: <3.15,>=3.10
Requires-Dist: numpy>=1.26.0
Requires-Dist: loguru~=0.7
Requires-Dist: soundfile>=0.13
Requires-Dist: pydantic~=2.10
Requires-Dist: pydantic-settings~=2.8
Requires-Dist: av>=12.0
Requires-Dist: opencv-python-headless>=4.8
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: psutil>=5.9; extra == "test"
Provides-Extra: expression-2
Requires-Dist: ai-edge-litert>=2.1.5; extra == "expression-2"
Description-Content-Type: text/markdown

# bithuman

Run a bitHuman avatar on your own machine, in your own process.

```bash
pip install bithuman
python -m bithuman A63GVG1577 speech.wav       # -> A63GVG1577.mp4
```

Two arguments — **an avatar and some audio** — and a video you can play. The
avatar is the ten-character code the service gave it (fetched once, which is
free) or a file you already have. Run `python -m bithuman` with no arguments
to list the avatars your key can open.

**No audio to hand?** This package ships 15 s of speech, so the first run needs
nothing you do not already have:

```bash
python -m bithuman A63GVG1577 "$(python -c 'import bithuman,os;print(os.path.join(os.path.dirname(bithuman.__file__),"assets","demo_sample.wav"))')"
```

`python -m bithuman --help` prints that path on your machine.

In your own program it is the same two things:

```python
import bithuman, os

speech = os.path.join(os.path.dirname(bithuman.__file__),
                      "assets", "demo_sample.wav")   # 15 s, ships in the wheel

avatar = bithuman.open("A63GVG1577.imx")
for image in avatar.render(speech):
    show(image)
```

That is the whole thing: **open an avatar, then render audio through it.**

### both families, the same two lines

An **essence-2** avatar and an **expression-2** avatar are opened and rendered
by the code above, unchanged. Nothing you write says which one you have, and
you do not have to know.

expression-2 needs one extra package on the machine:

```bash
pip install "bithuman[expression-2]"
```

Open an expression-2 avatar without it and the refusal says so, and says that
line. Nothing else differs.

### offline rendering, without 3 GB of CUDA you will never run

`bithuman[offline]` adds torch and onnxruntime. From PyPI's default index that
resolves to the **CUDA** build of torch, and the extra costs **3.18 GB of
wheels — 2.45 GB of it `nvidia-*`, `cuda-*` and `triton`** that the offline
route never executes. Install torch first from
[PyTorch's own selector](https://pytorch.org/get-started/locally/) — choose the
*Compute Platform* **without** CUDA and run the one line it prints — and the
same extra then resolves to **0.18 GB, with no CUDA wheel in the set at all**
(the extra asks for `torch>=2.1`, and any build satisfies it):

```bash
pip install "bithuman[offline]"      # after torch, from the line the selector printed
```

(Measured 2026-09-19 on Linux x86_64 with `pip install --dry-run --report`:
49 wheels / 3.18 GB from the default index alone, 30 wheels / 0.18 GB with
PyTorch's CUDA-free index beside it. Use the default index only if you
actually want CUDA.)

---

## The surface — eight names

| you write | it means |
|---|---|
| `bithuman.open(source)` | open the avatar file on this machine; returns an `Avatar` |
| `avatar.render(audio)` | yield the frames for that audio |
| `Avatar` | what `open` gives you |
| `AvatarError` | catch this for any refusal |
| `InvalidAvatar` | we cannot find it, or it is not a usable avatar |
| `NotSupported` | this avatar cannot run here |
| `NotAuthorised` | the key is missing, invalid, or out of credit |
| `Failed` | we could not do it — the message says which |

There is nothing else, and nothing to configure. This package runs the avatar
on this machine, so there is no choice left about where or how it runs.

### audio in

`audio` is 16 kHz mono, and it is either a buffer or a stream — the same call:

```python
avatar.render(speech)                      # an audio file path
avatar.render(samples)                     # int16 or float32 in [-1, 1]
avatar.render(raw_bytes)                   # 16 kHz mono, signed 16-bit
avatar.render(microphone())                # any iterable of the above
```

### frames out

Each frame is a `(height, width, 3)` uint8 array in **RGB** order, in order, at
the avatar's own frame rate — which is a property of the avatar, not something
to choose. (This line read "one per 40 ms of speech" until 2026-09-06, which
was true of every avatar the package could open at the time and is not true of
an expression-2 one.)

```python
import cv2
for image in avatar.render(speech):
    cv2.imshow("avatar", image[:, :, ::-1])   # OpenCV wants BGR
    cv2.waitKey(1)
```

### stopping early

Someone interrupting the avatar is "stop consuming and close the iterator":

```python
frames = avatar.render(speech)
for image in frames:
    if interrupted:
        frames.close()
        break
    show(image)
```

### releasing it

`with` frees everything at the end of the block; without it, the avatar is
freed when it is garbage collected.

```python
with bithuman.open("A63GVG1577.imx") as avatar:
    for image in avatar.render(speech):
        show(image)
```

---

## The four refusals

Each one leads to a different fix, and none of them asks you to know anything
about how we are built.

```python
try:
    avatar = bithuman.open(source)
    for image in avatar.render(audio):
        show(image)
except bithuman.InvalidAvatar:
    ...   # fix the path or the code, or fetch the avatar again
except bithuman.NotSupported:
    ...   # use the cloud package, or another device
except bithuman.NotAuthorised:
    ...   # fix the credential
except bithuman.Failed:
    ...   # retry, then report it
```

Every one of them is an `AvatarError`, so `except bithuman.AvatarError` catches
all four.

---

## The key

Rendering is metered, and the key belongs in the environment rather than in
your code:

```bash
export BITHUMAN_API_SECRET=...
```

Without one, `render` refuses with `NotAuthorised` before it hands you a
frame. Get a key at <https://www.bithuman.ai/developer/api-keys>.

`python -m bithuman` also reads a `.env` file beside you, which is where a key
usually already is. What is already in the environment always wins.

---

## Where it runs

| | |
|---|---|
| Python | 3.10 – 3.14 |
| macOS | Apple silicon |
| Linux | x86-64 and arm64 |
| Windows, Intel Macs | not built — `pip install` refuses loudly rather than quietly giving you an old release |

`ffmpeg` is used to read an audio file when it is on your PATH; when it is
not, the decoder this package already installs reads the same file in this
process, so it is not something to install first.

Two environment variables:

| | |
|---|---|
| `BITHUMAN_API_SECRET` | your API secret, from https://www.bithuman.ai/developer/api-keys — rendering is metered, so it is required unless you pass `api_secret=`. `BITHUMAN_API_KEY` is read as a deprecated alias |
| `BITHUMAN_CACHE_DIR` | where a prepared avatar is kept (default `~/.cache/bithuman`) |

---

## This package never puts a command on your PATH

`pip install bithuman` installs a library and nothing else — and
`python -m bithuman` is why that costs you nothing: a module needs no script,
cannot collide with one, and is there the moment pip finishes. The full
`bithuman` command-line tool (a live avatar, a conversation) is a different
artifact and is **not** installed with pip:

```bash
curl -fsSL https://raw.githubusercontent.com/bithuman-product/homebrew-bithuman/main/install.sh | sh
brew install bithuman-product/bithuman/bithuman-cli      # macOS, equivalently
```

That is an invariant, not an accident: a pip-installed command named
`bithuman` would overwrite the one Homebrew put at the same path, and every
check would still report success. `tests/test_no_console_script.py` fails if a
release ever grows one — on **every** push and pull request (the source side,
with three firing controls) and again inside each publish job, run directly
against the wheels being uploaded. A directory that is declared and holds no
`bithuman` wheel exits **2**: a publish that cannot be graded is refused, not
passed.

---

## Using it from a LiveKit agent

The LiveKit integration is a separate package, `livekit-plugins-bithuman`,
published by LiveKit out of `github.com/livekit/agents`. **Install `pillow`
beside it:**

```bash
pip install livekit-plugins-bithuman pillow
```

That plugin imports `PIL.Image` at module scope and its published metadata does
not declare `pillow`, so installing it on its own ends at
`ModuleNotFoundError: No module named 'PIL'` the first time you import it. The
metadata is upstream's, not ours — this line is the whole fix, and
[the deploy guide](https://docs.bithuman.ai/guides/deploy-livekit) carries it
too.

On Python **3.10** and **3.14** there is a second one, and `pillow` alone does
not clear it. That plugin declares `bithuman` behind a
`python_version >= "3.11" and python_version < "3.14"` marker, so on those two
interpreters pip reports success and installs no `bithuman` at all — which takes
`cv2` with it, and the import dies there instead. This package publishes cp310
and cp314 wheels that install and import cleanly, so name it yourself:

```bash
pip install livekit-plugins-bithuman pillow bithuman     # Python 3.10 / 3.14
```

Both workarounds have an expiry: `pillow` and the dropped marker are already
merged upstream in
[livekit/agents#7280](https://github.com/livekit/agents/pull/7280) and are
waiting on a plugin release. A release after that commit needs neither word.

## Coming from 2.10.0?

3.0.0 is a clean break. Thirty-two names became eight, and fourteen error
classes became four.

| if you see | do this |
|---|---|
| `cannot import name 'AsyncBithuman'` (or `Bithuman`, `AudioChunk`, `VideoFrame`, `VideoControl`) | `bithuman.open(...)` and `avatar.render(audio)` replace all of them |
| `cannot import name 'Fixture'` (or `Runtime`, `EP_AUTO`, `ComposedFrame`) | same: they were the layer under `render`, and there is no layer to reach for now |
| `no module named 'bithuman.api'` (or `.models`, `.exceptions`, `.config`, `.bhci`) | the values they held are gone from the surface; the four refusals replace the error classes |
| a `DeprecationWarning` when you import the 2.x offline-render module | it still works until 4.0.0; the warning names the module and the class names to write instead (`bithuman.offline`, `OfflineRenderer`, `OfflineRenderError`) |
| `module 'bithuman' has no attribute '__version__'` | `importlib.metadata.version("bithuman")` |
| you install the 2.x extra for offline rendering | it still installs the same three packages until 4.0.0; the extra is now `bithuman[offline]` |
| your frames look blue | frames are RGB now, not BGR — `image[:, :, ::-1]` if you feed OpenCV |
| `except BithumanError` never fires | `except bithuman.AvatarError` |

Frames are still `(height, width, 3)` uint8 arrays, still 25 per second, still
in order.

2.10.0 is on PyPI forever and keeps resolving exactly as it does today. Pin
`bithuman<3` to stay on it.

---

## Licence

Proprietary — this package carries the runtime. See `LICENSE`.
