Skip to content

uxjulia/auto-epub-optimizer

Repository files navigation

Automated Baseline JPEG EPUB Optimizer Workflow

Drop EPUB into folder → Automatically gets optimized → File is moved to your library

Automatically (or manually) optimizes EPUB files for e-readers like the XTEINK X4. Converts images to baseline JPEG, applies grayscale, resizes to fit the display, and handles wide-image splitting with rotation. Cover images receive automatic contrast enhancement so light-colored covers (e.g. white text on a light background) render clearly on e-ink without any extra flags. The EPUB optimizer was forked from the built-in browser EPUB optimizer within Crosspoint. This repo just creates a standalone browser and node.js version that can be run via a shell script to automate the process.

Three ways to use it:

  1. Automatically with a watcher via Docker Compose or Systemd
  2. Manually via a browser-based GUI (browser/index.html), no install required.
  3. Manually by running the Node.js CLI (cli/optimize.js).

Why this exists

I have two e-readers: a Kindle Colorsoft and an XTEINK X4. I wanted a single drop folder where adding a book would automatically populate both devices' libraries: the original full-color EPUB going to Calibre for the Kindle, and a grayscale-optimized version landing in a separate library for the X4. That X4 library can be served by any OPDS server that doesn't require a Calibre database so the book shows up ready to read with no manual steps.

Features

  • Drop an .epub into one folder and have it appear in two separately managed libraries automatically
  • The original is copied to your Calibre watch folder and handled from there as normal
  • A grayscale-optimized copy is written to a separate X4 library, ready to serve via OPDS
  • Single-library mode also supported: leave CALIBRE_WATCH_FOLDER unset and only the optimized copy is produced

Usage

Note: The default settings are for the XTEINK X4 where the screen settings are 480x800. If you have a different device size, you need to change those settings in cli/optimize.js or in the browser when using the browser version.

Docker Compose

The repo includes a docker-compose.yml that runs a two-service pipeline without installing Node.js or inotify-tools on the host.

Setup

cp .env.example .env
# Edit .env - set BOOKDROP_DIR, WATCHER_DEST_DIR, and optionally CALIBRE_WATCH_FOLDER

The containers use fixed internal paths (/bookdrop, /output, /destination, /calibre). You only need to set the host-side paths in .env. EPUB_OUTPUT_DIR is handled internally via a shared Docker volume between the two services so you do not need to set it.

Why two services?

The optimizer writes finished EPUBs to an intermediate Docker volume (output), and the watcher moves them from there to WATCHER_DEST_DIR. This split exists because inotify is unreliable on Windows NTFS paths (e.g. /mnt/c/...) even inside Docker on WSL2 due to a kernel-level limitation. By keeping the handoff point on a pure Linux volume, the watcher can reliably detect new files and handle moving the file to a potential Windows-backed destination.

If your WATCHER_DEST_DIR is a plain Linux path (e.g. another Linux directory or a Linux-backed Docker bind mount), the two-service split is unnecessary. You can remove the epub-watcher service from docker-compose.yml and set the optimizer's EPUB_OUTPUT_DIR directly to your destination path.

Run

docker compose up -d

Logs

docker compose logs -f epub-optimizer
docker compose logs -f epub-watcher

Stop

docker compose down

Systemd Automated Watcher (Linux / WSL2)

The scripts/ folder contains two systemd user services that build a fully automated pipeline:

BOOKDROP_DIR  →[epub-optimizer]→  EPUB_OUTPUT_DIR  →[epub-watcher]→  WATCHER_DEST_DIR
  • epub-optimizer - polls a bookdrop folder for .epub files, runs optimize.js on each, and writes the result to an output folder. Uses polling instead of inotify so it works on Windows NTFS mounts (/mnt/c/) under WSL2.
  • epub-watcher - watches the output folder with inotifywait and moves finished files to a final destination (e.g. a Calibre/OPDS library folder).

Prerequisites

  • Node.js (run node --version to verify)
  • inotify-tools (the watcher installer will install this automatically if missing)
  • systemd user session enabled (standard on most modern Linux distros and WSL2 with systemd)

1. Configure

Copy the example config and fill in your paths:

mkdir -p ~/.config/epub-optimizer
cp .env.example ~/.config/epub-optimizer/.env

Edit ~/.config/epub-optimizer/.env:

Variable Description
BOOKDROP_DIR Drop .epub files here to trigger processing
CALIBRE_WATCH_FOLDER Optional - Calibre watch folder; files are copied here before optimization (for use when you want a separate workflow for Calibre)
OPTIMIZER_SCRIPT Absolute path to cli/optimize.js in this repo
EPUB_OUTPUT_DIR Where the optimizer writes finished EPUBs
WATCHER_DEST_DIR Where the watcher moves finished EPUBs (your final X4 library folder)
OPTIMIZER_LOG_FILE Log path for the optimizer service (default: ~/.local/log/epub-optimizer.log)
WATCHER_LOG_FILE Log path for the watcher service (default: ~/.local/log/epub-watcher.log)
POLL_INTERVAL Seconds between bookdrop scans (default: 5)
KEEP_DAYS Days to keep files in bookdrop/processed/ before auto-deletion (default: 5)
EPUB_NORMALIZE Optional - set to 1 to apply luminance normalization to interior images (see CLI: contrast options)
EPUB_CONTRAST Optional - set to a multiplier like 1.3 to boost contrast on interior images (see CLI: contrast options)

2. Install

Run the installers from the scripts/ directory. Install the watcher first! epub-optimizer.service has After=epub-watcher.service in its unit file, so systemd expects the watcher unit to exist before the optimizer is registered.

cd scripts

# Step 1: watcher (moves optimized files to their final destination)
./install-epub-watcher.sh

# Step 2: optimizer (polls bookdrop, runs optimize.js)
./install-epub-optimizer.sh

Each installer will:

  1. Check for dependencies
  2. Create the config file from .env.example if it doesn't exist yet
  3. Copy scripts to ~/.local/bin/
  4. Register and start the systemd user service

3. Use

Drop any .epub file into your BOOKDROP_DIR. The optimizer picks it up within POLL_INTERVAL seconds, processes it, and the watcher moves the result to WATCHER_DEST_DIR.

Inside BOOKDROP_DIR you'll find three subfolders that track state:

Subfolder Meaning
processing/ File is currently being optimized
processed/ Successfully optimized; auto-deleted after KEEP_DAYS
failed/ Optimizer returned an error, check the logs

Managing the services

# Status of both services
systemctl --user status epub-optimizer epub-watcher

# Follow live logs
journalctl --user -u epub-optimizer -f
journalctl --user -u epub-watcher -f

# Restart
systemctl --user restart epub-optimizer epub-watcher

# Stop
systemctl --user stop epub-optimizer epub-watcher

Browser (no install required)

Open browser/index.html directly in a browser. Everything runs locally, no files leave your machine.

  1. Drop one or more .epub files onto the drop zone (or click to select)
  2. Adjust settings if needed
  3. Click Optimize & Download

Settings

Setting Default Description
JPEG Quality 85% Compression quality for converted images
Max Width 480 px Images wider than this are resized
Max Height 800 px Images taller than this are resized
Split Mode None None / H-Split (rotate & split wide images) / V-Split (split tall images)
Overlap 5% Overlap between split halves
Rotation Clockwise Direction images are rotated before an H-Split
Grayscale On Convert images to grayscale

Cover contrast enhancement runs automatically in the browser version as well — no setting required.


CLI

Setup

cd cli
npm install

Usage

node optimize.js [options] <input.epub ...>
node optimize.js [options] <directory>

Options

Flag Default Description
-o, --output <dir> ./optimized Output directory
-q, --quality <n> 85 JPEG quality (1-100)
--no-grayscale - Disable grayscale conversion
-n, --normalize - Normalize luminance range on interior images
--contrast <n> - Contrast multiplier for interior images (e.g. 1.2, 1.5)
-W, --max-width <n> 480 Max image width in px
-H, --max-height <n> 800 Max image height in px
--split <mode> none Split mode: none, h-split, v-split
--overlap <n> 5 Overlap % for splits: 5/10/15/20/25
--rotation <cw|ccw> cw Rotation direction for H-Split
--suffix <str> -optimized Suffix appended to output filename
-v, --verbose - Print per-image details
--help - Show help

Cover contrast

Cover images are automatically enhanced for e-ink regardless of any flags. The optimizer detects the cover via the OPF manifest and applies a dedicated pipeline: grayscale → normalize → gamma correction. This pushes light-colored backgrounds (e.g. teal, beige, pale yellow) visibly darker while keeping white text at full brightness, preventing the washed-out look that occurs on low-contrast e-ink displays. No configuration is required.

Contrast options

--normalize and --contrast apply only to interior images — the cover is always handled separately as described above.

  • --normalize stretches the image's existing luminance range to fill 0–255. Best for scanned books or older EPUBs where the source images look flat because they were never encoded at full range (e.g. whites that top out at 220 instead of 255).
  • --contrast <n> applies a fixed linear boost pivoted around mid-gray. Best for images that are already full-range but still look soft on e-ink — values like 1.21.3 add punch without clipping.
  • Using both together first expands the range, then amplifies the separation. Use cautiously: values above 1.4 can crush shadow and highlight detail.

Examples

# Standard optimization (cover enhanced automatically)
node optimize.js book.epub

# Interior images that look flat from scanning
node optimize.js --normalize book.epub

# Interior images that need more punch on e-ink
node optimize.js --contrast 1.2 book.epub

# Both, for scanned books with soft interiors
node optimize.js --normalize --contrast 1.2 book.epub

# Custom output and display size
node optimize.js -q 80 -W 600 -H 900 --output ./out book.epub

Dependencies

  • jszip - EPUB read/write
  • sharp - Image processing

About

Workflow to automatically optimize epub files for use with devices like the XTEINK X4

Topics

Resources

Stars

Watchers

Forks

Contributors