#!/usr/bin/perl use strict; use warnings; use Image::ExifTool; use Getopt::Long; use Pod::Usage; use Panotools::Script; my $path_makefile; my $d_inc = 15; my $projection; my $deg_fov; my $crop_s; my $help = 0; my $verbose = 0; my $nostacks = 0; my @argv_save = @ARGV; GetOptions ('o|output=s' => \$path_makefile, 't|time=s' => \$d_inc, 'f|projection=i' => \$projection, 'v|fov=s' => \$deg_fov, 'k|selection=s' => \$crop_s, 'l|loquacious' => \$verbose, 'n|nostacks' => \$nostacks, 'h|help' => \$help); pod2usage (-verbose => 2) if $help; pod2usage (2) unless scalar @ARGV > 1; $path_makefile = 'Makefile' unless defined $path_makefile; my $groups = []; my $group_tmp = []; my $previous_time; my $previous_inc = 0; for my $path_photo (@ARGV) { my $exif_info = Image::ExifTool::ImageInfo ($path_photo); my $datetime = $exif_info->{'FileModificationDateTime'}; $datetime = $exif_info->{'DateTimeOriginal'} if (defined $exif_info->{'DateTimeOriginal'}); my $time_unix = Image::ExifTool::GetUnixTime ($datetime); $previous_time = $time_unix unless (defined $previous_time); my $inc = $time_unix - $previous_time; if (($inc - $previous_inc) > $d_inc) { push (@{$groups}, $group_tmp); $group_tmp = []; } push @{$group_tmp}, $path_photo; $previous_time = $time_unix; $previous_inc = $inc; } push (@{$groups}, $group_tmp); my $rule_ptos = new File::Pto::Mk::Rule; $rule_ptos->Targets ('pto'); my $rule_mks = new File::Pto::Mk::Rule; $rule_mks->Targets ('mk'); my $rule_imgs = new File::Pto::Mk::Rule; $rule_imgs->Targets ('images'); my $rule_movs = new File::Pto::Mk::Rule; $rule_movs->Targets ('qtvr'); my @rules_pto; my @rules_mk; my @rules_ldr_blended; my @rules_ldr_stacked_blended; my @rules_hdr_blended; my @rules_ldr_blended_equirect; my @rules_ldr_stacked_blended_equirect; for my $group (@{$groups}) { next if scalar @{$group} == 1; my $a = $group->[0]; my $b = $group->[-1]; $a =~ s/\.[[:alnum:]]+$//; $b =~ s/\.[[:alnum:]]+$//; $b =~ s/.*[\/\\]//; my $stub = "$a-$b"; $rule_ptos->Prerequisites ("$stub.pto"); $rule_mks->Prerequisites ("$stub.pto.mk"); my $path_img; my $path_mov; if (is_stacks (@{$group})) { $path_img = $stub ."_fused.tif"; $path_mov = $stub ."_fused.mov"; } else { $path_img = $stub .".tif"; $path_mov = $stub .".mov"; } $rule_imgs->Prerequisites ($path_img); $rule_movs->Prerequisites ($path_mov); print STDERR "$path_img: ". scalar @{$group} ." images\n" if $verbose; my $rule_pto = new File::Pto::Mk::Rule; $rule_pto->Targets ("$stub.pto"); $rule_pto->Prerequisites (@{$group}); my @stacks = '--stacks'; @stacks = () if $nostacks; $rule_pto->Command ('match-n-shift', @stacks, '--align', '$(AP_EXTRA_ARGS)', '--output', "$stub.pto"); $rule_pto->Command ('--projection', $projection) if defined $projection; $rule_pto->Command ('--fov', $deg_fov) if defined $deg_fov; $rule_pto->Command ('--selection', $crop_s) if defined $crop_s; $rule_pto->Command (@{$group}); push @rules_pto, $rule_pto; my $rule_mk = new File::Pto::Mk::Rule; $rule_mk->Targets ("$stub.pto.mk"); $rule_mk->Prerequisites ("$stub.pto"); $rule_mk->Command ('pto2mk', '-o', "$stub.pto.mk", '-p', $stub, "$stub.pto"); push @rules_mk, $rule_mk; my $rule_ldr_blended = new File::Pto::Mk::Rule; $rule_ldr_blended->Targets ($stub .".tif"); $rule_ldr_blended->Prerequisites ("$stub.pto", "$stub.pto.mk", @{$group}); $rule_ldr_blended->Command ('make', '-e', '-f', "$stub.pto.mk", $stub .".tif", '$(MAKE_EXTRA_ARGS)'); push @rules_ldr_blended, $rule_ldr_blended; my $rule_ldr_stacked_blended = new File::Pto::Mk::Rule; $rule_ldr_stacked_blended->Targets ($stub ."_fused.tif"); $rule_ldr_stacked_blended->Prerequisites ("$stub.pto", "$stub.pto.mk", @{$group}); $rule_ldr_stacked_blended->Command ('make', '-e', '-f', "$stub.pto.mk", $stub ."_fused.tif", '$(MAKE_EXTRA_ARGS)'); push @rules_ldr_stacked_blended, $rule_ldr_stacked_blended; my $rule_ldr_blended_equirect = new File::Pto::Mk::Rule; $rule_ldr_blended_equirect->Targets ("$stub.mov", "$stub-sky.jpg", "$stub-planet.jpg", "$stub-mercator.jpg"); $rule_ldr_blended_equirect->Prerequisites ("$stub.pto", "$stub.pto.mk", "$stub.tif"); $rule_ldr_blended_equirect->Command ('make', '-e', '-f', '$(HUGINDATADIR)/Makefile.equirect.mk', 'equirect_all', 'equirect_clean', "PTO=$stub.pto", 'FUSED_SUFFIX='); push @rules_ldr_blended_equirect, $rule_ldr_blended_equirect; my $stub_f = $stub .'_fused'; my $rule_ldr_stacked_blended_equirect = new File::Pto::Mk::Rule; $rule_ldr_stacked_blended_equirect->Targets ("$stub_f.mov", "$stub_f-sky.jpg", "$stub_f-planet.jpg", "$stub_f-mercator.jpg"); $rule_ldr_stacked_blended_equirect->Prerequisites ("$stub.pto", "$stub.pto.mk", "$stub_f.tif"); $rule_ldr_stacked_blended_equirect->Command ('make', '-e', '-f', '$(HUGINDATADIR)/Makefile.equirect.mk', 'equirect_all', 'equirect_clean', "PTO=$stub.pto", 'FUSED_SUFFIX=_fused'); push @rules_ldr_stacked_blended_equirect, $rule_ldr_stacked_blended_equirect; my $rule_hdr_blended = new File::Pto::Mk::Rule; $rule_hdr_blended->Targets ($stub ."_hdr.exr"); $rule_hdr_blended->Prerequisites ("$stub.pto", "$stub.pto.mk", @{$group}); $rule_hdr_blended->Command ('make', '-e', '-f', "$stub.pto.mk", $stub ."_hdr.exr", '$(MAKE_EXTRA_ARGS)'); push @rules_hdr_blended, $rule_hdr_blended; } my $rule_all = new File::Pto::Mk::Rule; $rule_all->Targets ('all'); $rule_all->Prerequisites ('images'); my $rule_phony = new File::Pto::Mk::Rule; $rule_phony->Targets ('.PHONY'); $rule_phony->Prerequisites (qw/all images pto mk/); my $rule_self = new File::Pto::Mk::Rule; $rule_self->Targets ($path_makefile); $rule_self->Prerequisites (@ARGV); $rule_self->Command ($0, @argv_save); open MAKE, ">", $path_makefile or die "cannot write-open $path_makefile"; print MAKE "# Created by Panotools::Script $Panotools::Script::VERSION\n"; print MAKE $rule_phony->Assemble; print MAKE $rule_all->Assemble; print MAKE $rule_ptos->Assemble; print MAKE $rule_mks->Assemble; print MAKE $rule_imgs->Assemble; print MAKE $rule_movs->Assemble; print MAKE "MAKE_EXTRA_ARGS=clean\n"; print MAKE "HUGINDATADIR=/usr/share/hugin\n"; print MAKE "AP_EXTRA_ARGS=--clean\n\n"; print MAKE map {$_->Assemble} @rules_pto; print MAKE map {$_->Assemble} @rules_mk; print MAKE map {$_->Assemble} @rules_ldr_blended; print MAKE map {$_->Assemble} @rules_ldr_stacked_blended; print MAKE map {$_->Assemble} @rules_ldr_blended_equirect; print MAKE map {$_->Assemble} @rules_ldr_stacked_blended_equirect; print MAKE map {$_->Assemble} @rules_hdr_blended; print MAKE $rule_self->Assemble; close MAKE; sub is_stacks { return 0 if $nostacks; my $speeds = {}; for my $path_photo (@_) { my $exif_info = Image::ExifTool::ImageInfo ($path_photo, 'ExposureTime', 'ShutterSpeed'); my $et = $exif_info->{ExposureTime} || $exif_info->{ShutterSpeed} || 0; $speeds->{$et} = 'TRUE'; } my $brackets = scalar keys (%{$speeds}); return 0 if (scalar (@_) % $brackets); return 0 if ($brackets < 2); return 1; } package File::Pto::Mk::Rule; use strict; use warnings; sub new { my $class = shift; $class = ref $class || $class; my $self = bless {targets => [], prerequisites => [], command => []}, $class; return $self; } sub Assemble { my $self = shift; return 0 unless scalar @{$self->{targets}}; my $text; $text .= join ' ', (map { _quoteshell ($_)} @{$self->{targets}}); $text .= ' : '; $text .= join ' ', (map { _quoteshell ($_)} @{$self->{prerequisites}}); $text .= "\n\t" if scalar @{$self->{command}}; $text .= join ' ', (map { _quoteshell ($_)} @{$self->{command}}); $text .= "\n\n"; return $text; } sub Targets { my $self = shift; push @{$self->{targets}}, @_; } sub Prerequisites { my $self = shift; push @{$self->{prerequisites}}, @_; } sub Command { my $self = shift; push @{$self->{command}}, @_; } sub _quoteshell { my $string = shift; $string =~ s/(['"\[\] `&*+~#:|])/\\$1/g; return $string; } __END__ =head1 NAME panostart - identify likely panorama sequences =head1 SYNOPSIS panostart [options] --output Makefile image1 image2 [...] Options: -o | --output name Filename of created Makefile -t | --time Number of seconds between panoramas -f | --projection Panotools style input projection number. Use 0 for rectilinear, 2 for circular fisheye and 3 for full-frame fisheye images. -v | --fov Horizontal field of view in degrees -k | --selection Crop selection boundary, eg -459,2459,-57,2861 -n | --nostacks Don't try and align stacks for point and shoot cameras -h | --help Outputs help documentation. -l | --loquacious Verbose output listing targets and numbers of images. =head1 DESCRIPTION B takes a list of image files and creates a Makefile containing rules to generate panoramas from the images. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. =head1 SEE ALSO L =head1 AUTHOR Bruno Postle - August 2008. =cut