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.

Tags: life, programming

0 comments

Comments are closed

If you really, really want to comment, please mail miek@miek.nl.

0 comments in moderator queue