#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use DBI; use Flickr::API; use XML::LibXML; use Compress::Zlib; # boggle # put in your NSID here my $user_id = ''; # mine is '48600109393@N01' # flickr initialisation my $key = undef; my $secret = undef; # your key and secret here my $api = new Flickr::API({'key' => $key, 'secret' => $secret}); my $auth_token = '' || get_auth_token(); my $debug = 0; # TODO argument # dbi initialisation my $dbh = DBI->connect("dbi:SQLite:dbname=$ENV{'HOME'}/.flickrexif.db"); db_create($dbh); # main app my @labels = ("Aperture", "Exposure", "Exposure Bias", "Flash", "Focal Length", "ISO Speed", "Make", "Model",); print "Initialised\n"; my @photo_ids = get_all_photo_ids($user_id); # if you want to only handle the first page, put in a second argument # which is true, like 'only first page' print "I have ".scalar(@photo_ids)." to process\n"; print join(", ", @photo_ids) if ($debug >= 2); foreach my $photo_id (@photo_ids) { print "Handling photo $photo_id\n" if $debug; my $exif = get_exif($photo_id); set_machine_tags($photo_id, $exif); db_add_data($dbh, $photo_id, $exif) if (scalar keys %{ $exif }); } ### Flickr sub get_all_photo_ids { my $user_id = shift; my $return = shift; my @photo_ids; my ($photo_ids, $pages) = get_photo_ids($user_id, 1); push @photo_ids, @{ $photo_ids }; # handy for debugging- use one page only return @photo_ids if $return; foreach my $page (2..$pages) { my ($photo_ids) = get_photo_ids($user_id, $page); push @photo_ids, @{ $photo_ids }; } return @photo_ids; } sub get_photo_ids { my $user_id = shift; my $page = shift; my $photo_ids = []; my $info = get_flickr_data('flickr.photos.search', { 'user_id' => $user_id, 'per_page' => 500, 'page' => $page, 'extras' => 'tags', }); my $nodes = $info->findnodes('/rsp/photos'); my $pages = $nodes->[0]->findvalue('@pages'); print " Got page $page (of $pages) of photos\n"; my $photo_nodes = $info->findnodes('//photo'); foreach my $photo_node (@{ $photo_nodes }) { next if ($photo_node->getAttribute('tags') =~ /meta:exif/); push @{ $photo_ids }, $photo_node->getAttribute('id'); } return ($photo_ids, $pages); } sub get_exif { my $photo_id = shift; my $info = get_flickr_data('flickr.photos.getExif', {'photo_id' => $photo_id}); my $exif; foreach my $label (@labels) { # TODO bad global print " Looking for $label\n" if $debug; my $data = $info->findvalue("//exif[\@label='$label']/clean"); if (!$data) { print " ... looking for raw data\n" if $debug; $data = $info->findvalue("//exif[\@label='$label']/raw"); } if ($data->size() > 1) { $data = $data->shift(); } $data = $data->to_literal(); print " ... got '$data'\n" if $debug; $exif->{$label} = $data unless (!defined($data) || $data eq ''); } # special case for filename my $name = $info->findvalue("//exif[\@label='Object Name (Title)']/raw"); $exif->{'Filename'} = $name unless !defined $name; return $exif; } sub set_machine_tags { my ($photo_id, $exif) = @_; my @machine_tags; foreach my $label (@labels) { # TODO bad global if ($exif->{$label}) { my $predicate = "exif"; my $value = $exif->{$label}; my $name = lc($label); $name =~ s/ /_/g; if ($name eq 'make' || $name eq 'model') { $predicate = "camera"; } if ($name eq 'make' || $name eq 'model') { $predicate = "camera"; } print qq(Got tag "$predicate:$name=$value"\n) if $debug; push @machine_tags, '"'.$predicate.':'.$name.'='.$value.'"'; } } # special case for filename if ($exif->{'Filename'}) { push @machine_tags, '"file:name='.$exif->{'Filename'}.'"'; } # TODO extension? find full path ('location') too? use this # for lens tags? if (scalar @machine_tags) { print " Got ".scalar(@machine_tags)." exif tags for $photo_id.\n"; push @machine_tags, "meta:exif=".time; } else { print " Got no exif tags for $photo_id.\n"; push @machine_tags, "meta:exif=none"; } my $machine_tags = join(" ", @machine_tags); # finally, do tagging my $info = get_flickr_data('flickr.photos.addTags', { 'photo_id' => $photo_id, 'tags' => $machine_tags, }); print " Set ".scalar(@machine_tags)." machine tags for $photo_id.\n"; } ### SQLite database handling sub db_create { my $dbh = shift; my $create = <do($create); } sub db_add_data { my $dbh = shift; my $photo_id = shift; my $exif = shift; # TODO bad global my @fields = @labels; @fields = map { s/ /_/g; lc $_ } sort @fields; my $sql = "INSERT OR REPLACE INTO exif(id, ".join(", ", @fields).") "; $sql .= "VALUES ("."?," x scalar(@fields)."?)"; my $sth = $dbh->prepare($sql); my @insert = ($photo_id); foreach my $label (sort @labels) { push @insert, $exif->{$label}; } print " About to insert '".join("', '", @insert)."'\n" if $debug; $sth->execute(@insert); # TODO error capture print " Stored local info for '$photo_id'\n"; return 1; } # Flickr - utility wrapper sub get_flickr_data { my ($method, $args) = @_; $args->{'auth_token'} = $auth_token; # TODO remove global my $request = new Flickr::API::Request({ 'method' => $method, 'args' => $args, }); my $response = $api->execute_request($request); die "Error calling $method: $response->{error_message}\n" unless $response->{'success'}; my $buffer = $response->content; my $content = Compress::Zlib::memGunzip($buffer); if (!$content) { $content = $response->content; } my $parser = XML::LibXML->new(); my $info = $parser->parse_string( $content ); return $info; } # Flickr - auth token fetching cheekily nicked from flickr_upload sub get_auth_token { # The user wants to authenticate. There's really no nice way to handle this. # So we have to spit out a URL, then hang around or something until # the user hits enter, then exchange the frob for a token, then tell the user what # the token is and hope they care enough to stick it into .flickrrc so they # only have to go through this crap once. # 1. get a frob my $frob = get_frob( $api ); # 2. get a url for the frob my $url = $api->request_auth_url('write', $frob); # 3. tell the user what to do with it print "1. Enter the following URL into your browser\n\n", "$url\n\n", "2. Follow the instructions on the web page\n", "3. Hit when finished.\n\n"; # 4. wait for enter. ; # 5. Get the token from the frob my $auth_token = get_token( $api, $frob ); die "Failed to get authentication token!" unless defined $auth_token; # 6. Tell the user what they won. print "Your authentication token for this application is\n\t\t", $auth_token, "\n"; print "Edit this program and insert this at line 20\n"; exit 0; } sub get_frob { my $api = shift; my $res = $api->execute_method("flickr.auth.getFrob"); return undef unless defined $res and $res->{success}; # FIXME: error checking, please. At least look for the node named 'frob'. return $res->{tree}->{children}->[1]->{children}->[0]->{content}; } sub get_token { my $api = shift; my $frob = shift; my $res = $api->execute_method("flickr.auth.getToken", { 'frob' => $frob } ); return undef unless defined $res and $res->{success}; # FIXME: error checking, please. return $res->{tree}->{children}->[1]->{children}->[1]->{children}->[0]->{content}; }