Releases: markbyrne/libris
Release list
v0.4.1 — Config endpoint for path auto-detection
Adds authenticated GET /api/v1/config to the directive API, returning {incoming_dir, state_db, review_dir, version} so Librarr's new Detect paths button can auto-fill its Libris plugin settings instead of the user hand-typing paths from another machine.
Same fail-closed X-Api-Key auth as the rest of the API. 530 tests green, CI green on Python 3.10/3.11/3.12.
🤖 Generated with Claude Code
v0.4.0 — Directive API
Directive API
External tools (e.g. Librarr) can now pre-register the correct match for an incoming file, so Libris imports it with exact, already-verified metadata instead of re-matching from scratch.
POST /api/v1/directives— register a match (title, author, ISBN, year, cover, series) keyed on the incoming filename. API-key auth (X-Api-Key), disabled by default (api.enabled+api.api_keyin config).GET /api/v1/ping— connectivity/auth check.- The watcher applies a matching directive at pickup and skips all metadata lookups (Google Books / Open Library / DuckDuckGo); files without a directive are matched exactly as before.
- Directives are one-shot (consumed on use) and purged after 48h if the file never arrives.
- Structured double-dash filenames remain the zero-config fallback path.
Everything downstream — duplicate detection, format conversion, tagging, calibredb import — is unchanged.
v0.3.17b0
Bug fixes
-
revert-import broken on split-library setups —
_export_from_book_fileshad its owncalibredb listcall that always returned empty formats for relocated books. Now falls back to readingmetadata.dbdirectly, the same pattern used elsewhere in the codebase. -
Duplicate not caught when series prefix differs —
_find_calibre_duplicatesused exact-title search only, so importing "The Merchant of Death" would create a duplicate alongside an existing "Pendragon: The Merchant of Death". A secondary contains-mode search now runs when the primary returns empty, catching mismatches in both directions.
Feature
- Richer Anna's Archive filename parsing — the
title -- author -- year -- publisher -- md5convention is now fully parsed: author initials (D_ J_→D.J.), narrator split from author field, ISBN fromisbn13/isbn10fields, series name + ordinal fromSeries_ Book N, Title, and attribution strings (Anna's Archive, etc.) consumed silently. All fields are wired into the metadata lookup, yielding near-perfect confidence scores for AA filenames.
v0.3.16b0
What's new
Cover placeholder rejection (#71)
Two-layer defence against junk cover images being saved to the Calibre library:
Layer 1 — OpenLibrary ?default=false
OpenLibrary's cover CDN serves a full-size "image not available" placeholder JPEG with HTTP 200 when a cover is missing. Content inspection cannot reliably distinguish it from a real cover. Adding ?default=false to all OL cover URLs converts these into 404 responses, which are already rejected.
Layer 2 — _download_cover validation
Even when a URL returns HTTP 200 with image/*, the body is validated before saving:
- Content-type must start with
image/(rejects HTML error pages, JSON, etc.) - Body must be ≥ 1024 bytes (rejects blank GIF stubs)
- Pixel dimensions must be ≥ 100px in both axes (rejects 1×1 tracking pixels); unknown formats (WebP, etc.) fail open
The pixel sniffer parses PNG IHDR, GIF header, and JPEG SOF0/SOF2 segments from the first ~500 bytes — no imaging library required.
New tests
12 tests in tests/unit/test_cover_validation.py covering _image_dimensions(), _download_cover() validation paths, and OL URL ?default=false on both cover_i and ISBN fallback URLs.
Full diff: v0.3.15b0...v0.3.16b0
v0.3.15b0
What's new
Shadow-library filename convention (#70)
Files named {title} -- {author} -- {year} -- {publisher} -- {md5}.ext (Anna's Archive et al) are now parsed as structured fields: title and author drive the search directly, the year becomes a scoring hint, and the publisher and content hash are discarded as query noise. Previously these filenames produced garbage queries with no author hint.
Also: trailing md5/sha1/sha256 content hashes are stripped from any filename, and stray -- separators collapse like single dashes.
Full Changelog: v0.3.14b0...v0.3.15b0
v0.3.14b0
Fixed
- set_cover failures are now surfaced (#68) —
get-coverspreviously reported ✓ even when calibredb could not write the cover (e.g. permission errors); it now prints ✗ with a hint, counts failures, and exits non-zero - Cover relocation, rename sync, and split-mode export no longer silently no-op for relocated books (#69) —
_get_format_pathsfalls back to readingbooks.path+datafrom metadata.db whencalibredb listreports no formats (the same split-mode blindness fixed forlist_booksin v0.3.13b0, one layer deeper). Production symptom: covers written to the metadata.db side never moved to the book-files side, breaking calibre-web thumbnail generation.
Full Changelog: v0.3.13b0...v0.3.14b0
v0.3.13b0
Fixed
Split-library mode libraries were invisible to get-covers and clean-library (#67). calibredb list --fields formats only reports a format when the file exists under the metadata.db directory — true for no properly-relocated book in split mode, so 84 of 90 books in the reporting library were silently skipped. list_books now falls back to reading books.path + data directly from metadata.db (read-only) when calibredb reports no formats.
Full Changelog: v0.3.12b0...v0.3.13b0
v0.3.12b0
What's new
Covers (#66)
- The matched cover is now always saved as
cover.jpgin the book's library directory on import (viacalibredb set_cover, split-library aware) — previously gated behindoutput.embed_cover_art output.embed_cover_artnow does exactly what its name says: embed the art inside the audio file as album art (it was never actually passed to the tagger before)- New
libris get-coverscommand backfillscover.jpgfor books already in the library: uses the cover URL recorded at import time, falls back to a fresh Google Books / OpenLibrary lookup by title/author;--dry-runto preview
Full Changelog: v0.3.11b0...v0.3.12b0
v0.3.11b0
What's new
Fixed
libris --versioncrashed for PyPI installs — it still looked up the oldlibrisdistribution name after thepylibrisrename (#65)- Standalone
install.shruns no longer install a stale hardcoded git tag (v0.2.0-beta) — remote installs now pull the latest release from PyPI (#64) install.shlocal-mode detection updated for thepylibrisrename (#64)
Added
- Donation links: Ko-fi and GitHub Sponsors — repo Sponsor button, README Support section,
--helpepilog, installer finale (#65)
Full Changelog: v0.3.10b0...v0.3.11b0
v0.3.10b0 — first public release
First public release 🎉
Libris is now open source (MIT) and published on PyPI as pylibris:
pip install pylibris # installs the libris commandWhat's new since v0.3.9b0
calibre-web coexistence (#62)
New calibre.reconnect_url config — libris pings calibre-web's /reconnect endpoint after every import and removal so calibre-web reopens its database connection instead of holding a stale view across external calibredb writes (the mechanism behind database disk image is malformed). Best-effort: failures never affect a completed import. See the new Coexisting with calibre-web README section for the full corruption-prevention guide.
Packaging
- Distribution renamed to
pylibris(libriswas taken on PyPI); import package and CLI are unchanged __version__now derives from package metadata- CI publishes to PyPI via trusted publishing on every GitHub release
Full Changelog: v0.3.9b0...v0.3.10b0