* /trunk/src/gpstools/GPST.pm
[gpstools.git] / gpst-pic
blob5d7517b3c89f280e7a5c1b6daa13a2b6bc96d96c
1 #!/usr/bin/perl -w
3 #=======================================================================
4 # $Id$
5 # File ID: 18245170-f924-11dd-93fc-0001805bf4b1
6 # Extract EXIF data from pictures for use with COPY in Postgres
8 # Character set: UTF-8
9 # ©opyleft 2008– Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 2 or later, see end of
11 # file for legal stuff.
12 #=======================================================================
14 BEGIN {
15 our @version_array;
18 use strict;
19 use Getopt::Long;
21 BEGIN {
22 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
23 our @version_array;
26 use GPST;
27 use GPSTxml;
29 $| = 1;
31 our $Debug = 0;
32 our $NA = '\N';
33 our %Std = (
35 'output-format' => 'pgtab',
36 'timezone' => '',
39 our %Opt = (
41 'author' => '',
42 'debug' => 0,
43 'description' => '',
44 'help' => 0,
45 'output-format' => $Std{'output-format'},
46 'strip-whitespace' => 0,
47 'timezone' => $Std{'timezone'},
48 'verbose' => 0,
49 'version' => 0,
53 our $progname = $0;
54 $progname =~ s/^.*\/(.*?)$/$1/;
56 my $rcs_id = '$Id$';
57 my $id_date = $rcs_id;
58 $id_date =~ s/^.*?\d+ (\d\d\d\d-.*?\d\d:\d\d:\d\d\S+).*/$1/;
60 push(@main::version_array, $rcs_id);
62 Getopt::Long::Configure("bundling");
63 GetOptions(
65 "author|a=s" => \$Opt{'author'},
66 "debug" => \$Opt{'debug'},
67 "description|d=s" => \$Opt{'description'},
68 "help|h" => \$Opt{'help'},
69 "output-format|o=s" => \$Opt{'output-format'},
70 "strip-whitespace|w" => \$Opt{'strip-whitespace'},
71 "timezone|T=s" => \$Opt{'timezone'},
72 "verbose|v+" => \$Opt{'verbose'},
73 "version" => \$Opt{'version'},
75 ) || die("$progname: Option error. Use -h for help.\n");
77 $Opt{'debug'} && ($Debug = 1);
78 $Opt{'help'} && usage(0);
79 if ($Opt{'version'}) {
80 print_version();
81 exit(0);
84 $GPST::Spc = $Opt{'strip-whitespace'} ? "" : " ";
85 my $Spc = $GPST::Spc; # FIXME
86 my $tz_str = "";
87 if (length($Opt{'timezone'})) {
88 if ($Opt{'timezone'} =~ /^[\+\-][0-2][0-9]{3}$/) {
89 $tz_str = $Opt{'timezone'};
90 } elsif ($Opt{'timezone'} =~ /^z$/i) {
91 $tz_str = $Opt{'timezone'};
92 } elsif ($Opt{'timezone'} =~ /^[a-z]+$/i) {
93 $tz_str = " $Opt{'timezone'}";
94 } else {
95 die("$progname: $Opt{'timezone'}: Invalid time zone\n");
97 $tz_str = uc($tz_str);
100 if ($Opt{'output-format'} eq "xml") {
101 print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpstpic>\n");
104 if ($#ARGV < 0) {
105 while (<>) {
106 chomp();
107 print_entry($_);
109 } else {
110 for my $fname (@ARGV) {
111 print_entry($fname);
115 if ($Opt{'output-format'} eq "xml") {
116 print("</gpstpic>\n");
119 sub print_entry {
120 # {{{
121 my $filename = shift;
122 my $Retval = 0;
123 my ($date, $coor) =
124 ( '', '');
125 my @Dates = ();
126 local *PicFP;
127 D("filename = '$filename'");
128 if (open(PicFP, "exifprobe -L \"$filename\" |")) { # FIXME: Quick & Dirty™
129 while (<PicFP>) {
130 if (/DateTime/) {
131 s/^.*'(.*?)'.*$/$1/;
132 chomp($date = $_);
133 $date =~ s/^(\d\d\d\d)(.)(\d\d)(.)(\d\d)(.)(\d\d:\d\d:\d\d)(.*)/$1-$3-${5}T$7$8/;
134 D("date = '$date'");
135 push(@Dates, $date);
138 close(PicFP);
139 @Dates = reverse sort @Dates;
140 $date = $Dates[0];
141 defined($date) || ($date = '');
142 D("final date = '$date'");
143 if ($date =~ /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d$/) {
144 $filename =~ s/^.*\/(.*?)$/$1/;
145 my $Output = "";
146 if ($Opt{'output-format'} eq "xml") {
147 if (length("$filename$date")) {
148 $Output = join("",
149 "$Spc$Spc<img>\n",
150 length($filename)
151 ? sprintf("$Spc$Spc$Spc$Spc<filename>%s</filename>\n",
152 txt_to_xml($filename))
153 : "",
154 length($date)
155 ? sprintf("$Spc$Spc$Spc$Spc<date>%s</date>\n",
156 txt_to_xml("$date$tz_str"))
157 : "",
158 length($Opt{'description'})
159 ? sprintf("$Spc$Spc$Spc$Spc<desc>%s</desc>\n",
160 txt_to_xml($Opt{'description'}))
161 : "",
162 length($Opt{'author'})
163 ? sprintf("$Spc$Spc$Spc$Spc<author>%s</author>\n",
164 txt_to_xml($Opt{'author'}))
165 : "",
166 "$Spc$Spc</img>\n",
169 } elsif ($Opt{'output-format'} eq "pgtab") {
170 # Version information {{{
171 # Without version field (same as version 1):
172 # date \t
173 # "(lat,lon)"-coordinates \t
174 # description \t
175 # filename \t
176 # author \t
177 # Version 1:
178 # "1" \t
179 # date \t
180 # "(lat,lon)"-coordinates \t
181 # description \t
182 # filename \t
183 # author \n
184 # }}}
185 $Output = pgtab_entry(
186 1, # Version number
187 $date,
188 $coor,
189 $Opt{'description'},
190 $filename,
191 $Opt{'author'}
193 } else {
194 die("$progname: $Opt{'output-format'}: Unknown output format\n");
196 print($Output);
197 $Opt{'verbose'} && print(STDERR $Output);
198 } else {
199 if (length($date)) {
200 warn("$filename: $date: Invalid date format");
201 $Retval = 2;
204 } else {
205 warn("$filename: Cannot open exifprobe(1) pipe: $!");
206 $Retval = 1;
208 return($Retval);
209 # }}}
210 } # print_entry()
212 sub pgtab_entry {
213 # {{{
214 my ($Version, $Date, $Coor, $Descr, $Filename, $Author) = @_;
215 defined($Date) || ($Date = $NA);
216 defined($Coor) || ($Coor = $NA);
217 defined($Descr) || ($Descr = $NA);
218 defined($Filename) || ($Filename = $NA);
219 defined($Author) || ($Author = $NA);
220 my $Retval = "";
221 if ($Version == 1) {
222 $Retval = join("\t",
223 1, # Version number
224 postgresql_copy_safe($Date) . $tz_str,
225 length($Coor)
226 ? postgresql_copy_safe($Coor)
227 : $NA,
228 length($Opt{'description'})
229 ? postgresql_copy_safe($Opt{'description'})
230 : $NA,
231 length($Filename)
232 ? postgresql_copy_safe($Filename)
233 : $NA,
234 length($Opt{'author'})
235 ? postgresql_copy_safe($Opt{'author'})
236 : $NA
237 ) . "\n";
239 return($Retval);
240 # }}}
241 } # pgtab_entry()
243 sub print_version {
244 # Print program version {{{
245 for (@main::version_array) {
246 print("$_\n");
248 # }}}
249 } # print_version()
251 sub usage {
252 # Send the help message to stdout {{{
253 my $Retval = shift;
255 if ($Opt{'verbose'}) {
256 print("\n");
257 print_version();
259 print(<<END);
261 Usage: $progname [options] [file [files [...]]]
263 Extract EXIF info from pictures for use with PostgreSQL's COPY command.
264 If no filenames are specified on the command line, file names are read
265 from stdin.
267 Options:
269 -a, --author x
270 Specify author of picture.
271 -d, --description x
272 Specify description for picture.
273 -h, --help
274 Show this help.
275 -v, --verbose
276 Increase level of verbosity. Can be repeated.
277 -o x, --output-format x
278 Create output of type x:
280 pgtab
281 Default: "$Std{'output-format'}".
282 -T X, --timezone X
283 Prepend X as timezone to the date. Valid formats:
284 UTC offset
285 A '+' or '-' followed by a four-digit number (HHMM) which
286 indicates the offset relative to UTC. Examples:
287 +0000
288 -1600
289 +0630
290 Time zone abbreviation. Examples:
294 -w, --strip-whitespace
295 Strip all unnecessary whitespace.
296 --version
297 Print version information.
298 --debug
299 Print debugging messages.
302 exit($Retval);
303 # }}}
304 } # usage()
306 sub msg {
307 # Print a status message to stderr based on verbosity level {{{
308 my ($verbose_level, $Txt) = @_;
310 if ($Opt{'verbose'} >= $verbose_level) {
311 print(STDERR "$progname: $Txt\n");
313 # }}}
314 } # msg()
316 sub D {
317 # Print a debugging message {{{
318 $Debug || return;
319 my @call_info = caller;
320 chomp(my $Txt = shift);
321 my $File = $call_info[1];
322 $File =~ s#\\#/#g;
323 $File =~ s#^.*/(.*?)$#$1#;
324 print(STDERR "$File:$call_info[2] $$ $Txt\n");
325 return("");
326 # }}}
327 } # D()
329 __END__
331 # Plain Old Documentation (POD) {{{
333 =pod
335 =head1 NAME
339 =head1 REVISION
341 $Id$
343 =head1 SYNOPSIS
345 [options] [file [files [...]]]
347 =head1 DESCRIPTION
351 =head1 OPTIONS
353 =over 4
355 =item B<-h>, B<--help>
357 Print a brief help summary.
359 =item B<-v>, B<--verbose>
361 Increase level of verbosity. Can be repeated.
363 =item B<--version>
365 Print version information.
367 =item B<--debug>
369 Print debugging messages.
371 =back
373 =head1 BUGS
377 =head1 AUTHOR
379 Made by Øyvind A. Holm S<E<lt>sunny@sunbase.orgE<gt>>.
381 =head1 COPYRIGHT
383 Copyleft © Øyvind A. Holm E<lt>sunny@sunbase.orgE<gt>
384 This is free software; see the file F<COPYING> for legalese stuff.
386 =head1 LICENCE
388 This program is free software; you can redistribute it and/or modify it
389 under the terms of the GNU General Public License as published by the
390 Free Software Foundation; either version 2 of the License, or (at your
391 option) any later version.
393 This program is distributed in the hope that it will be useful, but
394 WITHOUT ANY WARRANTY; without even the implied warranty of
395 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
396 See the GNU General Public License for more details.
398 You should have received a copy of the GNU General Public License along
399 with this program; if not, write to the Free Software Foundation, Inc.,
400 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
402 =head1 SEE ALSO
404 =cut
406 # }}}
408 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :
409 # End of file $Id$