Album
view release on metacpan or search on metacpan
script/album view on Meta::CPAN
}
}
elsif ( $f =~ /^(.+)\.$movpat$/i ) {
$el->type(T_MPG);
my $assoc = $1."s.jpg";
$el->assoc_name($assoc);
if ( @files && $files[0] eq $assoc ) {
shift(@files);
warn(d_large($assoc).": Skipped still\n") if $verbose;
}
}
$gotlist->add($el, $f);
}
}
sub load_import {
my $dh = do { local *DH; *DH; };
opendir($dh, $import_dir)
or die("Cannot opendir $import_dir: $!\n");
my @files = sort grep { !/^\./ && /$suffixpat$/ } readdir($dh);
closedir($dh);
while ( @files ) {
my $f = shift(@files);
next unless -f fjoin($import_dir, $f);
my $el = new ImageInfo(fjoin($import_dir, $f));
if ( $import_exif ) {
shift(@files) if handle_exif($f, $files[0], $el);
}
else {
$el->type(T_JPG);
if ( $f =~ /^(.+)\.$movpat$/i ) {
$el->type(T_MPG);
$el->assoc_name($1."s.jpg");
}
$implist->add($el, $f);
}
}
}
sub handle_exif {
my ($file, $next, $el) = @_;
# Sony DSC-V1 produces the following files:
# DSC0nnnn.JPG still image
# DSC0nnnn.JPE mail mode image*
# DSC0nnnn.MPG voice mode image*
# DSC0nnnn.TIF uncompressed image*
# CLP0nnnn.GIF clip motion file
# CLP0nnnn.HTM clip motion file index
# MBL0nnnn.GIF clip motion file, mobile mode
# MBL0nnnn.HTM clip motion file index, mobile mode
# MOV0nnnn.MPG movie
# Files marked with * have a normal still image associated.
# Normal still image.
if ( $file =~ /^(.{4})(\d{4})\.($picpat)$/i ) {
my ($type, $seq, $ext) = ($1, $2, $3);
my $fd = $el->DateTime || "";
if ( $fd =~ /(\d\d\d\d):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)/ ) {
my $time = timelocal($6,$5,$4,$3,$2-1,$1);
my $new = "$1$2$3$4$5$6$seq";
my $ii = cache_entry("$new.$ext");
if ( $ii && !$ii->orig_name ) {
$ii->orig_name(fjoin($import_dir, $file));
}
$el->type(T_JPG);
$el->dest_name("$new.$ext");
$el->timestamp($time);
$file = "$new.$ext";
cache_entry($file, $el) unless $ii;
}
else {
warn(fjoin($import_dir, $file).": Missing or unparsable file date [$fd]\n")
if $verbose;
$el->type(T_JPG);
}
if ( $next && $next eq "$type$seq.mpg" ) {
warn(fjoin($import_dir, $file).": Changed to VOICE\n") if $verbose;
$el->type(T_VOICE);
(my $t = $file) =~ s/\.jpg$/.mp3/i;
$el->assoc_name($t);
$implist->add($el);
return 1;
}
$implist->add($el);
}
# MPEG movie.
elsif ( $file =~ /^(.{4})(\d{4})\.($movpat)$/i ) {
my ($type, $seq, $ext) = ($1, $2, $3);
# We have to trust the file date...
my $time = $el->timestamp;
my @tm = localtime($time);
my $new = sprintf("%04d%02d%02d%02d%02d%02d$seq",
1900+$tm[5], 1+$tm[4], @tm[3,2,1,0]);
my $ii = cache_entry("$new.$ext");
if ( $ii && !$ii->orig_name ) {
$ii->orig_name(fjoin($import_dir, $file));
}
$el->type(T_MPG);
$el->dest_name("$new.$ext");
$el->assoc_name($new."s.jpg");
$implist->add($el, "$new.$ext");
$file = "$new.$ext";
cache_entry($file, $el) unless $ii;
}
# Assume ordinary JPEG or some picture.
elsif ( $file =~ /^.*$picpat$/) {
$el->type(T_JPG);
$el->orig_name(fjoin($import_dir, $file));
$el->dest_name($file);
$implist->add($el, $file);
}
# Assume ordinary MPEG or some movie.
script/album view on Meta::CPAN
return '' unless $t;
$t =~ s/\n+/$br/go;
$t;
}
sub indent($$) {
# Shift contents to the right so it fits pretty.
my ($t, $n) = @_;
$n = " " x $n;
return $n unless $t;
$t = detab($t);
$t =~ s/\n+$//;
$t =~ s/\n/\n$n/g;
$t;
}
sub img($%) {
my ($file, %atts) = @_;
my $ret = "<img src='" . $file . "'";
foreach ( sort(keys(%atts)) ) {
$ret .= " $_='" . $atts{$_} . "'";
}
$ret . ">";
}
#### Size helpers.
sub bytes($) {
my $t = shift;
return $t . "b" if $t < 10*1024;
return ($t >> 10) . "kb" if $t < 10*1024*1024;
($t >> 20) . "Mb";
}
sub size_info($;$) {
my ($el, $med) = @_;
return unless $el->width;
my $ret = "";
$ret .= $el->width . "x" . $el->height if $el->width;
for ( $med ? $el->medium_size : $el->file_size ) {
next unless $_;
$ret .= "," if $ret;
$ret .= bytes($_);
}
$ret;
}
#### EXIF helpers.
sub restyle_exif($) {
my ($el) = @_;
my $ret = "";
my $v;
my $app = sub {
$ret .= "<tr><td>".htmln($_[0])."</td>".
"<td>".htmln($_[1])."</td></tr>\n";
};
$app->("Date", $v) if $v = $el->DateTime;
my $t = $el->ExposureTime || 0;
if ( $t && $t <= 0.5 ) {
$t = "1/".int(0.5 + 1/$t)."s";
}
$app->("Exposure",
join(" ", $el->ExposureMode || "",
$el->ExposureProgram || "", $t));
$app->("Aperture", sprintf("%.1f", $v))
if $v = $el->FNumber;
if ( $v = $el->FocalLength ) {
if ( $el->Model eq "DSC-V1" ) {
$v .= sprintf("mm (%.1fmm equiv.)", $v*4.857);
}
else {
$v .= "mm";
}
$app->("Focal length", $v);
}
$app->("ISO", $v) if $v = $el->ISOSpeedRatings;
$app->("Flash", $v)
if ($v = $el->Flash) && $v ne "Flash did not fire";
$app->("Metering", $v) if $v = $el->MeteringMode;
$app->("Scene", $v) if $v = $el->SceneCaptureType;
$app->("Camera",
join(" ", $v, $el->Model))
if $v = $el->Make;
}
#### Caption helpers.
sub f_caption($) {
my ($el) = @_;
my $s = htmln($el->dest_name);
if ( $el->Make ) {
$s = " $s<a href='#' class='info'> <span>".
"<table border='1' width='100%'>\n".
restyle_exif($el) . "</table>\n".
"</span></a>";
}
$s;
}
sub s_caption($) {
my ($el) = @_;
size_info($el, $medium);
}
sub t_caption($) {
my ($el) = @_;
$el->tag ? htmln($el->tag) : "";
}
sub c_caption($) {
my ($el) = @_;
my $t = $el->description || "";
$t =~ s/\n.*//;
htmln($t);
}
#### Misc.
script/album view on Meta::CPAN
if ( $import_dir ) {
die("$import_dir: Not a directory\n")
unless -d $import_dir;
$import_dir =~ s;^\./;;;
}
}
sub app_ident {
print STDERR ("This is $my_package [$my_name $my_version]\n");
}
sub app_usage {
my ($exit) = @_;
app_ident();
print STDERR heredoc(<<" EndOfUsage", 4);
Usage: $0 [options] [ directory ]
Album:
--info XXX description file, default "@{[DEFAULTS->{info}]}" (if it exists)
--title XXX album title, default "@{[DEFAULTS->{title}]}"
--[no]icon [do not] produce an album icon
Index:
--cols NN number of columns per page, default @{[DEFAULTS->{indexcols}]}
--rows NN number of rows per page, default @{[DEFAULTS->{indexrows}]}
--thumbsize NNN the max size of thumbnail images, default @{[DEFAULTS->{thumbsize}]}
--captions XXX f: filename s: size c: description t: tag
Medium:
--medium produce medium sized images of size @{[DEFAULTS->{mediumsize}]}
--mediumsize NNN the max size of medium sized images, default @{[DEFAULTS->{mediumsize}]}
--mediumonly ignore large images and links (for web export)
Importing:
--import XXX original images
--exif use w/ EXIF info, if possible
--dcim XXX as --import with --exif
--update add new entries from import, if needed
--[no]link [do not] link to original, instead of copying. Default is link.
Miscellaneous:
--clobber recreate everything (except large)
--test verify only
--help this message
--ident show identification
--verbose verbose information
EndOfUsage
exit $exit if defined $exit && $exit != 0;
}
################ Modules ################
package ImageInfo;
my @std_fields;
my @exif_fields;
my $exif_rot;
INIT {
@std_fields = qw(type seq next prev
dest_name orig_name assoc_name
timestamp file_size medium_size
tag description annotation
rotation mirror);
@exif_fields = qw(DateTime ExifImageLength ExifImageWidth
ExposureMode ExposureProgram ExposureTime
FNumber Flash FocalLength ISOSpeedRatings
ImageDescription Make Model
MeteringMode SceneCaptureType Orientation
height width file_ext);
$exif_rot = { top_left => [ 0, '' ], # 1: no corr. needed
top_right => [ 0, 'v' ], # 2: flop (V)
bot_right => [ 180, '' ], # 3: 180
bot_left => [ 0, 'h' ], # 4: flip (H)
left_top => [ 90, 'h' ], # 5: flip 90
right_top => [ 90, '' ], # 6: 90
right_bot => [ 90, 'v' ], # 7: flop 90
left_bot => [ 270, '' ], # 8: 270
};
}
my $largepat;
sub basename_nolarge {
my ($f) = @_;
unless ( $largepat ) {
$largepat = quotemeta(::d_large());
$largepat = qr;^$largepat[/\\];;
}
$f =~ s;$largepat;;;
$f;
}
sub new {
my ($pkg, $file) = @_;
$pkg = ref($pkg) if ref($pkg);
my $self = { $file ?
(orig_name => $file,
dest_name => basename_nolarge($file)) : (),
description => "",
annotation => [],
tag => "",
};
if ( $file && -f $file ) {
my @st = stat(_);
my $ii = ::cache_entry($file);
if ( $ii ){
$self = $ii;
delete($self->{$_}) foreach grep { /^_/ } keys(%$self);
}
# Else, get image info.
else {
my $ii = Image::Info::image_info($file);
$self->{file_size} = $st[7];
$self->{timestamp} = $st[9];
unless ( exists($ii->{error}) ) {
for my $key ( @exif_fields ) {
my $val = $ii->{$key};
next unless defined $val;
if ( $key eq "Orientation" ) {
($self->{rotation}, $self->{mirror}) =
@{$exif_rot->{$val}}
( run in 1.628 second using v1.01-cache-2.11-cpan-140bd7fdf52 )