package Llgal::Config ;
use strict ;
require Llgal::Messages ;
require Llgal::Utils ;
use Getopt::Long ;
use I18N::Langinfo qw(langinfo CODESET) ;
use Locale::gettext ;
use POSIX qw(strftime) ;
use Text::ParseWords ;
######################################################################
# default values that will be restored if the associated option is set to -1
my $index_cellpadding_default = 3 ;
my $pixels_per_row_default = 0 ; # unlimited
my $thumbnail_width_max_default = 113 ;
my $thumbnail_height_max_default = 75 ;
my $thumbnails_per_row_default = 5 ;
my $slide_height_max_default = 0 ; # unlimited
my $slide_width_max_default = 0 ; # unlimited
my $text_slide_width_default = 400 ;
my $text_slide_height_default = 300 ;
######################################################################
# early cmdline parsing
sub early_parse_cmdline_options {
my $self = shift ;
Getopt::Long::Configure('passthrough') ;
GetOptions(
'd=s' => \$self->{destination_dir},
) ;
# restore default behavior: process all options and warn on error
Getopt::Long::Configure('nopassthrough') ;
}
######################################################################
# gettext strings are prefixed to distinguish between identical strings
sub init_llgal_gettext {
my $self = shift ;
textdomain ("llgal") ;
bindtextdomain ("llgal", $self->{locale_dir}) ;
}
sub llgal_gettext {
my $id = shift ;
my $string = gettext ($id) ;
$string =~ s/^[^|]+\|// ;
return $string ;
}
######################################################################
# configuration variables characteristics
# normal option characteristics
my $OPT_IS_NUMERIC = 1 ;
my $OPT_IS_STRING = 2 ;
my $OPT_IS_NONEMPTY_STRING = 3 ;
my $normal_opts_type = {
# Name of generic llgal files
captions_header_filename => $OPT_IS_NONEMPTY_STRING,
css_filename => $OPT_IS_NONEMPTY_STRING,
filmtile_filename => $OPT_IS_NONEMPTY_STRING,
index_link_image_filename => $OPT_IS_NONEMPTY_STRING,
prev_slide_link_image_filename => $OPT_IS_NONEMPTY_STRING,
next_slide_link_image_filename => $OPT_IS_NONEMPTY_STRING,
indextemplate_filename => $OPT_IS_NONEMPTY_STRING,
slidetemplate_filename => $OPT_IS_NONEMPTY_STRING,
# location of generic llgal files that are available on the web
css_location => $OPT_IS_STRING,
filmtile_location => $OPT_IS_STRING,
index_link_image_location => $OPT_IS_STRING,
prev_slide_link_image_location => $OPT_IS_STRING,
next_slide_link_image_location => $OPT_IS_STRING,
# Location and name of generated files
index_filename => $OPT_IS_NONEMPTY_STRING,
slide_filenameprefix => $OPT_IS_STRING,
scaled_image_filenameprefix => $OPT_IS_NONEMPTY_STRING,
thumbnail_image_filenameprefix => $OPT_IS_NONEMPTY_STRING,
captions_filename => $OPT_IS_NONEMPTY_STRING,
user_scaled_image_filenameprefix => $OPT_IS_NONEMPTY_STRING,
user_thumbnail_image_filenameprefix => $OPT_IS_NONEMPTY_STRING,
path_separator_replacement => $OPT_IS_NONEMPTY_STRING,
# Index
index_cellpadding => $OPT_IS_NUMERIC, # >= 0, -1 for default
list_links => $OPT_IS_NUMERIC,
pixels_per_row => $OPT_IS_NUMERIC, # > 0, 0 for unlimited, -1 for default
thumbnails_per_row => $OPT_IS_NUMERIC, # > 0, 0 for unlimited, -1 for default
thumbnail_height_max => $OPT_IS_NUMERIC, # > 0, -1 for default
thumbnail_width_max => $OPT_IS_NUMERIC, # > 0, 0 for unlimited, -1 for default
show_caption_under_thumbnails => $OPT_IS_NUMERIC,
show_film_effect => $OPT_IS_NUMERIC,
# Slides
make_no_slides => $OPT_IS_NUMERIC,
make_slide_filename_from_filename => $OPT_IS_NUMERIC,
make_slide_filename_from_extension => $OPT_IS_NUMERIC,
slide_dimensions_from_css => $OPT_IS_NUMERIC,
slide_width_max => $OPT_IS_NUMERIC, # > 0, 0 for unlimited, -1 for default
slide_height_max => $OPT_IS_NUMERIC, # > 0, 0 for unlimited, -1 for default
text_slide_width => $OPT_IS_NUMERIC, # > 0, -1 for default
text_slide_height => $OPT_IS_NUMERIC, # > 0, -1 for default
index_link_image => $OPT_IS_NUMERIC,
prev_slide_link_image => $OPT_IS_NUMERIC,
next_slide_link_image => $OPT_IS_NUMERIC,
prev_slide_link_preview => $OPT_IS_NUMERIC,
next_slide_link_preview => $OPT_IS_NUMERIC,
link_between_last_and_first => $OPT_IS_NUMERIC,
make_slide_title_from_caption => $OPT_IS_NUMERIC,
show_all_exif_tags => $OPT_IS_NUMERIC,
# Captions
captions_removal_line => $OPT_IS_NONEMPTY_STRING,
make_caption_from_image_comment => $OPT_IS_STRING,
make_caption_from_image_timestamp => $OPT_IS_NUMERIC,
make_caption_from_filename => $OPT_IS_NUMERIC,
make_caption_from_extension => $OPT_IS_NUMERIC,
show_dimensions => $OPT_IS_NUMERIC,
show_size => $OPT_IS_NUMERIC,
slide_counter_format => $OPT_IS_STRING,
# Text
index_title => $OPT_IS_STRING,
index_link_text => $OPT_IS_NONEMPTY_STRING,
parent_gallery_link_text => $OPT_IS_NONEMPTY_STRING,
prev_gallery_link_text => $OPT_IS_STRING,
next_gallery_link_text => $OPT_IS_STRING,
prev_slide_link_text => $OPT_IS_NONEMPTY_STRING,
next_slide_link_text => $OPT_IS_NONEMPTY_STRING,
MVI_link_text => $OPT_IS_STRING,
FIL_link_text => $OPT_IS_STRING,
DIR_link_text => $OPT_IS_STRING,
alt_full_text => $OPT_IS_STRING,
alt_scaled_text => $OPT_IS_STRING,
alt_thumbnail_text => $OPT_IS_STRING,
alt_film_tile_text => $OPT_IS_STRING,
over_scaled_text => $OPT_IS_STRING,
over_thumbnail_text => $OPT_IS_STRING,
over_index_link_text => $OPT_IS_STRING,
over_prev_slide_link_text => $OPT_IS_STRING,
over_next_slide_link_text => $OPT_IS_STRING,
show_size_unit => $OPT_IS_STRING,
timestamp_format_in_caption => $OPT_IS_NONEMPTY_STRING,
credits_text => $OPT_IS_STRING,
# What files to insert
image_extensions => $OPT_IS_STRING,
movie_extensions => $OPT_IS_STRING,
add_all_files => $OPT_IS_NUMERIC,
add_subdirs => $OPT_IS_NUMERIC,
sort_criteria => $OPT_IS_NONEMPTY_STRING,
# Sections
entitle_sections => $OPT_IS_NUMERIC,
separate_sections => $OPT_IS_NUMERIC,
recursive_sections => $OPT_IS_NUMERIC,
# Recursion
parent_gallery_link => $OPT_IS_NUMERIC,
link_subgalleries => $OPT_IS_NUMERIC,
# Various
codeset => $OPT_IS_STRING,
language => $OPT_IS_STRING,
scaled_create_command => $OPT_IS_STRING,
thumbnail_create_command => $OPT_IS_STRING,
force_image_regeneration => $OPT_IS_NUMERIC,
recursive => $OPT_IS_NUMERIC,
www_access_rights => $OPT_IS_NUMERIC,
www_extension => $OPT_IS_NONEMPTY_STRING,
} ;
# options whose input or output is non-trivial:
# options that have a special parsing routine
my $special_opts_with_input = {
local_llgal_dir => 1,
exclude => 1,
include => 1,
additional_configuration_file => 1,
additional_template_dir => 1,
show_exif_tags => 1,
section_dir => 1,
verbose => 1,
} ;
# internal options that have a special outputting routine
my $special_opts_with_output = {
excludes => 1,
template_dirs => 1,
show_exif_tags => 1,
section_dirs => 1,
} ;
# stuff that is not stored in the option hash but need be outputted
my $special_nonopts_with_output = {
local_llgal_dir => 1,
verbose => 1,
} ;
# internal options that are never output
my $special_opts_without_output = {
default_thumb_xdim => 1,
default_thumb_ydim => 1,
} ;
# options whose merging is done as array
my $special_opts_merging_as_array = {
excludes => 1,
template_dirs => 1,
} ;
######################################################################
# manage configuration variables
# overwrite current_opts (1st argument) with overwriting_opts (2nd)
sub merge_opts {
my $current_opts = shift ;
my $overwriting_opts = shift ;
foreach my $key (keys %{$overwriting_opts}) {
if (defined $overwriting_opts->{$key}) {
if (defined $special_opts_merging_as_array->{$key}
and defined $current_opts->{$key}) {
$current_opts->{$key} = [@{$current_opts->{$key}}, @{$overwriting_opts->{$key}}] ;
} else {
$current_opts->{$key} = $overwriting_opts->{$key}
}
}
}
}
# overwrite current_opts with overwriting_opts in a new hash, without changing the originals
sub merge_opts_into {
my $current_opts = shift ;
my $overwriting_opts = shift ;
my $new_opts = {} ;
merge_opts ($new_opts, $current_opts) ;
merge_opts ($new_opts, $overwriting_opts) ;
return $new_opts ;
}
# set default to all uninitialized options
sub add_defaults {
my $current_opts = shift ;
my $default_opts = {
# Name of generic llgal files
captions_header_filename => "captions.header",
css_filename => "llgal.css",
filmtile_filename => "tile.png",
index_link_image_filename => "index.png",
prev_slide_link_image_filename => "prev.png",
next_slide_link_image_filename => "next.png",
indextemplate_filename => "indextemplate.html",
slidetemplate_filename => "slidetemplate.html",
# Location of llgal files if already on the web
css_location => "",
filmtile_location => "",
index_link_image_location => "",
prev_slide_link_image_location => "",
next_slide_link_image_location => "",
# Location and name of generated files
# name of the index file (-i)
index_filename => "index",
# prefix of HTML slide filenames
slide_filenameprefix => "slide_",
# scaled and thumbnail image filename prefix
scaled_image_filenameprefix => "scaled_",
thumbnail_image_filenameprefix => "thumb_",
# captions filename
captions_filename => "captions",
# additional prefix of user-provided scaled images
user_scaled_image_filenameprefix => "my",
# additional prefix of user-provided thumbnails
user_thumbnail_image_filenameprefix => "my",
# character to use to replace / in the thumbnail/scaled of subdir images
path_separator_replacement => "@",
# Index
# cellpadding value for the thumbnail index tables (-p)
index_cellpadding => $index_cellpadding_default,
# list links outside f the table (-L)
list_links => 0,
# how many pixels per row at most in index (--wx)
pixels_per_row => $pixels_per_row_default,
# how many thumbnails per row at most in index (-w)
thumbnails_per_row => $thumbnails_per_row_default,
# max height of a thumbnail (--ty)
thumbnail_height_max => $thumbnail_height_max_default,
# scale thumb longer dimension (--tx)
thumbnail_width_max => $thumbnail_width_max_default,
# write captions under thumbnails on index page (-u)
show_caption_under_thumbnails => 0,
# show the film effect in the index of thumbnails (--fe)
show_film_effect => 0,
# Slides
# make no slides, just thumbnail links to images (-s)
make_no_slides => 0,
# use image names as slide names (-n)
make_slide_filename_from_filename => 0,
# also use image extension in the slide names
make_slide_filename_from_extension => 0,
# do not output dimensions for slides, let the css do it
slide_dimensions_from_css => 0,
# max width of the slides (--sx)
slide_width_max => $slide_width_max_default,
# max height of the slides (--sy)
slide_height_max => $slide_height_max_default,
# default text slide width
text_slide_width => $text_slide_width_default,
# default text slide height
text_slide_height => $text_slide_height_default,
# use an image for link from a slide to the index, previous or next slide
index_link_image => 0,
prev_slide_link_image => 0,
next_slide_link_image => 0,
# use an image preview for link from a slide to the previous or next slide
prev_slide_link_preview => 0,
next_slide_link_preview => 0,
# do not link between last and first slides or galleries
link_between_last_and_first => 1,
# use captions as slide titles (-k)
make_slide_title_from_caption => 0,
# show a table of exif tags
show_exif_tags => [],
# Captions
# this line will be placed in caption generated file
# the user may remove it to prevent llgal from removing the caption file with --clean
captions_removal_line => "REMOVE THIS LINE IF LLGAL SHOULD NOT REMOVE THIS FILE",
# use image comment as captions (--cc)
make_caption_from_image_comment => 0,
# use image timestamp as captions (--ct)
make_caption_from_image_timestamp => 0,
# preserve image names as captions (--cf)
make_caption_from_filename => 0,
# to write image dimensions (-a or --ad)
show_dimensions => 0,
# to write image size (-a or --as)
show_size => 0,
# omit the image slide from the caption (--nc)
slide_counter_format => " (%0n/%t)",
# Text
# title of the gallery (--title)
index_title => llgal_gettext ("index_title|Index of pictures"),
# label of link to the parent gallery
parent_gallery_link_text => llgal_gettext ("parent_gallery_link_text|Back to parent gallery"),
# label of link to previous gallery
prev_gallery_link_text => llgal_gettext ("prev_gallery_link_text|Previous gallery "),
# label of link to previous gallery
next_gallery_link_text => llgal_gettext ("next_gallery_link_text|Next gallery "),
# label of link from slides to index
index_link_text => llgal_gettext ("index_link_text|Index"),
# label of link from a slide to the previous one
prev_slide_link_text => llgal_gettext ("prev_slide_link_text|<<Prev"),
# label of link from a slide to the next one
next_slide_link_text => llgal_gettext ("next_slide_link_text|Next>>"),
# label of text inserted as a section title
section_text => llgal_gettext ("section_text|Section "),
# text prefixing the link text
MVI_link_text => llgal_gettext ("MVI_link_text|Open movie "),
FIL_link_text => llgal_gettext ("FIL_link_text|Download file "),
DIR_link_text => llgal_gettext ("DIR_link_text|Open subgallery "),
# alternative text for full-size images in slides
alt_full_text => llgal_gettext ("alt_full_text|"),
# alternative text for scaled images in slides
alt_scaled_text => llgal_gettext ("alt_scaled_text|Scaled image "),
# alternative text for thumbnails in the index
alt_thumbnail_text => llgal_gettext ("alt_thumbnail_text|Thumbnail "),
# alternative text for film tile
alt_film_tile_text => llgal_gettext ("alt_film_tile_text|Film Tile"),
# text shown when the mouse pointer is over a scaled image in a slide
over_scaled_text => llgal_gettext ("over_scaled_text|Click to see full size "),
# text shown when the mouse pointer is over a thumbnail
over_thumbnail_text => llgal_gettext ("over_thumbnail_text|Click to enlarge "),
# text shown when the mouse pointer is over a link to the index
over_index_link_text => llgal_gettext ("over_index_link_text|Return to the index"),
# text shown when the mouse pointer is over a link to the previous slide
over_prev_slide_link_text => llgal_gettext ("over_prev_slide_link_text|Previous slide "),
# text shown when the mouse pointer is over a link to the next slide
over_next_slide_link_text => llgal_gettext ("over_next_slide_link_text|Next slide "),
# change kB to another unit (--asu)
show_size_unit => llgal_gettext ("show_size_unit|kB"),
# format of the timestamp in captions (--ctf)
timestamp_format_in_caption => llgal_gettext ("timestamp_format_in_caption|%y-%m-%d %H:%M:%S"),
# credits line at the bottom of the index
credits_text => llgal_gettext ("credits_text|created with llgal"),
# What files to insert in the gallery
# image and movie extensions
image_extensions => "jpg|jpeg|png|gif|tif|tiff|bmp",
movie_extensions => "mpg|mpeg|avi|mov|ogm|wmv",
# add all files, not only images and movies (-A)
add_all_files => 0,
# add subdirectories to entry list (-S)
add_subdirs => 0,
# exclude/include
excludes => [],
# sort criteria
sort_criteria => "name",
# Sections
# section directories
section_dirs => [],
# do we add a text slide to entitle sections
entitle_sections => 0,
# do we separate sections in the gallery
separate_sections => 0,
# do we create sections for all subdirectories
recursive_sections => 0,
# Recursion
# add links between subgalleries
link_subgalleries => 0,
# generate header and footer for link to parent gallery
parent_gallery_link => 0,
# Various
# codeset to be set in HTML headers (--codeset)
codeset => langinfo (CODESET ()),
# language to be used for generated text in HTML pages (--lang)
# default is actually useless since we set it before reading the defaults
language => "",
# user-added directories where templates might be found
template_dirs => [],
# commands to generate thumbnails and scaled images
scaled_create_command => "convert -scale x ",
thumbnail_create_command => "convert -scale x ",
# force thumbnails and scaled images regeneration
force_image_regeneration => 0,
# makes everything world-readable (--www)
www_access_rights => 0,
# extension of generate webpages (--php)
www_extension => "html",
} ;
# merge given options _into_ defaults
merge_opts ($default_opts, $current_opts) ;
return $default_opts ;
}
######################################################################
# Create usage now to use defaults values
sub die_usage {
my $self = shift ;
my $usage = << "END_OF_USAGE" ;
This is llgal $self->{version}, the image slide show generator.
Syntax: llgal [-option -option ...]
Behavior Options:
llgal generates a gallery, except with one of the following options:
--clean remove all generated files
--cleanall remove all generated and user modified files
--gc generate or update the captions file
--gt [] give templates to the directory
-h, --help display this brief help
-v, --verbose display notice messages
-V, --version show version
Additional Behavior Options:
The behavior might also be modified with the following options,
either when generating a gallery or not:
--config pass an additional configuration files
-d operate in directory (current directory)
-f force thumbnail and scaled slide regeneration
--gencfg generate the configuration file
--option pass an additional option as in configuration files
-R run recursively in subdirectories
Selecting files:
-A add all non-image non-video files to the list of slides
--exclude exclude matching files
--include include matching files that were excluded
-P add a section with images in subdirectory
--Pall add sections and images from all subdirectories
--Ps separate sections in the index with a line and a title
-S add subdirectories to the list of slides
Layout Options:
-a write image sizes under thumbnails on index page
--ad like -a, but write only the image dimensions
--as like -a, but write only the file size
--asu change file size unit (kB)
--cc [] use image comments as captions
--cf use file names as captions
--ct use image timestamps as captions
--ctf timestamp format in captions
--codeset change the codeset in HTML pages
--exif [] show exif tags on each slide
--fe show a film effect in the indexof thumbnails
-i name of the main thumbnail index file (index)
-k use the image captions for the HTML slide titles
-L list links outside of the table
--lang change the locale
--li use images for links in slides
--lt use thumbnail preview for links in slides
-n use image file names for the HTML slide files
--nc omit the image count from the captions
-p cellpadding value of thumbnail index tables (3)
--parent-gal add links to the parent gallery
--php use php extension for generated webpages
--Rl add links between subgalleries
-s make no HTML slides, link thumbnails to images
--sort sort files with criteria
--templates use templates in
--title title of the index of the gallery (Index of pictures)
--sx limit image width in slides to pixels
--sy limit image height in slides to pixels
--tx limit thumbnail width to pixels (113)
--ty limit thumbnail height to pixels (75)
-u write captions under thumbnails on index page
--uc use a CSS file that is already available on the web
--ui use filmtile and link images that are already available on the web
-w rows in thumbnail index are at most images wide (5)
--www make all llgal files world-readable
--wx rows in thumbnail index are at most pixels wide
Default values are given in parentheses (where applicable).
Author: Brice Goglin
Homepage: http://home.gna.org/llgal
Report bugs to:
END_OF_USAGE
die $usage ;
}
######################################################################
# do not show usage when dying from inside Getoptions
my $no_usage_on_getoptions_error = 0 ;
######################################################################
# parse options in config files
# forward declaration
sub parse_custom_config_file ;
# option parsing global variables
my $recursive_included_configuration_file = 0 ;
my $current_configuration_file = undef ;
# in case of option parsing error
sub process_option_error {
my $error = shift ;
$no_usage_on_getoptions_error = 1 ;
if (defined $current_configuration_file) {
my $file = $current_configuration_file ;
$current_configuration_file = undef ; # reset here since we won't be able to reset anywhere else
die "Line #$. of $file:\n $error.\n" ;
} else {
die "$error.\n" ;
}
}
# process one option line
sub process_option {
my $self = shift ;
my $opts = shift ;
my $messages = $self->{messages} ;
my $line = shift ;
chomp $line ;
if ($line =~ m/^([^ ]+)\s*=.+$/) {
my $optname = $1 ;
my $type = $normal_opts_type->{$optname} ;
if (defined $type) {
# use the options type to choose parsing
if ($type == $OPT_IS_NUMERIC) {
# any match is fine
if ($line =~ m/^[^ ]+\s*=\s*(\d+)$/) {
$opts->{$optname} = $1 ;
} else {
process_option_error "Configuration option <$optname> value must be numeric (in <$line>)" ;
}
} elsif ($type == $OPT_IS_STRING) {
# value must be quoted
if ($line =~ m/^[^ ]+\s*=\s*"(.*)"$/) {
$opts->{$optname} = $1 ;
} else {
process_option_error "Configuration option <$optname> value must be a string (in <$line>)" ;
}
} elsif ($type == $OPT_IS_NONEMPTY_STRING) {
# value must be quoted and non-empty
if ($line =~ m/^[^ ]+\s*=\s*"(.+)"$/) {
$opts->{$optname} = $1 ;
} else {
process_option_error "Configuration option <$optname> value must be a non-empty string (in <$line>)" ;
}
}
} elsif ($special_opts_with_input->{$optname}) {
# not a normal option, must be special
if ($line =~ m/^local_llgal_dir\s*=\s*"(.*)"$/) {
# the local llgal directory name may only be changed
# during early configuration
if ($self->{early_configuration}) {
$self->{local_llgal_dir} = $1 ;
} elsif ($1 ne $self->{local_llgal_dir}) {
$messages->warning ("Ignoring changes to the name of the local llgal directory outside of") ;
$messages->warning ("system- and user-wide configuration files.") ;
}
} elsif ($line =~ m/^exclude\s*=\s*"(.+)"$/) {
my $entry = () ;
$entry->{excluded} = 1 ;
$entry->{filter} = $1 ;
push (@{$opts->{excludes}}, $entry) ;
} elsif ($line =~ m/^exclude\s*=\s*"(.+)"$/) {
my $entry = () ;
$entry->{excluded} = 0 ;
$entry->{filter} = $1 ;
push (@{$opts->{excludes}}, $entry) ;
} elsif ($line =~ m/^additional_configuration_file\s*=\s*"(.+)"$/) {
my $file = $1 ;
if ($recursive_included_configuration_file++ >= 10) {
$no_usage_on_getoptions_error = 1 ;
die "Not opening '$file' since too much configuration files were recursively included, loop detected ?\n" ;
}
my $saved_current_configuration_file = $current_configuration_file ;
parse_custom_config_file $self, $opts, $file ;
$current_configuration_file = $saved_current_configuration_file ;
$recursive_included_configuration_file-- ;
} elsif ($line =~ m/^additional_template_dir\s*=\s*"(.+)"$/) {
push (@{$opts->{template_dirs}}, $1) ;
} elsif ($line =~ m/^show_exif_tags\s*=\s*"(.*)"$/) {
push (@{$opts->{show_exif_tags}}, split (/,/, $1));
} elsif ($line =~ m/^section_dir\s*=\s*"(.+)"$/) {
push (@{$opts->{section_dirs}}, $1) ;
} elsif ($line =~ m/^verbose\s*=\s*(\d+)$/) {
$messages->{verbose} = $1 ;
} else {
process_option_error "Unrecognized configuration option line <$line>" ;
}
} else {
process_option_error "Unknown configuration option <$optname>" ;
}
} else {
process_option_error "Configuration must be given = (was $line)" ;
}
return $opts ;
}
sub parse_generic_config_file {
my $self = shift ;
my $messages = $self->{messages} ;
my $opts = {} ;
my $file = shift ;
# TODO: remove this one day (maybe on march 14th 2006, since it will be 6 month ?)
# warn on obsolete file
my $oldconffile = $file ;
$oldconffile =~ s@$self->{local_llgal_dir}/llgalrc$@.llgalrc@ ;
$oldconffile =~ s@llgal/llgalrc$@llgalrc@ ;
$messages->warning ("Obsolete configuration file $oldconffile skipped, should be moved to $file.")
if -e $oldconffile ;
# generic configuration files may not exist
if (open CONF, $file) {
$current_configuration_file = $file ;
while () {
process_option $self, $opts, $_
if (/^[^#]/ and !/^(\s*)$/) ;
}
$current_configuration_file = undef ;
close CONF ;
}
return $opts ;
}
sub parse_custom_config_file {
my $self = shift ;
my $opts = shift ;
my $file = shift ;
my $messages = $self->{messages} ;
# don't use a capitalized file handle since it would be shared across recursive calls
if (open my $conf, $file) {
$messages->notice ("Reading additional configuration file '$file'...\n");
$current_configuration_file = $file ;
while (<$conf>) {
process_option $self, $opts, $_
if (/^[^#]/ and !/^(\s*)$/) ;
}
$current_configuration_file = undef ;
close $conf ;
} else {
# custom configuration files have to exist
$no_usage_on_getoptions_error = 1 ;
die "Failed to open additional configuration file '$file' ($!).\n" ;
}
}
######################################################################
# process command-line arguments (overriding defaults above)
sub parse_cmdline_options {
my $self = shift ;
my $opts = {} ;
if (!GetOptions(
# behaviors
'clean' => \$self->{clean_asked},
'cleanall' => \$self->{cleanall_asked},
'gc' => \$self->{generate_captions},
'gt:s' => sub {
shift ; my $value = shift ;
$value = "local" if !$value ;
$self->{give_templates} = $value ;
},
'v|verbose' => \$self->{messages}->{verbose},
'h|help' => \$self->{help_asked},
'V|version' => \$self->{version_asked},
'f' => \$opts->{force_image_regeneration},
'gencfg=s' => \$self->{generate_config},
'config=s' => sub { shift ; parse_custom_config_file $self, $opts, shift ; },
'R' => \$opts->{recursive},
# layout
'A' => \$opts->{add_all_files},
'a' => sub { $opts->{show_dimensions} = $opts->{show_size} = 1 ; },
'ad' => \$opts->{show_dimensions},
'as' => \$opts->{show_size},
'asu=s' => \$opts->{show_size_unit},
'cc:s' => sub {
shift ; my $value = shift ;
if ($value eq "") { $opts->{make_caption_from_image_comment} = "std,exif" ; }
elsif ($value eq "0") { $opts->{make_caption_from_image_comment} = "" ; }
else { $opts->{make_caption_from_image_comment} = $value ; }
},
'cf' => \$opts->{make_caption_from_filename},
'ct' => \$opts->{make_caption_from_image_timestamp},
'ctf=s' => \$opts->{timestamp_format_in_caption},
'codeset=s' => \$opts->{codeset},
'exif:s' => sub {
shift ; my $value = shift ;
if ($value eq "") { $opts->{show_all_exif_tags} = 1 ; }
else { push (@{$opts->{show_exif_tags}}, split (/,/, $value)) ; }
},
'fe' => \$opts->{show_film_effect},
'i=s' => \$opts->{index_filename},
'k' => \$opts->{make_slide_title_from_caption},
'L' => \$opts->{list_links},
'lang=s' => \$opts->{language},
'li' => sub { $opts->{index_link_image} = 1 ; $opts->{prev_slide_link_image} = 1 ; $opts->{next_slide_link_image} = 1 ; },
'lt' => sub { $opts->{prev_slide_link_preview} = 1 ; $opts->{next_slide_link_preview} = 1 ; },
'n' => \$opts->{make_slide_filename_from_filename},
'nc' => sub { $opts->{slide_counter_format} = "" ; },
'option=s' => sub { shift ; process_option $self, $opts, shift ; },
'P=s' => \@{$opts->{section_dirs}},
'Pall' => \$opts->{recursive_sections},
'Ps' => sub { $opts->{entitle_sections} = 1 ; $opts->{separate_sections} = 1 ; },
'p=i' => \$opts->{index_cellpadding},
'php' => sub { $opts->{www_extension} = "php" ; },
'Rl' => \$opts->{link_subgalleries},
'S' => \$opts->{add_subdirs},
's' => \$opts->{make_no_slides},
'sort=s' => \$opts->{sort_criteria},
'templates=s' => \@{$opts->{template_dirs}},
'title=s' => \$opts->{index_title},
'sx=i' => \$opts->{slide_width_max},
'sy=i' => \$opts->{slide_height_max},
'tx=i' => \$opts->{thumbnail_width_max},
'ty=i' => \$opts->{thumbnail_height_max},
'u' => \$opts->{show_caption_under_thumbnails},
'uc=s' => \$opts->{css_location},
'ui=s' => sub { shift ; my $url = shift ;
$opts->{filmtile_location} = $url ;
$opts->{index_link_image_location} = $url ;
$opts->{prev_slide_link_image_location} = $url ;
$opts->{next_slide_link_image_location} = $url ;
},
'w=i' => \$opts->{thumbnails_per_row},
'www' => \$opts->{www_access_rights},
'wx=i' => \$opts->{pixels_per_row},
'exclude=s' => sub {
shift ;
my $entry = () ;
$entry->{filter} = shift ;
$entry->{excluded} = 1 ;
push @{$opts->{excludes}}, $entry ;
},
'include=s' => sub {
shift ;
my $entry = () ;
$entry->{filter} = shift ;
$entry->{excluded} = 0 ;
push @{$opts->{excludes}}, $entry ;
},
'parent-gal' => \$opts->{parent_gallery_link},
)) {
# there has been an error during command line parsing
if ($no_usage_on_getoptions_error) {
# the error has already been displayed
exit -1 ;
} else {
die_usage ($self) ;
}
}
return $opts ;
}
######################################################################
# check and defaults
# check whether an argument is an integer
sub is_integer {
my $s = shift ;
return $s eq int($s) ;
}
# various common checks and definitions
sub prepare_common_variables {
my $self = shift ;
my $opts = shift ;
# check a few string that have to be non-empty and may need to be single file without path
die "Please give a non-empty directory name without path as a local llgal directory\n"
if $self->{local_llgal_dir} eq "" or $self->{local_llgal_dir} =~ m@/@ ;
}
# various checks and definitions to process the captions
sub prepare_captions_variables {
my $self = shift ;
my $opts = shift ;
prepare_common_variables $self, $opts ;
# check a few string that have to be non-empty and may need to be single file without path
die "Please give a non-empty filename without path as a caption filename\n"
if $opts->{captions_filename} eq "" or $opts->{captions_filename} =~ m@/@ ;
die "Please give a non-empty caption removal line\n"
if $opts->{captions_removal_line} eq "" ;
}
# various check and definitions to create the gallery
sub prepare_gallery_variables {
my $self = shift ;
my $opts = shift ;
my $messages = $self->{messages} ;
prepare_captions_variables $self, $opts ;
# check a few string that have to be non-empty and may need to be single file without path
die "Please give a non-empty filename without path as a CSS filename\n"
if $opts->{css_filename} eq "" or $opts->{css_filename} =~ m@/@ ;
die "Please give a non-empty filename without path as a film tile filename\n"
if $opts->{filmtile_filename} eq "" or $opts->{filmtile_filename} =~ m@/@ ;
die "Please give a non-empty filename without path as a index link image filename\n"
if $opts->{index_link_image_filename} eq "" or $opts->{index_link_image_filename} =~ m@/@ ;
die "Please give a non-empty filename without path as a previous slide link image filename\n"
if $opts->{prev_slide_link_image_filename} eq "" or $opts->{prev_slide_link_image_filename} =~ m@/@ ;
die "Please give a non-empty filename without path as a next slide link image filename\n"
if $opts->{next_slide_link_image_filename} eq "" or $opts->{next_slide_link_image_filename} =~ m@/@ ;
die "Please give a non-empty filename without path as a index template filename\n"
if $opts->{indextemplate_filename} eq "" or $opts->{indextemplate_filename} =~ m@/@ ;
die "Please give a non-empty filename without path as a slide template filename\n"
if $opts->{slidetemplate_filename} eq "" or $opts->{slidetemplate_filename} =~ m@/@ ;
die "Please give a non-empty string without path as a slide filename prefix\n"
if $opts->{scaled_image_filenameprefix} eq "" or $opts->{scaled_image_filenameprefix} =~ m@/@ ;
die "Please give a non-empty string without path as a thumbnail filename prefix\n"
if $opts->{thumbnail_image_filenameprefix} eq "" or $opts->{thumbnail_image_filenameprefix} =~ m@/@ ;
die "Please give a non-empty filename without path as a index filename\n"
if $opts->{index_filename} eq "" or $opts->{index_filename} =~ m@/@ ;
die "Please give a non-empty string without path as a user-given scaled image prefix\n"
if $opts->{user_scaled_image_filenameprefix} eq "" or $opts->{user_scaled_image_filenameprefix} =~ m@/@ ;
die "Please give a non-empty string without path as a user-given thumbnail image prefix\n"
if $opts->{user_thumbnail_image_filenameprefix} eq "" or $opts->{user_thumbnail_image_filenameprefix} =~ m@/@ ;
# css location
if ($opts->{css_location} =~ m@^(.*/)$@) {
$opts->{css_location} .= $opts->{css_filename} ;
}
# images location
if ($opts->{filmtile_location} =~ m@^(.*)/$@) {
$opts->{filmtile_location} .= $opts->{filmtile_filename} ;
}
if ($opts->{index_link_image_location} =~ m@^(.*/)$@) {
$opts->{index_link_image_location} .= $opts->{index_link_image_filename} ;
}
if ($opts->{prev_slide_link_image_location} =~ m@^(.*/)$@) {
$opts->{prev_slide_link_image_location} .= $opts->{prev_slide_link_image_filename} ;
}
if ($opts->{next_slide_link_image_location} =~ m@^(.*/)$@) {
$opts->{next_slide_link_image_location} .= $opts->{next_slide_link_image_filename} ;
}
# cannot have --sx/sy and -s
die "Please choose between --sx/--sy and -s\n"
if ($opts->{slide_height_max} or $opts->{slide_width_max}) and $opts->{make_no_slides} ;
# thumbnail_width_max must be > 0 or 0 for unlimited
die "Please give an integer value for thumbnail width max\n"
unless is_integer ($opts->{thumbnail_width_max}) ;
if ($opts->{thumbnail_width_max} < 0) {
$messages->notice ("Thumbnail width max value < 0, restoring to default (".
($thumbnail_width_max_default?$thumbnail_width_max_default:"unlimited") .")\n") ;
$opts->{thumbnail_width_max} = $thumbnail_width_max_default ;
}
die "Please give a positive thumbnail width max value (or 0 for unlimited)\n"
unless $opts->{thumbnail_width_max} >= 0 ;
# thumbnail_height_max must be > 0
die "Please give an integer value for thumbnail height max\n"
unless is_integer ($opts->{thumbnail_height_max}) ;
if ($opts->{thumbnail_height_max} < 0) {
$messages->notice ("Thumbnail height max value < 0, restoring to default ($thumbnail_height_max_default)\n") ;
$opts->{thumbnail_height_max} = $thumbnail_height_max_default ;
}
die "Please give a positive thumbnail height max value\n"
unless $opts->{thumbnail_height_max} > 0 ;
# thumbnails_per_row must be > 0 or 0 for unlimited
die "Please give an integer value for thumbnails per row\n"
unless is_integer ($opts->{thumbnails_per_row}) ;
if ($opts->{thumbnails_per_row} < 0) {
$messages->notice ("Thumbnails per row value < 0, restoring to default (".
($thumbnails_per_row_default?$thumbnails_per_row_default:"unlimited") .")\n") ;
$opts->{thumbnails_per_row} = $thumbnails_per_row_default ;
}
die "Please give a positive thumbnails per row value (or 0 for unlimited)\n"
unless $opts->{thumbnails_per_row} >= 0 ;
# pixels_per_row must be > 0 or 0 for unlimited
die "Please give an integer value for pixels per row\n"
unless is_integer ($opts->{pixels_per_row}) ;
if ($opts->{pixels_per_row} < 0) {
$messages->notice ("Pixels per row value < 0, restoring to default (".
($pixels_per_row_default?$pixels_per_row_default:"unlimited") .")\n") ;
$opts->{pixels_per_row} = $pixels_per_row_default ;
}
die "Please give a positive pixels per row value (or 0 for unlimited)\n"
unless $opts->{pixels_per_row} >= 0 ;
# index_cellpadding must be >= 0
die "Please give an integer value for index cellpadding\n"
unless is_integer ($opts->{index_cellpadding}) ;
if ($opts->{index_cellpadding} < 0) {
$messages->notice ("Index cellpadding value < 0, restoring to default ($index_cellpadding_default)\n") ;
$opts->{index_cellpadding} = $index_cellpadding_default ;
}
die "Please give a positive or null index cellpadding value\n"
unless $opts->{index_cellpadding} >= 0 ;
# text_slide_width must be > 0
die "Please give an integer value for text slide width\n"
unless is_integer ($opts->{text_slide_width}) ;
if ($opts->{text_slide_width} < 0) {
$messages->notice ("Text slide width value < 0, restoring to default ($text_slide_width_default)\n") ;
$opts->{text_slide_width} = $text_slide_width_default ;
}
die "Please give a positive text slide width value\n"
unless $opts->{text_slide_width} > 0 ;
# text_slide_height must be > 0
die "Please give an integer value for text slide height\n"
unless is_integer ($opts->{text_slide_height}) ;
if ($opts->{text_slide_height} < 0) {
$messages->notice ("Text slide height value < 0, restoring to default ($text_slide_height_default)\n") ;
$opts->{text_slide_height} = $text_slide_height_default ;
}
die "Please give a positive text slide height value\n"
unless $opts->{text_slide_height} > 0 ;
# slide_width_max must be > 0, 0 for unlimited
die "Please give an integer value for slide width max\n"
unless is_integer ($opts->{slide_width_max}) ;
if ($opts->{slide_width_max} < 0) {
$messages->notice ("Slide width max value < 0, restoring to default (".
($slide_width_max_default?$slide_width_max_default:"unlimited") .")\n") ;
$opts->{slide_width_max} = $slide_width_max_default ;
}
die "Please give a positive slide width max value (or 0 for unlimited)\n"
unless $opts->{slide_width_max} >= 0 ;
# slide_height_max must be > 0, 0 for unlimited
die "Please give an integer value for slide height max\n"
unless is_integer ($opts->{slide_height_max}) ;
if ($opts->{slide_height_max} < 0) {
$messages->notice ("Slide height max value < 0, restoring to default (".
($slide_height_max_default?$slide_height_max_default:"unlimited") .")\n") ;
$opts->{slide_height_max} = $slide_height_max_default ;
}
die "Please give a positive slide height max value (or 0 for unlimited)\n"
unless $opts->{slide_height_max} >= 0 ;
# either pixels or thumbnails per row must be limited
die "Please limit pixels or thumbnails per row\n"
unless $opts->{pixels_per_row} > 0 or $opts->{thumbnails_per_row} > 0 ;
# adapt text slide width and height in case of --sx/sy
if ($opts->{slide_height_max} > 0) {
$opts->{text_slide_width} = $opts->{text_slide_width} / $opts->{text_slide_height} * $opts->{slide_height_max} ;
$opts->{text_slide_height} = $opts->{slide_height_max} ;
}
if ($opts->{slide_width_max} > 0 and $opts->{text_slide_width} > $opts->{slide_width_max}) {
$opts->{text_slide_height} = $opts->{text_slide_height} / $opts->{text_slide_width} * $opts->{slide_width_max} ;
$opts->{text_slide_width} = $opts->{slide_width_max} ;
}
# thumbnail default size (for text and link slide especially)
if ($opts->{thumbnail_width_max} > 0) {
($opts->{default_thumb_xdim}, $opts->{default_thumb_ydim}) = ($opts->{thumbnail_width_max}, $opts->{thumbnail_height_max}) ;
} else {
($opts->{default_thumb_xdim}, $opts->{default_thumb_ydim}) = ($opts->{thumbnail_height_max} * 4/3, $opts->{thumbnail_height_max}) ;
}
# convert options for thumbnails
my $thumb_MAXH = $opts->{thumbnail_height_max} ;
my $thumb_MAXW = $opts->{thumbnail_width_max} > 0 ? $opts->{thumbnail_width_max} : "" ;
$opts->{thumbnail_create_command} =~ s//$thumb_MAXH/g ;
$opts->{thumbnail_create_command} =~ s//$thumb_MAXW/g ;
# convert options for slides
my $scaled_MAXH = $opts->{slide_height_max} > 0 ? $opts->{slide_height_max} : "" ;
my $scaled_MAXW = $opts->{slide_width_max} > 0 ? $opts->{slide_width_max} : "" ;
$opts->{scaled_create_command} =~ s//$scaled_MAXH/g ;
$opts->{scaled_create_command} =~ s//$scaled_MAXW/g ;
}
#######################################################################
# If --gencfg was invoked, generate a configuration file
sub generate_config {
my $self = shift ;
my $file = shift ;
my $opts = shift ;
my $messages = $self->{messages} ;
$messages->print ("Generating config file '$file'.\n") ;
if (-e "$file") {
my $old_file = "$file.save.". (strftime('%Y-%m-%d_%H:%M:%S' ,localtime)) ;
$messages->warning ("Renaming old configuration file '$file' as '$old_file'") ;
rename "$file", "$old_file" ;
}
open NEWCFG, ">$file"
or die "Cannot open $file ($!).\n" ;
print NEWCFG "# This is a llgal configuration file.\n" ;
print NEWCFG "# It was automatically generated on ". (scalar localtime) .".\n" ;
print NEWCFG "# You may modify and reuse it as you want.\n" ;
print NEWCFG "\n" ;
for my $optname (sort (keys %{$normal_opts_type}, keys %{$special_opts_with_output}, keys %{$special_nonopts_with_output})) {
if (defined $special_nonopts_with_output->{$optname}) {
if ($optname eq "local_llgal_dir") {
print NEWCFG "local_llgal_dir = \"$self->{local_llgal_dir}\"\n" ;
} elsif ($optname eq "verbose") {
print NEWCFG "verbose = $messages->{verbose}\n" ;
} else {
die "Unknown non-options $optname to output.\n" ;
}
} elsif (defined $special_opts_with_output->{$optname}) {
next if not defined $opts->{$optname} ;
# special options that need special output
if ($optname eq "show_exif_tags") {
print NEWCFG "show_exif_tags = \"". (join (",", @{$opts->{show_exif_tags}})) ."\"\n" ;
} elsif ($optname eq "template_dirs") {
if (@{$opts->{template_dirs}}) {
map {
print NEWCFG "additional_template_dir = \"$_\"\n" ;
} @{$opts->{template_dirs}} ;
}
} elsif ($optname eq "excludes") {
if (@{$opts->{excludes}}) {
map {
print NEWCFG ($_->{excluded} ? "exclude" : "include"). " = \"". $_->{filter} ."\"\n" ;
} @{$opts->{excludes}} ;
}
} elsif ($optname eq "section_dirs") {
if (@{$opts->{section_dirs}}) {
map {
print NEWCFG "section_dir = \"$_\"\n" ;
} @{$opts->{section_dirs}} ;
}
} else {
die "Unknown special internal option $optname to output.\n" ;
}
} else {
next if not defined $opts->{$optname} ;
# normal options
my $type = $normal_opts_type->{$optname} ;
if (defined $type) {
if ($type == $OPT_IS_NUMERIC) {
print NEWCFG "$optname = $opts->{$optname}\n" ;
} elsif ($type == $OPT_IS_STRING or $type == $OPT_IS_NONEMPTY_STRING) {
print NEWCFG "$optname = \"$opts->{$optname}\"\n" ;
} else {
die "Unknown normal option type $type.\n" ;
}
} else {
die "Unknown ge option $optname to output.\n" ;
}
}
}
print NEWCFG "\n" ;
close NEWCFG ;
}
1 ;