Skip to main content

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_card mirroring the PNG path.
  • charx (V3 zip) support — the V3 spec defines a .charx container (a zip with card.json plus 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)

Source distribution for tavern-card 0.1.1
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)

Table of built distributions (wheels) for tavern-card 0.1.1
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

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