I wanted to count all the books we own, and furthermore classify them in a few categories. Ofcourse this would mean a lot of typing, so the idea is to use a barcode reader. With a little help from books.google you can get the author, genre, title from a book back.
With that you can easily make a small library.
Hard- and software setup
I've bought I scanner somehwere online, from 50 to 100 euros you can buy a decent barcode scanner.
Next I wrote a Perl script that would use the ISBN number from the book and would fetch the information from google.
Archiving the books
Database
I'm using SQLite3 as a backend. The "database" was initialized with the following tables:
CREATE TABLE books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
isbn INTEGER,
title VARCHAR,
author VARCHAR,
genre VARCHAR,
year_published INTEGER,
epoch_added INTEGER,
loc_code VARCHAR
);
The loc_code columns may be used in the future as I may implemented a
location code for each book.
CREATE TABLE lending (
id INTEGER,
isbn INTEGER,
date INTEGER,
who VARCHAR
);
The lending table isn't used yet.
Perl script
I'm using a small Perl script to retrieve the information from google and to parse the author, title and stuff. This is a simple regexp based parsing - no fancy HTML parsing or whatever. The database backend used is SQLite.
The gist of the script is:
Read in the barcode from STDIN. Fire off links to get the HTML.
while(<>) {
chomp;
if (! /^\d+$/) {
print "** Invalid\n";
next;
}
my @html = `links -width 200 -dump "http://books.google.com/books?q=+$_&btnG=Search+Books"`;
# title,author,genre,year of first publication
my @det = details(@html);
if (scalar @det == 0) {
print "** Onbekend\n";
} else {
my $epoch = time();
# genre
if ($det[2] =~ /pages/) {
# problably couldn't fetch it
$det[2] = "none";
}
# first published, if empty, make it up
if ($det[3] eq "") {
$det[3] = "0";
}
if (retrieve $_) {
insert $_, $det[0], $det[1], $det[2], $det[3], $epoch;
} else {
print "** Boek already there: $_: $det[0]\n";
}
}
}
Parse the HTML
This function reads in the HTML and returns a list with the author and
title details. This in done in details.
Normalize the genre
The genre isn't standardized, so I needed to map them to genre I want.
This is done in normalize_genre.
Check if the book is already there, the function retrieve does a
simple database lookup:
sub retrieve {
my $isbn = shift;
my $sth = $dbh->prepare('SELECT * FROM books WHERE isbn = ?')
or die "Couldn't prepare statement: " . $dbh->errstr;
$sth->execute($isbn);
while (my @data = $sth->fetchrow_array()) {
#print "@data" . "\n";
}
# ... We have to do this after the while loop that fetches
# whatever rows were available, because with some
# databases you don't know how many rows there were until after
# you've gotten them all.
my $row = $sth->rows;
$sth->finish;
if ($dryrun == 0) {
return ($row == 0);
} else {
return 1;
}
}
If we don't have the book yet - do a simple insert:
sub insert {
my ($isbn, $title, $author, $genre, $year, $epoch) = @_;
$genre = lc normalize_genre $genre;
print join "|", $title, $author, $genre, $year, $epoch . "\n";
if ($dryrun == 0) {
my $sth = $dbh->prepare('INSERT INTO books VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
or die "Couldn't prepare statement: " . $dbh->errstr;
$sth->execute(undef, $isbn, "$title", "$author", "$genre", $year, $epoch, undef)
or die "Couldn't insert book: " . $dbh->errstr;
$sth->finish;
} else {
print "** No commit\n";
}
}
Extra tools
With SQLite you can write shell oneliners to get the info from your database:
sqlite3 biblio.db "SELECT isbn,title,genre,author FROM books ORDER BY genre,title"
Or to get it ordered on genre, this has one argument, the genre (stored in $1):
sqlite3 biblio.db "SELECT title, author FROM books WHERE genre LIKE '%$1' ORDER BY title";
From this last command I generate a LaTeX file to create a list of books
per genre. Note I'm using marginnote, each new letter will be printed
in the margin and with the default options of LaTeX you can only have
like 13 of these margin paragraphs and I needed more. For the rest it's
simple LaTeX and Perl:
#!/usr/bin/perl -w
use strict;
my $genre = shift @ARGV;
print <<EOF;
\\documentclass[a4paper]{article}
\\setlength{\\parindent}{0mm}
\\usepackage{fullpage}
\\usepackage{marginnote}
\\renewcommand{\\familydefault}{\\sfdefault}
\\begin{document}
\\pagestyle{empty}
EOF
print "\\emph{\\textbf{\\Large $genre}}\\\\\n";
my $lastletter = "";
while (<>) {
chomp;
s/&/\\&/g;
my ($t, $a) = split /\|/;
my $c = substr $t, 0, 1;
print "\\marginnote{$c}" if ($c ne $lastletter);
print "\\textbf{$t}" . "," . "{\\small $a}" . "\\\\\n";
$lastletter = $c;
}
print <<EOF;
\\end{document}
EOF
Download
For the entire Perl script, click here.

