
An Android app (Kotlin + Jetpack Compose) that scans bookshelf photos, identifies book spines using OCR / AI, enriches results via the Google Books API, and maintains a local library with loan tracking, ratings, and cover images.
A settings strip sits between the camera preview and action buttons. Language, Layout, and Engine are persisted to DataStore and restored across sessions.
Auto — Detects language from OCR output (Hebrew character ratio > 30%).
English — Forces English for all scanned books in this session.
עברית — Forces Hebrew for all scanned books in this session.
Dropdown describing how books are arranged in the photo. The selection is injected as a plain‑English sentence into the AI prompts.
Vertical spines (default) — Books upright, spines facing forward. Text rotated 90°. Best engine: Any.
Horizontal spines — Books lying flat in stacks; text runs along the narrow edge. Best engine: Full‑image AI.
Front covers — Camera faces front covers. Best engine: Any.
Mixed — Combination of orientations. Best engine: Full‑image AI.
Layouts marked “Full‑image AI” set preferFullImage = true. If the current engine is a two‑stage or on‑device mode, a red warning appears below the strip.
Dropdown listing all OcrMode entries with name and one‑line description. Changes apply immediately — no Save button — via OcrPreferencesRepository.
The engine switches automatically when language or layout changes:
Language → English — Switch to the configured English default (default: GPT Vision).
Language → Hebrew — Switch to the configured Hebrew default (default: Gemini Vision).
Layout → Horizontal or Mixed — If current engine is not a full‑image AI, switch to one.
Configure defaults in Settings → Scan Defaults.
When a layout benefits from a full‑image AI (preferFullImage = true) but the selected engine is not one (isFullImageAi = false):
Full-image AI engine recommended for <layout name>
The engine dropdown border turns red.
1st choice: Gemini 2.0 Flash Vision — free (200/day). Best for Hebrew; handles RTL, rotated spines, mixed shelves.
High volume: Gemini 1.5 Flash Vision — free (1,500/day). Lower Hebrew accuracy than 2.0, but 7.5× more daily calls.
On-device only: Tesseract — free. Fully offline; lower accuracy on rotated spine text.
Not recommended for Hebrew: GPT‑4o (Latin‑script bias). Use Gemini for any shelf with Hebrew books.
Avoid for Hebrew: MLKIT_ONLY, TWO_STAGE_MLKIT (ML Kit is trained on Latin scripts). Also avoid any two‑stage mode — spine detection crops tend to misalign on RTL spines.
Vision prompts (GPT‑4o and Gemini) are intentionally conservative:
• Report only text visible in the image — do not infer or complete titles from memory.
• If an author is not printed on the spine, the author field is left blank.
• Ambiguous partial titles are returned as‑is; the Google Books enrichment resolves them.
• API errors (429, 404, auth, 503) surface as a dismissible red banner on the Scan Result screen. Each engine reports errors independently — results from one engine still show if the other fails.
Every book can have a cover photo. Images are stored as JPEGs in filesDir/covers/ so they persist across restarts.
Setting a cover — Scan Result / Add‑Edit: preview box in the form; tap Gallery or Camera; tap × to remove.
From Book detail: Change / Add cover (gallery), Camera to shoot, or Remove (deletes local file).
The three cover buttons use compact labels so they fit one row on all widths.
Automatic covers: During Google Books enrichment, thumbnail URLs are stored in imagePath and displayed via Coil. You can replace them any time with a local photo.
Export and import the full library — books, authors, ratings, read status, loans, and cover images — as a ZIP file.
Backup format
library_backup_YYYYMMDD.zip
├── backup.json — all books + authors (cover paths are relative: "covers/<name>")
└── covers/ — every local cover image
Remote cover URLs (from Google Books) remain as URLs in JSON — not downloaded.
Export — Settings → Backup & Restore → Export:
Save to file: open system file picker (Downloads, Drive, USB, …).
Share: build the ZIP in cache and open the share sheet.
Import — Settings → Backup & Restore → Import:
Replace: wipe current library and restore backup verbatim (author IDs preserved).
Merge: add books not already present (duplicates by normalised title + author are skipped). New authors inserted; existing matched by name. Returns a count of books added.
Note: loanedContactUri values may not resolve on a different device/account; the loan name/date are still restored.
Export — Settings → CSV Export / Import → Export CSV. Creates an RFC 4180‑compliant CSV of all books and opens the Android share sheet.
Export columns
Title — Book title; Subtitle — Subtitle; Author — Author name; ISBN — ISBN‑13 or ISBN‑10; DanaCode — Israeli 9‑digit code; Publisher — Publisher; Series — Series name; Volume — Volume number; Format — paperback/hardcover/ebook/audiobook/other; Genre — Category; Year — Publication year; Pages — Page count; Language — en/he/other; Rating — 1–5 (blank = unrated); Read — true/false; Description — Back‑cover text; Translators — Comma‑separated; Illustrators — Comma‑separated; Editors — Comma‑separated; Narrators — Comma‑separated; Tags — Semicolon‑separated; Notes — Personal notes; Price — Text with currency symbol; WebLink — Google Books info link or manual URL.
Import — Settings → CSV Export / Import → Import CSV. Handles quoted fields, embedded newlines, and escaped quotes. Column names are case‑insensitive; unknown columns are ignored.
Supported header name variations
Title — Title, Book Title, Name
Author — Author, Authors, Primary Author, By
ISBN — ISBN, ISBN10, ISBN13, ISBN-10, ISBN-13
Format — Format, Binding, Book Format, Type
Genre — Genre, Category, Bookshelves, Shelves
Year — Year, Year Published, Original Publication Year
Pages — Pages, Page Count, Num Pages, Number of Pages
Rating — Rating, Stars, My Rating
Read — Read, Already Read, Finished, Date Read
Description — Description, Synopsis, Summary, Blurb
Notes — Notes, My Review, Private Notes
Translators — Translators, Translated By
Illustrators — Illustrators, Illustrated By, Additional Authors
Editors — Editors, Edited By
Narrators — Narrators, Narrated By, Read By
Tags — Tags (semicolon‑separated)
Language — Language, Lang, Language Code
Publisher — Publisher, Published By
Series — Series, Series Name
Goodreads compatibility
Goodreads export maps to: Title → title; Author → author; ISBN / ISBN13 → isbn (leading '=' and quotes stripped); My Rating → stars (0 → unrated); Publisher → publisher; Binding → format; Number of Pages → pageCount; Original Publication Year → year; Exclusive Shelf → alreadyRead (read → true); My Review → notes; Additional Authors → illustrators.
For books that didn’t match Google Books (e.g. Hebrew spine text, partial titles), run enrichment in bulk.
Open: tap the sparkle icon (✨) in the Library top bar.
Workflow: shows count with no cover → tap Search Now → queries Google Books per book with progress → results listed (new info first) → review and select per book → Save N to apply.
Updates when new data exists: cover image, title, subtitle, author, ISBN, publisher, series, volume, genre, year, page count, description, web link. Existing user‑added local covers are not overwritten.
Open: tap the duplicate pages icon (⧉) in the Library top bar.
Detection: duplicates = same title AND author (case‑insensitive, trimmed).
Auto‑selection: in each group, all but the “best” copy are marked DELETE. Best = most metadata filled (cover, genre, year, rating, link, read flag); ties go to the lowest DB ID (oldest).
Workflow: list groups with header (title + author + count) → each row shows KEEP/DELETE and metadata → toggle any row → Delete N confirms (irreversible; red confirm button).
Header: top bar shows “My Library” with current count ($N books). Action icons (Search, ISBN Scan, Find Book Details, Find Duplicates, Add Book, Settings) appear on the line below, right‑aligned.
Status chips
All — every book.
Read — alreadyRead = true.
Unread — alreadyRead = false.
★ Favorites — books with a star rating.
Loaned — currently loaned out (loanedTo IS NOT NULL), ordered by loan date desc.
Tag chips: when any book has a tag, a #tag row appears. Tap to filter; tap again to clear. One tag active at a time.
Publisher chips: when any book has a publisher, a publisher row appears. Tap to filter; tap again to clear. Stacks with status, tag, and search filters; names sorted A→Z.
Add/Edit Book: TagEditor shows current tags as removable chips; type to add or pick from suggestions; tap a chip to remove.
Batch tag (Scan Result): add a tag to all books saved in the session; applied on Confirm.
Book detail: tags shown as chips above Read/Loaned toggles.
Filtering: #tag chip row appears when at least one tag exists.
Database: tags table + book_tags junction (composite PK bookId+tagId, CASCADE both ways); unused tags pruned automatically.
Genre in Add/Edit is a dropdown populated live from distinct genres in the library. Options:
(None) — clears; any existing genre — selects; Add new genre… — opens dialog. The list updates reactively when genres change elsewhere.
How it works: enter a 10‑ or 13‑digit ISBN and tap Look Up, or use the live barcode scanner (CameraX + ML Kit: EAN‑13 / EAN‑8 / Code‑128). A visual viewfinder helps aiming; detected ISBN is returned automatically. Manual entry also supported.
On success: title, author, genre, year, page count, cover URL, web link, and language are filled.
Language detection: first by Google Books language (he/iw → Hebrew; en → English); fallback by Hebrew‑letter ratio (> 30% across title + author).
DanaCode detection: detecting a 9‑digit DanaCode stores it and switches to Text mode, instructing to scan the copyright page; the DanaCode is saved for future lookup.
Scanner screen: Barcode mode (default) and Text mode; duplicate barcode callbacks are guarded; manual field works in both; DanaCode is passed back with the ISBN.
Identification
Title (required); Subtitle; ISBN; DanaCode.
Publication
Author; Publisher; Genre (dropdown); Publication year; Series; Volume; Format; Page count; Language (auto‑detected with manual override).
Content
Description; Illustrators; Translators; Editors; Narrators.
Personal
Rating (1–5, half‑star steps, null = unrated); Read (alreadyRead); Tags; Notes; Price; Quotes (with page refs).
Media
Cover image (local JPEG in covers/ or remote URL); Web link.
Tracking
Added date; Loaned to; Loan date; Loan history (never deleted).
OCR scan (Shelf) → Google Books enrichment; ISBN lookup → Google Books fills all; Bulk enrichment → fills missing covers/metadata; Manual entry → every field editable; DanaCode flow → detect DanaCode → OCR copyright page → ISBN → lookup; Loan history → logged on clear.
Keep books to find, read, or buy in a private wish list separate from the library.
Open: tap the bookmark icon (🔖) in the Library top bar.
Add: + Add Book opens the same Add/Edit form (pre‑configured for wish list) — type by hand, paste a link, look up by ISBN, or scan a barcode.
Move to library: open the detail screen and tap Add to Library in the blue banner.
Sorting: chips for Title, Author, Year, or Date Added with asc/desc toggle; author sorts by last name (keeps particles like “van”, “de”, “Le”, “von”). Unknown years/dates go last when ascending.
Implementation: stored in the same books table with isWishlist = 1; main library filters exclude them.