Skip to main content

Zero-dependency parser/writer for SillyTavern character cards (V1/V2/V3 JSON + PNG tEXt/zTXt embedding)

Project description

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).

Project details


Download files

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

Source Distribution

tavern_card-0.1.1.tar.gz (14.0 kB view details)

Uploaded Source

Built Distribution

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

tavern_card-0.1.1-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file tavern_card-0.1.1.tar.gz.

File metadata

  • Download URL: tavern_card-0.1.1.tar.gz
  • Upload date:
  • Size: 14.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for tavern_card-0.1.1.tar.gz
Algorithm Hash digest
SHA256 23abeb6fcded68de800bb19c214520378e7b961c191f22371cf1263952700408
MD5 9131135bfdf5829ab3ed92121abf9eaa
BLAKE2b-256 618e5dec12b07c1d780b62bd96250a591386bbbe155f8f36da928b13d03acd47

See more details on using hashes here.

File details

Details for the file tavern_card-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: tavern_card-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 10.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for tavern_card-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6b910eaa947fca4440b736136ac38ba2a77d2d72aa1e6ab621afc7937305b6d1
MD5 2541af3365817e5ebae3b7e0be458ab3
BLAKE2b-256 deccd681f0f966d900385a4720d8c2b2286abe6a24ac0e6dfa8ac56f7b10510c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page