ePUB to reMarkable Converter

March 31, 2025 ยท View on GitHub

This is a web application to convert epubs into a PDF compatible with the reMarkable Paper Pro and Boox Air 4C.

This project has been heavily influenced by AI tooling of claude.ai.

Caution

Due to the software architecture and the handling of temporary files and downloads I strongly recommend not to use it as a public webservice.

Screenshots

Upload interface

Upload interface

Custom parameters

Custom parameters

Conversion progress reporting

Conversion progress reporting

Finished conversion

Finished conversion

Environment variables

Here's a list of all environment variables that can be used to configure the application:

VariableDescriptionDefault Value
DEBUG_MODEEnable debug mode for more verbose loggingfalse
TEMP_DIRDirectory for temporary files/tmp
JOB_TIMEOUTTime (in seconds) that conversion results remain available after completion300
GUNICORN_TIMEOUTTimeout for the Gunicorn worker (in seconds)300
VIRTUAL_HOSTHostname for Nginx proxy-
LETSENCRYPT_HOSTHostname for Let's Encrypt SSL-
LETSENCRYPT_EMAILEmail address for Let's Encrypt notifications-
reMarkable Profile
REMARKABLE_INPUT_PROFILEInput profile for reMarkable Paper Prodefault
REMARKABLE_OUTPUT_PROFILEOutput profile for reMarkable Paper Progeneric_eink_hd
REMARKABLE_BASE_FONT_SIZEBase font size for reMarkable Paper Pro12
REMARKABLE_DEFAULT_FONT_SIZEDefault font size for reMarkable Paper Pro18
REMARKABLE_MONO_FONT_SIZEMonospace font size for reMarkable Paper Pro16
REMARKABLE_CUSTOM_SIZECustom size (width x height) for reMarkable Paper Pro1620x2160
REMARKABLE_UNITUnit for custom size for reMarkable Paper Propixel
REMARKABLE_PDF_SANS_FAMILYSans font family for reMarkable Paper ProIBM Plex Sans
REMARKABLE_PDF_SERIF_FAMILYSerif font family for reMarkable Paper ProIBM Plex Serif
REMARKABLE_PDF_MONO_FAMILYMonospace font family for reMarkable Paper ProIBM Plex Mono
REMARKABLE_PDF_STANDARD_FONTStandard font for reMarkable Paper Proserif
REMARKABLE_PDF_PAGE_MARGIN_LEFTLeft page margin for reMarkable Paper Pro72
REMARKABLE_PDF_PAGE_MARGIN_RIGHTRight page margin for reMarkable Paper Pro20
REMARKABLE_PDF_PAGE_MARGIN_TOPTop page margin for reMarkable Paper Pro20
REMARKABLE_PDF_PAGE_MARGIN_BOTTOMBottom page margin for reMarkable Paper Pro20
REMARKABLE_EMBED_ALL_FONTSEmbed all fonts in PDF for reMarkable Paper Protrue
REMARKABLE_SUBSET_EMBEDDED_FONTSSubset embedded fonts for reMarkable Paper Protrue
REMARKABLE_UNSMARTEN_PUNCTUATIONSimplify punctuation for reMarkable Paper Protrue
REMARKABLE_PRESERVE_COVER_ASPECT_RATIOPreserve cover aspect ratio for reMarkable Paper Protrue
REMARKABLE_CHANGE_JUSTIFICATIONText justification for reMarkable Paper Projustify
Boox Air 4C Profile
BOOX_AIR_4C_INPUT_PROFILEInput profile for Boox Air 4Cdefault
BOOX_AIR_4C_OUTPUT_PROFILEOutput profile for Boox Air 4Cgeneric_eink_hd
BOOX_AIR_4C_BASE_FONT_SIZEBase font size for Boox Air 4C12
BOOX_AIR_4C_DEFAULT_FONT_SIZEDefault font size for Boox Air 4C16
BOOX_AIR_4C_MONO_FONT_SIZEMonospace font size for Boox Air 4C14
BOOX_AIR_4C_CUSTOM_SIZECustom size (width x height) for Boox Air 4C1860x2480
BOOX_AIR_4C_UNITUnit for custom size for Boox Air 4Cpixel
BOOX_AIR_4C_PDF_SANS_FAMILYSans font family for Boox Air 4CIBM Plex Sans
BOOX_AIR_4C_PDF_SERIF_FAMILYSerif font family for Boox Air 4CIBM Plex Serif
BOOX_AIR_4C_PDF_MONO_FAMILYMonospace font family for Boox Air 4CIBM Plex Mono
BOOX_AIR_4C_PDF_STANDARD_FONTStandard font for Boox Air 4Cserif
BOOX_AIR_4C_PDF_PAGE_MARGIN_LEFTLeft page margin for Boox Air 4C72
BOOX_AIR_4C_PDF_PAGE_MARGIN_RIGHTRight page margin for Boox Air 4C20
BOOX_AIR_4C_PDF_PAGE_MARGIN_TOPTop page margin for Boox Air 4C20
BOOX_AIR_4C_PDF_PAGE_MARGIN_BOTTOMBottom page margin for Boox Air 4C20
BOOX_AIR_4C_EMBED_ALL_FONTSEmbed all fonts in PDF for Boox Air 4Ctrue
BOOX_AIR_4C_SUBSET_EMBEDDED_FONTSSubset embedded fonts for Boox Air 4Ctrue
BOOX_AIR_4C_UNSMARTEN_PUNCTUATIONSimplify punctuation for Boox Air 4Ctrue
BOOX_AIR_4C_PRESERVE_COVER_ASPECT_RATIOPreserve cover aspect ratio for Boox Air 4Ctrue
BOOX_AIR_4C_CHANGE_JUSTIFICATIONText justification for Boox Air 4Cjustify

The application can be configured using these environment variables in the .env file or directly in the docker-compose.yml.

REST API

This document describes the REST API for the eBook to PDF converter. The API allows you to convert EPUB files to PDF programmatically, check conversion status, and download the converted files.

API Endpoints

Health Check

GET /api/v1/health

Check if the API and Calibre services are running correctly.

Response:

{
  "status": "operational",
  "api_version": "1.0.0",
  "calibre": {
    "status": "available",
    "version": "6.4.0"
  }
}

Get Device Profiles

GET /api/v1/device_profiles

Retrieve all available device profiles and their parameters.

Response:

{
  "reMarkable": {
    "input_profile": "default",
    "output_profile": "generic_eink_hd",
    "base_font_size": "12",
    ...
  },
  "boox_air_4c": {
    "input_profile": "default",
    "output_profile": "generic_eink_hd",
    "base_font_size": "12",
    ...
  }
}

Convert EPUB to PDF

POST /api/v1/convert

Convert an EPUB file to PDF. This is an asynchronous operation that returns a job ID.

Request: multipart/form-data

ParameterRequiredDescription
epub_fileYesThe EPUB file to convert
device_profileNoDevice profile to use (reMarkable, boox_air_4c, or custom)

If using a custom profile, you can include any or all of the following parameters:

  • input_profile
  • output_profile
  • base_font_size
  • default_font_size
  • mono_font_size
  • embed_all_fonts (true/false)
  • subset_embedded_fonts (true/false)
  • unsmarten_punctuation (true/false)
  • custom_size
  • unit
  • pdf_sans_family
  • pdf_serif_family
  • pdf_mono_family
  • pdf_standard_font
  • pdf_page_margin_left
  • pdf_page_margin_right
  • pdf_page_margin_top
  • pdf_page_margin_bottom
  • preserve_cover_aspect_ratio (true/false)
  • change_justification

Response:

{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status_url": "http://example.com/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/status",
  "download_url": "http://example.com/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download",
  "status": "processing"
}

Check Conversion Status

GET /api/v1/jobs/{job_id}/status

Check the status of a conversion job.

Parameters:

ParameterTypeDescription
job_idstringThe job ID returned from the convert endpoint

Response:

{
  "status": "running",
  "progress": 45,
  "message": "Converting page 45/100",
  "logs": [
    "Starting conversion...",
    "Converting page 44/100",
    "Converting page 45/100"
  ]
}

When the conversion is completed:

{
  "status": "completed",
  "progress": 100,
  "message": "Conversion completed successfully",
  "download_url": "http://example.com/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download"
}

If the conversion fails:

{
  "status": "failed",
  "progress": 67,
  "message": "Conversion failed with code 1",
  "logs": [
    "Error: Unable to convert page 67",
    "Process terminated abnormally"
  ]
}

Download Converted PDF

GET /api/v1/jobs/{job_id}/download

Download the converted PDF file.

Parameters:

ParameterTypeDescription
job_idstringThe job ID returned from the convert endpoint

Response:

The PDF file as a binary stream with Content-Type: application/pdf.

Example Usage

Using cURL

# Health check
curl -X GET http://example.com/api/v1/health

# Get device profiles
curl -X GET http://example.com/api/v1/device_profiles

# Convert EPUB to PDF
curl -X POST http://example.com/api/v1/convert \
  -F "epub_file=@/path/to/book.epub" \
  -F "device_profile=reMarkable"

# Check conversion status
curl -X GET http://example.com/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/status

# Download converted PDF
curl -X GET http://example.com/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download \
  -o converted.pdf

Using Python

import requests

# Health check
response = requests.get("http://example.com/api/v1/health")
print(response.json())

# Convert EPUB to PDF
with open('/path/to/book.epub', 'rb') as f:
    files = {'epub_file': f}
    data = {'device_profile': 'reMarkable'}
    response = requests.post("http://example.com/api/v1/convert", files=files, data=data)

job_data = response.json()
job_id = job_data['job_id']
print(f"Job ID: {job_id}")

# Check conversion status
status_url = job_data['status_url']
status_response = requests.get(status_url)
print(status_response.json())

# Download converted PDF when ready
if status_response.json()['status'] == 'completed':
    download_url = status_response.json()['download_url']
    pdf_response = requests.get(download_url)
    
    with open('converted.pdf', 'wb') as f:
        f.write(pdf_response.content)
    print("PDF downloaded successfully")

Error Codes

HTTP StatusDescription
200Success
202Accepted (for conversion requests)
400Bad Request (missing file or invalid parameters)
404Not Found (job ID not found)
500Server Error