Skip to main content

A parser for CBML (Comic Book Markup Language)

Project description

cbml-parser

A Python library for parsing CBML (Comic Book Markup Language) documents into structured data, ready for use in comic generation pipelines or any other downstream tooling.


What is CBML?

CBML is a plain-text format for authoring comic books by hand. It defines page layouts, panel compositions, characters, locations, dialogue, and caption boxes in a lightweight, human-readable syntax. See the CBML Standard for the full language specification.

A CBML document looks like this:

## The Signal at Pier 9
author: A. Reyes
genre: mystery, coastal noir

PAGE preset:grid-2x2

PANEL A
loc: pier9_night_fog
chars: ELARA
shot: extreme wide, tiny figure on long pier, fog everywhere
mood: isolated, eerie
> Elara walks the length of Pier 9 alone. The fog is thicker than usual tonight.
[caption bg:#0a0f14 color:#a0c8d8 pos:top-left] 2:17 AM.

PANEL B
loc: pier9_night_fog
chars: ELARA
shot: medium shot, stopping mid-step
mood: the moment before something changes
> She stops. She heard something.

PANEL C
loc: lighthouse_radio_room
chars: ELARA
shot: closeup on old radio dial, needle moving
mood: impossible, wrong in the best way
> The dial reads the frequency of the Maren Clare. The ship that sank in 1983.

PANEL D
loc: lighthouse_radio_room
chars: ELARA
shot: closeup, receiver to her ear
> She picks up the receiver with both hands.
ELARA: ~Say something. Please say something.~

Installation

pip install cbml-parser

Quick Start

from cbml_parser import CBMLParser

parser = CBMLParser()
comic = parser.parse_file("my_comic.cbml")

Or parse from a string directly:

cbml_text = """
## My Comic
author: Jane Doe

PAGE preset:splash

PANEL A
loc: dark_forest
> A lone figure stands between the trees.
"""

comic = parser.parse_string(cbml_text)

Worked Example

Given this CBML input:

## Ghost Signal
aspect: 2:3
author: T. Navarro
genre: cyberpunk thriller

PAGE grid:3x2
  A: [1-2, 1]
  B: [3, 1-2]
  C: [1, 2]
  D: [2, 2]

PANEL A
loc: neon_alley_rain
chars: KAI
shot: wide, low angle, running toward camera
mood: desperate, urgent
> Kai sprints through the neon-lit alley, puddles exploding underfoot
[caption bg:#0d0d1a color:#ff4444 pos:top-left] Three minutes behind her. And closing.

PANEL B
loc: neon_alley_rain
chars: AGENT_CROSS
shot: closeup face, rain on visor
mood: cold, relentless
> Agent Cross raises one fist — a silent signal

PANEL C
loc: neon_alley_rain
chars: KAI
shot: over-the-shoulder, glancing back
> Kai looks back — blue strobes, closer than before

PANEL D
loc: neon_alley_rain
chars: KAI
shot: extreme closeup, eyes wide
> Her eyes: calculating, terrified, alive
KAI: ~Left. Always go left. Ghost taught me that.~

The parser produces the following structure:

Comic(
    title="Ghost Signal",
    metadata={
        "author": "T. Navarro",
        "genre": "cyberpunk thriller"
    },
    aspect=(2, 3),
    pages=[
        Page(
            index=0,
            layout=CustomGrid(cols=3, rows=2),
            slots={
                "A": Slot(cols=(1,2), rows=(1,1)),
                "B": Slot(cols=(3,3), rows=(1,2)),
                "C": Slot(cols=(1,1), rows=(2,2)),
                "D": Slot(cols=(2,2), rows=(2,2))
            },
            panels=[
                Panel(
                    label="A",
                    slot=Slot(cols=(1,2), rows=(1,1)),
                    loc="neon_alley_rain",
                    chars=["KAI"],
                    shot="wide, low angle, running toward camera",
                    mood="desperate, urgent",
                    action="Kai sprints through the neon-lit alley, puddles exploding underfoot",
                    dialogue=[],
                    captions=[
                        Caption(
                            text="Three minutes behind her. And closing.",
                            bg="#0d0d1a",
                            color="#ff4444",
                            pos="top-left"
                        )
                    ]
                ),
                Panel(
                    label="B",
                    slot=Slot(cols=(3,3), rows=(1,2)),
                    loc="neon_alley_rain",
                    chars=["AGENT_CROSS"],
                    shot="closeup face, rain on visor",
                    mood="cold, relentless",
                    action="Agent Cross raises one fist — a silent signal",
                    dialogue=[],
                    captions=[]
                ),
                Panel(
                    label="C",
                    slot=Slot(cols=(1,1), rows=(2,2)),
                    loc="neon_alley_rain",
                    chars=["KAI"],
                    shot="over-the-shoulder, glancing back",
                    mood=None,
                    action="Kai looks back — blue strobes, closer than before",
                    dialogue=[],
                    captions=[]
                ),
                Panel(
                    label="D",
                    slot=Slot(cols=(2,2), rows=(2,2)),
                    loc="neon_alley_rain",
                    chars=["KAI"],
                    shot="extreme closeup, eyes wide",
                    mood=None,
                    action="Her eyes: calculating, terrified, alive",
                    dialogue=[
                        DialogueLine(
                            character="KAI",
                            text="Left. Always go left. Ghost taught me that.",
                            bubble_type="thought"
                        )
                    ],
                    captions=[]
                )
            ]
        )
    ]
)

Validation

The parser validates documents against the CBML standard and reports errors and warnings:

from cbml_parser import CBMLParser, CBMLValidationError

parser = CBMLParser()

try:
    comic = parser.parse_file("my_comic.cbml")
except CBMLValidationError as e:
    print(e.errors)    # list of blocking errors
    print(e.warnings)  # list of non-blocking warnings

Validation can also be run separately without full parsing:

result = parser.validate_file("my_comic.cbml")
print(result.valid)
print(result.errors)
print(result.warnings)

Passing Known Resources

To enable validation of character and location identifiers against known uploads, pass resource manifests to the parser:

comic = parser.parse_file(
    "my_comic.cbml",
    known_characters=["KAI", "AGENT_CROSS", "NOVA"],
    known_locations=["neon_alley_rain", "construction_site_night"]
)

Unrecognised identifiers will generate warnings but will not block parsing.


Manga / Right-to-Left Support

CBML documents are always written in western (left-to-right) reading order. To produce a manga-style comic, write your script forward as normal and pass manga=True at parse time:

comic = parser.parse_file("my_manga.cbml", manga=True)

The parser will automatically:

  • Reverse page order — your last page becomes the reader's first page
  • Mirror panel positions — panels flip horizontally within each page grid
  • Mirror caption positions — e.g. top-left becomes top-right

This keeps the .cbml source readable as a linear narrative while producing the correct physical layout for right-to-left reading.

You can also apply the transformation to an already-parsed comic:

western_comic = parser.parse_file("my_comic.cbml")
manga_comic = CBMLParser.make_manga(western_comic)

See the CBML Standard §12 for full details on the reading-order conventions.


Documentation


License

MIT


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

cbml_parser-1.1.0.tar.gz (22.1 kB view details)

Uploaded Source

Built Distribution

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

cbml_parser-1.1.0-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file cbml_parser-1.1.0.tar.gz.

File metadata

  • Download URL: cbml_parser-1.1.0.tar.gz
  • Upload date:
  • Size: 22.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cbml_parser-1.1.0.tar.gz
Algorithm Hash digest
SHA256 272fc3c89826ab1ea3cd76aab43efaa3ab338f2e6b6b9e13244bdf524e43a638
MD5 f03f64206aa02837c2102ff705ac7a98
BLAKE2b-256 410c9368e286830b083d4e7ca26733e64bd28909f7a81c883f61b6a8d7c3c120

See more details on using hashes here.

Provenance

The following attestation bundles were made for cbml_parser-1.1.0.tar.gz:

Publisher: publish.yml on AdventuresInDataScience/cbml_parser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cbml_parser-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: cbml_parser-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cbml_parser-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c3224b49876c6b50b137e374b2a372e093a7534a68cdef43a717c6540250231
MD5 b79878fc7b61fc7692641d7a74cde6bf
BLAKE2b-256 9133cf54441e361fdc17e76994eb8d0a40fd4dd77f3c80e86636637a7fdf79c3

See more details on using hashes here.

Provenance

The following attestation bundles were made for cbml_parser-1.1.0-py3-none-any.whl:

Publisher: publish.yml on AdventuresInDataScience/cbml_parser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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