Saturday 10 March 2012

Quick Tutorial On Re-using My Perl Scripts

Hi All,

What a busy week for this little monkey!
A fellow monkey recently contacted me about some problems they had getting my "exif2map.pl" script to work on SIFT. Specifically, they were getting "command not found" errors whenever they launched their version.

I thought I'd write a quick guide just in case anyone else was having issues. This procedure applies to all of my Perl programs I have posted on this blog.

I'll use my latest "exif2map.pl" as an example:

# START CODE


#!/usr/bin/perl -w

# Perl script to take the output of exiftool and conjure up a web link
# to google maps if the image has stored GPS lat/long info.

use strict;

use Image::ExifTool;
use Image::ExifTool::Location;
use Getopt::Long;
use HTML::QuickTable;
use File::Find;

# commented out for now - apparently File:Find can issue some weird warnings
#no warnings 'File::Find';

my $version = "exif2map.pl v2012.02.21";
my $help = ''; # help flag
my $htmloutput = ''; #html flag
my @filenames; # input files from -f flag
my @directories; # input directories from -dir flag (must use absolute paths)

my %file_listing; # stored results

GetOptions('help|h' => \$help,
    'html' => \$htmloutput,
    'f=s@' => \@filenames,
    'dir=s@' => \@directories);

if ($help||(@filenames == 0 && @directories == 0))
{
    print("\n$version\n");
    print("Perl script to take the output of exiftool and conjure up a web link\n");
    print("to google maps if the image has stored GPS lat/long info.\n");

    print("\nUsage: exif2map.pl [-h|help] [-f filename] [-html]\n");
    print("-h|help .......... Help (print this information). Does not run anything else.\n");
    print("-f filename ...... File(s) to extract lat/long from\n");
    print("-dir directory ... Absolute path to folder containing file(s) to extract lat/long from\n");
    print("-html ............ Also output results as a timestamped html file in current directory\n");

    print("\nExample: exif2map.pl -f /cases/galloping-gonzo.jpg");
    print("\nExample: exif2map.pl -f /cases/krazy-kermit.jpg -dir /cases/rockin-rowlf-pics/ -html\n\n");
    print("Note: Outputs results to command line and (if specified) to a timestamped html file\n");
    print("in the current directory (e.g. exif2map-output-TIMESTAMP.html)\n\n");
   
    exit;
}

# Main processing loop
print("\n$version\n");

# Process filenames specified using the -f flag first
if (@filenames)
{
    foreach my $name (@filenames)
    {
        ProcessFilename($name);
    }
}

# Process folders specified using the -dir flag
# Note: Will NOT follow symbolic links to files
if (@directories)
{
    find(\&ProcessDir, @directories);
}

# If html output required AND we have actually retrieved some data ...
if ( ($htmloutput) && (keys(%file_listing) > 0) )
{   
    #timestamped output filename
    my $htmloutputfile = "exif2map-output-".time.".html";

    open(my $html_output_file, ">".$htmloutputfile) || die("Unable to open $htmloutputfile for writing\n");

    my $htmltable = HTML::QuickTable->new(border => 1, labels => 1);

    # Added preceeding "/" to "Filename" so that the HTML::QuickTable sorting doesn't result in
    # the column headings being re-ordered after / below a filename beginning with a "\".
    $file_listing{"/Filename"} = "GoogleMaps Link";

    print $html_output_file "<HTML>";
    print $html_output_file $htmltable->render(\%file_listing);
    print $html_output_file "<\/HTML>";

    close($htmloutputfile);
    print("\nPlease refer to \"$htmloutputfile\" for a clickable link output table\n\n");
}

sub ProcessFilename
{
    my $filename = shift;

    if (-e $filename) #file must exist
    {
        my $exif = Image::ExifTool->new();
        # Extract all info from existing image
        if ($exif->ExtractInfo($filename))
        {
            # Ensure all 4 GPS params are present
            # ie GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef
            # The Ref values indicate North/South and East/West
            if ($exif->HasLocation())
            {
                my ($lat, $lon) = $exif->GetLocation();
                print("\n$filename contains Lat: $lat, Long: $lon\n");
                print("URL: http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\n");
                if ($htmloutput) # save GoogleMaps URL to global hashmap indexed by filename
                {
                    $file_listing{$filename} = "<A HREF = \"http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\"> http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en</A>";
                }
                return 1;
            }
            else
            {
                print("\n$filename : No Location Info available!\n");
                return 0;
            }
        }
        else
        {
            print("\n$filename : Cannot Extract Info!\n");
            return 0;
        }
    }
    else
    {
        print("\n$filename does not exist!\n");
        return 0;
    }
}

sub ProcessDir
{
    # $File::Find::dir is the current directory name,
    # $_ is the current filename within that directory
    # $File::Find::name is the complete pathname to the file.
    my $filename = $File::Find::name; # should contain absolute path eg /cases/pics/krazy-kermit.jpg

    if (-f $filename) # must be a file not a directory name ...
    {
        my $exif = Image::ExifTool->new();
        # Extract all info from existing image
        if ($exif->ExtractInfo($filename))
        {
            # Ensure all 4 GPS params are present
            # ie GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef
            # The Ref values indicate North/South and East/West
            if ($exif->HasLocation())
            {
                my ($lat, $lon) = $exif->GetLocation();
                print("\n$filename contains Lat: $lat, Long: $lon\n");
                print("URL: http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\n");
                if ($htmloutput) # save GoogleMaps URL to global hashmap indexed by filename
                {
                    $file_listing{$filename} = "<A HREF = \"http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\"> http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en</A>";
                }
                return 1;
            }
            else
            {
                print("\n$filename : No Location Info available!\n");
                return 0;
            }
        }
        else
        {
            print("\n$filename : Cannot Extract Info!\n");
            return 0;
        }
    }
}

# END CODE


1. OK, what I should have mentioned was that the very first line in your "exif2map.pl" script is the line containing " #!/usr/bin/perl -w".
This line tells Unix, "Hey make sure you use the Perl interpreter on this script". It's GOT to be the first thing Unix sees in the script. Otherwise, you will probably get a bunch of "command not found" errors. The "#START CODE" and "#END CODE" comments aren't supposed to be in the script file.

2. Copy/paste everything from that first line down to just BEFORE the "# END CODE".

3. According to this article it might be best if you don't try saving it into a Windows text editor if you intend to eventually run it on SIFT. You might be better off, using Firefox / gedit on SIFT. The reason being, the Windows text editor may save it using an incorrect character set / add extra control characters. Probably unlikely to happen but documented here just in case.

4. After saving the script (preferably in "/usr/local/bin/"), don't forget to make it executable using "chmod a+x /usr/local/bin/exif2map.pl".

5. For some reason, when I used CPAN to install Image::ExifTool::Location, the module seemed to download/install OK but then I got some testing failures. I am not sure why this is happening - maybe the version of CPAN on SIFT is not compatible with Image::ExifTool::Location's tests? Maybe the module is expecting a particular environment path? I don't know. The weird thing was that none of the other CPAN modules I have subsequently installed had any testing issues *shrug*.
Anyway, like the true hack I am - I decided to code it anyway and see if it worked afterwards :) Subsequently,  I couldn't see any problems after testing the "exif2map.pl" script against the ExifTool Firefox plugin. Popular Programmers Paradigm - if it works, don't touch it !

So that's the end of this "quick" little post. Hope this helps!