Mercurial > geeqie.yaz
diff src/metadata.c @ 1178:f6449c17306b
Move comments/keywords read and write stuff to new metadata.{c,h}.
author | zas_ |
---|---|
date | Tue, 25 Nov 2008 17:32:51 +0000 |
parents | |
children | bb02d0e2a573 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/metadata.c Tue Nov 25 17:32:51 2008 +0000 @@ -0,0 +1,575 @@ +/* + * Geeqie + * (C) 2004 John Ellis + * Copyright (C) 2008 The Geeqie Team + * + * Author: John Ellis, Laurent Monin + * + * This software is released under the GNU General Public License (GNU GPL). + * Please read the included file COPYING for more information. + * This software comes with no warranty of any kind, use at your own risk! + */ + + +#include "main.h" +#include "metadata.h" + +#include "cache.h" +#include "exif.h" +#include "filedata.h" +#include "misc.h" +#include "secure_save.h" +#include "ui_fileops.h" +#include "ui_misc.h" +#include "utilops.h" + + +/* + *------------------------------------------------------------------- + * keyword / comment read/write + *------------------------------------------------------------------- + */ + +static gint comment_file_write(gchar *path, GList *keywords, const gchar *comment) +{ + SecureSaveInfo *ssi; + + ssi = secure_open(path); + if (!ssi) return FALSE; + + secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION); + + secure_fprintf(ssi, "[keywords]\n"); + while (keywords && secsave_errno == SS_ERR_NONE) + { + const gchar *word = keywords->data; + keywords = keywords->next; + + secure_fprintf(ssi, "%s\n", word); + } + secure_fputc(ssi, '\n'); + + secure_fprintf(ssi, "[comment]\n"); + secure_fprintf(ssi, "%s\n", (comment) ? comment : ""); + + secure_fprintf(ssi, "#end\n"); + + return (secure_close(ssi) == 0); +} + +static gint comment_legacy_write(FileData *fd, GList *keywords, const gchar *comment) +{ + gchar *comment_path; + gint success = FALSE; + + /* If an existing metadata file exists, we will try writing to + * it's location regardless of the user's preference. + */ + comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); + if (comment_path && !access_file(comment_path, W_OK)) + { + g_free(comment_path); + comment_path = NULL; + } + + if (!comment_path) + { + gchar *comment_dir; + mode_t mode = 0755; + + comment_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode); + if (recursive_mkdir_if_not_exists(comment_dir, mode)) + { + gchar *filename = g_strconcat(fd->name, GQ_CACHE_EXT_METADATA, NULL); + + comment_path = g_build_filename(comment_dir, filename, NULL); + g_free(filename); + } + g_free(comment_dir); + } + + if (comment_path) + { + gchar *comment_pathl; + + DEBUG_1("Saving comment: %s", comment_path); + + comment_pathl = path_from_utf8(comment_path); + + success = comment_file_write(comment_pathl, keywords, comment); + + g_free(comment_pathl); + g_free(comment_path); + } + + return success; +} + +typedef enum { + MK_NONE, + MK_KEYWORDS, + MK_COMMENT +} MetadataKey; + +static gint comment_file_read(gchar *path, GList **keywords, gchar **comment) +{ + FILE *f; + gchar s_buf[1024]; + MetadataKey key = MK_NONE; + GList *list = NULL; + GString *comment_build = NULL; + + f = fopen(path, "r"); + if (!f) return FALSE; + + while (fgets(s_buf, sizeof(s_buf), f)) + { + gchar *ptr = s_buf; + + if (*ptr == '#') continue; + if (*ptr == '[' && key != MK_COMMENT) + { + gchar *keystr = ++ptr; + + key = MK_NONE; + while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++; + + if (*ptr == ']') + { + *ptr = '\0'; + if (g_ascii_strcasecmp(keystr, "keywords") == 0) + key = MK_KEYWORDS; + else if (g_ascii_strcasecmp(keystr, "comment") == 0) + key = MK_COMMENT; + } + continue; + } + + switch(key) + { + case MK_NONE: + break; + case MK_KEYWORDS: + { + while (*ptr != '\n' && *ptr != '\0') ptr++; + *ptr = '\0'; + if (strlen(s_buf) > 0) + { + gchar *kw = utf8_validate_or_convert(s_buf); + + list = g_list_prepend(list, kw); + } + } + break; + case MK_COMMENT: + if (!comment_build) comment_build = g_string_new(""); + g_string_append(comment_build, s_buf); + break; + } + } + + fclose(f); + + *keywords = g_list_reverse(list); + if (comment_build) + { + if (comment) + { + gint len; + gchar *ptr = comment_build->str; + + /* strip leading and trailing newlines */ + while (*ptr == '\n') ptr++; + len = strlen(ptr); + while (len > 0 && ptr[len - 1] == '\n') len--; + if (ptr[len] == '\n') len++; /* keep the last one */ + if (len > 0) + { + gchar *text = g_strndup(ptr, len); + + *comment = utf8_validate_or_convert(text); + g_free(text); + } + } + g_string_free(comment_build, TRUE); + } + + return TRUE; +} + +static gint comment_delete_legacy(FileData *fd) +{ + gchar *comment_path; + gchar *comment_pathl; + gint success = FALSE; + if (!fd) return FALSE; + + comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); + if (!comment_path) return FALSE; + + comment_pathl = path_from_utf8(comment_path); + + success = !unlink(comment_pathl); + + g_free(comment_pathl); + g_free(comment_path); + + return success; +} + +static gint comment_legacy_read(FileData *fd, GList **keywords, gchar **comment) +{ + gchar *comment_path; + gchar *comment_pathl; + gint success = FALSE; + if (!fd) return FALSE; + + comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); + if (!comment_path) return FALSE; + + comment_pathl = path_from_utf8(comment_path); + + success = comment_file_read(comment_pathl, keywords, comment); + + g_free(comment_pathl); + g_free(comment_path); + + return success; +} + +static GList *remove_duplicate_strings_from_list(GList *list) +{ + GList *work = list; + GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal); + GList *newlist = NULL; + + while (work) + { + gchar *key = work->data; + + if (g_hash_table_lookup(hashtable, key) == NULL) + { + g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1)); + newlist = g_list_prepend(newlist, key); + } + work = work->next; + } + + g_hash_table_destroy(hashtable); + g_list_free(list); + + return g_list_reverse(newlist); +} + +#define COMMENT_KEY "Xmp.dc.description" +#define KEYWORD_KEY "Xmp.dc.subject" + +static gint comment_xmp_read(FileData *fd, GList **keywords, gchar **comment) +{ + ExifData *exif; + + exif = exif_read_fd(fd); + if (!exif) return FALSE; + + if (comment) + { + gchar *text; + ExifItem *item = exif_get_item(exif, COMMENT_KEY); + + text = exif_item_get_string(item, 0); + *comment = utf8_validate_or_convert(text); + g_free(text); + } + + if (keywords) + { + ExifItem *item; + guint i; + + *keywords = NULL; + item = exif_get_item(exif, KEYWORD_KEY); + for (i = 0; i < exif_item_get_elements(item); i++) + { + gchar *kw = exif_item_get_string(item, i); + gchar *utf8_kw; + + if (!kw) break; + + utf8_kw = utf8_validate_or_convert(kw); + *keywords = g_list_append(*keywords, (gpointer) utf8_kw); + g_free(kw); + } + + /* FIXME: + * Exiv2 handles Iptc keywords as multiple entries with the + * same key, thus exif_get_item returns only the first keyword + * and the only way to get all keywords is to iterate through + * the item list. + */ + for (item = exif_get_first_item(exif); + item; + item = exif_get_next_item(exif)) + { + guint tag; + + tag = exif_item_get_tag_id(item); + if (tag == 0x0019) + { + gchar *tag_name = exif_item_get_tag_name(item); + + if (strcmp(tag_name, "Iptc.Application2.Keywords") == 0) + { + gchar *kw; + gchar *utf8_kw; + + kw = exif_item_get_data_as_text(item); + if (!kw) continue; + + utf8_kw = utf8_validate_or_convert(kw); + *keywords = g_list_append(*keywords, (gpointer) utf8_kw); + g_free(kw); + } + g_free(tag_name); + } + } + } + + exif_free_fd(fd, exif); + + return (comment && *comment) || (keywords && *keywords); +} + +static gint comment_xmp_write(FileData *fd, GList *keywords, const gchar *comment) +{ + gint success; + gint write_comment = (comment && comment[0]); + ExifData *exif; + ExifItem *item; + + exif = exif_read_fd(fd); + if (!exif) return FALSE; + + item = exif_get_item(exif, COMMENT_KEY); + if (item && !write_comment) + { + exif_item_delete(exif, item); + item = NULL; + } + + if (!item && write_comment) item = exif_add_item(exif, COMMENT_KEY); + if (item) exif_item_set_string(item, comment); + + while ((item = exif_get_item(exif, KEYWORD_KEY))) + { + exif_item_delete(exif, item); + } + + if (keywords) + { + GList *work; + + item = exif_add_item(exif, KEYWORD_KEY); + + work = keywords; + while (work) + { + exif_item_set_string(item, (gchar *) work->data); + work = work->next; + } + } + + success = exif_write(exif); + + exif_free_fd(fd, exif); + + return success; +} + +gint comment_write(FileData *fd, GList *keywords, const gchar *comment) +{ + if (!fd) return FALSE; + + if (options->save_metadata_in_image_file && + comment_xmp_write(fd, keywords, comment)) + { + comment_delete_legacy(fd); + return TRUE; + } + + return comment_legacy_write(fd, keywords, comment); +} + +gint comment_read(FileData *fd, GList **keywords, gchar **comment) +{ + GList *keywords1 = NULL; + GList *keywords2 = NULL; + gchar *comment1 = NULL; + gchar *comment2 = NULL; + gint res1, res2; + + if (!fd) return FALSE; + + res1 = comment_xmp_read(fd, &keywords1, &comment1); + res2 = comment_legacy_read(fd, &keywords2, &comment2); + + if (!res1 && !res2) + { + return FALSE; + } + + if (keywords) + { + if (res1 && res2) + *keywords = g_list_concat(keywords1, keywords2); + else + *keywords = res1 ? keywords1 : keywords2; + + *keywords = remove_duplicate_strings_from_list(*keywords); + } + else + { + if (res1) string_list_free(keywords1); + if (res2) string_list_free(keywords2); + } + + + if (comment) + { + if (res1 && res2 && comment1 && comment2 && comment1[0] && comment2[0]) + *comment = g_strdup_printf("%s\n%s", comment1, comment2); + else + *comment = res1 ? comment1 : comment2; + } + if (res1 && (!comment || *comment != comment1)) g_free(comment1); + if (res2 && (!comment || *comment != comment2)) g_free(comment2); + + // return FALSE in the following cases: + // - only looking for a comment and didn't find one + // - only looking for keywords and didn't find any + // - looking for either a comment or keywords, but found nothing + if ((!keywords && comment && !*comment) || + (!comment && keywords && !*keywords) || + ( comment && !*comment && keywords && !*keywords)) + return FALSE; + + return TRUE; +} + +void metadata_set_keywords(FileData *fd, GList *keywords_to_use, gchar *comment_to_use, gint add) +{ + gchar *comment = NULL; + GList *keywords = NULL; + GList *save_list = NULL; + + comment_read(fd, &keywords, &comment); + + if (comment_to_use) + { + if (add && comment && *comment) + { + gchar *tmp = comment; + + comment = g_strconcat(tmp, comment_to_use, NULL); + g_free(tmp); + } + else + { + g_free(comment); + comment = g_strdup(comment_to_use); + } + } + + if (keywords_to_use) + { + if (add && keywords && g_list_length(keywords) > 0) + { + GList *work; + + work = keywords_to_use; + while (work) + { + gchar *key; + GList *p; + + key = work->data; + work = work->next; + + p = keywords; + while (p && key) + { + gchar *needle = p->data; + p = p->next; + + if (strcmp(needle, key) == 0) key = NULL; + } + + if (key) keywords = g_list_append(keywords, g_strdup(key)); + } + save_list = keywords; + } + else + { + save_list = keywords_to_use; + } + } + + comment_write(fd, save_list, comment); + + string_list_free(keywords); + g_free(comment); +} + +gint keyword_list_find(GList *list, const gchar *keyword) +{ + while (list) + { + gchar *haystack = list->data; + + if (haystack && keyword && strcmp(haystack, keyword) == 0) return TRUE; + + list = list->next; + } + + return FALSE; +} + +#define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b') + +GList *string_to_keywords_list(const gchar *text) +{ + GList *list = NULL; + const gchar *ptr = text; + + while (*ptr != '\0') + { + const gchar *begin; + gint l = 0; + + while (KEYWORDS_SEPARATOR(*ptr)) ptr++; + begin = ptr; + while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr)) + { + ptr++; + l++; + } + + /* trim starting and ending whitespaces */ + while (l > 0 && g_ascii_isspace(*begin)) begin++, l--; + while (l > 0 && g_ascii_isspace(begin[l-1])) l--; + + if (l > 0) + { + gchar *keyword = g_strndup(begin, l); + + /* only add if not already in the list */ + if (keyword_list_find(list, keyword) == FALSE) + list = g_list_append(list, keyword); + else + g_free(keyword); + } + } + + return list; +} + +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */