All formats

py7zr

LanguagePython (3.10+)
LicenseLGPL-2.1-or-later
Repositorygithub.com/miurahr/py7zr
Documentationpy7zr.readthedocs.io
Depends onPython packages (pycryptodomex, pyppmd, pybcj, brotli, inflate64, multivolumefile; backports.zstd before Python 3.14)
Read / writeyes / yes (LZMA, LZMA2, BZip2, Deflate, Zstd, PPMd, Copy; AES encryption)

py7zr reads and writes 7z archives without the 7-Zip binary. SevenZipFile lists entries with getnames() or list(), extracts to disk with extract/extractall, and writes with write, writeall and writestr. It’s the library comicbox uses for CB7.

Version 1.0 removed the old read() and readall() methods. To get file contents in memory, pass a factory such as py7zr.io.BytesIOFactory to extract: py7zr writes each extracted file into an in-memory buffer you can read afterwards. After any extract call the archive object is at its end; call reset() before extracting again.

Installation#

Shell
pip install py7zr

Basic usage: list pages in natural order#

Python
import re
import sys
from pathlib import PurePosixPath

import py7zr

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".avif", ".jxl"}


def is_page(name: str) -> bool:
    name = name.replace("\\", "/")
    if "__MACOSX/" in name:
        return False
    p = PurePosixPath(name)
    return not p.name.startswith(".") and p.suffix.lower() in IMAGE_EXTS


def natural_key(name: str):
    return [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", name)]


def list_pages(z: py7zr.SevenZipFile) -> list[str]:
    names = [f.filename for f in z.list() if not f.is_directory and is_page(f.filename)]
    return sorted(names, key=natural_key)


with py7zr.SevenZipFile(sys.argv[1]) as z:
    print("solid:", z.archiveinfo().solid)
    for n, name in enumerate(list_pages(z), 1):
        print(f"{n:3} {name}")

How to read pages into memory#

BytesIOFactory(limit) collects each extracted file in a buffer of at most limit bytes. Seek to the start before reading:

Python
from py7zr.io import BytesIOFactory

MAX_PAGE = 100 * 1024 * 1024  # bytes; larger files are truncated


def read_files(path: str, names: list[str]) -> dict[str, bytes]:
    factory = BytesIOFactory(MAX_PAGE)
    with py7zr.SevenZipFile(path) as z:
        z.extract(targets=names, factory=factory)
    out = {}
    for name in names:
        buf = factory.get(name)
        buf.seek(0)
        out[name] = buf.read()
    return out

Extract several pages in one call: in a solid archive every extract decodes the block from the start, so one call with all the pages you need is much faster than one call per page.

How to extract the cover#

Python
with py7zr.SevenZipFile("comic.cb7") as z:
    pages = list_pages(z)
if pages:
    data = read_files("comic.cb7", [pages[0]])[pages[0]]
    with open("cover" + PurePosixPath(pages[0]).suffix.lower(), "wb") as f:
        f.write(data)

How to read ComicInfo.xml#

Python
import xml.etree.ElementTree as ET

with py7zr.SevenZipFile("comic.cb7") as z:
    names = z.getnames()
    pages = list_pages(z)
info_name = next((n for n in names if n.lower() == "comicinfo.xml"), None)
if info_name:
    root = ET.fromstring(read_files("comic.cb7", [info_name])[info_name])
    print(root.findtext("Series"), "#" + (root.findtext("Number") or ""), root.findtext("Title"))
    for page in root.iterfind("Pages/Page"):
        if "FrontCover" in page.get("Type", ""):
            print("cover:", pages[int(page.get("Image"))])  # index in sorted page order

How to create a CB7 from a folder of images#

Use the Copy filter (no compression) for JPEG/PNG pages. py7zr writes all files into one solid block, so with real compression every page read would decode the whole archive; with Copy that costs almost nothing.

Python
from pathlib import Path

import py7zr


def make_cb7(folder: str, out: str) -> None:
    files = sorted((p for p in Path(folder).iterdir() if p.is_file() and is_page(p.name)),
                   key=lambda p: natural_key(p.name))
    with py7zr.SevenZipFile(out, "w", filters=[{"id": py7zr.FILTER_COPY}]) as z:
        for i, p in enumerate(files, 1):
            z.write(p, arcname=f"{i:03}{p.suffix.lower()}")
        info = Path(folder) / "ComicInfo.xml"
        if info.exists():
            z.write(info, arcname="ComicInfo.xml")


make_cb7("Akira v01", "Akira v01.cb7")

For uncompressed source images (BMP, TIFF) use filters=[{"id": py7zr.FILTER_LZMA2, "preset": 9}] instead. z.writestr(data, "ComicInfo.xml") adds a file from a string or bytes.

Tips#