A Perl script catalogs books and CDs with the help of barcodes
Checkout
Barcodes efficiently speed us through supermarket checkout lines, but the technology is also useful for totally different applications. An inexpensive barcode scanner can help you organize your private library, CD, or DVD collection.
Dealextreme.com, a company from Hong Kong, offers all kinds of inexpensive goodies. Customers can pay with PayPal, and shipping is free. Interested in a laser pointer for less than two dollars or a SATA/IDE adapter for just eight dollars? If you don't mind waiting up to two weeks for delivery, you're guaranteed to find a bargain with Dealextreme.
For quite a while I had my eye on the CCD-based barcode scanner for US$ 42 dollars (see Figure 1) (one of Dealextreme's most expensive products [2]) before I finally pressed the Buy button.
Mail from Hong Kong
When the mailman finally delivered the package, I could hardly wait to get started. The obvious choice was to write an application to scan the barcodes in my extensive collection of technical literature and store the results in a database. Depending on where the book comes from, the barcodes are either printed in UPC (Universal Product Number) or EAN (European Article Number) format, and Amazon.com offers a free web service that gives you detailed product information if you submit either barcode. This means that a Perl script can easily identify the author and title of a book or the artist for a CD that you scan. The data returned by the service includes CD and book cover images. Adding a graphical user interface to the application lets me display the book cover or CD case onscreen and in color after scanning.
The reader has a USB connector, and Linux immediately identifies it as a second keyboard. If you hold the sensor over the barcode of a book, CD, or DVD and press the button, the scanner switches the red light on (Figure 2) and enables the CCD sensor; then, the internal CPU starts to analyze the bars of different thicknesses to discover the encoded number.
The barcode scanner is very reliable; it beeps when it's done and sends the numbers to the computer's USB port, just as if the user had typed them at the keyboard and then pressed Return.
If the scanner fails to identify the barcode, which did not happen in my experiments, the user can still type the number in the input box of the application discussed in this article – the effect is the same.
Full-Color Image Included
The script, upcscan (Listing 1), uses a graphical interface based on the Tk toolkit. The GUI's text input box immediately grabs the keyboard focus when launched. Once the barcode scanner has identified a code, the numbers appear in the input box. The scanner sends a return key code when it is done, and the GUI responds with the callback function scan_done(). The function sends the barcode to the Amazon Web Services (AWS) and, after a delay of about a second, receives the title and author or artist of the book, CD, or DVD plus a URL that points to a JPEG image of the book or CD cover.
Figure 3 shows the application shortly after scanning the barcode printed on the back of a JavaScript book. The data fields are filled in correctly, and the program has received the right book cover from Amazon in return. Figure 4 shows the results after scanning a CD by Beach Boys vocalist Brian Wilson. In both cases, the script drops the retrieved data into a local SQLite database, which I can then browse to my heart's desire and write applications around (Figure 5).
Listing 1
upcscan
001 #!/usr/local/bin/perl -w
002 ###########################################
003 # upcscan - Scan/store UPC coded articles
004 # Mike Schilli, 2008 (m@perlmeister.com)
005 ###########################################
006 use strict;
007 use Tk;
008 use Tk::JPEG;
009 use POE;
010 use LWP::UserAgent::POE;
011 use Net::Amazon;
012 use Net::Amazon::Request::UPC;
013 use MIME::Base64;
014 use Rose::DB::Object::Loader;
015 use Log::Log4perl qw(:easy);
016
017 my @MODES = qw(books music dvd);
018
019 my $UA = LWP::UserAgent::POE->new();
020
021 my $loader = Rose::DB::Object::Loader->new(
022 db_dsn =>
023 "dbi:SQLite:dbname=articles.dat",
024 db_options => {
025 AutoCommit => 1, RaiseError => 1 },
026 );
027 $loader->make_classes();
028
029 my $top = $poe_main_window;
030 $top->configure(-title => "UPC Reader",
031 -background=> "#a2b2a3");
032 $top->geometry("200x300");
033
034 my $FOOTER = $top->Label();
035 $FOOTER->configure(-text =>
036 "Scan next item");
037
038 my $BYWHO = $top->Label();
039 my $UPC = $top->Label();
040 my $PHOTO = $top->Photo(-format => 'jpeg');
041 my $photolabel =
042 $top->Label(-image => $PHOTO);
043 my $entry = $top->Entry(
044 -textvariable => \my $UPC_VAR);
045
046 my $PRODUCT = $top->Label();
047
048 $entry->focus();
049
050 for my $w ($entry, $photolabel, $PRODUCT,
051 $BYWHO, $UPC, $FOOTER) {
052 $w->pack(-side => 'top', -expand => 1,
053 -fill => "x" );
054 }
055
056 $entry->bind("<Return>", \&scan_done);
057
058 my $session = POE::Session->create(
059 inline_states => {
060 _start => sub{
061 $poe_kernel->delay("_start", 60);
062 }
063 });
064
065 POE::Kernel->run();
066
067 ###########################################
068 sub scan_done {
069 ###########################################
070 $PHOTO->blank();
071 $PRODUCT->configure(-text => "");
072 $FOOTER->configure(-text =>
073 "Processing ...");
074 $BYWHO->configure(-text => "");
075 $UPC->configure(-text => $UPC_VAR);
076 resp_process(
077 amzn_fetch( $UPC_VAR ) );
078 $UPC_VAR = "";
079 }
080
081 ###########################################
082 sub amzn_fetch {
083 ###########################################
084 my($upc) = @_;
085
086 my $resp;
087
088 my $amzn = Net::Amazon->new(
089 token => 'XXXXXXXXXXXXXXXXXXXX',
090 ua => $UA,
091 );
092
093 for my $mode (@MODES) {
094
095 my $req =
096 Net::Amazon::Request::UPC->new(
097 upc => $upc,
098 mode => $mode,
099 );
100
101 $resp = $amzn->request($req);
102
103 if($resp->is_success()) {
104 return($resp, $mode, $upc);
105 last;
106 }
107
108 WARN "Nothing found in mode '$mode'";
109 }
110 return $resp;
111 }
112
113 ###########################################
114 sub resp_process {
115 ###########################################
116 my($resp, $mode, $upc) = @_;
117
118 if($resp->is_error()) {
119 $PRODUCT->configure(
120 -text => "NOT FOUND");
121 return 0;
122 }
123
124 my ($property) = $resp->properties();
125 my $imgurl = $property->ImageUrlMedium();
126 img_display( $imgurl );
127
128 my $a = Article->new();
129 $a->upc($upc);
130 $a->type($mode);
131 $a->title( $property->Title() );
132
133 if($mode eq "books") {
134 $a->bywho( $property->author() );
135 } elsif( $mode eq "music") {
136 $a->bywho( $property->artist() );
137 } else {
138 $a->bywho( "" );
139 }
140
141 $BYWHO->configure(-text => $a->bywho() );
142 $PRODUCT->configure(
143 -text => $a->title() );
144
145 if($a->load( speculative => 1 )) {
146 $PRODUCT->configure(
147 -text => "ALREADY EXISTS");
148 } else {
149 $a->save();
150 }
151
152 $FOOTER->configure(
153 -text => "Scan next item");
154 return 1;
155 }
156
157 ###########################################
158 sub img_display {
159 ###########################################
160 my($imgurl) = @_;
161
162 my $imgresp = $UA->get( $imgurl );
163
164 if($imgresp->is_success()) {
165 $PHOTO->configure( -data =>
166 encode_base64( $imgresp->content() ));
167 }
168 }
Keep on Ticking
Thanks to the Tk package from CPAN, slick GUIs are no problem for Perl scripts. Unfortunately, I had a problem with the application I had in mind: Longer operations, such as the web requests, caused the interface to freeze. Querying Amazon with a barcode can take a couple of seconds, and the interface would freeze in the meantime.
The POE module, also from CPAN, gave me a workaround – it lets the GUI run in an event-oriented userspace "kernel." However, don't confuse this with the Linux kernel; in POE, "kernel" is just a fancy name for an event loop. It provides mechanisms for cooperative multitasking.
The script does not handle web requests synchronously in this environment; instead, it sends a request to the web server and then immediately hands control back to the POE kernel and, therefore, the GUI event loop. When the response comes back from the Internet, the kernel wakes up the waiting task and passes in the data.
The Net::Amazon CPAN module handles communications with Amazon and supports a variety of requests to the giant retailer's web service. Internally, it does not use the asynchronous POE module to query the Amazon database. Instead, it uses the synchronous LWP::UserAgent. You can use the ua parameter to tell the module to work with a user agent that you pass in to it.
CPAN has LWP::UserAgent::POE, an agent with an LWP interface that respects the special asynchronous needs of the POE kernel. While the module issues web requests, and seemingly waits synchronously for the results, some black magic inside the module allows the POE kernel to keep on ticking, thus giving other tasks their turn.
Our Services
Direct Download
Read full article as PDF » 074-078_perl.pdf (527.70 kB)Tag Cloud
News
-
FSF Outs the World Wide Web Consortium over DRM Proposal
Richard Stallman calls for the W3C to remain independent of vendor interests.
-
Debian 7.0 Debuts
The new release supports nine architectures, 73 human languages, and zero non-Free components.
-
Alpha Version of Fedora 19 Released
Fedora developers release the first alpha version of Fedora 19, known as Schrödinger’s Cat, for general testing. The final release is expected in July 2013.
-
ack 2.0 Released
ack is a grep-like, command-line tool that has been optimized for programmers to search large trees of source code.
-
SUSE Studio 1.3 Released
New features in SUSE Studio 1.3 include enhanced cloud integration, VM platform support, and lifecycle management.
-
Xen To Become Linux Foundation Collaborative Project
The Linux Foundation recently announced that the Xen Project is becoming a Linux Foundation Collaborative Project.
-
RunRev Releases Open Source Version of LiveCode
Open source version of LiveCode is now available for developing apps, games, and utilities for all major platforms.
-
OpenDaylight Project Formed
OpenDaylight is an open source software-defined networking project committed to furthering adoption of SDN and accelerating innovation in a vendor-neutral and open environment.
-
Gnome 3.8 Released
The new Gnome release includes privacy and sharing settings, allowing more user control over access to personal information.
-
Mozilla and Samsung Collaborate on New Browser Engine
Mozilla is collaborating with Samsung on a new web browser engine called Servo.
