comparison 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
comparison
equal deleted inserted replaced
1177:5a20c47e7a14 1178:f6449c17306b
1 /*
2 * Geeqie
3 * (C) 2004 John Ellis
4 * Copyright (C) 2008 The Geeqie Team
5 *
6 * Author: John Ellis, Laurent Monin
7 *
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
11 */
12
13
14 #include "main.h"
15 #include "metadata.h"
16
17 #include "cache.h"
18 #include "exif.h"
19 #include "filedata.h"
20 #include "misc.h"
21 #include "secure_save.h"
22 #include "ui_fileops.h"
23 #include "ui_misc.h"
24 #include "utilops.h"
25
26
27 /*
28 *-------------------------------------------------------------------
29 * keyword / comment read/write
30 *-------------------------------------------------------------------
31 */
32
33 static gint comment_file_write(gchar *path, GList *keywords, const gchar *comment)
34 {
35 SecureSaveInfo *ssi;
36
37 ssi = secure_open(path);
38 if (!ssi) return FALSE;
39
40 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
41
42 secure_fprintf(ssi, "[keywords]\n");
43 while (keywords && secsave_errno == SS_ERR_NONE)
44 {
45 const gchar *word = keywords->data;
46 keywords = keywords->next;
47
48 secure_fprintf(ssi, "%s\n", word);
49 }
50 secure_fputc(ssi, '\n');
51
52 secure_fprintf(ssi, "[comment]\n");
53 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
54
55 secure_fprintf(ssi, "#end\n");
56
57 return (secure_close(ssi) == 0);
58 }
59
60 static gint comment_legacy_write(FileData *fd, GList *keywords, const gchar *comment)
61 {
62 gchar *comment_path;
63 gint success = FALSE;
64
65 /* If an existing metadata file exists, we will try writing to
66 * it's location regardless of the user's preference.
67 */
68 comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
69 if (comment_path && !access_file(comment_path, W_OK))
70 {
71 g_free(comment_path);
72 comment_path = NULL;
73 }
74
75 if (!comment_path)
76 {
77 gchar *comment_dir;
78 mode_t mode = 0755;
79
80 comment_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
81 if (recursive_mkdir_if_not_exists(comment_dir, mode))
82 {
83 gchar *filename = g_strconcat(fd->name, GQ_CACHE_EXT_METADATA, NULL);
84
85 comment_path = g_build_filename(comment_dir, filename, NULL);
86 g_free(filename);
87 }
88 g_free(comment_dir);
89 }
90
91 if (comment_path)
92 {
93 gchar *comment_pathl;
94
95 DEBUG_1("Saving comment: %s", comment_path);
96
97 comment_pathl = path_from_utf8(comment_path);
98
99 success = comment_file_write(comment_pathl, keywords, comment);
100
101 g_free(comment_pathl);
102 g_free(comment_path);
103 }
104
105 return success;
106 }
107
108 typedef enum {
109 MK_NONE,
110 MK_KEYWORDS,
111 MK_COMMENT
112 } MetadataKey;
113
114 static gint comment_file_read(gchar *path, GList **keywords, gchar **comment)
115 {
116 FILE *f;
117 gchar s_buf[1024];
118 MetadataKey key = MK_NONE;
119 GList *list = NULL;
120 GString *comment_build = NULL;
121
122 f = fopen(path, "r");
123 if (!f) return FALSE;
124
125 while (fgets(s_buf, sizeof(s_buf), f))
126 {
127 gchar *ptr = s_buf;
128
129 if (*ptr == '#') continue;
130 if (*ptr == '[' && key != MK_COMMENT)
131 {
132 gchar *keystr = ++ptr;
133
134 key = MK_NONE;
135 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
136
137 if (*ptr == ']')
138 {
139 *ptr = '\0';
140 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
141 key = MK_KEYWORDS;
142 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
143 key = MK_COMMENT;
144 }
145 continue;
146 }
147
148 switch(key)
149 {
150 case MK_NONE:
151 break;
152 case MK_KEYWORDS:
153 {
154 while (*ptr != '\n' && *ptr != '\0') ptr++;
155 *ptr = '\0';
156 if (strlen(s_buf) > 0)
157 {
158 gchar *kw = utf8_validate_or_convert(s_buf);
159
160 list = g_list_prepend(list, kw);
161 }
162 }
163 break;
164 case MK_COMMENT:
165 if (!comment_build) comment_build = g_string_new("");
166 g_string_append(comment_build, s_buf);
167 break;
168 }
169 }
170
171 fclose(f);
172
173 *keywords = g_list_reverse(list);
174 if (comment_build)
175 {
176 if (comment)
177 {
178 gint len;
179 gchar *ptr = comment_build->str;
180
181 /* strip leading and trailing newlines */
182 while (*ptr == '\n') ptr++;
183 len = strlen(ptr);
184 while (len > 0 && ptr[len - 1] == '\n') len--;
185 if (ptr[len] == '\n') len++; /* keep the last one */
186 if (len > 0)
187 {
188 gchar *text = g_strndup(ptr, len);
189
190 *comment = utf8_validate_or_convert(text);
191 g_free(text);
192 }
193 }
194 g_string_free(comment_build, TRUE);
195 }
196
197 return TRUE;
198 }
199
200 static gint comment_delete_legacy(FileData *fd)
201 {
202 gchar *comment_path;
203 gchar *comment_pathl;
204 gint success = FALSE;
205 if (!fd) return FALSE;
206
207 comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
208 if (!comment_path) return FALSE;
209
210 comment_pathl = path_from_utf8(comment_path);
211
212 success = !unlink(comment_pathl);
213
214 g_free(comment_pathl);
215 g_free(comment_path);
216
217 return success;
218 }
219
220 static gint comment_legacy_read(FileData *fd, GList **keywords, gchar **comment)
221 {
222 gchar *comment_path;
223 gchar *comment_pathl;
224 gint success = FALSE;
225 if (!fd) return FALSE;
226
227 comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
228 if (!comment_path) return FALSE;
229
230 comment_pathl = path_from_utf8(comment_path);
231
232 success = comment_file_read(comment_pathl, keywords, comment);
233
234 g_free(comment_pathl);
235 g_free(comment_path);
236
237 return success;
238 }
239
240 static GList *remove_duplicate_strings_from_list(GList *list)
241 {
242 GList *work = list;
243 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
244 GList *newlist = NULL;
245
246 while (work)
247 {
248 gchar *key = work->data;
249
250 if (g_hash_table_lookup(hashtable, key) == NULL)
251 {
252 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
253 newlist = g_list_prepend(newlist, key);
254 }
255 work = work->next;
256 }
257
258 g_hash_table_destroy(hashtable);
259 g_list_free(list);
260
261 return g_list_reverse(newlist);
262 }
263
264 #define COMMENT_KEY "Xmp.dc.description"
265 #define KEYWORD_KEY "Xmp.dc.subject"
266
267 static gint comment_xmp_read(FileData *fd, GList **keywords, gchar **comment)
268 {
269 ExifData *exif;
270
271 exif = exif_read_fd(fd);
272 if (!exif) return FALSE;
273
274 if (comment)
275 {
276 gchar *text;
277 ExifItem *item = exif_get_item(exif, COMMENT_KEY);
278
279 text = exif_item_get_string(item, 0);
280 *comment = utf8_validate_or_convert(text);
281 g_free(text);
282 }
283
284 if (keywords)
285 {
286 ExifItem *item;
287 guint i;
288
289 *keywords = NULL;
290 item = exif_get_item(exif, KEYWORD_KEY);
291 for (i = 0; i < exif_item_get_elements(item); i++)
292 {
293 gchar *kw = exif_item_get_string(item, i);
294 gchar *utf8_kw;
295
296 if (!kw) break;
297
298 utf8_kw = utf8_validate_or_convert(kw);
299 *keywords = g_list_append(*keywords, (gpointer) utf8_kw);
300 g_free(kw);
301 }
302
303 /* FIXME:
304 * Exiv2 handles Iptc keywords as multiple entries with the
305 * same key, thus exif_get_item returns only the first keyword
306 * and the only way to get all keywords is to iterate through
307 * the item list.
308 */
309 for (item = exif_get_first_item(exif);
310 item;
311 item = exif_get_next_item(exif))
312 {
313 guint tag;
314
315 tag = exif_item_get_tag_id(item);
316 if (tag == 0x0019)
317 {
318 gchar *tag_name = exif_item_get_tag_name(item);
319
320 if (strcmp(tag_name, "Iptc.Application2.Keywords") == 0)
321 {
322 gchar *kw;
323 gchar *utf8_kw;
324
325 kw = exif_item_get_data_as_text(item);
326 if (!kw) continue;
327
328 utf8_kw = utf8_validate_or_convert(kw);
329 *keywords = g_list_append(*keywords, (gpointer) utf8_kw);
330 g_free(kw);
331 }
332 g_free(tag_name);
333 }
334 }
335 }
336
337 exif_free_fd(fd, exif);
338
339 return (comment && *comment) || (keywords && *keywords);
340 }
341
342 static gint comment_xmp_write(FileData *fd, GList *keywords, const gchar *comment)
343 {
344 gint success;
345 gint write_comment = (comment && comment[0]);
346 ExifData *exif;
347 ExifItem *item;
348
349 exif = exif_read_fd(fd);
350 if (!exif) return FALSE;
351
352 item = exif_get_item(exif, COMMENT_KEY);
353 if (item && !write_comment)
354 {
355 exif_item_delete(exif, item);
356 item = NULL;
357 }
358
359 if (!item && write_comment) item = exif_add_item(exif, COMMENT_KEY);
360 if (item) exif_item_set_string(item, comment);
361
362 while ((item = exif_get_item(exif, KEYWORD_KEY)))
363 {
364 exif_item_delete(exif, item);
365 }
366
367 if (keywords)
368 {
369 GList *work;
370
371 item = exif_add_item(exif, KEYWORD_KEY);
372
373 work = keywords;
374 while (work)
375 {
376 exif_item_set_string(item, (gchar *) work->data);
377 work = work->next;
378 }
379 }
380
381 success = exif_write(exif);
382
383 exif_free_fd(fd, exif);
384
385 return success;
386 }
387
388 gint comment_write(FileData *fd, GList *keywords, const gchar *comment)
389 {
390 if (!fd) return FALSE;
391
392 if (options->save_metadata_in_image_file &&
393 comment_xmp_write(fd, keywords, comment))
394 {
395 comment_delete_legacy(fd);
396 return TRUE;
397 }
398
399 return comment_legacy_write(fd, keywords, comment);
400 }
401
402 gint comment_read(FileData *fd, GList **keywords, gchar **comment)
403 {
404 GList *keywords1 = NULL;
405 GList *keywords2 = NULL;
406 gchar *comment1 = NULL;
407 gchar *comment2 = NULL;
408 gint res1, res2;
409
410 if (!fd) return FALSE;
411
412 res1 = comment_xmp_read(fd, &keywords1, &comment1);
413 res2 = comment_legacy_read(fd, &keywords2, &comment2);
414
415 if (!res1 && !res2)
416 {
417 return FALSE;
418 }
419
420 if (keywords)
421 {
422 if (res1 && res2)
423 *keywords = g_list_concat(keywords1, keywords2);
424 else
425 *keywords = res1 ? keywords1 : keywords2;
426
427 *keywords = remove_duplicate_strings_from_list(*keywords);
428 }
429 else
430 {
431 if (res1) string_list_free(keywords1);
432 if (res2) string_list_free(keywords2);
433 }
434
435
436 if (comment)
437 {
438 if (res1 && res2 && comment1 && comment2 && comment1[0] && comment2[0])
439 *comment = g_strdup_printf("%s\n%s", comment1, comment2);
440 else
441 *comment = res1 ? comment1 : comment2;
442 }
443 if (res1 && (!comment || *comment != comment1)) g_free(comment1);
444 if (res2 && (!comment || *comment != comment2)) g_free(comment2);
445
446 // return FALSE in the following cases:
447 // - only looking for a comment and didn't find one
448 // - only looking for keywords and didn't find any
449 // - looking for either a comment or keywords, but found nothing
450 if ((!keywords && comment && !*comment) ||
451 (!comment && keywords && !*keywords) ||
452 ( comment && !*comment && keywords && !*keywords))
453 return FALSE;
454
455 return TRUE;
456 }
457
458 void metadata_set_keywords(FileData *fd, GList *keywords_to_use, gchar *comment_to_use, gint add)
459 {
460 gchar *comment = NULL;
461 GList *keywords = NULL;
462 GList *save_list = NULL;
463
464 comment_read(fd, &keywords, &comment);
465
466 if (comment_to_use)
467 {
468 if (add && comment && *comment)
469 {
470 gchar *tmp = comment;
471
472 comment = g_strconcat(tmp, comment_to_use, NULL);
473 g_free(tmp);
474 }
475 else
476 {
477 g_free(comment);
478 comment = g_strdup(comment_to_use);
479 }
480 }
481
482 if (keywords_to_use)
483 {
484 if (add && keywords && g_list_length(keywords) > 0)
485 {
486 GList *work;
487
488 work = keywords_to_use;
489 while (work)
490 {
491 gchar *key;
492 GList *p;
493
494 key = work->data;
495 work = work->next;
496
497 p = keywords;
498 while (p && key)
499 {
500 gchar *needle = p->data;
501 p = p->next;
502
503 if (strcmp(needle, key) == 0) key = NULL;
504 }
505
506 if (key) keywords = g_list_append(keywords, g_strdup(key));
507 }
508 save_list = keywords;
509 }
510 else
511 {
512 save_list = keywords_to_use;
513 }
514 }
515
516 comment_write(fd, save_list, comment);
517
518 string_list_free(keywords);
519 g_free(comment);
520 }
521
522 gint keyword_list_find(GList *list, const gchar *keyword)
523 {
524 while (list)
525 {
526 gchar *haystack = list->data;
527
528 if (haystack && keyword && strcmp(haystack, keyword) == 0) return TRUE;
529
530 list = list->next;
531 }
532
533 return FALSE;
534 }
535
536 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
537
538 GList *string_to_keywords_list(const gchar *text)
539 {
540 GList *list = NULL;
541 const gchar *ptr = text;
542
543 while (*ptr != '\0')
544 {
545 const gchar *begin;
546 gint l = 0;
547
548 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
549 begin = ptr;
550 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
551 {
552 ptr++;
553 l++;
554 }
555
556 /* trim starting and ending whitespaces */
557 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
558 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
559
560 if (l > 0)
561 {
562 gchar *keyword = g_strndup(begin, l);
563
564 /* only add if not already in the list */
565 if (keyword_list_find(list, keyword) == FALSE)
566 list = g_list_append(list, keyword);
567 else
568 g_free(keyword);
569 }
570 }
571
572 return list;
573 }
574
575 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */