python-djvulibre
| Language | Python 3 (Cython extension) |
|---|---|
| License | GPL-2.0 |
| Repository | github.com/FriedrichFroebel/python-djvulibre |
| Documentation | djvulibre-python.readthedocs.io |
| Depends on | DjVuLibre 3.5.26 or newer, pkg-config and a C compiler to build |
| Read / write | yes / save whole documents or page subsets (no page encoding) |
python-djvulibre exposes the DjVuLibre ddjvuapi to Python as two modules: djvu.decode (documents, pages, rendering, messages) and djvu.sexpr (the s-expressions DjVuLibre uses for text, outline and annotations). It’s used by ocrodjvu and other DjVu tools.
The original project by Jakub Wilk is archived; the maintained fork is published on PyPI as djvulibre-python (the import name is still djvu). The fork supports Python 3 only and doesn’t build on Windows. For Windows or a non-GPL license, use djvu-rs.
Installation#
Install DjVuLibre with its headers first, then the package:
# Debian / Ubuntu
sudo apt install libdjvulibre-dev pkg-config
# macOS
brew install djvulibre pkg-config
pip install djvulibre-python
Basic usage: open a document and render a page#
Context runs DjVuLibre’s decoding threads. wait() blocks until a job is done. The example renders page 1 to 24-bit RGB and saves it with Pillow:
import djvu.decode
from PIL import Image
ctx = djvu.decode.Context()
doc = ctx.new_document(djvu.decode.FileURI("book.djvu"))
doc.decoding_job.wait()
print(len(doc.pages), "pages")
page = doc.pages[0] # 0-based
job = page.decode(wait=True)
width, height = job.size
print(width, height, job.dpi, "dpi")
fmt = djvu.decode.PixelFormatRgb()
fmt.rows_top_to_bottom = 1 # the default is bottom row first
rect = (0, 0, width, height)
data = job.render(djvu.decode.RENDER_COLOR, rect, rect, fmt)
Image.frombytes("RGB", (width, height), data).save("page1.png")
How to render a page at a smaller size#
The page is scaled to the size of the first rectangle; the second rectangle selects the part to return:
dpi = 100
w = width * dpi // job.dpi
h = height * dpi // job.dpi
data = job.render(djvu.decode.RENDER_COLOR, (0, 0, w, h), (0, 0, w, h), fmt)
Image.frombytes("RGB", (w, h), data).save("page1-small.png")
Other modes: RENDER_BLACK (black-and-white mask only, falls back to color), RENDER_BACKGROUND, RENDER_FOREGROUND.
How to extract text from a DjVu file#
TEXT_DETAILS_PAGE returns one zone per page: (page x0 y0 x1 y1 "text"). The text is the 6th element:
import djvu.decode
ctx = djvu.decode.Context()
doc = ctx.new_document(djvu.decode.FileURI("book.djvu"))
doc.decoding_job.wait()
for i, page in enumerate(doc.pages):
text = djvu.decode.PageText(page, djvu.decode.TEXT_DETAILS_PAGE)
text.wait()
sexpr = text.sexpr
if len(sexpr) > 5:
print(f"--- page {i + 1} ---")
print(sexpr[5].value)
Use TEXT_DETAILS_WORD or TEXT_DETAILS_LINE to get nested zones: each zone is (type x0 y0 x1 y1 children-or-text...), with coordinates in pixels from the bottom-left corner.
How to read the table of contents#
outline = doc.outline
outline.wait()
print(outline.sexpr) # (bookmarks ("Chapter 1" "#5" ...) ...)
How to read metadata#
DjVu metadata (author, title, year) lives in the annotations:
annotations = doc.annotations
annotations.wait()
for key, value in annotations.metadata.items():
print(key, value)
How to save some pages to a new file#
with open("part.djvu", "wb") as f:
doc.save(f, pages=[0, 1, 2]) # waits until the file is written
Tips#
- Subclass
djvu.decode.Contextand overridehandle_messageto seeErrorMessageobjects; otherwise decoding errors only show up as failed jobs. page.get_info()reads the page size from theINFOchunk without decoding the image; then usepage.width,page.heightandpage.dpi.doc.pages[i].text.sexprreturns the most detailed text zones.