I read a lot, and I read a lot across different devices. Usually, I have at least a book open on my Kindle, and one on
my mobile phone. I also like to highlight passages that make me laugh, or think, or that touch me in some way. I collect
some of those quotes in a
fortune(6) file for further usage: Put it on login shells, share it with friends, use it as
a tongue-in-cheek Patreon reward.
The Kindle just drops your highlights into a plain text file with a very readable structure, which is very pleasant to work with. It turned out that my Android reader app, FBReader isn't nearly so cooperative, so this post documents how to extract the data from an unrooted phone.
First off, I should mention a few things: FBReader actually does support sending your bookmarks (which is what they call highlights) from your phone – if you use their cloud sync. The sync also includes all your books and your reading positions, by way of Google Drive.
This is not an option for me: I'm very private about what I read, and when, and what I highlight or comment on in those books, and I don't want to share this information with anybody. It's very personal, and doesn't belong in the hands of anybody out there. Yes, that means that my Kindle, the little snitch that it wants to be, runs exclusively in offline mode since the day I got it.
That doesn't imply that I don't want to talk about books – I share what I read here on my blog as well as on GoodReads. But those are places where I decide who sees my opinion, and my choices, and my pacing. I don't include all books I read, and I typically fudge my reading times a bit (mostly by being lazy about reviewing in a timely manner). Just syncing all information doesn't afford me this degree of choice.
So, long story short: Online sync is not an option. But my phone is a god-damn Linux-ish computer, it must be possible to retrieve some data, right? Well.
Sadly, the source code for current versions of FBReader is not available any longer (though the older versions are still
unmaintained on GitHub), so we can't just fix the open issue about bookmarks
export and ask the developers to merge it. Instead I assumed that
this app will do what basically every app of this size and shape does: Put all data into an sqlite database in
First off, you'll need
adb. The Android Debug Bridge is our command
line interface to talk with the phone. It can do a lot of things, like providing port-forwarding, providing file
transfer to and from a phone, giving shell access to the phone (a regular unix shell – it's nice to feel at home on your
phone, so I'd encourage you to use this feature to take a look!), installing and uninstalling applications, granting app
permissions, and running a host of tools for backups, security, debugging, and general scripting. It's a very versatile
and pretty well-documented tool!
It's usually available via a package called
You'll also have to enable USB debugging in your phone's developer settings. Then, when you plug in your phone into your
PC, authorize the connection. You'll have to unlock your phone before executing
adb commands – a very reasonable
security feature to make sure nobody accesses your phone without your consent.
Getting data out of
/data isn't quite trivial though, especially since the app is not debuggable.
protected, so we can't just waltz in with an
adb shell and copy it (though you presumably can do this with a rooted
phone). If we had access to a debug build of the application, we could use
adb shell to use the
command to perform sudo-like command execution.
Instead, we'll use the
adb backup feature that allows us to extract application data:
adb backup -noapk com.fbreader
After confirming the transaction on your phone, you'll see a file called `backup.ab` locally on your PC.
You might think that this is where we're done. Oh, if only. We'll have to extract a usable file archive first. Information on the internet mentions a lot of "run this openssl zlib command":
dd if=backup.ab bs=24 skip=1 | openssl zlib -d > backup.tar
Since my openssl doesn't come with zlib, I was tempted to run the fallback internet advice:
dd if=backup.ab bs=1 skip=24 | python2 -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" >backup.tar
But when I saw that this command actually required Python 2 (with its deprecation rapidly approaching), I grew a bit stubborn – there had to be a better way.
[Turns out](https://android.stackexchange.com/questions/28481/how-do-you-extract-an-apps-data-from-a-full-backup-made-through-adb-backup/78183#78183), you can just prepend a proper tar archive header to convince `tar` to extract the archive! wzxhzdk:3
It will complain about an unexpected file end and report an unrecoverable error (because we're missing the footer
checksums), but it will nonetheless extract all data. Now you'll see a directory called
apps, and you can head
apps/com.fbreader/db, where the
books.db file will contain all we want to see.
Now we've got a sqlite database, which means we're basically done. The tables of interest to us are appropriately called
Bookmarks. Turns out I had read 136 books by 80 authors, and marked 164 quotes.
This is how you can extract the quotes directly into a fortunes format after you access the database with
SELECT Bookmarks.bookmark_text || char(10) || ' - ' || Authors.name || ', ' || Books.title || char(10) || '%' FROM Bookmarks JOIN Books on Bookmarks.book_id=Books.book_id JOIN BookAuthor on Books.book_id=BookAuthor.book_id JOIN Authors on BookAuthor.author_id=Authors.author_id;
Edit [2020-08-17]: If you're seeing duplicates in your export, try this more complex query provided by Alberto Corni – thank you!
SELECT bookmark_text || char(10) || ' - ' || Authors_name || ', ' || title || char(10) || '%' FROM ( SELECT Books.title, Bookmarks.bookmark_text, ( SELECT group_concat(Authors.name) FROM BookAuthor JOIN Authors on BookAuthor.author_id=Authors.author_id WHERE Books.book_id=BookAuthor.book_id ) as Authors_name FROM Bookmarks JOIN Books on Bookmarks.book_id=Books.book_id where Bookmarks.visible = 1 ) as subq;