postgres/hours.sql: New script to create and update 'hours' table
[gpstools.git] / gpst-pic
blobc9933ff18ec7f2263bf09191e489b9d09986b960
1 #!/usr/bin/env perl
3 #=======================================================================
4 # gpst-pic
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 3 or later, see end of
11 # file for legal stuff.
12 #=======================================================================
14 use strict;
15 use warnings;
16 use Getopt::Long;
18 BEGIN {
19 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
20 our @version_array;
23 use GPST;
24 use GPSTxml;
26 $| = 1;
28 our $Debug = 0;
29 our $NA = '\N';
30 our %Std = (
32 'output-format' => 'pgtab',
33 'timezone' => '',
36 our %Opt = (
38 'author' => '',
39 'debug' => 0,
40 'description' => '',
41 'help' => 0,
42 'output-format' => $Std{'output-format'},
43 'strip-whitespace' => 0,
44 'timezone' => $Std{'timezone'},
45 'verbose' => 0,
46 'version' => 0,
50 our $progname = $0;
51 $progname =~ s/^.*\/(.*?)$/$1/;
52 our $VERSION = "0.00";
54 Getopt::Long::Configure("bundling");
55 GetOptions(
57 "author|a=s" => \$Opt{'author'},
58 "debug" => \$Opt{'debug'},
59 "description|d=s" => \$Opt{'description'},
60 "help|h" => \$Opt{'help'},
61 "output-format|o=s" => \$Opt{'output-format'},
62 "strip-whitespace|w" => \$Opt{'strip-whitespace'},
63 "timezone|T=s" => \$Opt{'timezone'},
64 "verbose|v+" => \$Opt{'verbose'},
65 "version" => \$Opt{'version'},
67 ) || die("$progname: Option error. Use -h for help.\n");
69 $Opt{'debug'} && ($Debug = 1);
70 $Opt{'help'} && usage(0);
71 if ($Opt{'version'}) {
72 print_version();
73 exit(0);
76 $GPST::Spc = $Opt{'strip-whitespace'} ? "" : " ";
77 my $Spc = $GPST::Spc; # FIXME
78 my $tz_str = "";
79 if (length($Opt{'timezone'})) {
80 if ($Opt{'timezone'} =~ /^[\+\-][0-2][0-9]{3}$/) {
81 $tz_str = $Opt{'timezone'};
82 } elsif ($Opt{'timezone'} =~ /^z$/i) {
83 $tz_str = $Opt{'timezone'};
84 } elsif ($Opt{'timezone'} =~ /^[a-z]+$/i) {
85 $tz_str = " $Opt{'timezone'}";
86 } else {
87 die("$progname: $Opt{'timezone'}: Invalid time zone\n");
89 $tz_str = uc($tz_str);
92 if ($Opt{'output-format'} eq "xml") {
93 print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpstpic>\n");
96 if ($#ARGV < 0) {
97 while (<>) {
98 chomp();
99 print_entry($_);
101 } else {
102 for my $fname (@ARGV) {
103 print_entry($fname);
107 if ($Opt{'output-format'} eq "xml") {
108 print("</gpstpic>\n");
111 sub print_entry {
112 # {{{
113 my $filename = shift;
114 my $Retval = 0;
115 my ($date, $coor) =
116 ( '', '');
117 my @Dates = ();
118 D("filename = '$filename'");
119 if (open(my $pic_fp, "exifprobe -L \"$filename\" |")) { # FIXME: Quick & Dirty™
120 while (<$pic_fp>) {
121 if (/DateTime/) {
122 s/^.*'(.*?)'.*$/$1/;
123 chomp($date = $_);
124 $date =~ s/^(\d\d\d\d)(.)(\d\d)(.)(\d\d)(.)(\d\d:\d\d:\d\d)(.*)/$1-$3-${5}T$7$8/;
125 D("date = '$date'");
126 push(@Dates, $date);
129 close($pic_fp);
130 @Dates = reverse sort @Dates;
131 $date = $Dates[0];
132 defined($date) || ($date = '');
133 D("final date = '$date'");
134 if ($date =~ /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d$/) {
135 $filename =~ s/^.*\/(.*?)$/$1/;
136 my $Output = "";
137 if ($Opt{'output-format'} eq "xml") {
138 if (length("$filename$date")) {
139 $Output = join("",
140 "$Spc$Spc<img>\n",
141 length($filename)
142 ? sprintf("$Spc$Spc$Spc$Spc<filename>%s</filename>\n",
143 txt_to_xml($filename))
144 : "",
145 length($date)
146 ? sprintf("$Spc$Spc$Spc$Spc<date>%s</date>\n",
147 txt_to_xml("$date$tz_str"))
148 : "",
149 length($Opt{'description'})
150 ? sprintf("$Spc$Spc$Spc$Spc<desc>%s</desc>\n",
151 txt_to_xml($Opt{'description'}))
152 : "",
153 length($Opt{'author'})
154 ? sprintf("$Spc$Spc$Spc$Spc<author>%s</author>\n",
155 txt_to_xml($Opt{'author'}))
156 : "",
157 "$Spc$Spc</img>\n",
160 } elsif ($Opt{'output-format'} eq "pgtab") {
161 # Version information {{{
162 # Without version field (same as version 1):
163 # date \t
164 # "(lat,lon)"-coordinates \t
165 # description \t
166 # filename \t
167 # author \t
168 # Version 1:
169 # "1" \t
170 # date \t
171 # "(lat,lon)"-coordinates \t
172 # description \t
173 # filename \t
174 # author \n
175 # }}}
176 $Output = pgtab_entry(
177 1, # Version number
178 $date,
179 $coor,
180 $Opt{'description'},
181 $filename,
182 $Opt{'author'}
184 } else {
185 die("$progname: $Opt{'output-format'}: Unknown output format\n");
187 print($Output);
188 $Opt{'verbose'} && print(STDERR $Output);
189 } else {
190 if (length($date)) {
191 warn("$filename: $date: Invalid date format");
192 $Retval = 2;
195 } else {
196 warn("$filename: Cannot open exifprobe(1) pipe: $!");
197 $Retval = 1;
199 return($Retval);
200 # }}}
201 } # print_entry()
203 sub pgtab_entry {
204 # {{{
205 my ($Version, $Date, $Coor, $Descr, $Filename, $Author) = @_;
206 defined($Date) || ($Date = $NA);
207 defined($Coor) || ($Coor = $NA);
208 defined($Descr) || ($Descr = $NA);
209 defined($Filename) || ($Filename = $NA);
210 defined($Author) || ($Author = $NA);
211 my $Retval = "";
212 if ($Version == 1) {
213 $Retval = join("\t",
214 1, # Version number
215 postgresql_copy_safe($Date) . $tz_str,
216 length($Coor)
217 ? postgresql_copy_safe($Coor)
218 : $NA,
219 length($Opt{'description'})
220 ? postgresql_copy_safe($Opt{'description'})
221 : $NA,
222 length($Filename)
223 ? postgresql_copy_safe($Filename)
224 : $NA,
225 length($Opt{'author'})
226 ? postgresql_copy_safe($Opt{'author'})
227 : $NA
228 ) . "\n";
230 return($Retval);
231 # }}}
232 } # pgtab_entry()
234 sub print_version {
235 # Print program version {{{
236 print("$progname v$VERSION\n");
237 # }}}
238 } # print_version()
240 sub usage {
241 # Send the help message to stdout {{{
242 my $Retval = shift;
244 if ($Opt{'verbose'}) {
245 print("\n");
246 print_version();
248 print(<<END);
250 Usage: $progname [options] [file [files [...]]]
252 Extract EXIF info from pictures for use with PostgreSQL's COPY command.
253 If no filenames are specified on the command line, file names are read
254 from stdin.
256 Options:
258 -a, --author x
259 Specify author of picture.
260 -d, --description x
261 Specify description for picture.
262 -h, --help
263 Show this help.
264 -v, --verbose
265 Increase level of verbosity. Can be repeated.
266 -o x, --output-format x
267 Create output of type x:
269 pgtab
270 Default: "$Std{'output-format'}".
271 -T X, --timezone X
272 Prepend X as timezone to the date. Valid formats:
273 UTC offset
274 A '+' or '-' followed by a four-digit number (HHMM) which
275 indicates the offset relative to UTC. Examples:
276 +0000
277 -1600
278 +0630
279 Time zone abbreviation. Examples:
283 -w, --strip-whitespace
284 Strip all unnecessary whitespace.
285 --version
286 Print version information.
287 --debug
288 Print debugging messages.
291 exit($Retval);
292 # }}}
293 } # usage()
295 sub msg {
296 # Print a status message to stderr based on verbosity level {{{
297 my ($verbose_level, $Txt) = @_;
299 if ($Opt{'verbose'} >= $verbose_level) {
300 print(STDERR "$progname: $Txt\n");
302 # }}}
303 } # msg()
305 sub D {
306 # Print a debugging message {{{
307 $Debug || return;
308 my @call_info = caller;
309 chomp(my $Txt = shift);
310 my $File = $call_info[1];
311 $File =~ s#\\#/#g;
312 $File =~ s#^.*/(.*?)$#$1#;
313 print(STDERR "$File:$call_info[2] $$ $Txt\n");
314 return("");
315 # }}}
316 } # D()
318 __END__
320 # Plain Old Documentation (POD) {{{
322 =pod
324 =head1 NAME
328 =head1 SYNOPSIS
330 [options] [file [files [...]]]
332 =head1 DESCRIPTION
336 =head1 OPTIONS
338 =over 4
340 =item B<-h>, B<--help>
342 Print a brief help summary.
344 =item B<-v>, B<--verbose>
346 Increase level of verbosity. Can be repeated.
348 =item B<--version>
350 Print version information.
352 =item B<--debug>
354 Print debugging messages.
356 =back
358 =head1 BUGS
362 =head1 AUTHOR
364 Made by Øyvind A. Holm S<E<lt>sunny@sunbase.orgE<gt>>.
366 =head1 COPYRIGHT
368 Copyleft © Øyvind A. Holm E<lt>sunny@sunbase.orgE<gt>
369 This is free software; see the file F<COPYING> for legalese stuff.
371 =head1 LICENCE
373 This program is free software: you can redistribute it and/or modify it
374 under the terms of the GNU General Public License as published by the
375 Free Software Foundation, either version 3 of the License, or (at your
376 option) any later version.
378 This program is distributed in the hope that it will be useful, but
379 WITHOUT ANY WARRANTY; without even the implied warranty of
380 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
381 See the GNU General Public License for more details.
383 You should have received a copy of the GNU General Public License along
384 with this program.
385 If not, see L<http://www.gnu.org/licenses/>.
387 =head1 SEE ALSO
389 =cut
391 # }}}
393 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :