08 Apr 2022

# Read e-books from your Calibre library inside Emacs

I read many books in electronic format. I manage my e-book library with the great Calibre software. For non fiction books (science, philosophy, productivity, computer science, etc.) I like to read on my laptop, so I can easily take notes, or actually generate Zettels. Since I create new zettels with org-capture, reading inside Emacs is very handy. For EPUB format, I use the excellent nov.el and for PDF files I use pdf-tools. Both of these packages provide org links, so I don't have to do anything special to capture notes and have a back-link to the point in the e-book where the note was taken from. This is standard org-capture.

I also use org links as bookmarks between reading sessions. Yes, that is bookmarks as marks in the book, like real, paper books.

The part in my reading workflow where Emacs wasn't used was managing the e-book library. Usually, when I got a new book, I would open Calibre, add the book, fetch missing meta-data and cover if needed and then close Calibre. After that, I would use calibre-mode.el to call calibre-find to search for the book and insert an org link into my reading list.

I have been aware of calibredb.el for a couple of years (or maybe a little less), but I had not used it very much. After a recent update of my packages, I had a look at it again and I discovered that it provides all the features I use in the Calibre GUI, so I have started to use it to add books and curate meta-data and covers.

calibredb has a function to open a book from the list: calibredb-open-file-with-default-tool which in my case runs ebook-viewer for EPUB and evince for PDF. I did not find an option or a variable to change that, so I looked at the code of the function:

(defun calibredb-open-file-with-default-tool (arg &optional candidate)
"Open file with the system default tool.
If the universal prefix ARG is used, ignore calibredb-preferred-format'.
Optional argument CANDIDATE is the selected item."
(interactive "P")
(unless candidate
(setq candidate (car (calibredb-find-candidate-at-point))))
(if arg
(let ((calibredb-preferred-format nil))
(calibredb-open-with-default-tool (calibredb-get-file-path candidate t)))
(calibredb-open-with-default-tool (calibredb-get-file-path candidate t))))


This function calls calibredb-open-with-default-tool, which looks like this:

(defun calibredb-open-with-default-tool (filepath)
"TODO: consolidate default-opener with dispatcher.
Argument FILEPATH is the file path."
(cond ((eq system-type 'gnu/linux)
(call-process "xdg-open" nil 0 nil (expand-file-name filepath)))
((eq system-type 'windows-nt)
(start-process "shell-process" "*Messages*"
"cmd.exe" "/c" (expand-file-name filepath)))
((eq system-type 'darwin)
(start-process "shell-process" "*Messages*"
"open" (expand-file-name filepath)))
(t (message "unknown system!?"))))


So in GNU/Linux, it calls xdg-open, which delegates to the appropriate applications. But of course, I want to use Emacs.

I could use xdg-settings to change the behavior, but that would mean using emacsclient which seems weird given that I am already inside Emacs (actually, I use EXWM, so it is still weirder). Furthermore, since my Emacs is configured to open EPUB files with nov.el and PDF with pdf-view, I only need to call find-file on the selected file.

So I just adapted calibredb-open-file-with-default-tool and bound it to the same key as the original function:

  (defun my/calibredb-open-file-with-emacs (&optional candidate)
"Open file with Emacs.
Optional argument CANDIDATE is the selected item."
(interactive "P")
(unless candidate
(setq candidate (car (calibredb-find-candidate-at-point))))
(find-file (calibredb-get-file-path candidate t)))
(define-key calibredb-search-mode-map "V" #'my/calibredb-open-file-with-emacs)
`

This is yet another demonstration of the power of Emacs: packages that were not designed to work together can be combined exactly as you want. You don't need to be a skilled programmer to do that.

That's user freedom, the real meaning of software freedom.