#!/usr/bin/perl -w

use FileHandle ;
use Getopt::Long ;
use Image::Size ;
use Image::Info qw(image_info) ;
use POSIX qw(strftime) ;
use Time::Local ;
use URI::Escape ;
use Text::ParseWords ;
use Cwd ;
use I18N::Langinfo qw(langinfo CODESET) ;
use Locale::gettext ;
use POSIX qw (setlocale) ;

use strict ;
no strict "refs" ;

Getopt::Long::Configure('noignorecase', 'noautoabbrev', 'bundling') ;
STDOUT->autoflush("1") ;

my $version = "0.12.1" ;

######################################################################
# store command-line options upfront to write in the index <HEAD>
my @llgal_options = @ARGV ;

######################################################################
# a few routines that are required early

# split at each space, except between quotes
sub parse_convert_options {
    my $line = shift ;
    return parse_line (' +', 0, $line) ;
}

# join with spaces, and protect spaces
sub join_convert_options {
    return join (' ', map { my $val = $_ ; $val =~ s/( +)/\'$1\'/g ; $val } @_) ;
}

######################################################################
# configuration files

my @generic_config_files = () ;
# system-wide configuration file
push (@generic_config_files, "/etc/llgal/llgalrc") ;
# user-wide configuration file
push (@generic_config_files, $ENV{HOME}."/.llgal/llgalrc") ;

# destination configuration file
my $destination_config_file = ".llgal/llgalrc" ;

######################################################################
# early configuration that have to be now

# additional configuration files
my @additional_config_files = () ;
# look in current directory "."
my $destination_dir = "." ;

Getopt::Long::Configure('passthrough') ;
GetOptions(
	'config=s'	=> \@additional_config_files,
	'd=s'	=> \$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

setlocale (LC_MESSAGES, "") ;
textdomain ("llgal") ;
bindtextdomain ("llgal", "/usr/share/locale") ;

sub llgal_gettext {
    my $id = shift ;
    my $string = gettext ($id) ;
    $string =~ s/^[^|]+\|// ;
    return $string ;
}

######################################################################
# codeset

my $codeset = langinfo(CODESET()) ;

######################################################################
# general command-line options

# show version
my $version_asked = 0 ;
# displays brief usage message
my $help_asked = 0 ;
# clean up all generated files
my $clean_asked = 0 ;
# clean up all generated and user modified files
my $cleanall_asked = 0 ;
# generate (or update) captions
my $generate_captions = 0 ;
# give templates to the given directory
my $give_templates = undef ;

# Special behavior options
# force thumbnails and scaled images regeneration
my $force_image_regeneration = 0 ;
# generate a config file
my $generate_config = undef ;
# do we run recursively in subdirectories ?
my $make_recursive = 0 ;

######################################################################
# 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 $filmtile_height_default = 21 ;
my $thumbnail_width_max_default = 0 ; # unlimited
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 ;

######################################################################
# all configuration variables

my $opts = {
# llgal directories
    llgal_share_dir => "/usr/share/llgal",
    user_share_dir => $ENV{HOME}."/.llgal",
# user-added directories where templates might be found
    template_dirs => [],

# 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
# local llgal directory
    local_llgal_dir => ".llgal",
# name of the index file (-i)
    index_filename => "index",
# prefix of HTML slide filenames
    slide_filenameprefix => "",
# prefix of HTML slide filenames when generated from filename
# while there's no file associated (text, link, ...)
    slide_filenameprefix_nofile => "NOFILE",
# 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",

# 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 (--xy or -y)
    thumbnail_height_max => $thumbnail_height_max_default,
# scale thumb longer dimension (--xy)
    thumbnail_width_max => $thumbnail_width_max_default,
# write captions under thumbnails on index page (-u)
    show_caption_under_thumbnails => 0,
# omit the film effect altogether (-r)
    show_no_film_effect => 0,
# height of the tile.png tiled image (-t)
    filmtile_height => $filmtile_height_default,

# 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,
# max width of the slides (--bigxy)
    slide_width_max => $slide_width_max_default,
# max height of the slides (--bigxy or --bigy)
    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,
# use captions as slide titles (-k)
    make_slide_title_from_caption => 0,

# 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)
    show_no_slide_counter => 0,

# 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|&lt;&lt;Prev"),
# label of link from a slide to the next one
    next_slide_link_text => llgal_gettext ("next_slide_link_text|Next&gt;&gt;"),
# 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 "),
# 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 <a href=\"http://home.gna.org/llgal\">llgal</a>"),

# Recursion
# add links between subgalleries
    link_subgalleries => 0,
# generate header and footer for link to parent gallery
    parent_gallery_link => 0,
# generate header and footer for link to previous subgallery
    prev_gallery_link => 0,
    prev_gallery_link_target => "",
# generate header and footer for link to next subgallery
    next_gallery_link => 0,
    next_gallery_link_target => "",
# add indentation to the begining of messages
    indent => "",

# 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",

# Various
# options to be passed to convert (--con)
    convert_options => [],
    scaled_convert_options => [],
    thumbnail_convert_options => [],
# makes everything world-readable (--www)
    www_access_rights => 0,
# extension of generate webpages (--php)
    www_extension => "html",

} ;

######################################################################
# Create usage now to use defaults values

my $usage = << "END_OF_USAGE" ;
This is llgal $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 [<dir>]       give templates to the directory <dir>
    -h, --help         displays this brief help
    -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 <s>       pass an additional configuration files
    -d <dir>           operate on files in directory <dir> ($destination_dir)
    -f                 force thumbnail and scaled slide regeneration
    --gencfg <file>    generate the configuration file <file>
    --option <s>       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 <s>      exclude matching files
    --include <s>      include matching files that were excluded
    -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 (in kbytes)
    --asu <s>          change file size unit ($opts->{show_size_unit})
    --bigxy <n>        like --xy, use it if you have very large image files
    --bigy <n>         like -y, use it if you have very large image files
    --cc [<s>]         use image comments as captions
    --cf               use file names as captions
    --ct               use image timestamps as captions
    --ctf <s>          timestamp format in captions (strftime format)
    --con <s>          options to pass to convert (e.g. -quality N)
    -i <file>          name of the main thumbnail index file ($opts->{index_filename})
    -k                 use the image captions for the HTML slide titles
    -L                 list links outside of the table
    --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 <n>             cellpadding value of thumbnail index tables ($opts->{index_cellpadding})
    --php              use php extension for generated webpages
    -r                 omit the film effect altogether
    --Rl               add links between subgalleries
    -s                 make no HTML slides, link thumbnails to images
    --sort <s>         sort files with criteria <s>
    -t <n>             height of the film tiled image ($opts->{filmtile_height})
    --templates <dir>  use templates in <dir>
    --title <s>        title of the index of the gallery ($opts->{index_title})
    -u                 write captions under thumbnails on index page
    --uc <url>         use a CSS file that is already available on the web
    --ui <url>         use filmtile and link images that are already available on the web
    -w <n>             rows in thumbnail index are at most <n> images wide ($opts->{thumbnails_per_row})
    --www              make all llgal files world-readable
    --wx <n>           rows in thumbnail index are at most <n> pixels wide
    --xy <n>           scale thumbs to <n> pixels in their longer dimension
    -y <n>             scale all thumbnails to the same height <n> ($opts->{thumbnail_height_max})
  Default values are given in parentheses (where applicable).
Author:  Brice Goglin
Homepage:  http://home.gna.org/llgal
Report bugs to:  <llgal-users AT gna.org>
END_OF_USAGE

######################################################################
# die showing something else than usage

my $die_without_GetOptions_usage = 0 ;
sub die_without_GetOptions_usage {
    $die_without_GetOptions_usage = 1 ;
    die shift ;
}

sub GetOptions_failed {
    if ($die_without_GetOptions_usage) {
	# the error has already been displayed
	exit -1 ;
    } else {
	die $usage ;
    }
}

######################################################################
# parse options in config files

sub process_option {
    my $opts = shift ;
    $_ = shift ;
    chomp $_ ;
# llgal directories
    if (/^llgal_share_dir\s*=\s*"(.+)"$/) {
	$opts->{llgal_share_dir} = $1 ;
    } elsif (/^user_share_dir\s*=\s*"(\/.+)"$/) {
	$opts->{user_share_dir} = $1 ;
    } elsif (/^user_share_dir\s*=\s*"(.+)"$/) {
	$opts->{user_share_dir} = $ENV{HOME}."/".$1 ;
    } elsif (/^template_dir\s*=\s*"(.+)"$/) {
	push (@{$opts->{template_dirs}}, $1) ;
# Name of generic llgal files
    } elsif (/^captions_header_filename\s*=\s*"(.+)"$/) {
	$opts->{captions_header_filename} = $1 ;
    } elsif (/^css_filename\s*=\s*"(.+)"$/) {
	$opts->{css_filename} = $1 ;
    } elsif (/^filmtile_filename\s*=\s*"(.+)"$/) {
	$opts->{filmtile_filename} = $1 ;
    } elsif (/^index_link_image_filename\s*=\s*"(.+)"$/) {
	$opts->{index_link_image_filename} = $1 ;
    } elsif (/^prev_slide_link_image_filename\s*=\s*"(.+)"$/) {
	$opts->{prev_slide_link_image_filename} = $1 ;
    } elsif (/^next_slide_link_image_filename\s*=\s*"(.+)"$/) {
	$opts->{next_slide_link_image_filename} = $1 ;
    } elsif (/^indextemplate_filename\s*=\s*"(.+)"$/) {
	$opts->{indextemplate_filename} = $1 ;
    } elsif (/^slidetemplate_filename\s*=\s*"(.+)"$/) {
	$opts->{slidetemplate_filename} = $1 ;
# location of generic llgal files that are available on the web
    } elsif (/^css_location\s*=\s*"(.*)"$/) {
	$opts->{css_location} = $1 ;
    } elsif (/^filmtile_location\s*=\s*"(.*)"$/) {
	$opts->{filmtile_location} = $1 ;
    } elsif (/^index_link_image_location\s*=\s*"(.*)"$/) {
	$opts->{index_link_image_location} = $1 ;
    } elsif (/^prev_slide_link_image_location\s*=\s*"(.*)"$/) {
	$opts->{prev_slide_link_image_location} = $1 ;
    } elsif (/^next_slide_link_image_location\s*=\s*"(.*)"$/) {
	$opts->{next_slide_link_image_location} = $1 ;
# Location and name of generated files
    } elsif (/^local_llgal_dir\s*=\s*"(.+)"$/) {
	$opts->{local_llgal_dir} = $1 ;
    } elsif (/^index_filename\s*=\s*"(.+)"$/) {
	$opts->{index_filename} = $1 ;
    } elsif (/^slide_filenameprefix\s*=\s*"(.*)"$/) {
	$opts->{slide_filenameprefix} = $1 ;
    } elsif (/^slide_filenameprefix_nofile\s*=\s*"(.+)"$/) {
	$opts->{slide_filenameprefix_nofile} = $1 ;
    } elsif (/^scaled_image_filenameprefix\s*=\s*"(.+)"$/) {
	$opts->{scaled_image_filenameprefix} = $1 ;
    } elsif (/^thumbnail_image_filenameprefix\s*=\s*"(.+)"$/) {
	$opts->{thumbnail_image_filenameprefix} = $1 ;
    } elsif (/^captions_filename\s*=\s*"(.+)"$/) {
	$opts->{captions_filename} = $1 ;
    } elsif (/^user_scaled_image_filenameprefix\s*=\s*"(.+)"$/) {
	$opts->{user_scaled_image_filenameprefix} = $1 ;
    } elsif (/^user_thumbnail_image_filenameprefix\s*=\s*"(.+)"$/) {
	$opts->{user_thumbnail_image_filenameprefix} = $1 ;
# Index
    } elsif (/^index_cellpadding\s*=\s*(.+)$/) { # >= 0, -1 for default
	$opts->{index_cellpadding} = $1 ;
    } elsif (/^list_links\s*=\s*(.+)$/) {
	$opts->{list_links} = $1 ;
    } elsif (/^pixels_per_row\s*=\s*(.+)$/) { # > 0, 0 for unlimited, -1 for default
	$opts->{pixels_per_row} = $1 ;
    } elsif (/^thumbnails_per_row\s*=\s*(.+)$/) { # > 0, 0 for unlimited, -1 for default
	$opts->{thumbnails_per_row} = $1 ;
    } elsif (/^thumbnail_height_max\s*=\s*(.+)$/) { # > 0, -1 for default
	$opts->{thumbnail_height_max} = $1 ;
    } elsif (/^thumbnail_width_max\s*=\s*(.+)$/) { # > 0, 0 for unlimited, -1 for default
	$opts->{thumbnail_width_max} = $1 ;
    } elsif (/^show_caption_under_thumbnails\s*=\s*(.+)$/) {
	$opts->{show_caption_under_thumbnails} = $1 ;
    } elsif (/^show_no_film_effect\s*=\s*(.+)$/) {
	$opts->{show_no_film_effect} = $1 ;
    } elsif (/^filmtile_height\s*=\s*(.+)$/) { # >= 0, -1 for default
	$opts->{filmtile_height} = $1 ;
# Slides
    } elsif (/^make_no_slides\s*=\s*(.+)$/) {
	$opts->{make_no_slides} = $1 ;
    } elsif (/^make_slide_filename_from_filename\s*=\s*(.+)$/) {
	$opts->{make_slide_filename_from_filename} = $1 ;
    } elsif (/^make_slide_filename_from_extension\s*=\s*(.+)$/) {
	$opts->{make_slide_filename_from_extension} = $1 ;
    } elsif (/^slide_width_max\s*=\s*(.+)$/) { # > 0, 0 for unlimited, -1 for default
	$opts->{slide_width_max} = $1 ;
    } elsif (/^slide_height_max\s*=\s*(.+)$/) { # > 0, 0 for unlimited, -1 for default
	$opts->{slide_height_max} = $1 ;
    } elsif (/^text_slide_width\s*=\s*(.+)$/) { # > 0, -1 for default
	$opts->{text_slide_width} = $1 ;
    } elsif (/^text_slide_height\s*=\s*(.+)$/) { # > 0, -1 for default
	$opts->{text_slide_height} = $1 ;
    } elsif (/^index_link_image\s*=\s*(.+)$/) {
	$opts->{index_link_image} = $1 ;
    } elsif (/^prev_slide_link_image\s*=\s*(.+)$/) {
	$opts->{prev_slide_link_image} = $1 ;
    } elsif (/^next_slide_link_image\s*=\s*(.+)$/) {
	$opts->{next_slide_link_image} = $1 ;
    } elsif (/^prev_slide_link_preview\s*=\s*(.+)$/) {
	$opts->{prev_slide_link_preview} = $1 ;
    } elsif (/^next_slide_link_preview\s*=\s*(.+)$/) {
	$opts->{next_slide_link_preview} = $1 ;
    } elsif (/^make_slide_title_from_caption\s*=\s*(.+)$/) {
	$opts->{make_slide_title_from_caption} = $1 ;
# Captions
    } elsif (/^captions_removal_line\s*=\s*"(.+)"$/) {
	$opts->{captions_removal_line} = $1 ;
    } elsif (/^make_caption_from_image_comment\s*=\s*"(.*)"$/) {
	$opts->{make_caption_from_image_comment} = $1 ;
    } elsif (/^make_caption_from_image_timestamp\s*=\s*(.+)$/) {
	$opts->{make_caption_from_image_timestamp} = $1 ;
    } elsif (/^make_caption_from_filename\s*=\s*(.+)$/) {
	$opts->{make_caption_from_filename} = $1 ;
    } elsif (/^show_dimensions\s*=\s*(.+)$/) {
	$opts->{show_dimensions} = $1 ;
    } elsif (/^show_size\s*=\s*(.+)$/) {
	$opts->{show_size} = $1 ;
    } elsif (/^show_no_slide_counter\s*=\s*(.+)$/) {
	$opts->{show_no_slide_counter} = $1 ;
# Text
    } elsif (/^index_title\s*=\s*"(.*)"$/) {
	$opts->{index_title} = $1 ;
    } elsif (/^index_link_text\s*=\s*"(.+)"$/) {
	$opts->{index_link_text} = $1 ;
    } elsif (/^parent_gallery_link_text\s*=\s*"(.+)"$/) {
	$opts->{parent_gallery_link_text} = $1 ;
    } elsif (/^prev_gallery_link_text\s*=\s*"(.*)"$/) {
	$opts->{prev_gallery_link_text} = $1 ;
    } elsif (/^next_gallery_link_text\s*=\s*"(.*)"$/) {
	$opts->{next_gallery_link_text} = $1 ;
    } elsif (/^prev_slide_link_text\s*=\s*"(.+)"$/) {
	$opts->{prev_slide_link_text} = $1 ;
    } elsif (/^next_slide_link_text\s*=\s*"(.+)"$/) {
	$opts->{next_slide_link_text} = $1 ;
    } elsif (/^MVI_link_text\s*=\s*"(.*)"$/) {
	$opts->{MVI_link_text} = $1 ;
    } elsif (/^FIL_link_text\s*=\s*"(.*)"$/) {
	$opts->{FIL_link_text} = $1 ;
    } elsif (/^DIR_link_text\s*=\s*"(.*)"$/) {
	$opts->{DIR_link_text} = $1 ;
    } elsif (/^alt_full_text\s*=\s*"(.*)"$/) {
	$opts->{alt_full_text} = $1 ;
    } elsif (/^alt_scaled_text\s*=\s*"(.*)"$/) {
	$opts->{alt_scaled_text} = $1 ;
    } elsif (/^alt_thumbnail_text\s*=\s*"(.*)"$/) {
	$opts->{alt_thumbnail_text} = $1 ;
    } elsif (/^over_scaled_text\s*=\s*"(.*)"$/) {
	$opts->{over_scaled_text} = $1 ;
    } elsif (/^over_thumbnail_text\s*=\s*"(.*)"$/) {
	$opts->{over_thumbnail_text} = $1 ;
    } elsif (/^over_index_link_text\s*=\s*"(.*)"$/) {
	$opts->{over_index_link_text} = $1 ;
    } elsif (/^over_prev_slide_link_text\s*=\s*"(.*)"$/) {
	$opts->{over_prev_slide_link_text} = $1 ;
    } elsif (/^over_next_slide_link_text\s*=\s*"(.*)"$/) {
	$opts->{over_next_slide_link_text} = $1 ;
    } elsif (/^show_size_unit\s*=\s*"(.*)"$/) {
	$opts->{show_size_unit} = $1 ;
    } elsif (/^timestamp_format_in_caption\s*=\s*"(.+)"$/) {
	$opts->{timestamp_format_in_caption} = $1 ;
    } elsif (/^credits_text\s*=\s*"(.*)"$/) {
	$opts->{credits_text} = $1 ;
# Recursion
    } elsif (/^make_recursive\s*=\s*(.+)$/) {
	$make_recursive = $1 ;
    } elsif (/^parent_gallery_link\s*=\s*(.+)$/) {
	$opts->{parent_gallery_link} = $1 ;
    } elsif (/^prev_gallery_link\s*=\s*(.+)$/) {
	$opts->{prev_gallery_link} = $1 ;
    } elsif (/^prev_gallery_link_target\s*=\s*"(.*)"$/) {
	$opts->{prev_gallery_link_target} = $1 ;
    } elsif (/^next_gallery_link\s*=\s*(.+)$/) {
	$opts->{next_gallery_link} = $1 ;
    } elsif (/^next_gallery_link_target\s*=\s*"(.*)"$/) {
	$opts->{next_gallery_link_target} = $1 ;
    } elsif (/^link_subgalleries\s*=\s*(.+)$/) {
	$opts->{link_subgalleries} = $1 ;
# What files to insert
    } elsif (/^image_extensions\s*=\s*"(.*)"$/) {
	$opts->{image_extensions} = $1 ;
    } elsif (/^movie_extensions\s*=\s*"(.*)"$/) {
	$opts->{movie_extensions} = $1 ;
    } elsif (/^add_all_files\s*=\s*(.+)$/) {
	$opts->{add_all_files} = $1 ;
    } elsif (/^add_subdirs\s*=\s*(.+)$/) {
	$opts->{add_subdirs} = $1 ;
    } elsif (/^exclude\s*=\s*"(.+)"$/) {
	my $entry = () ;
	$entry->{excluded} = 1 ;
	$entry->{filter} = $1 ;
	push (@{$opts->{excludes}}, $entry) ;
    } elsif (/^include\s*=\s*"(.+)"$/) {
	my $entry = () ;
	$entry->{excluded} = 0 ;
	$entry->{filter} = $1 ;
	push (@{$opts->{excludes}}, $entry) ;
    } elsif (/^sort_criteria\s*=\s*"(.+)"$/) {
	$opts->{sort_criteria} = $1 ;
# Various
    } elsif (/^convert_options\s*=\s*"(.*)"$/) {
	push (@{$opts->{convert_options}}, parse_convert_options ($1)) ;
    } elsif (/^scaled_convert_options\s*=\s*"(.*)"$/) {
	push (@{$opts->{scaled_convert_options}}, parse_convert_options ($1)) ;
    } elsif (/^thumbnail_convert_options\s*=\s*"(.*)"$/) {
	push (@{$opts->{thumbnail_convert_options}}, $1) ;
    } elsif (/^force_image_regeneration\s*=\s*(.*)$/) {
	$force_image_regeneration = $1 ;
    } elsif (/^www_access_rights\s*=\s*(.+)$/) {
	$opts->{www_access_rights} = $1 ;
    } elsif (/^www_extension\s*=\s*"(.+)"$/) {
	$opts->{www_extension} = $1 ;
    } elsif (/^additional_configuration_file\s*=\s*"(.+)"$/) {
	push (@{$opts->{additional_config_files}}, parse_convert_options ($1)) ;
# Error
    } elsif (/^[^#]/ and !/^(\s*)$/) {
	if (defined $.) {
	    die "Unrecognized option line #$.: \"$_\"\n" ;
	} else {
	    die_without_GetOptions_usage "Unrecognized option: \"$_\"\n" ;
	}
    }
}

sub parse_generic_config_file {
    my $file = shift ;
    # warn on obsolete file
    my $oldconffile = $file ;
    $oldconffile =~ s/llgal\/llgalrc$/llgalrc/ ;
    print "!! Obsolete configuration file $oldconffile skipped, should be moved to $file.\n"
	if -e $oldconffile ;

    # read conf
    if (open CONF, $file) {
	while (<CONF>) {
	    process_option $opts, $_ ;
	}
	close CONF ;
    }
}

sub parse_additional_config_file {
    my $file = shift ;
    open CONF, $file
	or die "Failed to open additional configuration file '$file' ($!).\n" ;
    while (<CONF>) {
	process_option $opts, $_ ;
    }
   close CONF ;
}

######################################################################
# process command-line arguments (overriding defaults above)

sub parse_cmdline_options {
    GetOptions(
# behaviors
	'clean'		=> \$clean_asked,
	'cleanall'	=> \$cleanall_asked,
	'gc'		=> \$generate_captions,
	'gt:s'		=> sub {
				shift ; my $value = shift ;
				$value = "local" if !$value ;
				$give_templates = $value ;
				},
	'h|help'	=> \$help_asked,
	'v|version'	=> \$version_asked,
	'f'		=> \$force_image_regeneration,
	'gencfg=s'	=> \$generate_config,
	'R'		=> \$make_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},
	'bigxy=i'	=> sub { shift ; $opts->{slide_width_max} = $opts->{slide_height_max} = shift ; },
	'bigy=i'	=> \$opts->{slide_height_max},
	'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},
	'con=s'		=> sub { shift ; push (@{$opts->{convert_options}}, parse_convert_options (shift)) ; },
	'i=s'		=> \$opts->{index_filename},
	'indent=s'	=> \$opts->{indent},
	'k'		=> \$opts->{make_slide_title_from_caption},
	'L'		=> \$opts->{list_links},
	'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'		=> \$opts->{show_no_slide_counter},
	'option=s'	=> sub { shift ; process_option ($opts, shift) ; },
	'p=i'		=> \$opts->{index_cellpadding},
	'php'		=> sub { $opts->{www_extension} = "php" ; },
	'r'		=> \$opts->{show_no_film_effect},
	'Rl'		=> \$opts->{link_subgalleries},
	'S'		=> \$opts->{add_subdirs},
	's'		=> \$opts->{make_no_slides},
	'sort=s'	=> \$opts->{sort_criteria},
	't=i'		=> \$opts->{filmtile_height},
	'templates=s'	=> \@{$opts->{template_dirs}},
	'title=s'	=> \$opts->{index_title},
	'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},
	'xy=i'		=> sub { shift ; $opts->{thumbnail_width_max} = $opts->{thumbnail_height_max} = shift ; },
	'y=i'		=> \$opts->{thumbnail_height_max},
	'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},
	'prev-gal=s'	=> sub { shift ; $opts->{prev_gallery_link_target} = shift ; $opts->{prev_gallery_link} = 1 ; },
	'next-gal=s'	=> sub { shift ; $opts->{next_gallery_link_target} = shift ; $opts->{next_gallery_link} = 1 ; },
	) or GetOptions_failed ;
}

######################################################################
# various temporary globals
my $destination_string ;
my $local_llgal_url ;

######################################################################
# a few routines

# Print messages with indentation according to recursion level

sub indprint {
    print $opts->{indent} ;
    print @_ ;
}

# Warnings are shown after each step of processing to avoid
# breaking precentage progressions and so

my $tmp_warning = "" ;

sub add_warning {
    $tmp_warning .= "!! ".(shift)."\n" ;
}

sub add_external_warnings {
    while (@_) {
	my $line = shift ;
	chomp $line ;
	add_warning "# $line" ;
    }
}

sub show_warnings {
    print $tmp_warning ;
    $tmp_warning = "" ;
}

sub immediate_warning {
    print "!! ".(shift)."\n" ;
}

sub immediate_external_warnings {
    while (@_) {
	my $line = shift ;
	chomp $line ;
	immediate_warning "# $line" ;
    }
}

# Print percentage

sub init_percentage {
    print "  0%" ;
}

sub print_percentage {
    my ($i,$n) = (shift,shift) ;
    my $val = int($i*100/$n) ;
    print "\b\b\b\b" ;
    if ($val == 100) {
	print "100%" ;
    } elsif ($val >= 10) {
	print " ".$val."%" ;
    } else {
	print "  ".$val."%" ;
    }
}

sub end_percentage {
    print "\b\b\b\b100%\n" ;
}

# system routine which:
# - takes a description followed by cmdline arguments
# - returns a table composed of the status followed by STDERR and STDOUT lines

sub system_with_output {
    my $descr = shift ;
    pipe (my $pipe_out, my $pipe_in) ;
    my $pid = fork() ;
    if ($pid < 0) {
	close $pipe_in ;
	close $pipe_out ;
	return ( -1, "Fork failed while trying to $descr\n" ) ;
    } elsif ($pid > 0) {
	close $pipe_in ;
	waitpid ($pid, 0) ;
	my $status = $? >> 8 ;
	$status = -1
	    if $status == 255 ;
	my @lines = <$pipe_out> ;
	close $pipe_out ;
	return ( $status , @lines ) ;
    } else {
	close $pipe_out ;
	open STDERR, ">&", $pipe_in ;
	open STDOUT, ">&", $pipe_in ;
	{ exec @_ } ;
	print $pipe_in "Exec of $_[0] failed while trying to $descr\n" ;
	close $pipe_in ;
	exit -1 ;
    }
}

# check whether an argument is an integer

sub is_integer {
    my $s = shift ;
    return $s eq int($s) ;
}

# copy a file
sub copy_file {
    my $filename = shift ;
    my $srcdir = shift ;
    my $destdir = shift ;
    my ($status, @output) = system_with_output
	("copy '$filename' from '$srcdir'",
	"cp", "-f", "$srcdir/$filename", "$destdir/$filename") ;
    if ($status) {
	# die on whatever error
	immediate_external_warnings @output ;
	die "Failed to get generic '$filename' file.\n" ;
    }
}

# find path to the template file in generic directories
sub find_generic_template_file {
    my ($filename, $check) = (shift, shift) ;
    foreach my $dir (@{$opts->{template_dirs}}) {
	return $dir
	    if -e "$dir/$filename" ;
    }
    return $opts->{user_share_dir}
	if -e "$opts->{user_share_dir}/$filename" ;
    die "File '$opts->{llgal_share_dir}/$filename' does not exist.\nPlease install llgal properly.\n"
	unless -e "$opts->{llgal_share_dir}/$filename" or !$check ;
    return $opts->{llgal_share_dir} ;
}

# find path to the template, using the local one if it exists
sub find_template_file {
    my ($filename, $check) = (shift, shift) ;
    return $opts->{local_llgal_dir}
	if -e "$opts->{local_llgal_dir}/$filename" ;
    return find_generic_template_file ($filename, $check) ;
}

# get a template file from generic directories and save it to local directory
sub get_template_file {
    my $filename = shift ;
    if (-e "$destination_dir$opts->{local_llgal_dir}/$filename") {
	indprint "Found $filename in $destination_dir$opts->{local_llgal_dir}/, using it.\n" ;
    } else {
	my $srcdir = find_generic_template_file ($filename, 1) ;
	indprint "No $filename in $destination_dir$opts->{local_llgal_dir}/, getting a copy from $srcdir\n" ;
	copy_file ($filename, $srcdir, "$destination_dir$opts->{local_llgal_dir}" ) ;
    }
}

# revert a path into ..
sub back_path {
    my $dir = shift ;
    $dir =~ s/([^\/]+)/../g ;
    return $dir ;
}

# generating safe url
sub make_safe_url_nowarn {
    my $file = shift ;
    my $safe = uri_escape ($file) ;
    return $safe ;
}

sub make_safe_url {
    my $file = shift ;
    my $safe = uri_escape ($file) ;

    add_warning "Non-ascii characters were escaped in filename '$file'."
	if $safe ne $file and $file =~ /[\x80-\xFF]/ ;

    return $safe ;
}

######################################################################
# sanity check

sub sanity_checks {
    my $opts = shift ;

    # cannot have --bigy/xy and -s
    die "Please choose between --bigy/xy and -s\n"
	if ($opts->{slide_height_max} or $opts->{slide_width_max}) and $opts->{make_no_slides} ;

    # 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 llgal share directory\n"
	if $opts->{llgal_share_dir} eq "" ;
    die "Please give a non-empty user share directory\n"
	if $opts->{user_share_dir} eq "" ;
    die "Please give a non-empty filename without path as a CSS filename\n"
	if $opts->{css_filename} eq "" or $opts->{css_filename} =~ /\// ;
    die "Please give a non-empty filename without path as a film tile filename\n"
	if $opts->{filmtile_filename} eq "" or $opts->{filmtile_filename} =~ /\// ;
    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} =~ /\// ;
    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} =~ /\// ;
    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} =~ /\// ;
    die "Please give a non-empty filename without path as a index template filename\n"
	if $opts->{indextemplate_filename} eq "" or $opts->{indextemplate_filename} =~ /\// ;
    die "Please give a non-empty filename without path as a slide template filename\n"
	if $opts->{slidetemplate_filename} eq "" or $opts->{slidetemplate_filename} =~ /\// ;
    die "Please give a non-empty directory name without path as a local llgal directory\n"
	if $opts->{local_llgal_dir} eq "" or $opts->{local_llgal_dir} =~ /\// ;
    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} =~ /\// ;
    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} =~ /\// ;
    die "Please give a non-empty filename without path as a index filename\n"
	if $opts->{index_filename} eq "" or $opts->{index_filename} =~ /\// ;
    die "Please give a non-empty filename without path as a caption filename\n"
	if $opts->{captions_filename} eq "" or $opts->{captions_filename} =~ /\// ;
    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} =~ /\// ;
    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} =~ /\// ;
    die "Please give a non-empty caption removal line\n"
	if $opts->{captions_removal_line} eq "" ;
}

######################################################################
# check and prepare numerical options

sub prepare_numerical_options {
    my $opts = shift ;

    # 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) {
	indprint "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) {
	indprint "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) {
	indprint "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) {
	indprint "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) {
	indprint "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 ;

    # filmtile_height must be >= 0
    die "Please give an integer value for film tile height\n"
	unless is_integer ($opts->{filmtile_height}) ;
    if ($opts->{filmtile_height} < 0) {
	indprint "Film tile height value < 0, restoring to default value ($filmtile_height_default)\n" ;
	$opts->{filmtile_height} = $filmtile_height_default ;
    }
    die "Please give a positive or null film tile height value\n"
	unless $opts->{filmtile_height} >= 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) {
	indprint "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) {
	indprint "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) {
	indprint "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) {
	indprint "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 ;
}

######################################################################
# prepare a few variables for later

sub prepare_various_variables {
    my $opts = shift ;

    # adapt text slide width and height in case of --bigxy or --bigy
    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 @thumbnail_scale_options ;
    if ($opts->{thumbnail_width_max} > 0) {
	@thumbnail_scale_options = ("-scale", $opts->{thumbnail_width_max}."x".$opts->{thumbnail_height_max}.">") ;
    } else {
	@thumbnail_scale_options = ("-scale", "x".$opts->{thumbnail_height_max}) ;
    }
    @{$opts->{thumbnail_create_command}} = ("convert", "+profile", "*", @{$opts->{convert_options}}, @{$opts->{thumbnail_convert_options}}, @thumbnail_scale_options) ;

    # convert options for slides
    @{$opts->{slide_create_command}} = ("convert", "+profile", "*", @{$opts->{convert_options}}, @{$opts->{scaled_convert_options}} ) ;
    if ($opts->{slide_height_max} > 0) {
	if ($opts->{slide_width_max} > 0) {
	    push (@{$opts->{slide_create_command}}, ("-scale", "$opts->{slide_width_max}x$opts->{slide_height_max}")) ;
	} else {
	    push (@{$opts->{slide_create_command}}, ("-scale", "x$opts->{slide_height_max}")) ;
	}
    } else {
	if ($opts->{slide_width_max} > 0) {
	    push (@{$opts->{slide_create_command}}, ("-scale", "$opts->{slide_width_max}x")) ;
	}
    }
    @{$opts->{slide_copy_command}} = ("cp", "-f") ;

    # css location
    if ($opts->{css_location} =~ /^(.*\/)$/) {
	$opts->{css_location} .= $opts->{css_filename} ;
    }

    # images location
    if ($opts->{filmtile_location} =~ /^(.*)\/$/) {
	$opts->{filmtile_location} .= $opts->{filmtile_filename} ;
    }
    if ($opts->{index_link_image_location} =~ /^(.*\/)$/) {
	$opts->{index_link_image_location} .= $opts->{index_link_image_filename} ;
    }
    if ($opts->{prev_slide_link_image_location} =~ /^(.*\/)$/) {
	$opts->{prev_slide_link_image_location} .= $opts->{prev_slide_link_image_filename} ;
    }
    if ($opts->{next_slide_link_image_location} =~ /^(.*\/)$/) {
	$opts->{next_slide_link_image_location} .= $opts->{next_slide_link_image_filename} ;
    }
}

######################################################################
# --clean and --cleanall option

sub clean_files {
    my $cleanall = shift ;
    my $notdeleted = 0 ;
    if ($cleanall) {
	indprint "Cleaning all in $destination_string\n" ;
    } else {
	indprint "Cleaning in $destination_string\n" ;
    }

    opendir DIR, "$destination_dir$opts->{local_llgal_dir}"
	or die "Can't open directory $destination_dir$opts->{local_llgal_dir} ($!)\n" ;
    while ($_ = readdir DIR) {
	if (/^$opts->{thumbnail_image_filenameprefix}/ or /^$opts->{scaled_image_filenameprefix}/) {
	    unlink "$destination_dir$opts->{local_llgal_dir}/$_" ;
	} elsif (/^$opts->{filmtile_filename}$/ or /^$opts->{index_link_image_filename}$/
		 or /^$opts->{prev_slide_link_image_filename}$/ or /^$opts->{next_slide_link_image_filename}$/
		 or /^$opts->{css_filename}$/) {
	    my $original = (find_generic_template_file $_, 0)."/".$_ ;
	    my $diff = 0 ;
	    my @output ;
	    # default is cleanall, which removes everything, as if file were not changed
	    if (!$cleanall) {
		($diff, @output) = system_with_output
		    ("compare $_ with its original",
		     "cmp", "-s", "$destination_dir$opts->{local_llgal_dir}/$_", "$original") ;
		if ($diff == -1) {
		    immediate_external_warnings @output ;
		    die "Failed to execute 'cmp' to check whether $destination_dir$opts->{local_llgal_dir}/$_ has been modified ($!).\n" ;
		}
		add_external_warnings @output ;
		# on other errors, assume files are different ($diff != 0)
	    }
	    if ($diff) {
		add_warning "Preserved $destination_dir$opts->{local_llgal_dir}/$_ since it seems to be modified." ;
		$notdeleted++ ;
	    } else {
		if (!unlink "$destination_dir$opts->{local_llgal_dir}/$_" and ! $!{ENOENT}) {
		    add_warning "Failed to remove file $destination_dir$opts->{local_llgal_dir}/$_ ($!)." ;
		}
	    }
	} elsif (/^$opts->{captions_filename}$/) {
	    my $grep = 0 ;
	    my @output ;
	    # default is cleanall, which removes everything, as if the removal line was here
	    if (!$cleanall) {
		($grep, @output) = system_with_output
		    ("check for caption removal line",
		     "grep", "-q", "$opts->{captions_removal_line}", "$destination_dir$opts->{local_llgal_dir}/$_") ;
		if ($grep == -1) {
		    immediate_external_warnings @output ;
		    die "Failed to execute 'grep' to check whether $destination_dir$opts->{local_llgal_dir}/$_ has been modified ($!).\n" ;
		}
		add_external_warnings @output ;
		# on other errors, assume file doesn't contain the caption removal line ($grep != 0)
	    }
	    if ($grep) {
		add_warning "Preserved $destination_dir$opts->{local_llgal_dir}/$_ since it seems to be modified." ;
		$notdeleted++ ;
	    } else {
		if (!unlink "$destination_dir$opts->{local_llgal_dir}/$_" and ! $!{ENOENT}) {
		    add_warning "Failed to remove file $destination_dir$opts->{local_llgal_dir}/$_." ;
		}
	    }
	} else {
	    $notdeleted++ ;
	}
    }
    closedir DIR ;
    if ($notdeleted <= 2 and $cleanall) {
	if (!rmdir "$destination_dir$opts->{local_llgal_dir}"  and ! $!{ENOENT}) {
	    add_warning "Failed to remove directory $destination_dir$opts->{local_llgal_dir} ($!)." ;
	}
    }

    opendir DIR, $destination_dir ? $destination_dir : "./" ; # destination is empty for './'
    while ($_ = readdir DIR ) {
	if (/.$opts->{www_extension}$/) {
	    unlink "$destination_dir$_" ;
	}
    }
    closedir DIR ;
}

#####################################################################
# Give templates to the given directory

sub give_templates {
    my $destdir = shift ;
    if ( ! -e $destdir ) {
	indprint "Creating template directory $destdir...\n" ;
	mkdir $destdir
	    or die "Failed to create $destdir ($!)" ;
    }

    foreach my $filename
	( $opts->{css_filename}, $opts->{filmtile_filename}, $opts->{index_link_image_filename},
	  $opts->{prev_slide_link_image_filename}, $opts->{next_slide_link_image_filename},
	  $opts->{indextemplate_filename}, $opts->{slidetemplate_filename} ) {
	if ( -e "$destdir/$filename" ) {
	    indprint "$filename already exists in $destdir.\n" ;
	} else {
	    my $srcdir = find_generic_template_file ($filename, 1) ;
	    indprint "$filename does not exist in $destdir, getting a copy from $srcdir...\n" ;
	    copy_file ($filename, $srcdir, $destdir ) ;
	}
    }
}

#####################################################################
# Main variables

# The main gallery is composed of
# - @entries (the entry list)
# - @listed_entries (list of entries which are shown as simple links)
# - @headers and @footers

sub init_gallery {
    my $gallery = () ;
    @{$gallery->{headers}} = () ;
    @{$gallery->{footers}} = () ;
    @{$gallery->{entries}} = () ;
    @{$gallery->{listed_entries}} = () ;
    return $gallery ;
}

# Entries are hashed composed of
# - type: see below
# - filename (original filename with extension)
# - url (url of the filename)
# - linktext (an associated text that may be used in a link, www-safe)
# - caption (the caption of the slide, www-safe)
# - title (title, www-safe)

# - xdim, ydim, kbytes (details about the image or movie)
# - dimstring (string containing dimensions and/or size that were requested)

# - slide_filename (name of the HTML slide filename)
# - slide_url (url of the HTML slide filename)

# - thumb_xdim, thumb_ydim (details about the thumbnail image)
# - thumb_dimstring (string containing dimensions)
# - thumb_filename (name of the thumbnail image file)
# - thumb_url (url of the thumbnail image file)

# - scaled_xdim, scaled_ydim, scaled_kbytes (details about the scaled image)
# - scaled_dimstring (string containing dimensions and/or size that were requested)
# - scaled_filename (name of the scaled image file)
# - scaled_url (url of the scaled image file)

# types
my $TYPE_TXT = 0 ;
my $TYPE_LNK = 1 ;
my $TYPE_IMG = 2 ;
my $TYPE_MVI = 3 ;
my $TYPE_FIL = 4 ;
my $TYPE_DIR = 5 ;

###################################################################################
# checking files

sub check_file {
    my $filename = shift ;

    if (! -e $filename) {
	add_warning "Cannot find file '$filename', skipping it." ;
	return -1 ;
    }

    my $excluded = 0 ;
    for(my $i = 0; $i < @{$opts->{excludes}}; $i++) {
	my $entry = @{$opts->{excludes}}[$i] ;
	if ($filename =~ /^(.*\/)*$entry->{filter}$/) {
	    $excluded = $entry->{excluded}
	}
    }
    return -1
	if $excluded ;

    return 0 ;
}

sub check_image {
    my $filename = shift ;
    my ($status, @output) = system_with_output
	( "check \"$filename\" validity",
	  "identify", $filename ) ;
    if ($status == -1) {
	immediate_external_warnings @output ;
	die "Failed to check \"$filename\" validity\n" ;
    } elsif ($status) {
	# do not show external warnings
	add_warning "Bad image file '$filename', skipping it." ;
	return -1 ;
    }
    return 0 ;
}

sub check_movie {
    # TODO
    return 0 ;
}

###################################################################################
# returns a filename that the user provided in the llgal directory
# to be used as a thumbnail or scaled image

sub find_user_thumbnail {
    my $entry = shift ;
    my $type = $entry->{type} ;
    my $filename = $entry->{filename} ;
    my $user_thumbnail_filename = "$opts->{user_thumbnail_image_filenameprefix}$opts->{thumbnail_image_filenameprefix}$filename" ;

    if ($type == $TYPE_IMG) {
	if (-f "$opts->{local_llgal_dir}/$user_thumbnail_filename") {
	    return $user_thumbnail_filename ;
	}
    } else {
	foreach my $ext (split (/\|/, $opts->{image_extensions})) {
	    if (-f "$opts->{local_llgal_dir}/$user_thumbnail_filename.$ext") {
		return "$user_thumbnail_filename.$ext" ;
	    }
	}
    }
    return "" ;
}

sub find_user_scaled {
    my $entry = shift ;
    my $type = $entry->{type} ;
    my $filename = $entry->{filename} ;
    my $user_scaled_filename = "$opts->{user_scaled_image_filenameprefix}$opts->{scaled_image_filenameprefix}$filename" ;

    if ($type == $TYPE_IMG) {
	if (-f "$opts->{local_llgal_dir}/$user_scaled_filename") {
	    return $user_scaled_filename ;
	}
    } else {
	foreach my $ext (split (/\|/, $opts->{image_extensions})) {
	    if (-f "$opts->{local_llgal_dir}/$user_scaled_filename.$ext") {
		return "$user_scaled_filename.$ext" ;
	    }
	}
    }
    return "" ;
}

###################################################################################
# extract caption from image infos

# Standard comment such as JFIF or GIF
sub std_comment {
    my $filename = shift ;
    my $infos = shift ;
    my $comment = $infos->{Comment} ;
    if (ref($comment) eq 'ARRAY' ) {
	# concat string array
	$comment = join(' ', @{$comment}) ;
    }
    # remove \000
    $comment =~ s/\000//g ;
    return $comment ;
}

# Exif comment
sub exif_comment {
    my $filename = shift ;
    my $infos = shift ;
    my $comment = $infos->{UserComment} ;
    # Remove trailing spaces
    $comment =~ s/\s*$// ;
    if ($comment =~ /^ASCII\0\0\0(.*)$/) {
	# ASCII Exif Comment
	return  $1 ;
    } elsif (substr ($comment,0,8) ne "\0\0\0\0\0\0\0\0") {
	add_warning "Unrecognized Exif UserComment '$comment' length ".(length $comment)." in image '$filename'\n" ;
    }
    return "" ;
}

# Image comment
sub generate_caption_from_image_comment {
    my $filename = shift ;
    my $infos = shift ;
    my @texts = () ;
    # Loop until a non-empty comment list is found
    foreach my $types (split (/,/, $opts->{make_caption_from_image_comment})) {
	# types is a + separated string of types
	foreach my $type (split (/\+/, $types)) {
	    if ($type =~ /^std$/i) {
		my $comment = std_comment ($filename, $infos) ;
		push (@texts, $comment)
		    if $comment ;
	    } elsif ($type =~ /^exif$/i) {
		my $comment = exif_comment ($filename, $infos) ;
		push (@texts, $comment)
		    if $comment ;
	    } else {
		die "Unrecognized image comment type '$type'\n" ;
	    }
	}
	return @texts
	    if @texts ;
    }
    return () ;
}

# Image timestamp
sub generate_caption_from_image_timestamp {
    my $filename = shift ;
    my $infos = shift ;
    my $imagetime = $infos->{DateTimeOriginal} ;
    if ($imagetime ne "") {
	my @timearray = split (/[: ]/, $imagetime) ;
	my @gmtime = ($timearray[5], $timearray[4], $timearray[3], $timearray[2], $timearray[1]-1, $timearray[0]-1900, 0, 0) ;
	return strftime ($opts->{timestamp_format_in_caption}, @gmtime) ;
    }
    return "" ;
}

# Image infos
sub generate_caption_from_image_infos {
     my $filename = shift ;
     my $infos = image_info ("$destination_dir$filename") ;
     my @texts = () ;
     if ($opts->{make_caption_from_image_comment}) {
	 my @comments = generate_caption_from_image_comment ($filename, $infos) ;
	 push (@texts, @comments) ;
     }
     if ($opts->{make_caption_from_image_timestamp}) {
	 my $timestamp = generate_caption_from_image_timestamp ($filename, $infos) ;
	 push (@texts, $timestamp) ;
     }
     return @texts ;
 }

###################################################################################
# Generate entries

sub create_file_entry {
    my $entry = () ;
    my $type = shift ;
    my $filename = shift ;
    my $linktext = shift ;
    my $caption = shift ;

    # check file
    return undef
	if check_file ("$destination_dir$filename") < 0 ;
    if ($type == $TYPE_IMG) {
	return undef
	    if check_image ("$destination_dir$filename") < 0 ;
    } elsif ($type == $TYPE_MVI) {
	return undef
	    if check_movie ("$destination_dir$filename") < 0 ;
    }

    # set the type
    $entry->{type} = $type ;

    # set the filename
    $entry->{filename} = $filename ;

    # make a safe url
    $entry->{url} = make_safe_url ($filename) ;

    # the link text (will be web-safe after caption file generation)
    if (not defined $linktext) {
	$linktext = "" ;
	if ($type == $TYPE_MVI) {
	    $linktext = $opts->{MVI_link_text}.$filename ;
	} elsif ($type == $TYPE_FIL) {
	    $linktext = $opts->{FIL_link_text}.$filename ;
	} elsif ($type == $TYPE_DIR) {
	    $linktext = $opts->{DIR_link_text}.$filename ;
	}
	$linktext =~ s/&/&amp;/g ;
#       $linktext =~ s/"/&\#34;/g ;
    }
    $entry->{linktext} = $linktext ;

    # the caption (will be web-safe after caption file generation)
    if (not defined $caption) {
	my @texts = () ;
	push (@texts, $filename)
	    if $opts->{make_caption_from_filename} ;
	push (@texts, generate_caption_from_image_infos ($filename))
	    if $type == $TYPE_IMG ;
	$caption = join (' - ', grep {$_} @texts) ;
	$caption =~ s/&/&amp;/g ;
#	$caption =~ s/"/&\#34;/g ;
    }
    chomp $caption ;
    $entry->{caption} = $caption ;

    # title is based on the filename
    my $title = $filename ;
    $title =~ s/&/&amp;/g ;
#    $title =~ s/"/&\#34;/g ;

    # add caption to the title if asked
    $title .= ": ". $caption
	if $opts->{make_slide_title_from_caption} ;

    $entry->{title} = $title ;

    # do not generate more if we are only generating captions
    return $entry ;
}

sub fill_file_entry {
    my $entry = shift ;
    my $type = $entry->{type} ;
    my $filename = $entry->{filename} ;

    # keep real filename for later
    my $real_filename = "$destination_dir$filename" ;

    # xdim, ydim, kbytes of the image or movie
    if ($type == $TYPE_IMG) {
	my ($x,$y) = imgsize ("$destination_dir$filename") ;
	$entry->{xdim} = $x ;
	$entry->{ydim} = $y ;
	# initialize here so that we can use it even when scaled are not used
	$entry->{scaled_xdim} = $x ;
	$entry->{scaled_ydim} = $y ;
    }
    if ($type == $TYPE_IMG or $type == $TYPE_MVI or $type == $TYPE_FIL) {
	my $kbytes = (-s "$destination_dir$filename") >> 10 ;
	$entry->{kbytes} = $kbytes ;
	# initialize here so that we can use it even when scaled are not used
	$entry->{scaled_kbytes} = $kbytes ;
    }

    # dimstring
    if ($type == $TYPE_IMG) {
	if ($opts->{show_dimensions} and $opts->{show_size}) {
	    $entry->{dimstring} = "($entry->{xdim}x$entry->{ydim}, $entry->{kbytes}$opts->{show_size_unit})" ;
	} elsif ($opts->{show_size}) {
	    $entry->{dimstring} = "($entry->{kbytes}$opts->{show_size_unit})" ;
	} elsif ($opts->{show_dimensions}) {
	    $entry->{dimstring} = "($entry->{xdim}x$entry->{ydim})" ;
	} else {
	    $entry->{dimstring} = "" ;
	}
    } elsif ($type == $TYPE_MVI or $type == $TYPE_FIL) {
	if ($opts->{show_size}) {
	    $entry->{dimstring} = "($entry->{kbytes}$opts->{show_size_unit})" ;
	} else {
	    $entry->{dimstring} = "" ;
	}
    } else {
	$entry->{dimstring} = "" ;
    }

    # thumbnail, its url and dimensions
    my $thumb_filename = find_user_thumbnail ($entry) ;
    if ($thumb_filename) {
	# there's a user provided thumbnail, just use it

	$entry->{thumb_filename} = "$opts->{local_llgal_dir}/$thumb_filename" ;
	$entry->{thumb_url} = "$local_llgal_url/". make_safe_url_nowarn ($thumb_filename) ;

	# thumbnails dimensions
	my ($x, $y) = imgsize ("$opts->{local_llgal_dir}/$thumb_filename") ;
	if ($y > $opts->{thumbnail_height_max}) {
	    $x = $x * ($opts->{thumbnail_height_max} / $y) ;
	    $y = $opts->{thumbnail_height_max} ;
	}
	if ($opts->{thumbnail_width_max} > 0 and $x > $opts->{thumbnail_width_max}) {
	    $y = $y * ($opts->{thumbnail_width_max} / $x) ;
	    $x = $opts->{thumbnail_width_max} ;
	}

	($entry->{thumb_xdim},$entry->{thumb_ydim}) = ($x, $y) ;

	# dimstring
	if ($opts->{show_dimensions}) {
	    $entry->{thumb_dimstring} = "(${x}x${y})" ;
	} else {
	    $entry->{thumb_dimstring} = "" ;
	}

    } elsif ($type == $TYPE_IMG) {
	# no user provided thumbnail, generate one

	# filename and url
	$thumb_filename = "$opts->{thumbnail_image_filenameprefix}$filename" ;
	$entry->{thumb_filename} = "$opts->{local_llgal_dir}/$thumb_filename" ;
	$entry->{thumb_url} = "$local_llgal_url/". make_safe_url_nowarn ($thumb_filename) ;

	# thumbnails dimensions
	my $real_thumb_filename = "$destination_dir$entry->{thumb_filename}" ;
	if ((! -e "$real_thumb_filename") or $force_image_regeneration) {
	    my ($status, @output) = system_with_output
		( "create '$real_filename' thumbnail",
		  @{$opts->{thumbnail_create_command}}, "$real_filename", "$real_thumb_filename" ) ;
	    if ($status == -1) {
		immediate_external_warnings @output ;
		die "Failed to create '$real_filename' thumbnail\n" ;
	    } elsif ($status) {
		add_warning "Failed to create '$real_filename' thumbnail" ;
		add_external_warnings @output ;
		return undef ;
	    }
	}
	($entry->{thumb_xdim},$entry->{thumb_ydim}) = imgsize ("$real_thumb_filename") ;

	# dimstring
	if ($opts->{show_dimensions}) {
	    $entry->{thumb_dimstring} = "($entry->{thumb_xdim}x$entry->{thumb_ydim})" ;
	} else {
	    $entry->{thumb_dimstring} = "" ;
	}

    } else {
	# default thumbnail dimensions
	$entry->{thumb_xdim} = $opts->{default_thumb_xdim} ;
	$entry->{thumb_ydim} = $opts->{default_thumb_ydim} ;

	# dimstring is empty
        $entry->{thumb_dimstring} = "" ;
    }

    if (!$opts->{make_no_slides}) {
	# scaled image, its url and dimensions

	my $scaled_filename = find_user_scaled ($entry) ;
	if ($scaled_filename and ($opts->{slide_width_max} > 0 or $opts->{slide_height_max} > 0)) {
	    # there's a user provided scaled, just use it

	    $entry->{scaled_filename} = "$opts->{local_llgal_dir}/$scaled_filename" ;
	    $entry->{scaled_url} = "$local_llgal_url/". make_safe_url_nowarn ($scaled_filename) ;

	    # thumbnails dimensions
	    my ($x, $y) = imgsize ("$opts->{local_llgal_dir}/$scaled_filename") ;
	    if ($y > $opts->{slide_height_max} and $y > $opts->{slide_height_max}) {
		$x = $x * ($opts->{slide_height_max} / $y) ;
		$y = $opts->{slide_height_max} ;
	    }
	    if ($opts->{slide_width_max} > 0 and $x > $opts->{slide_width_max}) {
		$y = $y * ($opts->{slide_width_max} / $x) ;
		$x = $opts->{slide_width_max} ;
	    }

	    ($entry->{scaled_xdim},$entry->{scaled_ydim}) = ($x, $y) ;
	    $entry->{scaled_kbytes} = (-s "$opts->{local_llgal_dir}/$scaled_filename") >> 10 ;

	    # dimstring
	    if ($opts->{show_dimensions} and $opts->{show_size}) {
		$entry->{scaled_dimstring} = "($entry->{scaled_xdim}x$entry->{scaled_ydim}, $entry->{scaled_kbytes}$opts->{show_size_unit})" ;
	    } elsif ($opts->{show_size}) {
		$entry->{scaled_dimstring} = "($entry->{scaled_kbytes}$opts->{show_size_unit})" ;
	    } elsif ($opts->{show_dimensions}) {
		$entry->{scaled_dimstring} = "($entry->{scaled_xdim}x$entry->{scaled_ydim})" ;
	    } else {
		$entry->{scaled_dimstring} = "" ;
	    }

	} elsif ($type == $TYPE_IMG and ($opts->{slide_width_max} > 0 or $opts->{slide_height_max} > 0)) {
	    # no user provided scaled, generate one

	    # filename and url
	    $scaled_filename = "$opts->{scaled_image_filenameprefix}$filename" ;
	    $entry->{scaled_filename} = "$opts->{local_llgal_dir}/$scaled_filename" ;
	    $entry->{scaled_url} = "$local_llgal_url/". make_safe_url_nowarn ($scaled_filename) ;

	    # scaled image dimensions
	    my $real_scaled_filename = "$destination_dir$entry->{scaled_filename}" ;
	    my ($x,$y) ;
	    if ((! -e "$real_scaled_filename") or $force_image_regeneration) {
		# only scale down, never up.
		($x,$y) = imgsize ("$real_filename") ;
		my ($status, @output) ;
		if ($x <= $opts->{slide_width_max} and $y <= $opts->{slide_height_max}) {
		    ($status, @output) = system_with_output
			( "copy '$real_filename' scaled image",
			  @{$opts->{slide_copy_command}}, "$real_filename", "$real_scaled_filename" ) ;
		} else {
		    ($status, @output) = system_with_output
			( "create '$real_filename' scaled image",
			  @{$opts->{slide_create_command}}, "$real_filename", "$real_scaled_filename" ) ;
		}
		if ($status == -1) {
		    immediate_external_warnings @output ;
		    die "Failed to create '$real_filename' scaled image\n" ;
		} elsif ($status) {
		    add_warning "Failed to create '$real_filename' scaled image" ;
		    add_external_warnings @output ;
		    return undef ;
		}
	    }
	    ($entry->{scaled_xdim},$entry->{scaled_ydim}) = imgsize ("$real_scaled_filename") ;
	    $entry->{scaled_kbytes} = (-s "$real_scaled_filename") >> 10 ;

	    # dimstring
	    if ($opts->{show_dimensions} and $opts->{show_size}) {
		$entry->{scaled_dimstring} = "($entry->{scaled_xdim}x$entry->{scaled_ydim}, $entry->{scaled_kbytes}$opts->{show_size_unit})" ;
	    } elsif ($opts->{show_size}) {
		$entry->{scaled_dimstring} = "($entry->{scaled_kbytes}$opts->{show_size_unit})" ;
	    } elsif ($opts->{show_dimensions}) {
		$entry->{scaled_dimstring} = "($entry->{scaled_xdim}x$entry->{scaled_ydim})" ;
	    } else {
		$entry->{scaled_dimstring} = "" ;
	    }

	} else {
	    $entry->{scaled_dimstring} = $entry->{dimstring} ;
	}
    }
    return $entry ;
}

sub create_nofile_entry {
    my $entry = () ;
    my $type = shift ;
    my $url = shift ;
    my $linktext = shift ;
    my $caption = shift ;

    # set the type
    $entry->{type} = $type ;

    # make a safe url
    if (defined $url) {
	$entry->{url} = make_safe_url ($url) ;
    }

    # the link text (will be web-safe after caption file generation)
    $entry->{linktext} = $linktext ;

    # the caption (will be web-safe after caption file generation)
    $entry->{caption} = $caption ;

    # add caption to the title if asked
    my $title = "" ;
    $title = $caption
	if $opts->{make_slide_title_from_caption} ;
    $entry->{title} = $title ;

    # do not generate more if we are only generating captions
    return $entry ;
}

sub fill_nofile_entry {
    my $entry = shift ;

    # dimstring
    $entry->{dimstring} = "" ;

    # default thumbnail dimensions
    $entry->{thumb_xdim} = $opts->{default_thumb_xdim} ;
    $entry->{thumb_ydim} = $opts->{default_thumb_ydim} ;

    # dimstring is empty
    $entry->{thumb_dimstring} = "" ;

    if (!$opts->{make_no_slides}) {
	# scaled image dimensions
	$entry->{scaled_dimstring} = $entry->{dimstring} ;
    }
    return $entry ;
}

# fill entries, everything that's not required for
# captions generation, but may be done early
sub fill_entries {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;
    my @final_entries = () ;

    indprint "Preparing entries: " ;
    init_percentage () ;

    my $i = 0 ;
    for my $entry (@entries) {
	my $type = $entry->{type} ;
	my $final_entry ;
	if ($type == $TYPE_TXT or $type == $TYPE_LNK) {
	    $final_entry = fill_nofile_entry $entry ;
	} else {
	    $final_entry = fill_file_entry $entry ;
	}
	push @final_entries, $final_entry
	    if defined $final_entry ;
	print_percentage (($i++)+1, scalar @entries) ;
    }
    end_percentage () ;
    show_warnings ;

    @{$gallery->{entries}} = @final_entries ;
}

# generate slide number, filename and url only when the order of entries is fixed
sub finalize_entries {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;

    for(my $i=0; $i < @entries; $i++) {
	my $entry = $entries[$i] ;
	my $type = $entry->{type} ;
	my $number = $i+1 ;
	my $filename = $entry->{filename} ;

	# HTML slide and its url
	if (!$opts->{make_no_slides}) {
	    my $slide_filename ;
	    if ($opts->{make_slide_filename_from_filename}) {
		if ($type == $TYPE_TXT or $type == $TYPE_LNK) {
		    $slide_filename = "$opts->{slide_filenameprefix_nofile}$number.$opts->{www_extension}" ;
		} elsif ($opts->{make_slide_filename_from_extension}) {
		    $slide_filename = "$filename.$opts->{www_extension}" ;
		} else {
		    my @parts = split (/\./, $filename) ;
		    pop @parts ;
		    my $basename = join ('.', @parts) ;
		    $slide_filename = "$basename.$opts->{www_extension}" ;
		}
	    } else {
		$slide_filename = "$opts->{slide_filenameprefix}$number.$opts->{www_extension}" ;
	    }
	    $entry->{slide_filename} = $slide_filename ;
	    $entry->{slide_url} = make_safe_url_nowarn ($slide_filename) ;
	}
    }
    @{$gallery->{entries}} = @entries ;
}

#######################################################################
# Generating entry table from files in target directory

sub get_entries_from_directory {
    my $gallery = shift ;
    my @entries = () ;

    opendir DIR, $destination_dir ? $destination_dir : "./" # destination is empty for './'
	or die "Can't open $destination_string ($!)\n" ;
    # get all files, except dot-starting ones and webpages
    my @filenames = grep ((!/\.$opts->{www_extension}$/i and !/^\./), readdir DIR) ;
    closedir DIR ;

    # sort now so that slide numbering doesn't become wrong later
    if ($opts->{sort_criteria} eq "name") {
	@filenames = sort @filenames ;
    } elsif ($opts->{sort_criteria} eq "revname") {
	@filenames = sort { $b cmp $a } @filenames ;

    } elsif ($opts->{sort_criteria} eq "iname") {
	@filenames = sort { uc($a) cmp uc($b) } @filenames ;
    } elsif ($opts->{sort_criteria} eq "reviname") {
	@filenames = sort { uc($b) cmp uc($a) } @filenames ;

    } elsif ($opts->{sort_criteria} eq "date" or $opts->{sort_criteria} eq "time") {
	@filenames = sort { (-M $a) <=> (-M $b) } @filenames ;
    } elsif ($opts->{sort_criteria} eq "revdate" or $opts->{sort_criteria} eq "revtime") {
	@filenames = sort { (-M $b) <=> (-M $a) } @filenames ;

    } elsif ($opts->{sort_criteria} eq "size") {
	@filenames = sort { (-s $a) <=> (-s $b) } @filenames ;
    } elsif ($opts->{sort_criteria} eq "revsize") {
	@filenames = sort { (-s $b) <=> (-s $a) } @filenames ;

    } elsif ($opts->{sort_criteria} eq "none" or $opts->{sort_criteria} eq "") {
	# do not sort
    } else {
	die "Unknown sort criteria '$opts->{sort_criteria}'.\n" ;
    }

    indprint "Listing entries: " ;
    init_percentage () ;

    for(my $i = 0; $i < @filenames; $i++) {
	my $filename = $filenames[$i] ;
	if (-d "$destination_dir$filename") {
	    # directory
	    if ($opts->{add_subdirs} or $make_recursive) {
		my $entry = create_file_entry ($TYPE_DIR, $filename, undef, undef) ;
		push @entries, $entry
		    if defined $entry ;
	    }

	} else {
	    # file
	    my $entry = undef ;

	    if ($filename =~ /($opts->{image_extensions})$/i) {
		# image
		my $entry = create_file_entry ($TYPE_IMG, $filename, undef, undef) ;
		goto JUST_A_FILE
		    if not defined $entry ;
		push @entries, $entry ;

	    } elsif ($filename =~ /($opts->{movie_extensions})$/i) {
		# movie
		my $entry = create_file_entry ($TYPE_MVI, $filename, undef, undef) ;
		goto JUST_A_FILE
		    if not defined $entry ;
		push @entries, $entry ;

	    } else {
	      JUST_A_FILE:
		if ($opts->{add_all_files}) {
		    # not an image, not a movie, just a file
		    my $entry = create_file_entry ($TYPE_FIL, $filename, undef, undef) ;
		    push @entries, $entry
			if defined $entry ;
		}
	    }
	}

	print_percentage ($i+1, scalar @filenames) ;
    }
    end_percentage () ;
    show_warnings ;

    @{$gallery->{entries}} = @entries ;
}

#######################################################################
# Check acces rights and create local subdirectory to place all llgal files

sub check_destination {

    # cleanup destination
    # add a final /
    $destination_dir .= "/"
	unless $destination_dir =~ m@/$@ ;
    # remove starting ./
    $destination_dir =~ s/^(\.\/+)+// ;
    # factorization
    $destination_string = ($destination_dir ? "directory $destination_dir" : "current directory") ;

    # check destination
    die "Destination $destination_string does not exist.\n"
	if $destination_dir and ! -e $destination_dir ; # destination is empty for './'
    die "Destination $destination_string is not a directory.\n"
	if $destination_dir and ! -d $destination_dir ; # destination is empty for './'

    # Are we in .llgal ? just check the last part of the path by concatening
    # pwd and destination_dir even if destination_dir is an absolute path
    my $path = getcwd."/".$destination_dir ;
    if ($path =~ m@/*(?:[^/]+/+)*([^/]+)/+$@) {
	if ($1 eq $opts->{local_llgal_dir}) {
	    immediate_warning "!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!" ;
	    immediate_warning "Your working directory looks like a .llgal directory. !!" ;
	    immediate_warning "This might not be what you really want to do.         !!" ;
	    immediate_warning "!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!" ;
	}
    }

    # Create the url of the local llgal directory
    $local_llgal_url = make_safe_url_nowarn ($opts->{local_llgal_dir}) ;

    # temporary check, to be removed soon
    if ($opts->{local_llgal_dir} ne ".llgal.files" and -e "${destination_dir}.llgal.files") {
	immediate_warning "!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!" ;
	immediate_warning "llgal now uses '.llgal' instead '.llgal.files'. !!" ;
	immediate_warning "You should probably update it.                  !!" ;
	immediate_warning "!! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!" ;
    }

    if (! -e "$destination_dir$opts->{local_llgal_dir}") {
	mkdir "$destination_dir$opts->{local_llgal_dir}"
	    or die "Failed to create $destination_dir$opts->{local_llgal_dir} directory ($!)" ;
    }
    die "Local llgal $destination_dir$opts->{local_llgal_dir} is not a directory.\n"
	if ! -d "$destination_dir$opts->{local_llgal_dir}" ;
}

#######################################################################
# Generate captions file

sub generate_captions_file {
    my $gallery = shift ;

    get_entries_from_directory $gallery ;
    my @entries = @{$gallery->{entries}} ;

    indprint "Found ". (scalar @entries) ." files in $destination_string\n" ;

    my @captions = () ;

    # store old values from the captions file, if it exists
    my $old_title ;

    my $old_parent_gallery_link = 0 ;
    my $old_parent_gallery_link_text ;

    my $old_prev_gallery_link = 0 ;
    my $old_prev_gallery_link_target ;
    my $old_prev_gallery_link_text ;

    my $old_next_gallery_link = 0 ;
    my $old_next_gallery_link_target ;
    my $old_next_gallery_link_text ;

    if (-e "$destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename}") {
	indprint "Reading existing captions from $opts->{captions_filename}.\n" ;
	open (CAP, "$destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename}")
	    or die "Can't open $destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename} for reading\n" ;
	@captions = <CAP> ;
	close CAP ;

	foreach my $line (@captions) {
	    if ($line =~ /^TITLE:\s*(.*)\s*$/) {
		$old_title = $1 ;
	    } elsif ($line =~ /^PARENT:\s*(.*)\s*$/) {
		$old_parent_gallery_link = 1 ;
		$old_parent_gallery_link_text = $1 ;
	    } elsif ($line =~ /^PREV:\s*(.+)\s+----\s+(.+)\s*/) {
		$old_prev_gallery_link = 1 ;
		$old_prev_gallery_link_target = $2 ;
		$old_prev_gallery_link_text = $1 ;
	    } elsif ($line =~ /^NEXT:\s*(.+)\s+----\s+(.+)\s*/) {
		$old_next_gallery_link = 1 ;
		$old_next_gallery_link_target = $2 ;
		$old_next_gallery_link_text = $1 ;
	    }
	    print CAP $line ;
	}

	indprint "Appending new captions to $opts->{captions_filename}: " ;
	open (CAP, ">>$destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename}")
	    or die "Can't open $destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename} file to append\n" ;

	print CAP "\n" ;
	print CAP "# Appended new captions (", scalar localtime, ").\n" ;
	print CAP "\n" ;
    } else {
	indprint "Creating the $opts->{captions_filename} file: " ;
	open (CAP, ">$destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename}")
	    or die "Can't create $destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename} file\n" ;

	print CAP "# This is llgal's $opts->{captions_filename} file, first generated ", scalar localtime, ".\n" ;
	if (open CAPHEADER, "$opts->{llgal_share_dir}/$opts->{captions_header_filename}") {
	    while (my $line = <CAPHEADER>) { print CAP $line ; }
	    close CAPHEADER ;
	} else {
	    add_warning "Failed to open $opts->{captions_header_filename}\n" ;
	}
	print CAP "# $opts->{captions_removal_line}\n" ;
	print CAP "\n" ;
    }

    print CAP "TITLE: $opts->{index_title}\n"
	if !defined $old_title
	    or $old_title ne $opts->{index_title} ;
    print CAP "PARENT: $opts->{parent_gallery_link_text}\n"
	if $opts->{parent_gallery_link} and
	    ( !$old_parent_gallery_link
	    or $old_parent_gallery_link_text ne $opts->{parent_gallery_link_text} ) ;
    print CAP "PREV: $opts->{prev_gallery_link_text} ---- $opts->{prev_gallery_link_target}\n"
	if $opts->{prev_gallery_link} and
	    ( !$old_prev_gallery_link
	    or $old_prev_gallery_link_target ne $opts->{prev_gallery_link_target}
	    or $old_prev_gallery_link_text ne $opts->{prev_gallery_link_text} ) ;
    print CAP "NEXT: $opts->{next_gallery_link_text} ---- $opts->{next_gallery_link_target}\n"
	if $opts->{next_gallery_link} and
	    ( !$old_next_gallery_link
	    or $old_next_gallery_link_target ne $opts->{next_gallery_link_target}
	    or $old_next_gallery_link_text ne $opts->{next_gallery_link_text} ) ;
    print CAP "\n" ;

    init_percentage () ;

    for (my $i = 0; $i < @entries; $i++) {
	my $entry = $entries[$i] ;
	my $type = $entry->{type} ;
	if ($type == $TYPE_IMG) {
	    print CAP "IMG: ". $entry->{filename}
		." ---- ". $entry->{caption} ."\n"
		unless grep { $captions[$_] =~ m/^(\s*IMG:)?\s*$entry->{filename}\s+----\s/ }
		    ( 0 .. $#captions ) ;
	} elsif ($type == $TYPE_MVI) {
	    print CAP "MVI: " . $entry->{filename}
		." ---- ". $entry->{linktext}
		." ---- ". $entry->{caption} ."\n"
		unless grep { $captions[$_] =~ m/^\s*MVI:\s*$entry->{filename}\s+----\s/ }
		    ( 0 .. $#captions ) ;
	} elsif ($type == $TYPE_FIL) {
	    print CAP "FIL: " . $entry->{filename}
		." ---- ". $entry->{linktext}
		." ---- ". $entry->{caption} ."\n"
		unless grep { $captions[$_] =~ m/^\s*FIL:\s*$entry->{filename}\s+----\s/ }
		    ( 0 .. $#captions ) ;
	} elsif ($type == $TYPE_DIR) {
	    print CAP "DIR: " . $entry->{filename}
		." ---- ". $entry->{linktext}
		." ---- ". $entry->{caption} ."\n"
		unless grep { $captions[$_] =~ m/^\s*DIR:\s*$entry->{filename}\s+----\s/ }
		    ( 0 .. $#captions ) ;
	}
	print_percentage ($i+1, scalar @entries) ;
    }

    end_percentage () ;

    close CAP ;
    show_warnings ;
}

#######################################################################
# Read entry list in the captions file

sub read_captions_file {
    my $gallery = shift ;
    my @entries = () ;
    my @headers = () ;
    my @footers = () ;

    indprint "Reading the $opts->{captions_filename} file and preparing entries: " ;
    init_percentage () ;

    open(CAP,"$destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename}")
	or die "Can't open $destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename} file\n" ;

    my $size = (-s CAP) ;
    my $line ;
    while (defined ($line = <CAP>)) {
	chomp $line ;
	$line =~ s/^\s*// ;
	$line =~ s/\s\(\s*\)$/$1/ ;
	# only lines that don't start with # and are not empty
	if (!($line =~ m/^\#/) and !($line =~ /^$/)) {
	    if ($line =~ m/^TITLE:\s*(.*)\s*/) {
		# title
		$opts->{index_title} = $1 ;

	    } elsif ($line =~ m/^INDEXHEAD:\s*(.*)\s*$/) {
		# header for the index
		push @headers, $1 ;

	    } elsif ($line =~ m/^INDEXFOOT:\s*(.*)\s*$/) {
		# footer for the index
		push @footers, $1 ;

# TODO move these to $gallery
	    } elsif ($line =~ m/^PARENT:\s*(.*)\s*$/) {
		# parent gallery link label
		$opts->{parent_gallery_link} = 1 ;
		$opts->{parent_gallery_link_text} = $1 ;

	    } elsif ($line =~ m/^PREV:\s*(.*)\s+----\s+(.*)\s*$/) {
		# prev gallery link label
		$opts->{prev_gallery_link_text} = $1 ;
		$opts->{prev_gallery_link} = $2 ;

	    } elsif ($line =~ m/^NEXT:\s*(.*)\s+----\s+(.*)\s*$/) {
		# next gallery link label
		$opts->{next_gallery_link_text} = $1 ;
		$opts->{next_gallery_link} = $2 ;

	    } else {
		# that's a slide, create its entry
		my $entry ;
		# flexibility:
		# - the last \s might be omitted when there's no caption
		# - \s is facultative after TYP: at the begining
		if ($line =~ m/^TXT:\s*(.+)\s+----\s*(?:\s(.*))?\s*$/) {
		    # text slide
		    $entry = create_nofile_entry ($TYPE_TXT, undef, $1, $2) ;

		} elsif ($line =~ m/^LNK:\s*(.+)\s+----\s+(.+)\s+----\s*(?:\s(.*))?\s*$/) {
		    # link slide
		    $entry = create_nofile_entry ($TYPE_LNK, $1, $2, $3) ;

		} elsif ($line =~ m/^DIR:\s*(.+)\s+----\s+(.+)\s+----\s*(?:\s(.*))?\s*$/) {
		    # directory slide
		    $entry = create_file_entry ($TYPE_DIR, $1, $2, $3) ;

		} elsif ($line =~ m/^MVI:\s*(.+)\s+----\s+(.+)\s+----\s*(?:\s(.*))?\s*$/) {
		    # movie slide
		    $entry = create_file_entry ($TYPE_MVI, $1, $2, $3) ;

		} elsif ($line =~ m/^FIL:\s*(.+)\s+----\s+(.+)\s+----\s*(?:\s(.*))?\s*$/) {
		    # movie slide
		    $entry = create_file_entry ($TYPE_FIL, $1, $2, $3) ;

		} elsif ($line =~ m/^IMG:\s*(.+)\s+----\s*(?:\s(.*))?\s*$/
			 or $line =~ m/^(.+)\s+----\s*(?:\s(.*))?\s*$/) {
		    # image slide (default)
		    $entry = create_file_entry ($TYPE_IMG, $1, undef, $2) ;

		} else {
		    die "Unrecognized line #$. in captions file: \"$line\"\n" ;
		}

		# really add this entry
		push @entries, $entry
		    if defined $entry ;
	    }
	}
	print_percentage (tell CAP, $size) ;
    }
    close CAP ;
    end_percentage () ;
    show_warnings ;

    @{$gallery->{entries}} = @entries ;
    @{$gallery->{headers}} = @headers ;
    @{$gallery->{footers}} = @footers ;
}

#######################################################################
# Extract entries from the table

sub extract_listed_entries {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;

    my @table_entries = () ;
    my @listed_entries = () ;
    while (@entries) {
	my $entry = shift @entries ;
	my $type = $entry->{type} ;
	if ($type == $TYPE_LNK or $type == $TYPE_DIR or $type == $TYPE_MVI or $type == $TYPE_FIL) {
	    push @listed_entries, $entry ;
	} else {
	    push @table_entries, $entry ;
	}
    }
    @{$gallery->{entries}} = @table_entries ;
    @{$gallery->{listed_entries}} = @listed_entries ;
}

#######################################################################
# Add recursion header and footer

sub add_headers_footers {
    my $gallery = shift ;
    my @headers = @{$gallery->{headers}} ;
    my @footers = @{$gallery->{footers}} ;

    # parent, prev and next, in reverse order
    unshift @headers, "<div style=\"text-align: right;\"><a href=\"$opts->{next_gallery_link_target}\">$opts->{next_gallery_link_text}$opts->{next_gallery_link_target}</a></div>"
	if $opts->{next_gallery_link} ;
    unshift @headers, "<a href=\"$opts->{prev_gallery_link_target}\">$opts->{prev_gallery_link_text}$opts->{prev_gallery_link_target}</a>"
	if $opts->{prev_gallery_link} ;
    unshift @headers, "<a href=\"..\">$opts->{parent_gallery_link_text}</a>"
	if $opts->{parent_gallery_link} ;

    # prev, next and parent
    push @footers, "<a href=\"$opts->{prev_gallery_link_target}\">$opts->{prev_gallery_link_text}$opts->{prev_gallery_link_target}</a>"
	if $opts->{prev_gallery_link} ;
    push @footers, "<div style=\"text-align: right;\"><a href=\"$opts->{next_gallery_link_target}\">$opts->{next_gallery_link_text}$opts->{next_gallery_link_target}</a></div>"
	if $opts->{next_gallery_link} ;
    push @footers, "<a href=\"..\">$opts->{parent_gallery_link_text}</a>"
	if $opts->{parent_gallery_link} ;

    @{$gallery->{headers}} = @headers ;
    @{$gallery->{footers}} = @footers ;
}

#######################################################################
# Create the individual slide show files

sub generate_slides {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;

    # remove old webpages
    system ("rm -f ${destination_dir}*.$opts->{www_extension}") ;
    die "Failed to remove existing webpages.\n"
	if $? ;

    # find the slidetemplate
    my $slidetemplate = find_template_file ($opts->{slidetemplate_filename}, 1) . "/$opts->{slidetemplate_filename}" ;
    indprint "Using '$slidetemplate' as HTML slide template.\n" ;

    # keep the slidetemplate in memory instead of always reopening it
    my @slidetemplate_text ;
    open(SR,"$slidetemplate")
	or die "Can't open the slide template file '$slidetemplate'\n" ;
    @slidetemplate_text = <SR> ;
    close SR ;

    # create slides
    indprint "Creating individual slides: " ;
    init_percentage () ;
    for (my $i = 0; $i < @entries; $i++) {
	my $entry = $entries[$i] ;
	open(SW, ">$destination_dir$entry->{slide_filename}")
	    or die "Can't create slide file\n" ;
	my $type = $entry->{type} ;
	my $title = $entry->{title} ;

	my @slidetemplate_text_copy = @slidetemplate_text ; # don't touch the original template
        for my $line (@slidetemplate_text_copy) {
	    $line =~ s/LLGAL-CODESET/$codeset/g ;
	    $line =~ s/<!--SLIDE-TITLE-->/$title/g ;
	    $line =~ s/<!--TITLE-->/$opts->{index_title}/g ;
	    if ($opts->{css_location}) {
		$line =~ s/<!--CSS-->/$opts->{css_location}/g ;
	    } else {
		$line =~ s/<!--CSS-->/$opts->{local_llgal_dir}\/$opts->{css_filename}/g ;
	    }

	    # set the style
	    if ($line =~ m/<!--THIS-SLIDE-STYLE-->/g) {
		if ($entry->{scaled_url}) {
		    $line =~ s/<!--THIS-SLIDE-STYLE-->/image-slide/ ;
		} else {
		    $line =~ s/<!--THIS-SLIDE-STYLE-->/text-slide" style="width: $opts->{text_slide_width}px; height: $opts->{text_slide_height}px;/ ;
		}
	    }

	    # fill the main part
	    if ($line =~ m/<!--THIS-SLIDE-->/g) {
		my $content ;
		if ($type == $TYPE_IMG) {
		    # image slide

		    if (defined $entry->{scaled_filename}) {
			# scaled image with link to the real image
			$content = "<a href=\"$entry->{url}\" "
			    ."title=\"$opts->{over_scaled_text}$title $entry->{dimstring}\">" ;
			$content .= "<img src=\"$entry->{scaled_url}\" "
			    ."style=\"width: $entry->{scaled_xdim}px; height: $entry->{scaled_ydim}px;\" "
			    ."alt=\"$opts->{alt_scaled_text}$title $entry->{scaled_dimstring}\" />" ;
			$content .= "</a>" ;

		    } else {
			# real image
			$content = "<img src=\"$entry->{url}\" "
			    ."style=\"width: $entry->{scaled_xdim}px; height: $entry->{scaled_ydim}px;\" "
			    ."alt=\"$opts->{alt_full_text}$title $entry->{dimstring}\" "
			    ."title=\"$title $entry->{dimstring}\" />" ;

		    }
		} elsif ($entry->{scaled_url}) {
		    # another type, with a user-given scaled image
		    if ($type == $TYPE_TXT) {
			# text with scaled image ? ok...
			$content = "<img src=\"$entry->{scaled_url}\" "
			    ."style=\"width: $entry->{scaled_xdim}px; height: $entry->{scaled_ydim}px;\" "
			    ."title=\"$title $entry->{dimstring}\" "
			    ."alt=\"$entry->{linktext} $entry->{scaled_dimstring}\" />" ;
		    } else {
			# another type with scaled image linking to the target
			$content = "<a href=\"$entry->{url}\" "
			    ."title=\"$title $entry->{dimstring}\">" ;
			$content .= "<img src=\"$entry->{scaled_url}\" "
			    ."style=\"width: $entry->{scaled_xdim}px; height: $entry->{scaled_ydim}px;\" "
			    ."alt=\"$entry->{linktext} $entry->{scaled_dimstring}\" />" ;
			$content .= "</a>" ;
		    }
		} elsif ($type == $TYPE_TXT) {
		    # text slide
		    $content = $entry->{linktext} ;
		} else {
		    # link-style slide
		    $content = "<a href=\"$entry->{url}\" title=\"$title $entry->{dimstring}\">$entry->{linktext}</a>" ;
		}

		# finally replace <!--THIS-SLIDE-->
		$line =~ s/<!--THIS-SLIDE-->/$content/ ;
	    }

	    # add the caption, with the image counter unless --nc is specified
	    my $imagecaption ;
	    if ($opts->{show_no_slide_counter}) {
		$imagecaption = $entry->{caption} ;
	    } else{
		$imagecaption = $entry->{caption} . "&nbsp;&nbsp;&nbsp;(" . ($i+1) ."/". (scalar @entries) .")" ;
	    }

	    # add dimensions and size to the caption
	    $imagecaption .= "&nbsp;&nbsp;&nbsp;$entry->{dimstring}" ;

	    $line =~ s/<!--IMAGE-CAPTION-->/$imagecaption/g ;
	    $line =~ s/<!--THIS-SLIDE-->/$entry->{slide_url}/g ;

	    # process INDEX-FILE
	    my $index_link = $opts->{index_link_text} ;
	    if ($opts->{index_link_image}) {
		if ($opts->{index_link_image_location}) {
		    $index_link = "<img src=\"$opts->{index_link_image_location}\" "
			."alt=\"$opts->{index_link_text}\" "
			."class=\"image-link\" />" ;
		} else {
		    $index_link = "<img src=\"$opts->{local_llgal_dir}/". (make_safe_url $opts->{index_link_image_filename}) ."\" "
			."alt=\"$opts->{index_link_text}\" class=\"image-link\" />" ;
		}
	    }
	    $line =~ s/<!--INDEX-LINK-TEXT-->/$index_link/g ;
	    $line =~ s/<!--INDEX-FILE-->/$opts->{index_filename}.$opts->{www_extension}\" title=\"$opts->{over_index_link_text}/g ;

	    # process PREV-SLIDE
	    my $prev = $entries[$i-1] ;

	    my $prev_slide_link ;
	    if ($opts->{prev_slide_link_preview} and $prev->{thumb_url}) {
		$prev_slide_link = "<img src=\"$prev->{thumb_url}\" "
		    ."style=\"width: $prev->{thumb_xdim}px; height: $prev->{thumb_ydim}px;\" "
		    ."alt=\"$opts->{prev_slide_link_text}\" />" ;
	    } elsif ($opts->{prev_slide_link_image}) {
		if ($opts->{prev_slide_link_image_location}) {
		    $prev_slide_link = "<img src=\"$opts->{prev_slide_link_image_location}\" "
			."alt=\"$opts->{prev_slide_link_text}\" class=\"image-link\" />" ;
		} else {
		    $prev_slide_link = "<img src=\"$opts->{local_llgal_dir}/". (make_safe_url $opts->{prev_slide_link_image_filename}) ."\" "
			."alt=\"$opts->{prev_slide_link_text}\" class=\"image-link\" />" ;
		}
	    } else {
		$prev_slide_link = $opts->{prev_slide_link_text} ;
	    }
	    $line =~ s/<!--PREV-SLIDE-LINK-TEXT-->/$prev_slide_link/g ;
	    $line =~ s/<!--PREV-SLIDE-->/$prev->{slide_url}\" title=\"$opts->{over_prev_slide_link_text}$prev->{title}/g ;

	    # process NEXT-SLIDE
	    my $next = $entries[$i==@entries-1 ? 0 : $i+1] ;

	    my $next_slide_link ;
	    if ($opts->{next_slide_link_preview} and $next->{thumb_url}) {
		$next_slide_link = "<img src=\"$next->{thumb_url}\" "
		    ."style=\"width: $next->{thumb_xdim}px; height: $next->{thumb_ydim}px;\" "
		    ."alt=\"$opts->{next_slide_link_text}\" />" ;
	    } elsif ($opts->{next_slide_link_image}) {
		if ($opts->{next_slide_link_image_location}) {
		    $next_slide_link = "<img src=\"". $opts->{next_slide_link_image_location} ."\" "
			."alt=\"$opts->{next_slide_link_text}\" class=\"image-link\" />" ;
		} else {
		    $next_slide_link = "<img src=\"$opts->{local_llgal_dir}/". (make_safe_url $opts->{next_slide_link_image_filename}) ."\" "
			."alt=\"$opts->{next_slide_link_text}\" class=\"image-link\" />" ;
		}
	    } else {
		$next_slide_link = $opts->{next_slide_link_text} ;
	    }
	    $line =~ s/<!--NEXT-SLIDE-LINK-TEXT-->/$next_slide_link/g ;
	    $line =~ s/<!--NEXT-SLIDE-->/$next->{slide_url}\" title=\"$opts->{over_next_slide_link_text}$next->{title}/g ;

	    $line =~ s/<!--CREDITS-->/$opts->{credits_text}/g ;

	    # output the line
	    print SW "$line" ;
	}
	close SW ;
	print_percentage ($i+1, scalar @entries) ;
    }
    end_percentage () ;
}
show_warnings ;

#######################################################################
# Creating the index file

sub generate_index {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;
    my @listed_entries = @{$gallery->{listed_entries}} ;
    my @headers = @{$gallery->{headers}} ;
    my @footers = @{$gallery->{footers}} ;

    # find the indextemplate
    my $indextemplate = find_template_file ($opts->{indextemplate_filename}, 1) . "/$opts->{indextemplate_filename}" ;
    indprint "Using '$indextemplate' as HTML index template.\n" ;

    # open the template and the destination
    indprint "Creating the $opts->{index_filename}.$opts->{www_extension} file: " ;
    open(IXR, "$indextemplate")
	or die "Can't open the index template file '$indextemplate'\n" ;
    open(IXW, ">$destination_dir$opts->{index_filename}.$opts->{www_extension}")
	or die "Can't create main $opts->{index_filename}.$opts->{www_extension} file\n" ;

    # headers
    my $line ;
    while (defined($line = <IXR>)) {

	# stop at <!-- ********** -->
	last if $line =~ m/\*{10}/ ;

	if ($line =~ m/<!--HEADERS-->/) {
	    foreach my $header (@headers) {
		print IXW "    <div class=\"header\">" . $header . "</div>\n" ;
	    }
	} else {
	    $line =~ s/LLGAL-CODESET/$codeset/g ;
	    $line =~ s/LLGAL-OPTIONS/@llgal_options/g ;
	    $line =~ s/<!--TITLE-->/$opts->{index_title}/g ;
	    if ($opts->{css_location}) {
		$line =~ s/<!--CSS-->/$opts->{css_location}/g ;
	    } else {
		$line =~ s/<!--CSS-->/$opts->{local_llgal_dir}\/$opts->{css_filename}/g ;
	    }
	    print IXW "$line" ;
	}
    }
    print IXW "\n" ;

    # for each row
    my $num ;
    my $forced_width_warning = 0 ;
    init_percentage () ;
    for (my $i = 0; $i < @entries; $i += $num) {
	$num = 0 ;

	# figure out how many to put in next row.
	my $width = 0 ;
	while ( ($i+$num < @entries)
		and (!$opts->{thumbnails_per_row} or $num < $opts->{thumbnails_per_row})
		and (!$opts->{pixels_per_row} or $width < $opts->{pixels_per_row}) ) {
	    $width += $entries[$num+$i]->{thumb_xdim} ;
	    $num++ ;
	    # add cellpadding
	    $width += $opts->{index_cellpadding} ;
	}
	if ($opts->{pixels_per_row} > 0 and $width > $opts->{pixels_per_row}) {
	    if ($num >= 2) {
		$num-- ;
	    } else {
		$forced_width_warning++ ;
	    }
	}

	# Table header
	if ($opts->{show_no_film_effect}) {
	    print IXW "<table class=\"index\" style=\"border-spacing: ", $opts->{index_cellpadding}, "px 0px;\">\n" ;
	} else {
	    print IXW "<table class=\"index with-tile\" style=\"border-spacing: ", $opts->{index_cellpadding}, "px 0px;\">\n" ;
	}

	# Row header
	print IXW "  <tr><td class=\"tiled\" colspan=\"", ($opts->{show_no_film_effect}?$num:$num+2), "\">&nbsp;</td></tr>\n"
	    unless $opts->{show_no_film_effect} ;
	print IXW "  <tr>\n" ;
	print IXW "    <td class=\"thumb\">&nbsp;</td>\n"
	    unless $opts->{show_no_film_effect} ;

	# Actual row of thumbnails
	for (my $j = 0; $j < $num; $j++) {
	    my $entry = $entries[$i+$j] ;
	    my $type = $entry->{type} ;
	    my $title = $entry->{title} ;
	    my $width = $entry->{thumb_xdim} ;
	    my $height = $entry->{thumb_ydim} ;

	    if ($type == $TYPE_IMG) {
		# IMG thumbnail

		print IXW "    <td class=\"thumb\" style=\"width: ${width}px; height: ${height}px;\">\n" ;
		if ($opts->{make_no_slides}) {
		    print IXW "      <a href=\"$entry->{url}\"" ;
		} else {
		    print IXW "      <a href=\"$entry->{slide_url}\"" ;
		}
		print IXW " title=\"$opts->{over_thumbnail_text}$title $entry->{scaled_dimstring}\">\n" ;
		print IXW "        <img src=\"$entry->{thumb_url}\" "
			."style=\"width: $entry->{thumb_xdim}px; height: $entry->{thumb_ydim}px;\" "
			."alt=\"$opts->{alt_thumbnail_text}$title $entry->{thumb_dimstring}\" />\n" ;
		print IXW "      </a>\n" ;
		print IXW "    </td>\n" ;

	    } elsif ($entry->{thumb_url}) {
		# not IMG, but with a user-given thumbnail

		print IXW "    <td class=\"thumb\" style=\"width: ${width}px; height: ${height}px;\">\n" ;
		if ($type == $TYPE_TXT) {
		    # TXT with a thumbnail ? ok...
		    if ($opts->{make_no_slides}) {
			# thumbnail linking to the TXT slide
			print IXW "      <a href=\"$entry->{slide_url}\" "
				."title=\"$title $entry->{scaled_dimstring}\">\n" ;
			print IXW "        <img src=\"$entry->{thumb_url}\" "
				."style=\"width: $entry->{thumb_xdim}px; height: $entry->{thumb_ydim}px;\" "
				."alt=\"$entry->{linktext} $entry->{thumb_dimstring}\" />\n" ;
			print IXW "      </a>" ;

		    } else {
			# thumbnail without any link
			print IXW "      <img src=\"$entry->{thumb_url}\" "
				."title=\"$title $entry->{scaled_dimstring}\" "
				."style=\"width: $entry->{thumb_xdim}px; height: $entry->{thumb_ydim}px;\" "
				."alt=\"$entry->{linktext} $entry->{thumb_dimstring}\" />\n" ;
		    }
		} else {
		    # other types directly link to the target instead of the slide
		    print IXW "      <a href=\"$entry->{url}\" "
		    	."title=\"$title $entry->{scaled_dimstring}\">\n" ;
		    print IXW "        <img src=\"$entry->{thumb_url}\" "
			."style=\"width: $entry->{thumb_xdim}px; height: $entry->{thumb_ydim}px;\" "
			."alt=\"$entry->{linktext} $entry->{thumb_dimstring}\" />\n" ;
		    print IXW "      </a>\n" ;
		}
		print IXW "    </td>\n" ;
	    } else {
		# not IMG, without thumbnail
		print IXW "    <td class=\"text-thumb\" style=\"width: ", $width, "px; height: ", $height, "px;\">\n" ;
		if ($type == $TYPE_TXT) {
		    # thumbnail linking to the slide
		    print IXW "      <a href=\"$entry->{slide_url}\" title=\"$title\">$entry->{linktext}</a>\n" ;
		} else {
		    # thumbnail directly linking to the target
		    print IXW "      <a href=\"$entry->{url}\" title=\"$title $entry->{dimstring}\">$entry->{linktext}</a>\n" ;
		}
		print IXW "    </td>\n" ;
	    }
	    print_percentage ($i+$j+1, scalar @entries) ;
	}

	# Row footer
	print IXW "    <td class=\"thumb\">&nbsp;</td>\n"
	    unless $opts->{show_no_film_effect} ;
	print IXW "  </tr>\n" ;
	print IXW "  <tr><td class=\"tiled\" colspan=\"", $num+2, "\">&nbsp;</td></tr>\n"
	    unless $opts->{show_no_film_effect} ;

	# Dimensions and filesizes
	if ($opts->{show_dimensions} or $opts->{show_size}) {
	    print IXW "  <tr>\n" ;
	    print IXW "    <td>&nbsp;</td>\n"
		unless $opts->{show_no_film_effect} ;
	    for (my $j = 0; $j < $num; $j++) {
		my $entry = $entries[$i+$j] ;
		print IXW "    <td class=\"thumb-dim\">$entry->{scaled_dimstring}</td>\n" ;
	    }
	    print IXW "    <td>&nbsp;</td>\n"
		unless $opts->{show_no_film_effect} ;
	    print IXW "  </tr>\n" ;
	}

	# Write image captions under images if option -u is given
	if ($opts->{show_caption_under_thumbnails}) {
	    print IXW "  <tr>\n" ;
	    print IXW "<td>&nbsp;</td>\n"
		unless $opts->{show_no_film_effect} ;
	    for (my $j = 0; $j < $num; $j++) {
		my $entry = $entries[$i+$j] ;
		print IXW "    <td class=\"thumb-caption\">$entry->{caption}</td>\n" ;
	    }
	    print IXW "  </tr>\n" ;
	}

	# Table footer
	print IXW "</table>\n<br />\n" ;
    }

    # list links
    if ($opts->{list_links}) {
	foreach my $entry (@listed_entries) {
	    my $type = $entry->{type} ;
	    print IXW "<div class=\"header\">" ;
	    if ($type == $TYPE_TXT) {
		print IXW $entry->{linktext} ;
	    } else {
		# directly open links from the index
		print IXW "<a href=\"$entry->{url}\" title=\"$entry->{title} $entry->{dimstring}\">$entry->{linktext}</a>" ;
	    }
	    print IXW "</div>\n" ;
	}
    }

    # search next <!-- ********** -->
    while (defined($line = <IXR>)) {
	last if $line =~ m/\*{10}/ ;
    }
    print IXW "\n";

    # footers
    while (defined ($line = <IXR>)) {
	if ($line =~ m/<!--FOOTERS-->/) {
	    foreach my $footer (@footers) {
		print IXW "    <div class=\"footer\">" . $footer . "</div>\n" ;
	    }
	} else {
	    $line =~ s/<!--TITLE-->/$opts->{index_title}/g ;
	    $line =~ s/<!--CREDITS-->/$opts->{credits_text}/g ;
	    print IXW "$line" ;
	}
    }
    close IXW ;
    close IXR ;

    end_percentage () ;
    show_warnings ;

    immediate_warning "Row width max ($opts->{pixels_per_row}) too low for one single thumbnail. "
	. "Forced $forced_width_warning time". ($forced_width_warning>1?"s":"") ."."
	if $forced_width_warning ;
}

#######################################################################
# Get llgal files

sub get_files {
    # Get the film tile for the index
    if ($opts->{show_no_film_effect}) {
	indprint "Omitting film effect.\n" ;
    } elsif ($opts->{filmtile_location}) {
	indprint "Using the film tile that is available on $opts->{filmtile_location}.\n" ;
    } else {
	get_template_file ($opts->{filmtile_filename}) ;
	# rescale the tiled image if needed
	my $filmtileh = (imgsize ("$destination_dir$opts->{local_llgal_dir}/$opts->{filmtile_filename}"))[1] ;
	if ($opts->{filmtile_height} != $filmtileh) {
	    my ($status, @output) = system_with_output
		( "reduce film tile",
		  "mogrify", "-scale", "x$opts->{filmtile_height}", "$destination_dir$opts->{local_llgal_dir}/$opts->{filmtile_filename}" ) ;
	    if ($status) {
		# die on whatever error
		immediate_external_warnings @output ;
		die "Failed to execute 'mogrify' to reduce the film tile.\n" ;
	    }
	}
    }

    # Get link images
    if ($opts->{index_link_image}) {
	if ($opts->{index_link_image_location}) {
	    indprint "Using the index link image that is available on $opts->{index_link_image_location}.\n" ;
	} else {
	    get_template_file ($opts->{index_link_image_filename}) ;
	}
    }
    if ($opts->{prev_slide_link_image} and ! $opts->{prev_slide_link_preview}) {
	if ($opts->{prev_slide_link_image_location}) {
	    indprint "Using the prev slide link image that is available on $opts->{prev_slide_link_image_location}.\n" ;
	} else {
	    get_template_file ($opts->{prev_slide_link_image_filename}) ;
	}
    }
    if ($opts->{next_slide_link_image} and ! $opts->{next_slide_link_preview}) {
	if ($opts->{next_slide_link_image_location}) {
	    indprint "Using the next slide link image that is available on $opts->{next_slide_link_image_location}.\n" ;
	} else {
	    get_template_file ($opts->{next_slide_link_image_filename}) ;
	}
    }

    # Get the css
    if ($opts->{css_location}) {
	indprint "Using the CSS that is available on $opts->{css_location}.\n" ;
    } else {
	get_template_file ($opts->{css_filename}) ;
    }
}

#######################################################################
# Chmod

sub make_readable {
    my $file = shift ;
    my ($status, @output) = system_with_output ("make world readable",
						"chmod", "a+r", $file) ;
    add_external_warnings @output
	if $status ;
}

sub make_readable_and_traversable {
    my $file = shift ;
    my ($status, @output) = system_with_output ("make world readable and traversable",
						"chmod", "a+rx", $file) ;
    add_external_warnings @output
	if $status ;
}

# If --www was invoked make all files world-readable at the END
sub make_www_rights {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;
    my @listed_entries = @{$gallery->{listed_entries}} ;

    indprint "Making all llgal files world-readable for WWW publishing.\n" ;

    # index
    make_readable "$destination_dir$opts->{index_filename}.$opts->{www_extension}" ;
    # .llgal
    make_readable_and_traversable "$destination_dir$opts->{local_llgal_dir}" ;
    # css
    make_readable "$destination_dir$opts->{local_llgal_dir}/$opts->{css_filename}" ;
    # filmtile
    make_readable "$destination_dir$opts->{local_llgal_dir}/$opts->{filmtile_filename}"
	if ! $opts->{show_no_film_effect} ;
    # index link image
    make_readable "$destination_dir$opts->{local_llgal_dir}/$opts->{index_link_image_filename}"
 	if $opts->{index_link_image} ;
    # prev slide link image
    make_readable "$destination_dir$opts->{local_llgal_dir}/$opts->{prev_slide_link_image_filename}"
 	if $opts->{prev_slide_link_image} and ! $opts->{prev_slide_link_preview};
    # next slide link image
    make_readable "$destination_dir$opts->{local_llgal_dir}/$opts->{next_slide_link_image_filename}"
 	if $opts->{next_slide_link_image} and ! $opts->{next_slide_link_preview} ;

    # entries that have a slide
    foreach my $entry (@entries) {
	my $type = $entry->{type} ;

	# target
	my $file = "$destination_dir$entry->{filename}" ;
	if ($type == $TYPE_DIR) {
	    make_readable_and_traversable $file ;
	} else {
	    make_readable $file ;
	}

	# slide
	make_readable "$destination_dir$entry->{slide_filename}"
	    if ! $opts->{make_no_slides} ;

	if ($type == $TYPE_IMG) {
	    # thumbnail
	    make_readable "$destination_dir$entry->{thumb_filename}" ;
	    # scaled image
	    make_readable "$destination_dir$entry->{scaled_filename}"
		if ($opts->{slide_width_max} > 0 or $opts->{slide_height_max} > 0) and !$opts->{make_no_slides} ;
	}

    }

    # entries that are just listed
    foreach my $entry (@listed_entries) {
	my $type = $entry->{type} ;

	# target
	my $file = "$destination_dir$entry->{filename}" ;
	if ($type == $TYPE_DIR) {
	    make_readable_and_traversable $file ;
	} else {
	    make_readable $file ;
	}
    }

    show_warnings ;
}

#######################################################################
# If --gencfg was invoked, generate a configuration file

sub generate_config {
    my $file = shift ;
    die "Cannot generate $generate_config which already exists."
	if -e "$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.\n" ;
    print NEWCFG "# You may modify and reuse it as you want.\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# llgal directories:\n" ;
    print NEWCFG "llgal_share_dir = \"$opts->{llgal_share_dir}\"\n" ;
    print NEWCFG "user_share_dir = \"$opts->{user_share_dir}\"\n" ;
    map { print NEWCFG "template_dir = \"$_\"\n" ; } @{$opts->{template_dirs}} ;
    print NEWCFG "\n" ;
    print NEWCFG "# Name of generic llgal files:\n" ;
    print NEWCFG "captions_header_filename = \"$opts->{captions_header_filename}\"\n" ;
    print NEWCFG "css_filename = \"$opts->{css_filename}\"\n" ;
    print NEWCFG "filmtile_filename = \"$opts->{filmtile_filename}\"\n" ;
    print NEWCFG "index_link_image_filename = \"$opts->{index_link_image_filename}\"\n" ;
    print NEWCFG "prev_slide_link_image_filename = \"$opts->{prev_slide_link_image_filename}\"\n" ;
    print NEWCFG "next_slide_link_image_filename = \"$opts->{next_slide_link_image_filename}\"\n" ;
    print NEWCFG "indextemplate_filename = \"$opts->{indextemplate_filename}\"\n" ;
    print NEWCFG "slidetemplate_filename = \"$opts->{slidetemplate_filename}\"\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Location and name of generated files:\n" ;
    print NEWCFG "local_llgal_dir = \"$opts->{local_llgal_dir}\"\n" ;
    print NEWCFG "index_filename = \"$opts->{index_filename}\"\n" ;
    print NEWCFG "slide_filenameprefix = \"$opts->{slide_filenameprefix}\"\n" ;
    print NEWCFG "slide_filenameprefix_nofile = \"$opts->{slide_filenameprefix_nofile}\"\n" ;
    print NEWCFG "scaled_image_filenameprefix = \"$opts->{scaled_image_filenameprefix}\"\n" ;
    print NEWCFG "thumbnail_image_filenameprefix = \"$opts->{thumbnail_image_filenameprefix}\"\n" ;
    print NEWCFG "captions_filename = \"$opts->{captions_filename}\"\n" ;
    print NEWCFG "user_scaled_image_filenameprefix = \"$opts->{user_scaled_image_filenameprefix}\"\n" ;
    print NEWCFG "user_thumbnail_image_filenameprefix = \"$opts->{user_thumbnail_image_filenameprefix}\"\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Index:\n" ;
    print NEWCFG "index_cellpadding = $opts->{index_cellpadding}\n" ;
    print NEWCFG "list_links = $opts->{list_links}\n" ;
    print NEWCFG "pixels_per_row = $opts->{pixels_per_row}\n" ;
    print NEWCFG "thumbnails_per_row = $opts->{thumbnails_per_row}\n" ;
    print NEWCFG "thumbnail_width_max = $opts->{thumbnail_width_max}\n" ;
    print NEWCFG "thumbnail_height_max = $opts->{thumbnail_height_max}\n" ;
    print NEWCFG "show_caption_under_thumbnails = $opts->{show_caption_under_thumbnails}\n" ;
    print NEWCFG "show_no_film_effect = $opts->{show_no_film_effect}\n" ;
    print NEWCFG "filmtile_height = $opts->{filmtile_height}\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Slides:\n" ;
    print NEWCFG "make_no_slides = $opts->{make_no_slides}\n" ;
    print NEWCFG "make_slide_filename_from_filename = $opts->{make_slide_filename_from_filename}\n" ;
    print NEWCFG "make_slide_filename_from_extension = $opts->{make_slide_filename_from_extension}\n" ;
    print NEWCFG "slide_width_max = $opts->{slide_width_max}\n" ;
    print NEWCFG "slide_height_max = $opts->{slide_height_max}\n" ;
    print NEWCFG "text_slide_width = $opts->{text_slide_width}\n" ;
    print NEWCFG "text_slide_height = $opts->{text_slide_height}\n" ;
    print NEWCFG "index_link_image = $opts->{index_link_image}\n" ;
    print NEWCFG "prev_slide_link_image = $opts->{prev_slide_link_image}\n" ;
    print NEWCFG "next_slide_link_image = $opts->{next_slide_link_image}\n" ;
    print NEWCFG "prev_slide_link_preview = $opts->{prev_slide_link_preview}\n" ;
    print NEWCFG "next_slide_link_preview = $opts->{next_slide_link_preview}\n" ;
    print NEWCFG "make_slide_title_from_caption = $opts->{make_slide_title_from_caption}\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Captions:\n" ;
    print NEWCFG "captions_removal_line = \"$opts->{captions_removal_line}\"\n" ;
    print NEWCFG "make_caption_from_image_comment = \"$opts->{make_caption_from_image_comment}\"\n" ;
    print NEWCFG "make_caption_from_image_timestamp = $opts->{make_caption_from_image_timestamp}\n" ;
    print NEWCFG "make_caption_from_filename = $opts->{make_caption_from_filename}\n" ;
    print NEWCFG "show_dimensions = $opts->{show_dimensions}\n" ;
    print NEWCFG "show_size = $opts->{show_size}\n" ;
    print NEWCFG "show_no_slide_counter = $opts->{show_no_slide_counter}\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Text:\n" ;
    print NEWCFG "index_title = \"$opts->{index_title}\"\n" ;
    print NEWCFG "index_link_text = \"$opts->{index_link_text}\"\n" ;
    print NEWCFG "parent_gallery_link_text = \"$opts->{parent_gallery_link_text}\"\n" ;
    print NEWCFG "prev_gallery_link_text = \"$opts->{prev_gallery_link_text}\"\n" ;
    print NEWCFG "next_gallery_link_text = \"$opts->{next_gallery_link_text}\"\n" ;
    print NEWCFG "prev_slide_link_text = \"$opts->{prev_slide_link_text}\"\n" ;
    print NEWCFG "next_slide_link_text = \"$opts->{next_slide_link_text}\"\n" ;
    print NEWCFG "MVI_link_text = \"$opts->{MVI_link_text}\"\n" ;
    print NEWCFG "FIL_link_text = \"$opts->{FIL_link_text}\"\n" ;
    print NEWCFG "DIR_link_text = \"$opts->{DIR_link_text}\"\n" ;
    print NEWCFG "alt_full_text = \"$opts->{alt_full_text}\"\n" ;
    print NEWCFG "alt_scaled_text = \"$opts->{alt_scaled_text}\"\n" ;
    print NEWCFG "alt_thumbnail_text = \"$opts->{alt_thumbnail_text}\"\n" ;
    print NEWCFG "over_scaled_text = \"$opts->{over_scaled_text}\"\n" ;
    print NEWCFG "over_thumbnail_text = \"$opts->{over_thumbnail_text}\"\n" ;
    print NEWCFG "over_index_link_text = \"$opts->{over_index_link_text}\"\n" ;
    print NEWCFG "over_prev_slide_link_text = \"$opts->{over_prev_slide_link_text}\"\n" ;
    print NEWCFG "over_next_slide_link_text = \"$opts->{over_next_slide_link_text}\"\n" ;
    print NEWCFG "show_size_unit = \"$opts->{show_size_unit}\"\n" ;
    print NEWCFG "timestamp_format_in_caption = \"$opts->{timestamp_format_in_caption}\"\n" ;
    print NEWCFG "credits_text = \"$opts->{credits_text}\"\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Recursion:\n" ;
    print NEWCFG "make_recursive = $make_recursive\n" ;
    print NEWCFG "link_subgalleries = $opts->{link_subgalleries}\n" ;
    print NEWCFG "parent_gallery_link = $opts->{parent_gallery_link}\n" ;
    print NEWCFG "prev_gallery_link = $opts->{prev_gallery_link}\n" ;
    print NEWCFG "prev_gallery_link_target = \"$opts->{prev_gallery_link_target}\"\n" ;
    print NEWCFG "next_gallery_link = $opts->{next_gallery_link}\n" ;
    print NEWCFG "next_gallery_link_target = \"$opts->{next_gallery_link_target}\"\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# What files to insert in the gallery:\n" ;
    print NEWCFG "image_extensions = \"$opts->{image_extensions}\"\n" ;
    print NEWCFG "movie_extensions = \"$opts->{movie_extensions}\"\n" ;
    print NEWCFG "add_all_files = $opts->{add_all_files}\n" ;
    print NEWCFG "add_subdirs = $opts->{add_subdirs}\n" ;
    map {
	print NEWCFG ($_->{excluded} ? "exclude" : "include"). " = \"". $_->{filter} ."\"\n" ;
    } @{$opts->{excludes}} ;
    print NEWCFG "sort_criteria = \"$opts->{sort_criteria}\"\n" ;
    print NEWCFG "\n" ;
    print NEWCFG "# Various:\n" ;
    print NEWCFG "convert_options = \"". (join_convert_options (@{$opts->{convert_options}})) ."\"\n" ;
    print NEWCFG "scaled_convert_options = \"". (join_convert_options (@{$opts->{scaled_convert_options}})) ."\"\n" ;
    print NEWCFG "thumbnail_convert_options = \"". (join_convert_options (@{$opts->{thumbnail_convert_options}})) ."\"\n" ;
    print NEWCFG "force_image_regeneration = $force_image_regeneration\n" ;
    print NEWCFG "www_access_rights = $opts->{www_access_rights}\n" ;
    print NEWCFG "www_extension = \"$opts->{www_extension}\"\n" ;
    print NEWCFG "\n" ;

    close NEWCFG ;
}

#######################################################################
# Process subdirectories

sub call_recursive {
    my $gallery = shift ;
    my @entries = @{$gallery->{entries}} ;
    my @listed_entries = @{$gallery->{listed_entries}} ;

    for (my $i = 0; $i < @entries; $i++) {
	my $entry = $entries[$i] ;
	next if $entry->{type} != $TYPE_DIR ;
	my $dir = $entry->{filename} ;
	my $revdir = back_path $dir ;

	# prev and next urls
	my $prev = $entries[$i-1] ;
	my @prev_opts = () ;
	my $next = $entries[$i==@entries-1 ? 0 : $i+1] ;
	my @next_opts = () ;

	if ($opts->{link_subgalleries}) {
	    if ($prev->{type} == $TYPE_DIR) {
		@prev_opts = ("--prev-gal", "$revdir/$prev->{url}") ;
	    }
	    if ($next->{type} == $TYPE_DIR) {
	        @next_opts = ("--next-gal", "$revdir/$next->{url}") ;
	    }
	}

	print "\n" ;
	indprint "Entering subdirectory '$dir'...\n" ;
	system ( $0, @llgal_options, "-d", "$destination_dir$dir",
		 "--parent-gal", @prev_opts, @next_opts,
		 "--indent", $opts->{indent}."  " ) ;
	die "Failed to call $0 for subdirectory $destination_dir$dir ($!).\n"
	    if $? == -1 ;
	immediate_warning "$0 failed for subdirectory $destination_dir$dir."
	    if $? ;
	indprint "Leaving subdirectory '$dir'.\n" ;
    }
}

#######################################################################
# Main code

check_destination ;

foreach my $conf (@generic_config_files) {
    parse_generic_config_file $conf ;
}
parse_generic_config_file "${destination_dir}$destination_config_file" ;
foreach my $conf (@additional_config_files) {
    parse_additional_config_file $conf ;
}

# DO NOT MOVE THIS LINE BEFORE CONFIG FILE PARSING
parse_cmdline_options ();

die $usage
    if $help_asked ;
die "This is llgal version $version.\n"
    if $version_asked ;

# special behaviors

if ($clean_asked or $cleanall_asked) {
    my $cleanall = shift ;
    clean_files $cleanall_asked ;
    show_warnings ;

    if ($make_recursive) {
	opendir DIR, $destination_dir ? $destination_dir : "./" ; # destination is empty for './'
	while (my $dir = readdir DIR) {
	    if ($dir =~ /^[^.]/ and -d "$destination_dir$dir") {
		print "\n" ;
		indprint "Entering subdirectory '$dir'...\n" ;
		system ( $0, @llgal_options, "-d", "$destination_dir$dir", "--indent", $opts->{indent}."  " ) ;
		die "Failed to call $0 for subdirectory $destination_dir$dir ($!).\n"
		    if $? == -1 ;
		immediate_warning "$0 failed for subdirectory $destination_dir$dir."
		    if $? ;
		indprint "Leaving subdirectory '$dir'.\n" ;
	    }
	}
    }
    closedir DIR ;
    exit 0 ;
}

if ($give_templates) {
    my $destdir = $give_templates ;
    $destdir = $opts->{local_llgal_dir}
	if $destdir eq "local" ;
    $destdir = $opts->{user_share_dir}
	if $destdir eq "user" ;
    give_templates $destdir ;
    indprint "You may now edit templates in $destdir and generate new galleries.\n" ;
    indprint "You may also remove any template that you do not want to modify.\n" ;

    if ($make_recursive) {
	opendir DIR, $destination_dir ? $destination_dir : "./" ; # destination is empty for './'
	while (my $dir = readdir DIR) {
	    if ($dir =~ /^[^.]/ and -d "$destination_dir$dir") {
		print "\n" ;
		indprint "Entering subdirectory '$dir'...\n" ;
		system ( $0, @llgal_options, "-d", "$destination_dir$dir", "--indent", $opts->{indent}."  " ) ;
		die "Failed to call $0 for subdirectory $destination_dir$dir ($!).\n"
		    if $? == -1 ;
		immediate_warning "$0 failed for subdirectory $destination_dir$dir."
		    if $? ;
		indprint "Leaving subdirectory '$dir'.\n" ;
	    }
	}
    }
    closedir DIR ;
    exit 0 ;
}

# main gallery creation

sanity_checks $opts ;
prepare_numerical_options $opts ;
prepare_various_variables $opts ;

my $root_gallery = init_gallery () ;

if ($generate_captions) {
    generate_captions_file $root_gallery ;
    indprint "Now edit the $destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename} file to your liking and run llgal\n" ;

    if ($make_recursive) {
	my @dir_entries = grep { $_->{type} == $TYPE_DIR } @{$root_gallery->{entries}} ;
	print "\n"
	    if @dir_entries ;
	foreach my $entry (@dir_entries) {
	    my $dir = $entry->{filename} ;
	    indprint "Entering subdirectory '$dir'...\n" ;
	    system ( $0, @llgal_options, "-d", "$destination_dir$dir", "--indent", "  ".$opts->{indent} ) ;
	    die "Failed to call $0 for subdirectory $destination_dir$dir ($!).\n"
		if $? == -1 ;
	    immediate_warning "$0 failed for subdirectory $destination_dir$dir."
		if $? ;
	    indprint "Leaving subdirectory '$dir'.\n" ;
	    print "\n" ;
	}
    }
    exit 0 ;
}

if (-e "$destination_dir$opts->{local_llgal_dir}/$opts->{captions_filename}") {
    read_captions_file $root_gallery ;
    fill_entries $root_gallery ;
    indprint "Found ". (scalar @{$root_gallery->{entries}}) ." files in the captions file.\n" ;
} else {
    # generate the gallery without captions file
    get_entries_from_directory $root_gallery ;
    fill_entries $root_gallery ;
    indprint "Found ". (scalar @{$root_gallery->{entries}}) ." files in $destination_string\n" ;
}

extract_listed_entries $root_gallery
    if $opts->{list_links} ;

finalize_entries $root_gallery ;

add_headers_footers $root_gallery ;

if ($opts->{make_no_slides}) {
    indprint "Linking thumbnails directly to image files...  Making no html slides.\n" ;
} else {
    generate_slides $root_gallery ;
}

generate_index $root_gallery ;

get_files ;

make_www_rights $root_gallery
    if $opts->{www_access_rights} ;

generate_config $generate_config
    if defined $generate_config ;

call_recursive $root_gallery
    if $make_recursive ;
