#!/usr/bin/perl

use strict;
use warnings;

=head1 NAME

exif2rdf - reads Exif meta data from files then 
           prints or stores as RDF statements

Using ExifTool and Redland RDF Libraries.

=head1 SYNOPSIS

    exif2rdf [-c config] [-b base_uri] [-s syntax] file...
    exif2rdf [-c config] [-b base_uri] -p path [-n|-u] file...
    exif2rdf [-c config] -d
    exif2rdf -h

        -b|--base_uri  set base URI, must be absolute HTTP,
                       default is http://www.theflints.net.nz/
        -c|--config    load configuration from file
        -d|--dump      print current configuration
        -h|--help      print help
        -n|--new       create new database, default
        -p|--path      path of RDF database to store statements
        -s|--syntax    serialize statements in RDF syntax,
                       for example turtle or rdfxml-abbrev,
                       default is ntriples. 
                       For a list of possible values
                       run the Redland serializer utility
                       "rapper --help" and see output FORMATs.
        -u|--update    update existing database
        file           containing Exif meta data

=head1 TUTORIAL

This class, RDF::Redland::Model::ExifTool ,
extends the Redland model or set of RDF statements
(RDF::Redland::Model) to read Exif meta data from 
instances of ExifTool (Image::ExifTool).
This document explains how to use the class through the 
example script exif2rdf .

Exif meta data is in tag and value pairs, for example:

    Aperture 4.0

RDF is in subject, predicate (verb) and object triples 
called statements, for example:

    <file://.../t/data/lighthouse.jpg> <http://...#apertureValue> "4.0" .

This class can:

=over

=item *

translate an Exif tag and its value into an RDF statement

=item *

parse RDF statements from the values of Exif tags

=back

The following sections give examples of translating and
L<parsing|"Parse RDF from Exif"> Exif into RDF.
Those examples serialize or print RDF, the final one 
L<stores|"Store Exif in an RDF database"> RDF in a database.
The final section explains how to 
L<find out more|"Find out more about...">. 

=head2 Translate Exif to RDF - camera meta data

Let's get some meta data from an image by running the script
on one of the test images in this class' distribution:

    exif2rdf t/data/lighthouse.jpg

        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#focalLengthIn35mmFilm> "6.3 mm" .
        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#ISOSpeedRatings> "100" .
        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#apertureValue> "5.6" .
        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#shutterSpeedValue> "1/1200" .
        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#make> "FUJIFILM" .
        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#model> "FinePix S5600" .
        <file://.../t/data/lighthouse.jpg> <http://www.w3.org/2003/12/exif/ns#dateTimeOriginal> "2009-01-19T11:37:53" .

The RDF statements, in ntriples syntax, contains meta data from the camera: 
exposure, camera model and when the image was taken.

Here's how it works. 
First the script creates an instance of this class, 
a model or container for RDF statements. 
Then the class creates a subject URI from 
the image file's full path name. 
The TranslateTag hash in the class' configuration maps 
an Exif tag name to the equivalent RDF predicate URI. 
The class tries to translate each tag into a predicate. 
If successful an RDF statement is created from the 
subject and predicate with the tag's value as the object. 
Otherwise the tag is ignored. 
Finally the script serializes or prints out all the statements 
in the model.

All the examples in this tutorial use the default configuration 
which can be replaced.

=head2 Translate Exif to RDF - human meta data

People can add their own meta data to images with Exif tags 
including Artist, ImageDescription and Comment. 
Let's try another image:

    exif2rdf t/data/ship.jpg

        <file://.../tutorial/ship.jpg> <http://www.w3.org/2003/12/exif/ns#imageDescription> "a model container ship made of Lego" .
        <file://.../tutorial/ship.jpg> <http://www.w3.org/2003/12/exif/ns#artist> "Andrew Flint" .
        ...camera meta data...

The human meta data describes the image and names the artist!

This class can only read meta data from an image,
use ExifTool to add or update.

Both exif2rdf and exiftool -X translate but
only exif2rdf can parse meta data from the values of Exif tags.

=head2 Parse RDF from Exif

The human meta data in the last image was plain old text. 
But the next image has RDF statements in the value of the Comment tag. 
Try:

    exif2rdf --syntax turtle t/data/family.jpg

        <file://.../t/data/family.jpg>
            <http://purl.org/dc/terms/description> "family eating fish and chip lunch by the beach" ;
            <http://purl.org/dc/terms/spatial> "Raumati South, Wellington, NZ" ;
            <http://xmlns.com/foaf/0.1/depicts> (
                <http://www.theflints.net.nz/gregory/about#me>
                <http://www.theflints.net.nz/alison/about#me>
                <http://www.theflints.net.nz/russell/about#me>
                <http://www.theflints.net.nz/oliver/about#me> 
            ) ;
            <http://xmlns.com/foaf/0.1/maker> <http://www.theflints.net.nz/andrew/about#me> .
            <http://www.w3.org/2003/12/exif/ns#ISOSpeedRatings> "50" ;
            <http://www.w3.org/2003/12/exif/ns#apertureValue> "4.5" ;
            ...more camera meta data...

This time there is a wider range of human meta data, in the turtle syntax, 
including who appears in the image and where it was taken. 
The human and camera meta data are simply merged.

In the configuration, ParseTag lists the Exif tags to check for
statements and ParseSyntax lists the RDF syntax to parse with.
If a value does not contain RDF that tag is
L<translated|"Translate Exif to RDF - camera meta data"> instead.

=head2 Store Exif in an RDF database

So far the RDF statements have been serialized or printed.
This time the statements will be stored in a database. Try:

    exif2rdf --path exif_meta_data t/data/lighthouse.jpg t/data/ship.jpg t/data/family.jpg

This creates a set of database files, in the current directory,
called exif_meta_data-*.db ,
containing the meta data from all the images
in this tutorial.
To serialize with Redland RDF try:

    rdfproc exif_meta_data serialize turtle

        @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

        <file://.../t/data/ship.jpg>
            ...

        <file://.../t/data/family.jpg>
            ...

        <file://.../t/data/lighthouse.jpg>
            ...

=head2 Find out more about...

=over

=item *

this example script by running exif2rdf --help
or looking at the code

=item *

this class L<RDF::Redland::Model::ExifTool> and its
L<Configuration|RDF::Redland::Model::ExifTool/Configuration>

=item *

ExifTool L<Image::ExifTool>

=item *

Redland RDF Libraries Perl interface L<http://librdf.org/docs/perl.html>
including querying and reasoning with RDF databases

=back

=head1 COPYRIGHT & LICENSE

Copyright 2008-2010 Andrew Flint, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

use Carp;
use Getopt::Long;
use Image::ExifTool;
use RDF::Redland;
use RDF::Redland::Model::ExifTool;
use Regexp::Common qw /URI/;

my $DEFAULT_BASE_URI = "http://www.theflints.net.nz/";
my $DEFAULT_SYNTAX = "ntriples";

sub synopsis {
    my $SYNOPSIS = <<EOF;
exif2rdf [-c config] [-b base_uri] [-s syntax] file...
exif2rdf [-c config] [-b base_uri] -p path [-n|-u] file...
exif2rdf [-c config] -d
exif2rdf -h

    -b|--base_uri  set base URI, must be absolute HTTP,
                   default is http://www.theflints.net.nz/
    -c|--config    load configuration from file
    -d|--dump      print current configuration
    -h|--help      print help
    -n|--new       create new database, default
    -p|--path      path of RDF database to store statements
    -s|--syntax    serialize statements in RDF syntax,
                   for example turtle or rdfxml-abbrev,
                   default is ntriples. 
                   For a list of possible values
                   run the Redland serializer utility
                   "rapper --help" and see output FORMATs.
    -u|--update    update existing database
    file           containing Exif meta data
EOF
    my(@error) = @_;

    foreach my $e (@error) {
        print STDERR "$0: $e\n";
        my $status = 1;
    }
    print STDERR "\n$SYNOPSIS";
}


#
# main()
#
my @error = ();

my $base_uri = $DEFAULT_BASE_URI;
my($config, $dump, $help, $new, $path, $syntax, $update);
 
if (!GetOptions('base_uri|c=s' => \$base_uri,
                'config|c=s' => \$config,
                'dump' => \$dump,
                'help' => \$help,
                'new' => \$new,
                'path|c=s' => \$path,
                'syntax|c=s' => \$syntax,
                'update' => \$update,)) {
    synopsis;
    exit 1;
}

if ($help) {
    synopsis;
    exit 0;
}

if ($base_uri !~ /$RE{URI}{HTTP}/) {
    @error = (@error, 
              "base URI must be absolute HTTP protocol ($base_uri)");
}

my $store_type = "memory";
my $store_dir = ".";
my $store = "exif_meta_data";
if ((!$syntax) && (!$path)) {
    $syntax = $DEFAULT_SYNTAX;
} elsif (($syntax) && (!$path)) {
} elsif ((!$syntax) && ($path)) {
    $store_type = "bdb";
    my $p = File::Spec->rel2abs($path);
    if ($p) {
        my($v, $d);
        ($v, $d, $store) = File::Spec->splitpath($p);
        $store_dir = File::Spec->catdir($v, $d);
    } else {
        @error = (@error, "bad storage path ($path)");
    }
} else {
    @error = (@error, "set either syntax or path not both " .
                      "($syntax, $path)");
}

my $initialise = "yes";
if (($new) && (!$update)) {
} elsif ((!$new) && ($update)) {
    $initialise = "no";
} elsif ((!$new) && (!$update)) {
} else {
    @error = (@error, "set either new or update not both");
}

if ((!$dump) && (!@ARGV)) {
    @error = (@error, "must be file..., --dump or --help");
}

if (@error) {
    synopsis(@error);
    exit 1;
}

my $exiftool = new Image::ExifTool;
if (!$exiftool) {
    croak "$0: failed to create ExifTool";
}

my $storage = new RDF::Redland::Storage("hashes", "$store",
                       "new='$initialise',hash-type='$store_type'," .
                       "dir='$store_dir'");
if (!$storage) {
    croak "$0: failed to create storage for model";
}

my $model = new RDF::Redland::Model::ExifTool($storage, "");
if (!$model) {
    croak "$0: failed to create model";
}

my $serializer = undef;
if ($syntax) {
    $serializer = new RDF::Redland::Serializer($syntax);
    if (!$serializer) {
        croak "$0: failed to create serializer";
    }
}

if ($config) {
    @error = $model->set_exif_config_from_file($config);
    if (@error) {
        foreach my $e (@error) {
            print STDERR $e . "\n";
        }
        die;
    }
}

if ($dump) {
    print $model->get_exif_config_to_string;
    exit 0;
}

my @exif_tag = $model->get_exif_tags;

foreach my $file (@ARGV) {
    $exiftool->ImageInfo($file, @exif_tag);

    @error = $model->add_exif_statements($exiftool);
    foreach my $e (@error) {
        print STDERR $e . "\n";
    }
}
$model->sync;

if ((0 < $model->size) && (!@error) && ($serializer)) {
    print $serializer->serialize_model_to_string(
          new RDF::Redland::URINode($base_uri), 
          $model);
    undef $serializer;  # prevents librdf_serializer null exception
}

undef $model;
undef $storage;

exit 0;
