Skip to content

lncrawl/ebook-convert-api

Repository files navigation

ebook-convert-api

codecov Lint Docker build Dependabot Updates

A minimal Docker HTTP API that wraps Calibre's ebook-convert pipeline. Upload a file, get the converted ebook back. Uses the official Calibre Linux binary — no source build required.

Quick start

docker run -p 8000:8000 --cpus=2 --memory=3g ghcr.io/lncrawl/ebook-convert-api:latest

Convert an EPUB to MOBI:

curl -s \
  -F "file=@book.epub" \
  -F "output_format=mobi" \
  http://localhost:8000/convert \
  --output book.mobi

With conversion options (each option is its own form field):

curl -s \
  -F "file=@book.epub" \
  -F "output_format=epub" \
  -F "base_font_size=12" \
  -F "margin_top=36" \
  -F "embed_all_fonts=true" \
  http://localhost:8000/convert \
  --output book-restyled.epub

API

POST /convert

Converts a file. Blocks until done, then streams the result and cleans up.

Requestmultipart/form-data:

Field Type Required Description
file upload yes Input ebook. Format detected from filename extension.
output_format enum yes Target format: epub, mobi, azw3, pdf, ...
option fields varies no One typed field per Calibre option — see Conversion options.

Response — binary file with Content-Disposition: attachment.

Status Meaning
200 Converted file
400 Conversion failed — body contains Calibre error detail
413 Upload exceeds MAX_UPLOAD_MB
422 Invalid output_format or an option with the wrong type
503 All workers busy — retry after a moment
504 Conversion timed out

GET /formats

Returns the full list of supported input and output formats.

{
  "input_formats": ["azw", "epub", "fb2", "mobi", "pdf", ...],
  "output_formats": ["azw3", "epub", "mobi", "pdf", ...]
}

GET /formats/{in_fmt}/{out_fmt}/options

Returns every Calibre option valid for that conversion, grouped by category (pre-generated at image build time) — useful for building UIs or discovering option names. Each group has a group label and a list of option metadata (name, cli_flag, help, type, default, choices). The type is one of str, int, float, bool, choice, or file (an upload — see Conversion options). The groups are: Input (input-format options), the shared categories (Look & Feel, Structure Detection, Table of Contents, Heuristic Processing, Search & Replace, Metadata, General), and Output (output-format options). Server-only options (see the security note) are never included.

[
  {
    "group": "Look & Feel",
    "options": [
      {
        "name": "base_font_size",
        "cli_flag": "--base-font-size",
        "help": "The base font size in pts...",
        "type": "float",
        "default": 0,
        "choices": null
      }
    ]
  }
]

Responds 404 if either format is not supported.

GET /health

{
  "status": "ok",
  "calibre_version": "9.9.0",
  "max_concurrent_jobs": 2
}

GET /ready

Lightweight readiness probe used by Docker and Kubernetes health checks.


Conversion options

Every Calibre conversion option is exposed as its own typed form field on POST /convert, so the interactive docs at /docs render number/boolean inputs, enum dropdowns, and per-option help. All option fields are optional — omit a field and Calibre's own default applies.

curl -s \
  -F "file=@book.epub" \
  -F "output_format=epub" \
  -F "base_font_size=12" \
  -F "embed_all_fonts=true" \
  -F "epub_version=3" \
  http://localhost:8000/convert \
  --output book.epub

The form advertises the union of options across all formats. Only the options valid for the chosen input → output pair are applied; any others you send are ignored. To discover the exact set, types, defaults, and allowed choices for a given pair, call GET /formats/{in}/{out}/options.

File-path options

A few Calibre options take a file rather than a scalar value (type: "file" in the options metadata): cover, search_replace, transform_css_rules, transform_html_rules, and read_metadata_from_opf. Send these as additional file fields — the server stores the upload for the job and passes its path to Calibre. A raw server-side path is never accepted (sending one as a text value yields 422).

curl -s \
  -F "file=@book.epub" \
  -F "output_format=epub" \
  -F "cover=@cover.jpg" \
  http://localhost:8000/convert \
  --output book.epub

Security: Server-only flags are never exposed and are ignored if sent: the unsafe output-directory flags --debug-pipeline and --extract-to, and Calibre's Debug group (--verbose). The file-path options above are the only way to reference a file, and only via upload — the server never reads an arbitrary host path.


Configuration

Environment variables (all optional):

Variable Default Description
MAX_CONCURRENT_JOBS 2 Worker count (ProcessPoolExecutor size).
CONVERSION_TIMEOUT_SECONDS 300 Per-job timeout. 504 returned on breach.
MAX_UPLOAD_MB 100 Upload size limit. 413 returned on breach.
USE_AUTH false Enable auth middleware stub (not yet implemented).

Concurrency

MAX_CONCURRENT_JOBS sets the number of worker processes directly. Size it to the container's CPU allocation and the memory each ebook-convert job needs (the PDF path, via headless QtWebEngine, is the heaviest at ~350–550 MB per job). For example docker run --cpus=4 --memory=3g comfortably runs MAX_CONCURRENT_JOBS=4.


Building

Install uv and poethepoet, then:

uv sync

The poe task runner wraps all common workflows:

# Run production container (builds the image; the option catalog is generated during that build)
poe up

# Run dev container (live-reloads on local file changes via bind-mount)
poe dev
poe dev-exec          # open a shell in the running dev container

# Lint / format / typecheck
poe check

# Tests (integration tests hit a live server at API_BASE_URL, default http://localhost:8000;
# the whole session is skipped if it's unreachable — so run `poe dev`/`poe up` first)
poe test

# Regenerate the option catalog (data/catalog.json)
poe catalog           # uses local Calibre
poe catalog-dev       # uses the dev image's pinned Calibre (run while/after `poe dev`)

# Dependency management
uv add <pkg>          # add a dependency
uv lock --upgrade     # upgrade all deps, regenerate uv.lock

Development

The app always runs inside Docker. poe dev bind-mounts ./app into the container and starts uvicorn with --reload, so local edits trigger an automatic server restart without rebuilding the image.

poe dev
# edit any file under app/ → server reloads automatically

Add a dependency:

uv add <package>

Regenerate the lockfile after manual pyproject.toml edits:

uv lock

License

Apache 2.0 — see LICENSE.

About

Dockerized HTTP API that wraps Calibre's ebook conversion pipeline with maximum throughput in minimum resource footprint.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors