/* Cataract - Static web photo gallery generator * Copyright (C) 2008 Tomas Bzatek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include "setup.h" #include "items.h" #include "jpeg-utils.h" #include "gallery-utils.h" #include "replace-table.h" #include "block-parser.h" #include "stats.h" #include "generators.h" #define BUFFER_SIZE 65536 /* line cannot be longer than this */ #define POSITION_MARKER_FMT "i%d" static const gchar * get_index_filename (TAlbum *items, TGalleryDesignTheme *theme, TPathInfo *path_info, TIndexItem *retrieve_child) { gchar *s; TGalleryType gallery_type; gallery_type = items->type; if (retrieve_child) { s = g_build_filename (path_info->src_dir, retrieve_child->path, "index.xml", NULL); gallery_type = get_child_gallery_type (s); g_free (s); } return (gallery_type == GALLERY_TYPE_ALBUM || theme->index_filename == NULL) ? theme->album_filename : theme->index_filename; } /* Calculates number of directory levels one step up (in case of complex paths) */ static int get_parent_dir_level (TAlbum *items) { TAlbum *parent; TIndexItem *tmp_item; int level = 0; parent = items->parent_index; if (parent) { level = 1; tmp_item = g_ptr_array_index (parent->items, items->parent_item_index); if (tmp_item && tmp_item->path && strlen (tmp_item->path) > 0) level += count_dir_levels (tmp_item->path) - 1; } return level; } /* Calculates number of directory levels to the gallery root */ static int get_root_level (TAlbum *items) { TAlbum *parent = items; int level = 0; do { level += get_parent_dir_level (parent); } while ((parent = parent->parent_index)); return level; } static int get_item_index (TAlbum *items, TIndexItem *item) { int i; for (i = 0; i < items->items->len; i++) if (g_ptr_array_index (items->items, i) == item) return i + 1; return 0; } static int get_display_item_index (TAlbum *items, TIndexItem *item) { TIndexItem *tmp_item; int real_item_index = 0; int i; for (i = 0; i < items->items->len; i++) { tmp_item = g_ptr_array_index (items->items, i); if (tmp_item->type == INDEX_ITEM_TYPE_PICTURE && (! tmp_item->hidden)) real_item_index++; if (tmp_item == item) /* non-picture items shall be index-bound to the next picture item */ return real_item_index + ((tmp_item->type != INDEX_ITEM_TYPE_PICTURE) ? 1 : 0); } return 0; } static int get_display_item_count (TAlbum *items) { TIndexItem *tmp_item; int real_total_items = 0; int i; for (i = 0; i < items->items->len; i++) { tmp_item = g_ptr_array_index (items->items, i); if (tmp_item->type == INDEX_ITEM_TYPE_PICTURE && (! tmp_item->hidden)) real_total_items++; } return real_total_items; } static void get_item_titles (TGallerySetup *setup, TIndexItem *item, ExifData *exif, gchar **title, gchar **title_desc) { gchar *s; *title = g_strdup (item->title); *title_desc = g_strdup (item->title_description); if (setup->use_iptc_exif && *title == NULL && *title_desc == NULL && exif != NULL) { *title = get_exif_data_fixed (exif, IPTC_CAPTION); s = get_exif_data_fixed (exif, JPEG_COMMENT); if (s) { if (! *title) *title = g_strdup (s); else *title_desc = g_strdup (s); g_free (s); } s = get_exif_data_fixed (exif, EXIF_IMAGE_DESCRIPTION); if (s) { if (! *title) *title = g_strdup (s); /* if (! *title_desc) -- disabled *title_desc = g_strdup (s); */ g_free (s); } s = get_exif_data_fixed (exif, EXIF_COMMENT); if (s) { if (! *title) *title = g_strdup (s); if (! *title_desc) *title_desc = g_strdup (s); g_free (s); } /* Convert line breaks to be visible in the HTML code */ if (*title) { str_replace (title, "\r\n", "
"); str_replace (title, "\n", "
"); } if (*title_desc) { str_replace (title_desc, "\r\n", "
"); str_replace (title_desc, "\n", "
"); } } if (*title) *title = g_strstrip (*title); if (*title_desc) *title_desc = g_strstrip (*title_desc); } static gchar * get_image_source_path (TGallerySetup *setup, TAlbum *items, TIndexItem *item, TPathInfo *path_info, TImageSize *image_size) { const gchar *s; s = NULL; /* image_size == NULL means return real image source path */ if (image_size) { /* ignore combinations that are not valid */ if ((items->type == GALLERY_TYPE_INDEX && ! image_size->is_thumbnail) || (image_size->is_thumbnail && item->hidden) || (image_size->is_thumbnail && items->type == GALLERY_TYPE_INDEX && (item->thumbnail == NULL || strlen (item->thumbnail) == 0))) return NULL; if (image_size->is_thumbnail && items->type == GALLERY_TYPE_INDEX) s = item->thumbnail; if (s == NULL && ! image_size->is_hidpi && item->image_sizes != NULL) s = g_hash_table_lookup (item->image_sizes, image_size->name); } /* we have reached our target image size, s == NULL means the image should be resized from the source image */ if (s == NULL) s = item->path; if (s == NULL) { log_error ("Unable to find image source for item #%d (\"%s\") for image size \"%s\"\n", get_item_index (items, item), item->title, image_size->name); return NULL; } return g_build_filename (path_info->src_dir, s, NULL); } static gchar * get_image_dest_path (TGallerySetup *setup, TAlbum *items, TIndexItem *item, TPathInfo *path_info, TImageSize *image_size, gboolean page_link) { gchar *src; gchar *dst; gchar *s, *s2; gchar *target_image_dir; g_assert (image_size != NULL); src = get_image_source_path (setup, items, item, path_info, image_size); if (src == NULL) return NULL; target_image_dir = g_strdup_printf ("%s%s", TARGET_IMAGE_DIR_PREFIX, image_size->name); s = g_path_get_basename (src); if (image_size->is_thumbnail) { s2 = g_strdup_printf (THUMBNAIL_NAME_FORMAT, get_item_index (items, item), s); g_free (s); s = s2; } if (page_link) dst = g_build_filename (target_image_dir, s, NULL); else dst = g_build_filename (path_info->dest_dir, target_image_dir, s, NULL); g_free (s); g_free (target_image_dir); g_free (src); return dst; } static gchar * get_image_dest_path_with_fallback (TGallerySetup *setup, TAlbum *items, TIndexItem *item, TPathInfo *path_info, TImageSize *image_size, gboolean page_link) { gchar *dst; TImageSize *tmp_image_size; if (image_size->fallback_size == NULL) return get_image_dest_path (setup, items, item, path_info, image_size, page_link); tmp_image_size = image_size; while ((dst = get_image_dest_path (setup, items, item, path_info, tmp_image_size, FALSE)), tmp_image_size->fallback_size && dst != NULL && g_access (dst, R_OK) != 0) { g_free (dst); tmp_image_size = lookup_image_size_for_name (setup, tmp_image_size->fallback_size); } if (page_link) { g_free (dst); dst = get_image_dest_path (setup, items, item, path_info, tmp_image_size, page_link); } return dst; } static void metadata_apply_overrides (ExifData *exif_data, TGallerySetup *setup, TPathInfo *path_info, TAlbum *items, TIndexItem *item, TImageSize *image_size) { g_return_if_fail (exif_data != NULL); g_free (exif_data->override_copyright); exif_data->override_copyright = g_strdup (setup->add_copyright); exif_data->override_artist_name = get_prop_string (items, item, PROP_METADATA_OVERRIDE_ARTIST_NAME, setup->meta_artist_name); exif_data->timezone_shift = get_prop_int (items, item, PROP_METADATA_TZ_SHIFT, 0); exif_data->override_datetime = get_prop_double (items, item, PROP_METADATA_OVERRIDE_DATETIME, (time_t) -1); exif_data->override_aperture = get_prop_double (items, item, PROP_METADATA_OVERRIDE_APERTURE, -1); exif_data->override_focal_length = get_prop_double (items, item, PROP_METADATA_OVERRIDE_FOCAL_LENGTH, -1); g_free (exif_data->external_exif_data); exif_data->external_exif_data = NULL; if (setup->write_supplied_exif && item->metadata_external_exif) exif_data->external_exif_data = g_path_is_absolute (item->metadata_external_exif) ? g_strdup (item->metadata_external_exif) : g_build_filename (path_info->src_dir, item->metadata_external_exif, NULL); exif_data->thumbnail_crop_style = image_size->is_thumbnail ? image_size->thumb_crop_style : CROP_STYLE_NORMAL; exif_data->thumbnail_crop_hint = get_prop_int (items, item, PROP_THUMB_CROP_HINT, CROP_HINT_UNDEFINED); exif_data->shave_amount = get_prop_int (items, item, PROP_SHAVE_AMOUNT, 0); } static char * get_exif_value_cb (gchar **args, gpointer user_data) { ExifData *exif = user_data; if (exif == NULL) return NULL; g_return_val_if_fail (g_strv_length (args) != 2, NULL); /* incl. trailing NULL */ return get_exif_data (exif, *args); } static char * get_exif_value_fixed_cb (gchar **args, gpointer user_data) { ExifData *exif = user_data; if (exif == NULL) return NULL; g_return_val_if_fail (g_strv_length (args) != 2, NULL); /* incl. trailing NULL */ return get_exif_data_fixed (exif, *args); } static gboolean have_exif_key_cb (gchar **args, gpointer user_data) { ExifData *exif = user_data; if (exif == NULL) return FALSE; g_return_val_if_fail (g_strv_length (args) != 2, FALSE); /* incl. trailing NULL */ return exif_has_key (exif, *args); } struct HaveImageSizeData { TGallerySetup *setup; TAlbum *items; TIndexItem *item; TPathInfo *path_info; }; static gboolean have_generated_image (TGallerySetup *setup, TAlbum *items, TIndexItem *item, TPathInfo *path_info, TImageSize *image_size) { gchar *img_dst; int res; img_dst = get_image_dest_path (setup, items, item, path_info, image_size, FALSE); if (img_dst == NULL) return FALSE; res = g_access (img_dst, R_OK); g_free (img_dst); return (res == 0); } static gboolean have_image_size_cb (gchar **args, gpointer user_data) { struct HaveImageSizeData *data = user_data; TImageSize *image_size; g_return_val_if_fail (g_strv_length (args) != 2, FALSE); /* incl. trailing NULL */ /* no image size of that name available */ image_size = lookup_image_size_for_name (data->setup, *args); if (image_size == NULL) return FALSE; if (data->item->image_sizes && g_hash_table_lookup (data->item->image_sizes, *args)) return TRUE; /* check for generated image */ return have_generated_image (data->setup, data->items, data->item, data->path_info, image_size); } static gboolean have_album_image_size_cb (gchar **args, gpointer user_data) { struct HaveImageSizeData *data = user_data; TImageSize *image_size; TIndexItem *iter_item; int i; g_return_val_if_fail (g_strv_length (args) != 2, FALSE); /* incl. trailing NULL */ /* no image size of that name available */ image_size = lookup_image_size_for_name (data->setup, *args); if (image_size == NULL) return FALSE; for (i = 0; i < data->items->items->len; i++) { iter_item = g_ptr_array_index (data->items->items, i); if (iter_item == NULL || iter_item->type != INDEX_ITEM_TYPE_PICTURE || iter_item->hidden) continue; if (iter_item->image_sizes && g_hash_table_lookup (iter_item->image_sizes, *args)) return TRUE; /* check for generated image */ if (have_generated_image (data->setup, data->items, iter_item, data->path_info, image_size)) return TRUE; } return FALSE; } static gboolean generate_image_for_size (TGallerySetup *setup, TAlbum *items, TIndexItem *item, TPathInfo *path_info, TImageSize *image_size, gboolean query_update) { gboolean res; gchar *img_src; gchar *img_dst; gchar *img_ref_dst; unsigned long img_w, img_h; unsigned long src_img_w, src_img_h; unsigned long tmpw, tmph; unsigned long ref_img_w, ref_img_h; int src_img_quality, img_quality; ExifData *exif_data; int shave_amount; res = ! query_update; img_src = get_image_source_path (setup, items, item, path_info, image_size); if (img_src == NULL) return res; if (g_access (img_src, R_OK) != 0) { log_error (" Error opening image %s for size \"%s\": %s\n", img_src, image_size->name, g_strerror (errno)); g_free (img_src); return query_update; } img_dst = get_image_dest_path (setup, items, item, path_info, image_size, FALSE); if (img_dst == NULL) { g_free (img_src); return res; } exif_data = exif_data_new_empty (); metadata_apply_overrides (exif_data, setup, path_info, items, item, image_size); /* Do something when required */ res = res || needs_update (img_src, img_dst); if (! query_update) { /* Always copy supplied image size */ if (! image_size->is_thumbnail && ! image_size->is_hidpi && item->image_sizes && g_hash_table_lookup (item->image_sizes, image_size->name)) { if (! copy_file (img_src, img_dst)) log_error (" Error copying image %s to %s\n", img_src, img_dst); } /* Resize image */ else { get_image_sizes (img_src, &src_img_w, &src_img_h, &src_img_quality, setup->autorotate); shave_amount = get_prop_int (items, item, PROP_SHAVE_AMOUNT, 0); if (shave_amount > 0) { src_img_w -= 2 * shave_amount; src_img_h -= 2 * shave_amount; } if (src_img_w > 0 && src_img_h > 0) { img_quality = image_size->quality; if (image_size->is_hidpi) { /* Get reference regular size image dimensions */ img_ref_dst = get_image_dest_path (setup, items, item, path_info, image_size->hidpi_ref_size, FALSE); if (! img_ref_dst) { g_free (img_src); g_free (img_dst); return res; } ref_img_w = ref_img_h = 0; get_image_sizes (img_ref_dst, &ref_img_w, &ref_img_h, NULL, FALSE); g_free (img_ref_dst); if (ref_img_w <= 0 || ref_img_h <= 0) { g_free (img_src); g_free (img_dst); return res; } /* Browsers need exactly n-factor of the original size */ img_w = lround ((gdouble) ref_img_w * image_size->hidpi_scale_factor); img_h = lround ((gdouble) ref_img_h * image_size->hidpi_scale_factor); if ((gdouble) img_w * (100 - setup->design->hidpi_upscale_threshold) / 100 > src_img_w || (gdouble) img_h * (100 - setup->design->hidpi_upscale_threshold) / 100 > src_img_h) { /* g_print (" Warning: source image %s (%lux%lu) is not large enough for the \"%s\" image size (need %lux%lu)\n", img_src, src_img_w, src_img_h, image_size->name, img_w, img_h); */ g_free (img_src); g_free (img_dst); return res; } if (setup->warn_resize && (img_w > src_img_w || img_h > src_img_h)) printf (" Warning: upscaling image %s from %lux%lu to %lux%lu\n", img_src, src_img_w, src_img_h, img_w, img_h); } else { /* Not a HiDPI image size */ if (image_size->is_thumbnail && image_size->thumb_crop_style != CROP_STYLE_NORMAL) { switch (image_size->thumb_crop_style) { case CROP_STYLE_SQUARED: img_w = img_h = image_size->square_size; break; case CROP_STYLE_FIXED: img_w = image_size->crop_width; img_h = image_size->crop_height; break; default: g_assert_not_reached (); } } else { img_w = src_img_w; img_h = src_img_h; /* Calculate sizes */ if (img_w > img_h) { tmpw = image_size->landscape_width; tmph = image_size->landscape_height; } else { tmpw = image_size->portrait_width; tmph = image_size->portrait_height; } /* Check for image size suitability */ if (! image_size->is_thumbnail && src_img_w + image_size->availability_threshold < tmpw && src_img_h + image_size->availability_threshold < tmph) { /* printf (" Warning: source image %s (%lux%lu) is not large enough for the \"%s\" image size (need %lux%lu)\n", img_src, src_img_w, src_img_h, image_size->name, tmpw, tmph); */ g_free (img_src); g_free (img_dst); return res; } /* Calculate dimensions */ if (src_img_w < tmpw + image_size->no_resize_threshold && src_img_h < tmph + image_size->no_resize_threshold) { /* printf (" Note: image %s dimensions (%lux%lu) are within bounds (%lux%lu..%lux%lu), passing by...\n", img_src, src_img_w, src_img_h, tmpw - image_size->availability_threshold, tmph - image_size->availability_threshold, tmpw + image_size->no_resize_threshold, tmph + image_size->no_resize_threshold); */ } else { calculate_sizes (tmpw, tmph, &img_w, &img_h); if (setup->warn_resize) printf (" Warning: resizing image %s from %lux%lu to %lux%lu\n", img_src, src_img_w, src_img_h, img_w, img_h); } /* Calculate compression quality */ if (src_img_quality > image_size->quality + image_size->quality_threshold) { if (setup->warn_resize) printf (" Warning: lowering compression quality of %s from %d to %d\n", img_src, src_img_quality, img_quality); } else { img_quality = src_img_quality; } } } /* Perform resize and strip */ stats_images_inc (); if (src_img_w == img_w && src_img_h == img_h && src_img_quality == img_quality) { if (! copy_file (img_src, img_dst)) log_error (" Error copying image %s to %s\n", img_src, img_dst); } else { if (! resize_image (img_src, img_dst, img_w, img_h, img_quality, image_size->is_thumbnail, setup->autorotate, image_size->is_hidpi, exif_data, image_size->is_thumbnail ? setup->design->imgmagick_thumb_opts : setup->design->imgmagick_resize_opts)) log_error (" Error resizing image %s\n", img_src); } if (setup->warn_resize && image_size->is_hidpi) { get_image_sizes (img_dst, &tmpw, &tmph, NULL, FALSE); if (img_w != tmpw || img_h != tmph) g_print (" Warning: generated image '%s' doesn't have required dimensions: need %lux%lu, got %lux%lu\n", img_dst, img_w, img_h, tmpw, tmph); } } else { log_error ("generate_image: image %s sizes are %lux%lu\n", img_src, src_img_w, src_img_h); } } } if (! image_size->is_thumbnail) { modify_exif (img_dst, exif_data, setup->erase_exif_thumbnail, setup->strip_xmp); } exif_data_free (exif_data); g_free (img_src); g_free (img_dst); return res; } /* * generate_image: generate needed image sizes */ gboolean generate_image (TGallerySetup *setup, TAlbum *items, TIndexItem *item, TPathInfo *path_info, gboolean query_update) { gboolean res; GList *l; TImageSize *image_size; int i; res = ! query_update; for (i = 0; i < 2; i++) { /* process regular sizes first and then hidpi sizes in the second round */ for (l = g_list_first (setup->design->image_sizes); l; l = g_list_next (l)) { image_size = l->data; if ((i == 0 && image_size->is_hidpi) || (i == 1 && ! image_size->is_hidpi)) continue; res = (query_update && res) || generate_image_for_size (setup, items, item, path_info, image_size, query_update); } } return res; } static gchar * process_block (TGallerySetup *setup, BlockParser *block_parser, ReplaceTable *replace_table, GHashTable *defines, const gchar *str) { gchar *s; replace_table_set_strip_unused_tags (replace_table, setup->strip_unused_tags); replace_table_set_defines (replace_table, defines); block_parser_set_conditionals (block_parser, defines); s = block_parser_process (block_parser, str); replace_table_process (&s, replace_table); return s; } static gchar * make_navbar_string (TGallerySetup *setup, TGalleryDesignTheme *theme, BlockParser *block_parser, GHashTable *defines, TAlbum *items, int item_index, const gchar *current_title) { gchar *s1, *s2, *s3; int level, old_parent_item_index; TAlbum *parent; gboolean picture_element = (item_index >= 0); gboolean first = TRUE; BlockParser *local_block_parser; ReplaceTable *local_replace_table; GHashTable *local_defines; GString *block; block = g_string_new (""); /* the "current" element */ s1 = block_parser_get_data (block_parser, items->parent_index ? "NAV_BAR_CURRENT" : "NAV_BAR_CURRENT_ROOT"); if (s1) { local_block_parser = block_parser_new (); local_replace_table = replace_table_new (); local_defines = clone_string_hash_table (defines); replace_table_add_key (local_replace_table, "NAV_BAR_ELEM_TITLE", current_title); s2 = process_block (setup, local_block_parser, local_replace_table, local_defines, s1); g_string_prepend (block, s2); g_free (s2); g_free (s1); block_parser_free (local_block_parser); replace_table_free (local_replace_table); g_hash_table_destroy (local_defines); } parent = items; old_parent_item_index = items->parent_item_index + 1; level = picture_element ? 0 : get_parent_dir_level (items); while ((parent = picture_element ? items : parent->parent_index)) { /* NAV_BAR_FIRST takes priority and is guaranteed to be used */ s1 = block_parser_get_data (block_parser, parent->parent_index ? (first ? "NAV_BAR_LAST" : "NAV_BAR_ELEM") : "NAV_BAR_FIRST"); local_block_parser = block_parser_new (); local_replace_table = replace_table_new (); local_defines = clone_string_hash_table (defines); s2 = make_string ("../", level); s3 = setup->use_inpage_links ? g_strdup_printf ("#" POSITION_MARKER_FMT, picture_element ? item_index : old_parent_item_index) : g_strdup (""); replace_table_add_key_printf (local_replace_table, "NAV_BAR_ELEM_LINK", "%s%s%s", s2, get_index_filename (parent, theme, NULL, NULL), s3); replace_table_add_key (local_replace_table, "NAV_BAR_ELEM_TITLE", parent->ID); g_free (s2); g_free (s3); s2 = process_block (setup, local_block_parser, local_replace_table, local_defines, s1); g_string_prepend (block, s2); g_free (s2); g_free (s1); block_parser_free (local_block_parser); replace_table_free (local_replace_table); g_hash_table_destroy (local_defines); old_parent_item_index = parent->parent_item_index + 1; level += get_parent_dir_level (parent); picture_element = FALSE; first = FALSE; } return g_string_free (block, FALSE); } static void add_support_tags (TGallerySetup *setup, TAlbum *items, ReplaceTable *replace_table) { gchar *s1, *s2; int level; level = get_root_level (items); /* Supportfiles path */ s1 = make_string ("../", level); replace_table_add_key (replace_table, "TEMPLATES_PATH", setup->supplemental_files_use_common_root ? s1 : ""); g_free (s1); /* Atom feeds */ if (setup->feed_enabled) { s2 = make_string ("../", level); s1 = g_strdup_printf ("\t\n", s2, setup->feed_filename, setup->feed_title); g_free (s2); replace_table_add_key (replace_table, "CGG_ATOM_FEED_TAGS", s1); g_free (s1); } } static void add_meta_tags (TGallerySetup *setup, TAlbum *items, ReplaceTable *replace_table, const gchar *item_title) { int level; gchar *s1, *s2, *s3; gboolean override_title_meta; level = get_root_level (items); override_title_meta = setup->use_title_as_meta && item_title && (strlen (item_title) > 0); s1 = g_strdup_printf ("\t\n", APP_NAME_FULL, VERSION); if (setup->meta_author || items->meta_author) { s3 = g_strdup (items->meta_author ? items->meta_author : setup->meta_author); adjust_tags_parameter (&s3); s2 = g_strdup_printf ("%s\t\n", s1, s3); g_free (s3); g_free (s1); s1 = s2; } if (setup->meta_description || items->meta_description || override_title_meta) { s3 = g_strdup (override_title_meta ? item_title : (items->meta_description ? items->meta_description : setup->meta_description)); adjust_tags_parameter (&s3); s2 = g_strdup_printf ("%s\t\n", s1, s3); g_free (s3); g_free (s1); s1 = s2; } if ((setup->meta_keywords || items->meta_keywords) && (! override_title_meta)) { s3 = g_strdup (items->meta_keywords ? items->meta_keywords : setup->meta_keywords); adjust_tags_parameter (&s3); s2 = g_strdup_printf ("%s\t\n", s1, s3); g_free (s3); g_free (s1); s1 = s2; } if (setup->favicon_file && strlen (setup->favicon_file) > 0) { s3 = make_string ("../", level); if (setup->favicon_type) s2 = g_strdup_printf ("%s\t\n", s1, setup->favicon_type, setup->supplemental_files_use_common_root ? s3 : "", setup->favicon_file); else s2 = g_strdup_printf ("%s\t\n", s1, setup->supplemental_files_use_common_root ? s3 : "", setup->favicon_file); g_free (s1); g_free (s3); s1 = s2; } replace_table_add_key (replace_table, "CGG_META_TAGS", s1); g_free (s1); } static void add_next_prev_links (TGallerySetup *setup, TPathInfo *path_info, TGalleryDesignTheme *theme, TAlbum *items, TIndexItem *item, ReplaceTable *replace_table, TImageSize *image_size) { TIndexItem *previous_item = NULL; TIndexItem *next_item = NULL; TIndexItem *tmp_item; int item_index; int i; gchar *s; item_index = get_item_index (items, item); for (i = item_index - 2; i >= 0; i--) { tmp_item = g_ptr_array_index (items->items, i); if (tmp_item != NULL && tmp_item->type == INDEX_ITEM_TYPE_PICTURE && !tmp_item->hidden) { previous_item = tmp_item; break; } } for (i = item_index; i < items->items->len; i++) { tmp_item = g_ptr_array_index (items->items, i); if (tmp_item != NULL && tmp_item->type == INDEX_ITEM_TYPE_PICTURE && !tmp_item->hidden) { next_item = tmp_item; break; } } if (next_item) { s = get_item_target_filename (next_item); replace_table_add_key_printf (replace_table, "LINK_NEXT", theme->picture_filename, s); g_free (s); } else replace_table_add_key (replace_table, "LINK_NEXT", get_index_filename (items, theme, NULL, NULL)); if (previous_item) { s = get_item_target_filename (previous_item); replace_table_add_key_printf (replace_table, "LINK_PREV", theme->picture_filename, s); g_free (s); } else replace_table_add_key (replace_table, "LINK_PREV", get_index_filename (items, theme, NULL, NULL)); s = NULL; if (next_item != NULL && setup->preload && image_size != NULL) s = get_image_dest_path_with_fallback (setup, items, next_item, path_info, image_size, TRUE); replace_table_add_key (replace_table, "IMG_SRC_PRELOAD", s ? s : ""); g_free (s); } static ExifData * get_img_exif_data (TGallerySetup *setup, TPathInfo *path_info, TAlbum *items, TIndexItem *item, TImageSize *image_size) { ExifData *exif; gchar *img_src; gchar *img_dst; gchar *s; /* Use external EXIF file if specified */ exif = NULL; if (item->metadata_external_exif) { s = g_path_is_absolute (item->metadata_external_exif) ? g_strdup (item->metadata_external_exif) : g_build_filename (path_info->src_dir, item->metadata_external_exif, NULL); exif = read_exif (s); if (exif == NULL) log_error (" Error reading exif data from file %s\n", s); g_free (s); } /* Get EXIF data from the source image */ if (exif == NULL) { img_src = get_image_source_path (setup, items, item, path_info, NULL); if (img_src != NULL && g_access (img_src, R_OK) == 0) exif = read_exif (img_src); /* -- silently succeed if (exif == NULL) log_error ("write_html_image: error getting exif data from file \"%s\"\n", img_orig_src); */ g_free (img_src); } /* Try supplied image file */ if (exif == NULL && item->image_sizes != NULL) { img_src = get_image_source_path (setup, items, item, path_info, image_size); if (img_src != NULL && g_access (img_src, R_OK) == 0) exif = read_exif (img_src); g_free (img_src); } /* Try destination image size instead, though it might have the metadata stripped */ if (exif == NULL) { img_dst = get_image_dest_path (setup, items, item, path_info, image_size, FALSE); if (img_dst != NULL && g_access (img_dst, R_OK) == 0) exif = read_exif (img_dst); g_free (img_dst); } if (exif != NULL) metadata_apply_overrides (exif, setup, path_info, items, item, image_size); return exif; } /* This function fills data for the current item in the replace table and the defines hashtable */ static void process_img_item (TGallerySetup *setup, TPathInfo *path_info, TGalleryDesignTheme *theme, TAlbum *items, TIndexItem *item, BlockParser *block_parser, ReplaceTable *replace_table, GHashTable *defines, TImageSize *image_size, TImageSize *thumb_image_size, gboolean list_mode, gboolean all_image_sizes) { unsigned long img_w, img_h; unsigned long img_orig_w, img_orig_h; unsigned long img_thumb_w, img_thumb_h; int album_objects_count; gboolean album_protected; gchar *s1, *s2, *s3, *s4; gchar *img_dst; gchar *img_dst_page; gchar *title, *title_desc; TImageSize *tmp_image_size; ExifData *exif = NULL; struct HaveImageSizeData *img_size_data; GList *l; album_protected = FALSE; if (list_mode) { /* Index stuff */ if (items->type == GALLERY_TYPE_INDEX) { album_objects_count = 0; s1 = g_build_filename (path_info->src_dir, item->path, "index.xml", NULL); /* FIXME: doing additional I/O, port to global item tree */ get_album_info (s1, &album_objects_count, &album_protected); g_free (s1); replace_table_add_key_int (replace_table, "ALBUM_NUM_ITEMS", album_objects_count); replace_table_add_key_printf (replace_table, "ALBUM_SUBPATH", "%s/%s", item->path, get_index_filename (items, theme, path_info, item)); if (album_protected && theme->index_protected_thumbnail != NULL) g_hash_table_replace (defines, g_strdup ("IS_PROTECTED"), g_strdup ("")); } /* Thumbnail stuff */ if (thumb_image_size != NULL) { if (album_protected && theme->index_protected_thumbnail != NULL) { s1 = setup->supplemental_files_use_common_root ? make_string ("../", get_root_level (items)) : g_strdup (""); /* expecting both global and theme supplemental files are in place at this moment */ s2 = g_build_filename (path_info->dest_dir, s1, theme->index_protected_thumbnail, NULL); s3 = g_strconcat (s1, theme->index_protected_thumbnail, NULL); g_free (s1); } else { s2 = get_image_dest_path (setup, items, item, path_info, thumb_image_size, FALSE); s3 = get_image_dest_path (setup, items, item, path_info, thumb_image_size, TRUE); } img_thumb_w = img_thumb_h = 0; if (s2 != NULL) get_image_sizes (s2, &img_thumb_w, &img_thumb_h, NULL, setup->autorotate); if (img_thumb_w == img_thumb_h) replace_table_add_key (replace_table, "THUMB_ORIENTATION", "squared"); else replace_table_add_key (replace_table, "THUMB_ORIENTATION", (img_thumb_h > img_thumb_w) ? "portrait" : "landscape"); if (s3 != NULL) { replace_table_add_key (replace_table, "IMG_SRC_THUMB", s3); if (img_thumb_w > 0 && img_thumb_h > 0) { replace_table_add_key_int (replace_table, "IMG_SIZE_THUMB_W", img_thumb_w); replace_table_add_key_int (replace_table, "IMG_SIZE_THUMB_H", img_thumb_h); } } g_free (s2); g_free (s3); } s1 = get_item_target_filename (item); replace_table_add_key_printf (replace_table, "IMG_SUBPAGE", theme->picture_filename, s1); replace_table_add_key (replace_table, "IMG_FILENAME", s1); g_free (s1); replace_table_add_key (replace_table, "IMG_TITLE", item->title); replace_table_add_key (replace_table, "IMG_DESCRIPTION", item->title_description); } /* Image stuff */ for (l = g_list_first (setup->design->image_sizes); l; l = g_list_next (l)) { tmp_image_size = l->data; if (! all_image_sizes && tmp_image_size != image_size) continue; if ((items->type == GALLERY_TYPE_ALBUM && tmp_image_size->is_thumbnail) || (items->type == GALLERY_TYPE_INDEX && ! tmp_image_size->is_thumbnail) || album_protected) continue; /* First calculate image paths */ if (all_image_sizes) { img_dst = get_image_dest_path (setup, items, item, path_info, tmp_image_size, FALSE); if (g_access (img_dst, R_OK) != 0) { g_free (img_dst); continue; } img_dst_page = get_image_dest_path (setup, items, item, path_info, tmp_image_size, TRUE); } else { img_dst = get_image_dest_path_with_fallback (setup, items, item, path_info, tmp_image_size, FALSE); img_dst_page = get_image_dest_path_with_fallback (setup, items, item, path_info, tmp_image_size, TRUE); } /* Retrieve image sizes */ get_image_sizes (img_dst, &img_w, &img_h, NULL, setup->autorotate); if (all_image_sizes) { s1 = g_ascii_strup (tmp_image_size->name, -1); s2 = g_strdup_printf ("IMG_SIZE_W__%s", s1); replace_table_add_key_int (replace_table, s2, img_w); g_free (s2); s2 = g_strdup_printf ("IMG_SIZE_H__%s", s1); replace_table_add_key_int (replace_table, s2, img_h); g_free (s2); s2 = g_strdup_printf ("IMG_SRC__%s", s1); replace_table_add_key (replace_table, s2, img_dst_page); g_free (s2); g_hash_table_replace (defines, g_strdup_printf ("HAVE_IMG_SIZE_%s", s1), g_strdup ("")); g_free (s1); } if (tmp_image_size == image_size) { exif = get_img_exif_data (setup, path_info, items, item, tmp_image_size); replace_table_add_key_int (replace_table, "IMG_SIZE_W", img_w); replace_table_add_key_int (replace_table, "IMG_SIZE_H", img_h); replace_table_add_key (replace_table, "IMG_SRC", img_dst_page); } g_free (img_dst); g_free (img_dst_page); } if (! all_image_sizes && image_size != NULL) { /* Legacy stuff, subject to removal */ tmp_image_size = NULL; /* Take the last image size from the sorted list */ if (g_list_length (setup->design->image_sizes) > 0) tmp_image_size = (TImageSize *) (g_list_last (setup->design->image_sizes))->data; if (tmp_image_size != NULL && tmp_image_size != image_size && ! tmp_image_size->is_thumbnail) { img_dst = get_image_dest_path_with_fallback (setup, items, item, path_info, tmp_image_size, FALSE); img_dst_page = get_image_dest_path_with_fallback (setup, items, item, path_info, tmp_image_size, TRUE); get_image_sizes (img_dst, &img_orig_w, &img_orig_h, NULL, setup->autorotate); g_hash_table_replace (defines, g_strdup ("HAVE_FULLSIZE"), g_strdup ("")); replace_table_add_key_int (replace_table, "IMG_SIZE_FULLSIZE_W", img_orig_w); replace_table_add_key_int (replace_table, "IMG_SIZE_FULLSIZE_H", img_orig_h); replace_table_add_key (replace_table, "IMG_SRC_FULLSIZE", img_dst_page); g_free (img_dst); g_free (img_dst_page); } } /* Get title and description from IPTC/EXIF/JPEG if not defined */ get_item_titles (setup, item, exif, &title, &title_desc); replace_table_add_key (replace_table, "IMG_TITLE", title); replace_table_add_key (replace_table, "IMG_DESCRIPTION", title_desc); /* Page title */ s1 = (title && strlen (title) > 0) ? g_strdup_printf ("%s | ", title) : NULL; s2 = g_strdup_printf ("%s [%d/%d]", items->title ? items->title : items->ID, get_display_item_index (items, item), get_display_item_count (items)); s3 = setup->site_title ? g_strdup_printf (" | %s", setup->site_title) : NULL; s4 = g_strconcat (s1 ? s1 : "", s2, s3 ? s3 : "", NULL); replace_table_add_key (replace_table, "PAGE_TITLE", s4); g_free (s1); g_free (s2); g_free (s3); g_free (s4); g_free (title); g_free (title_desc); /* EXIF callbacks - all do handle NULL value pointer */ replace_table_register_function (replace_table, "get_exif_value", get_exif_value_cb, exif); replace_table_register_function (replace_table, "get_exif_value_fixed", get_exif_value_fixed_cb, exif); block_parser_register_function (block_parser, "have_exif_key", have_exif_key_cb, exif, (GDestroyNotify) exif_data_free); if (exif != NULL && exif_has_key (exif, EXIF_APERTURE) && exif_has_key (exif, EXIF_FOCAL_LENGTH) && exif_has_key (exif, EXIF_EXPOSURE)) g_hash_table_replace (defines, g_strdup ("HAVE_EXIF"), g_strdup ("")); /* Border style */ s1 = get_prop_string (items, item, PROP_BORDER_STYLE, NULL); if (s1) g_hash_table_replace (defines, g_strdup ("BORDER_STYLE"), s1); /* Common tags */ replace_table_add_key_printf (replace_table, "IMG_POS_MARKER", POSITION_MARKER_FMT, get_item_index (items, item)); replace_table_add_key_int (replace_table, "ITEM_INDEX", get_display_item_index (items, item)); /* Single image size callback */ img_size_data = g_new0 (struct HaveImageSizeData, 1); img_size_data->setup = setup; img_size_data->items = items; img_size_data->item = item; img_size_data->path_info = path_info; block_parser_register_function (block_parser, "have_image_size", have_image_size_cb, img_size_data, g_free); } /* * write_html_page: process template file * * template_src = template file of the album/index * dst = save generated file as * items = array of items in the album/index * item = when non-NULL, page will be written as a single picture page * */ gboolean write_html_page (TGallerySetup *setup, TPathInfo *path_info, TGalleryDesignTheme *theme, const gchar *template_src, const gchar *dst, TAlbum *items, TIndexItem *item) { FILE *fin; FILE *fout; gpointer buffer; gchar *line; GString *block; gchar *s1, *s2, *s3; TAlbum *parent; TIndexItem *iter_item; int level; gboolean res; int bb; int i; ReplaceTable *global_replace_table; ReplaceTable *local_replace_table; BlockParser *block_parser; BlockParser *local_block_parser; GHashTable *defines; GHashTable *local_defines; TImageSize *image_size, *thumb_image_size; struct HaveImageSizeData *img_size_data; gboolean all_sizes; res = TRUE; fin = fopen (template_src, "r"); if (fin == NULL) { log_error ("write_html_page: error reading file \"%s\": %s\n", template_src, strerror (errno)); return FALSE; } fout = fopen (dst, "w"); if (fout == NULL) { log_error ("write_html_page: error writing to file \"%s\": %s\n", dst, strerror (errno)); fclose (fin); return FALSE; } defines = clone_string_hash_table (theme->defines); global_replace_table = replace_table_new (); replace_table_set_strip_unused_tags (global_replace_table, setup->strip_unused_tags); replace_table_set_defines (global_replace_table, defines); block_parser = block_parser_new (); block_parser_set_conditionals (block_parser, defines); /* Page title */ if (item == NULL) { if (items->parent_index == NULL || setup->site_title == NULL) s1 = g_strdup (setup->site_title ? setup->site_title : items->ID); else s1 = g_strdup_printf ("%s | %s", items->title, setup->site_title); replace_table_add_key (global_replace_table, "PAGE_TITLE", s1); g_free (s1); } else { /* Filled below for picture page */ } /* Simple placeholders */ replace_table_add_key (global_replace_table, "ID", items->ID); replace_table_add_key (global_replace_table, "TITLE", items->title); replace_table_add_key (global_replace_table, "DESCRIPTION", items->desc); replace_table_add_key (global_replace_table, "FOOTER", setup->footer); replace_table_add_key_int (global_replace_table, "TOTAL_ITEMS", get_display_item_count (items)); if (item == NULL) { replace_table_add_key (global_replace_table, "FOOTNOTE", items->footnote); } /* Go Up string */ parent = items->parent_index; level = get_parent_dir_level (items); if (parent) { s3 = make_string ("../", level); s2 = setup->use_inpage_links ? g_strdup_printf ("#i%d", items->parent_item_index + 1) : g_strdup (""); replace_table_add_key_printf (global_replace_table, "GO_UP_LINK", "%s%s%s", s3, get_index_filename (parent, theme, NULL, NULL), s2); g_free (s2); g_free (s3); } if (! parent) g_hash_table_replace (defines, g_strdup ("IS_ROOT"), g_strdup ("")); /* META tags */ add_meta_tags (setup, items, global_replace_table, NULL); add_support_tags (setup, items, global_replace_table); /* Theming */ image_size = NULL; thumb_image_size = NULL; if (item != NULL) { image_size = lookup_image_size_for_name (setup, theme->picture_image_size); } else { if (items->type == GALLERY_TYPE_ALBUM) image_size = lookup_image_size_for_name (setup, theme->album_image_size); thumb_image_size = lookup_image_size_for_name (setup, items->type == GALLERY_TYPE_ALBUM ? theme->album_thumb_size : theme->index_thumb_size); } /* Album image size availability callback */ img_size_data = g_new0 (struct HaveImageSizeData, 1); img_size_data->setup = setup; img_size_data->items = items; img_size_data->path_info = path_info; block_parser_register_function (block_parser, "have_album_image_size", have_album_image_size_cb, img_size_data, g_free); /* Picture page tags */ if (item != NULL) { add_next_prev_links (setup, path_info, theme, items, item, global_replace_table, image_size); process_img_item (setup, path_info, theme, items, item, block_parser, global_replace_table, defines, image_size, thumb_image_size, FALSE, FALSE); } /* Setup block parser */ block_parser_register_key (block_parser, "IMG_LIST", "IMG_LIST"); block_parser_register_key (block_parser, "LIST_PICTURE", NULL); block_parser_register_key (block_parser, "LIST_PICTURE_ALL_SIZES", NULL); block_parser_register_key (block_parser, "LIST_SEPARATOR", NULL); block_parser_register_key (block_parser, "LIST_INTERSPACE", NULL); block_parser_register_key (block_parser, "NAV_BAR", "NAV_BAR"); block_parser_register_key (block_parser, "NAV_BAR_FIRST", NULL); block_parser_register_key (block_parser, "NAV_BAR_ELEM", NULL); block_parser_register_key (block_parser, "NAV_BAR_LAST", NULL); block_parser_register_key (block_parser, "NAV_BAR_CURRENT", NULL); block_parser_register_key (block_parser, "NAV_BAR_CURRENT_ROOT", NULL); /* Read through the template and replace placeholders with real data */ buffer = g_malloc0 (BUFFER_SIZE); while (fgets (buffer, BUFFER_SIZE, fin)) { line = block_parser_process (block_parser, buffer); if (block_parser_has_unused_data (block_parser, "IMG_LIST")) { /* Now we have all block placeholders read, generate the items: */ block = g_string_new (""); for (i = 0; i < items->items->len; i++) { iter_item = g_ptr_array_index (items->items, i); if (iter_item == NULL) { log_error ("write_html_page: error getting item %d\n", i); continue; } local_block_parser = block_parser_new (); local_replace_table = replace_table_new (); local_defines = clone_string_hash_table (defines); s1 = NULL; switch (iter_item->type) { case INDEX_ITEM_TYPE_PICTURE: if (! iter_item->hidden) { all_sizes = FALSE; s1 = block_parser_get_data (block_parser, "LIST_PICTURE_ALL_SIZES"); if (s1) all_sizes = TRUE; else s1 = block_parser_get_data (block_parser, "LIST_PICTURE"); process_img_item (setup, path_info, theme, items, iter_item, local_block_parser, local_replace_table, local_defines, image_size, thumb_image_size, TRUE, all_sizes); } break; case INDEX_ITEM_TYPE_SEPARATOR: s1 = block_parser_get_data (block_parser, "LIST_SEPARATOR"); replace_table_add_key (local_replace_table, "LIST_SEPARATOR_TITLE", iter_item->title); replace_table_add_key_int (local_replace_table, "ITEM_INDEX", get_display_item_index (items, iter_item)); break; case INDEX_ITEM_TYPE_INTERSPACE: s1 = block_parser_get_data (block_parser, "LIST_INTERSPACE"); replace_table_add_key (local_replace_table, "LIST_INTERSPACE_TITLE", iter_item->title); replace_table_add_key_int (local_replace_table, "ITEM_INDEX", get_display_item_index (items, iter_item)); break; } if (s1) { s2 = process_block (setup, local_block_parser, local_replace_table, local_defines, s1); g_string_append (block, s2); g_free (s2); g_free (s1); } block_parser_free (local_block_parser); replace_table_free (local_replace_table); g_hash_table_destroy (local_defines); } s1 = g_string_free (block, FALSE); replace_table_process (&s1, global_replace_table); replace_table_add_key (global_replace_table, "IMG_LIST", s1); g_free (s1); /* Clear processed nested keys for further use */ block_parser_clear_key_data (block_parser, "IMG_LIST"); block_parser_clear_key_data (block_parser, "LIST_PICTURE"); block_parser_clear_key_data (block_parser, "LIST_PICTURE_ALL_SIZES"); block_parser_clear_key_data (block_parser, "LIST_SEPARATOR"); block_parser_clear_key_data (block_parser, "LIST_INTERSPACE"); } if (block_parser_has_unused_data (block_parser, "NAV_BAR")) { s2 = item != NULL ? get_item_target_filename (item) : NULL; s1 = make_navbar_string (setup, theme, block_parser, defines, items, item ? get_item_index (items, item) : -1, s2 ? s2 : items->ID); g_free (s2); replace_table_process (&s1, global_replace_table); replace_table_add_key (global_replace_table, "NAV_BAR", s1); g_free (s1); block_parser_clear_key_data (block_parser, "NAV_BAR"); block_parser_clear_key_data (block_parser, "NAV_BAR_FIRST"); block_parser_clear_key_data (block_parser, "NAV_BAR_ELEM"); block_parser_clear_key_data (block_parser, "NAV_BAR_LAST"); block_parser_clear_key_data (block_parser, "NAV_BAR_CURRENT"); block_parser_clear_key_data (block_parser, "NAV_BAR_CURRENT_ROOT"); } /* Replace all known tags */ replace_table_process (&line, global_replace_table); /* Write to file */ bb = fputs (line, fout); g_free (line); if (bb < 0) { log_error ("write_html_page: error writing to file \"%s\": %s\n", dst, strerror (errno)); res = FALSE; break; } } fclose (fout); fclose (fin); g_free (buffer); g_hash_table_destroy (defines); replace_table_free (global_replace_table); block_parser_free (block_parser); return res; } /* * write_auth_passwd_file, write_auth_htaccess_file: setup authentication files for the current album * */ gboolean write_auth_passwd_file (TGallerySetup *setup, const gchar *dst, TAlbum *items) { GError *error = NULL; const gchar *argv[8]; argv[0] = "htpasswd"; argv[1] = "-b"; argv[2] = "-c"; argv[3] = "-m"; argv[4] = dst; argv[5] = items->auth_username; argv[6] = items->auth_passwd; argv[7] = NULL; if (! g_spawn_sync (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, NULL, &error)) { log_error ("Error writing password file: %s\n", error->message); g_error_free (error); return FALSE; } return TRUE; } gboolean write_auth_htaccess_file (TGallerySetup *setup, const gchar *dst, const gchar *passwd_file_name, TAlbum *items) { FILE *f; f = fopen (dst, "a"); if (f == NULL) { log_error ("Error writing htaccess file: %s\n", strerror (errno)); return FALSE; } fprintf (f, "\n"); fprintf (f, "# CGG auth data\n"); switch (items->auth_type) { case AUTH_TYPE_NONE: g_assert_not_reached(); break; case AUTH_TYPE_BASIC: fprintf (f, "AuthType Basic\n"); fprintf (f, "AuthName \"%s\"\n", items->auth_realm); fprintf (f, "AuthBasicProvider file\n"); fprintf (f, "AuthUserFile %s\n", passwd_file_name); fprintf (f, "Require user %s\n", items->auth_username); fprintf (f, "\n"); /* newline */ break; } if (fclose (f) != 0) { log_error ("Error writing htaccess file: %s\n", strerror (errno)); return FALSE; } return TRUE; }