Skip to content

Repository files navigation

longbox

Read and write CBZ comic archives with Anansi-schema ComicInfo.xml metadata. A longbox is the archival box comic collectors store issues in; this library is the archival format layer for digital comics.

Zero non-stdlib dependencies — just archive/zip and encoding/xml. Requires Go 1.21+.

go get github.com/ophymx/longbox

Writing a CBZ

Pages are added in reading order; that order is authoritative. Entries are named with zero-padded numerals (0001.jpg) so readers that sort by filename agree with the intended order — numeric input names like 1.jpg are normalized, not preserved. Images are stored uncompressed (JPEG/PNG don't deflate; recompressing just burns CPU), and ComicInfo.xml is written at the archive root with exactly that casing. AddPage rejects names without a recognized image extension — readers identify pages by extension, so such an entry would silently read back as junk instead of a page.

w := longbox.NewWriter(out)
w.SetComicInfo(&comicinfo.ComicInfo{
    Series: "Uchuu no Hako",
    Number: "1",
    Manga:  comicinfo.MangaYesAndRightToLeft, // RTL reading direction
})
for _, path := range pagePaths { // already in reading order
    f, _ := os.Open(path)
    if err := w.AddPage(filepath.Base(path), f); err != nil {
        return err
    }
    f.Close()
}
if err := w.Close(); err != nil {
    return err
}

Reading a CBZ

The reader tolerates archives from the wild: missing ComicInfo, junk entries (.DS_Store, __MACOSX/, Thumbs.db), pages nested in subdirectories, and unpadded numeric names (a natural sort puts 2.jpg before 10.jpg). Page access is random-access via the zip central directory — serving page N reads only page N, no unpacking.

a, err := longbox.Open("issue-001.cbz")
if err != nil {
    return err
}
defer a.Close()

if ci := a.ComicInfo(); ci != nil { // nil when the archive has none
    fmt.Println(ci.Series, ci.Number)
}
for i := 0; i < a.NumPages(); i++ {
    rc, err := a.Page(i) // reading order, random access
    ...
}

ComicInfo

The comicinfo package covers the full Anansi schema — v2.0 plus the v2.1 draft fields (Translator, Tags, StoryArcNumber, GTIN) — including the <Pages> table, creator roles, AgeRating, and Manga: YesAndRightToLeft for right-to-left reading direction. It can be used on its own:

ci, err := comicinfo.Read(r)       // parse ComicInfo.xml
err = ci.Validate()                // enum membership, field ranges
err = ci.Write(w)                  // UTF-8, no namespace on the root element

Marshaling follows the details strict consumers (Komga, Kavita, KOReader) require: no namespace on the root <ComicInfo> element, UTF-8 with an XML declaration, and enum values serialized with exact casing.

Out of scope

CBR/RAR, image processing, and other metadata formats (ComicBookInfo, CoMet, ACBF).

Testing

go test ./... runs round-trip tests (write → read yields identical page order, bytes, and metadata) and validates golden ComicInfo.xml fixtures against the vendored Anansi XSDs when xmllint is installed (skipped otherwise — XSD validation is test-only, never a runtime dependency). Regenerate goldens with go test ./comicinfo -update.

License

MIT

About

Go library for reading and writing CBZ comic archives with ComicInfo.xml metadata

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages