tavern-card
Zero-dependency parser/writer for SillyTavern character cards — V1/V2/V3 JSON plus PNG tEXt/zTXt embedding, with built-in protection against decompression bombs.
- No dependencies. Pure standard library (
base64,json,struct,zlib). - Every card shape → one canonical V2 dict. Flat V1, nested V2/V3, JSON, base64, or a PNG image.
- Round-trips through PNG. Read a card out of a PNG, write one back in.
- Hardened. Explicit size ceilings on file, chunk, and decompressed zTXt output.
Install
pip install tavern-card
Requires Python 3.10+.
Quickstart
Read a card embedded in a PNG:
from tavern_card import parse_png_card
with open("character.png", "rb") as f:
card = parse_png_card(f.read())
print(card["data"]["name"])
print(card["spec"]) # "chara_card_v2" or "chara_card_v3"
Write a card into a PNG:
from tavern_card import parse_card, write_png_card
card = parse_card({
"spec": "chara_card_v2",
"data": {"name": "Nyx", "description": "A wandering star."},
})
# Uses a 1x1 transparent base; pass template_png=<bytes> to embed into your own image.
png_bytes = write_png_card(card)
with open("nyx.png", "wb") as f:
f.write(png_bytes)
Normalize any JSON shape to V2:
from tavern_card import parse_card
# Flat V1, nested V2/V3, a JSON string, base64, or raw bytes — all accepted.
card = parse_card({"name": "Bob", "char_persona": "A tired knight."})
assert card["spec"] == "chara_card_v1"
assert card["data"]["name"] == "Bob"
assert card["data"]["description"] == "A tired knight."
API
| Function | Signature | Description |
|---|---|---|
parse_card |
parse_card(data: dict | str | bytes) -> dict |
Normalize a dict, JSON string, base64 string, or raw bytes into the canonical V2 dict. Unwraps common {"card": {...}} / {"character": {...}} envelopes. |
parse_png_card |
parse_png_card(blob: bytes) -> dict |
Extract and parse the card embedded in a PNG's tEXt/zTXt chunks (ccv3 preferred over chara). |
write_png_card |
write_png_card(v2_card: dict, template_png: bytes | None = None) -> bytes |
Embed a V2 card into a PNG chara tEXt chunk, optionally using your own PNG as the base image. |
Every function returns (or accepts) the same three-layer V2 shape:
{
"spec": "chara_card_v2", # or "chara_card_v3" / "chara_card_v1"
"spec_version": "2.0",
"data": {
"name": str, "description": str, "personality": str, "scenario": str,
"first_mes": str, "mes_example": str, "creator_notes": str,
"system_prompt": str, "post_history_instructions": str,
"alternate_greetings": list, "tags": list, "creator": str,
"character_version": str, "extensions": dict, "character_book": Any,
},
}
Command line
Installs a tavern-card console script (pure argparse):
# Read a PNG or JSON card and print pretty JSON to stdout.
tavern-card inspect character.png
# Convert between .png and .json (direction inferred from file extensions).
tavern-card convert character.png character.json
tavern-card convert character.json character.png --template base.png
PNG format
SillyTavern stores the character-card JSON — base64-encoded — inside a PNG text chunk. tavern-card reads and writes exactly that layout:
| Aspect | Behavior |
|---|---|
| Chunk keys | ccv3 (V3) is tried first, then chara (V1/V2). |
| Chunk types | tEXt (uncompressed) and zTXt (zlib-deflated) are both read; writes always use tEXt. |
| Payload | Base64-encoded UTF-8 JSON; raw JSON is also accepted on read. |
| Insertion | On write, the chara chunk is inserted immediately before IEND. |
Safety limits
Parsing untrusted images is bounded to defeat memory-exhaustion and zlib-bomb attacks:
| Limit | Value | Rationale |
|---|---|---|
| Whole PNG file | 10 MB | A single card image cannot exhaust the process. |
| Single chunk length | 8 MB | A declared chunk length is rejected before allocation. |
| zTXt decompressed | 4 MB | Streaming decompression aborts the instant output crosses the ceiling, so a tiny compressed payload can't inflate into gigabytes. |
Exceeding any limit raises ValueError rather than allocating.
Roadmap / Contributing
Contributions welcome. Two good first issues:
- WebP support — SillyTavern also ships cards embedded in WebP; add
parse_webp_card/write_webp_cardmirroring the PNG path. - charx (V3 zip) support — the V3 spec defines a
.charxcontainer (a zip withcard.jsonplus assets); add read/write for it.
Please keep the library dependency-free and preserve the existing parsing behavior (there is a test suite covering round-trips and the safety limits).
License
MIT — see LICENSE.
Acknowledgements
Extracted from the RPG Roleplay platform (https://github.com/felixchaos/rpg-roleplay-platform).
Metadata
Release files for tavern-card 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| tavern_card-0.1.1.tar.gz | 14.0 kB | Without provenance | Details for tavern_card-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tavern_card-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for tavern_card-0.1.1-py3-none-any.whl |
Total release size: 24.9 kB
Release files / tavern_card-0.1.1.tar.gz
| Download URL | tavern_card-0.1.1.tar.gz |
|---|---|
| Size | 14.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
23abeb6fcded68de800bb19c214520378e7b961c191f22371cf1263952700408
|
|
BLAKE2b-256 checksum How to use checksums |
618e5dec12b07c1d780b62bd96250a591386bbbe155f8f36da928b13d03acd47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / tavern_card-0.1.1-py3-none-any.whl
| Download URL | tavern_card-0.1.1-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6b910eaa947fca4440b736136ac38ba2a77d2d72aa1e6ab621afc7937305b6d1
|
|
BLAKE2b-256 checksum How to use checksums |
deccd681f0f966d900385a4720d8c2b2286abe6a24ac0e6dfa8ac56f7b10510c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Security for tavern-card 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.