Mercurial > geeqie.yaz
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: */ |