Ilya Shaplyko 463ff3eae5 fix(metadata): ensure EPUB version-aware metadata writing (#2998)
* fix(metadata): ensure EPUB version-aware metadata writing

EpubMetadataWriter unconditionally wrote EPUB3-only constructs into
all EPUB files regardless of version, producing invalid OPF documents
for EPUB3 files (e.g. opf:file-as/opf:role attributes on dc:creator)
and writing EPUB3-only elements into EPUB2 files.

Changes:
- createCreatorElement: EPUB3 uses <meta refines="#id"> for file-as/role;
  EPUB2 uses opf: attributes on dc:creator
- addFolderContentsToZip: mimetype is now STORED (uncompressed) and
  written as the first ZIP entry per EPUB spec
- replaceBelongsToCollection: EPUB3 uses belongs-to-collection with
  refines; EPUB2 uses calibre:series/calibre:series_index convention
- addSubtitleToTitle: EPUB3 uses separate dc:title with title-type
  refinement; EPUB2 stores subtitle via booklore:subtitle metadata only
- addBookloreMetadata/createBookloreMetaElement: EPUB3 uses property
  attribute with prefix; EPUB2 uses name/content attribute form
- removeAllBookloreMetadata: now handles both EPUB3 property and EPUB2
  name attributes
- cleanupCalibreArtifacts: preserves calibre:series and
  calibre:series_index metas used for EPUB2 series
- organizeMetadataElements: correctly categorizes EPUB2-style series
  and booklore metas into their respective buckets
- addBookloreMetadata: writes booklore:subtitle for round-trip fidelity
- Added isEpub3() helper method

Closes #2997

* fix(metadata): address review feedback for EPUB version-aware writing

- Only preserve calibre:series/calibre:series_index for EPUB2 in
  cleanupCalibreArtifacts; EPUB3 files now properly remove stale entries
- Use isEpub3() helper in createCreatorElement instead of inline detection
- Add trim() to isEpub3() to handle whitespace in version attribute
- Log warning when mimetype file is missing from extracted EPUB
2026-03-04 14:37:58 -07:00
2026-01-19 08:44:53 -07:00
2025-08-05 15:23:21 -06:00

BookLore

Your books deserve a home. This is it.

BookLore is a self-hosted app that brings your entire book collection under one roof.
Organize, read, annotate, sync across devices, and share, all without relying on third-party services.

Release License Docker Pulls Stars Discord Open Collective Translate

🌐 Website · 📖 Docs · 🎮 Demo · 🚀 Quick Start · 💬 Discord

BookLore Demo


Features

Feature Description
📚 Smart Shelves Custom and dynamic shelves that organize themselves with rule-based Magic Shelves, filters, and full-text search
🔍 Automatic Metadata Covers, descriptions, reviews, and ratings pulled from Google Books, Open Library, and Amazon, all editable
📖 Built-in Reader Open PDFs, EPUBs, and comics right in the browser with annotations, highlights, and reading progress
🔄 Device Sync Connect your Kobo, use any OPDS-compatible app, or sync progress with KOReader. Your library follows you everywhere
👥 Multi-User Ready Individual shelves, progress, and preferences per user with local or OIDC authentication
📥 BookDrop Drop files into a watched folder and BookLore detects, enriches, and queues them for import automatically
📧 One-Click Sharing Send any book to a Kindle, an email address, or a friend instantly

🚀 Quick Start

Tip

Looking for OIDC setup, advanced config, or upgrade guides? See the full documentation.

All you need is Docker and Docker Compose.

📦 Image Repositories
Registry Image
Docker Hub booklore/booklore
GitHub Container Registry ghcr.io/booklore-app/booklore

Legacy images at ghcr.io/adityachandelgit/booklore-app remain available but won't receive updates.

Step 1: Environment Configuration

Create a .env file:

# Application
APP_USER_ID=1000
APP_GROUP_ID=1000
TZ=Etc/UTC

# Database
DATABASE_URL=jdbc:mariadb://mariadb:3306/booklore
DB_USER=booklore
DB_PASSWORD=ChangeMe_BookLoreApp_2025!

# Storage: LOCAL (default) or NETWORK (for NFS/SMB, disables file reorganization)
DISK_TYPE=LOCAL

# MariaDB
DB_USER_ID=1000
DB_GROUP_ID=1000
MYSQL_ROOT_PASSWORD=ChangeMe_MariaDBRoot_2025!
MYSQL_DATABASE=booklore

Step 2: Docker Compose

Create a docker-compose.yml:

services:
  booklore:
    image: booklore/booklore:latest
    # Alternative: ghcr.io/booklore-app/booklore:latest
    container_name: booklore
    environment:
      - USER_ID=${APP_USER_ID}
      - GROUP_ID=${APP_GROUP_ID}
      - TZ=${TZ}
      - DATABASE_URL=${DATABASE_URL}
      - DATABASE_USERNAME=${DB_USER}
      - DATABASE_PASSWORD=${DB_PASSWORD}
    depends_on:
      mariadb:
        condition: service_healthy
    ports:
      - "6060:6060"
    volumes:
      - ./data:/app/data
      - ./books:/books
      - ./bookdrop:/bookdrop
    healthcheck:
      test: wget -q -O - http://localhost:6060/api/v1/healthcheck
      interval: 60s
      retries: 5
      start_period: 60s
      timeout: 10s
    restart: unless-stopped

  mariadb:
    image: lscr.io/linuxserver/mariadb:11.4.5
    container_name: mariadb
    environment:
      - PUID=${DB_USER_ID}
      - PGID=${DB_GROUP_ID}
      - TZ=${TZ}
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - ./mariadb/config:/config
    restart: unless-stopped
    healthcheck:
      test: [ "CMD", "mariadb-admin", "ping", "-h", "localhost" ]
      interval: 5s
      timeout: 5s
      retries: 10

Step 3: Launch

docker compose up -d

Open http://localhost:6060, create your admin account, and start building your library.


🎮 Live Demo

See BookLore in action before deploying your own instance.

🌐 URL demo.booklore.org
👤 Username booklore
🔑 Password 9HC20PGGfitvWaZ1

Note

This is a standard user account. Admin features like library creation, user management, and system settings are only available on your own instance.


📥 BookDrop: Zero-Effort Import

Drop book files into a folder. BookLore picks them up, pulls metadata, and queues everything for your review.

graph LR
    A[📁 Drop Files] --> B[🔍 Auto-Detect]
    B --> C[📊 Extract Metadata]
    C --> D[✅ Review & Import]
Step What Happens
1. Watch BookLore monitors the BookDrop folder around the clock
2. Detect New files are picked up and parsed automatically
3. Enrich Metadata is fetched from Google Books and Open Library
4. Import You review, tweak if needed, and add to your library

Mount the volume in docker-compose.yml:

volumes:
  - ./bookdrop:/bookdrop

🤝 Community & Support

🐞 Something not working? Report a Bug
💡 Got an idea? Request a Feature
🛠️ Want to help build? Contributing Guide
💬 Come hang out Discord Server

Warning

Before opening a PR: Open an issue first and get maintainer approval. PRs without a linked issue, without screenshots/video proof, or without pasted test output will be closed. All code must follow project backend and frontend conventions. AI-assisted contributions are welcome, but you must run, test, and understand every line you submit. See the Contributing Guide for full details.


💜 Support BookLore

BookLore is free, open source, and built with care. Here's how you can give back:

Action How
Star this repo It's the simplest way to help others find BookLore
💰 Sponsor development Open Collective funds hosting, testing, and new features
📢 Tell someone Share BookLore with a friend, a subreddit, or your local book club

Important

We're raising funds for a Kobo device to build and test native Kobo sync support. Contribute to the Kobo Bounty →


🌍 Translations

BookLore is used by readers around the world. Help make it accessible in your language on Weblate.

Translation status

📊 Project Analytics

Repository Activity

Star History

Star History Chart

👥 Contributors

Contributors

Every contribution matters. See how you can help →


🌟 Sponsors & Partners

Run on PikaPods

PikaPods

ElfHosted

ElfHosted

JetBrains

JetBrains

Want your logo here? Become a sponsor →


⚖️ License

GNU Affero General Public License v3.0

Copyright 20242026 BookLore

License: AGPL v3

Description
No description provided
Readme AGPL-3.0 70 MiB
Languages
Java 58.7%
TypeScript 22.2%
HTML 9.5%
SCSS 7.1%
JavaScript 2.3%