1 /*
2 * Copyright (C) 2006, Jamie McCracken <jamiemcc@gnome.org>
3 * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #ifndef _GNU_SOURCE
24 #define _GNU_SOURCE
25 #endif
26
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33
34 #include <gif_lib.h>
35
36 #include <libtracker-common/tracker-common.h>
37
38 #include <libtracker-extract/tracker-extract.h>
39
40 #define XMP_MAGIC_TRAILER_LENGTH 256
41 #define EXTENSION_RECORD_COMMENT_BLOCK_CODE 0xFE
42
43 typedef struct {
44 const gchar *title;
45 const gchar *date;
46 const gchar *artist;
47 } MergeData;
48
49 typedef struct {
50 gchar *width;
51 gchar *height;
52 gchar *comment;
53 } GifData;
54
55 typedef struct {
56 unsigned int byteCount;
57 char *bytes;
58 } ExtBlock;
59
60 static int
61 ext_block_append(ExtBlock *extBlock,
62 unsigned int len,
63 unsigned char extData[])
64 {
65 extBlock->bytes = realloc(extBlock->bytes,extBlock->byteCount+len);
66 if (extBlock->bytes == NULL) {
67 return (GIF_ERROR);
68 }
69
70 memcpy(&(extBlock->bytes[extBlock->byteCount]), &extData[0], len);
71 extBlock->byteCount += len;
72
73 return (GIF_OK);
74 }
75
76 #if GIFLIB_MAJOR >= 5
77 static inline void
78 gif_error (const gchar *action, int err)
79 {
80 const char *str = GifErrorString (err);
81 if (str != NULL) {
82 g_message ("%s, error: '%s'", action, str);
83 } else {
84 g_message ("%s, undefined error %d", action, err);
85 }
86 }
87 #endif /* GIFLIB_MAJOR >= 5 */
88
89 static void
90 read_metadata (TrackerSparqlBuilder *preupdate,
91 TrackerSparqlBuilder *metadata,
92 GString *where,
93 GifFileType *gifFile,
94 const gchar *uri,
95 const gchar *graph)
96 {
97 GifRecordType RecordType;
98 int frameheight;
99 int framewidth;
100 unsigned char *framedata = NULL;
101 GPtrArray *keywords;
102 guint i;
103 int status;
104 MergeData md = { 0 };
105 GifData gd = { 0 };
106 TrackerXmpData *xd = NULL;
107
108 do {
109 GifByteType *ExtData;
110 int ExtCode;
111 ExtBlock extBlock;
112
113 if (DGifGetRecordType(gifFile, &RecordType) == GIF_ERROR) {
114 #if GIFLIB_MAJOR < 5
115 PrintGifError ();
116 #else /* GIFLIB_MAJOR < 5 */
117 gif_error ("Could not read next GIF record type", gifFile->Error);
118 #endif /* GIFLIB_MAJOR < 5 */
119 return;
120 }
121
122 switch (RecordType) {
123 case IMAGE_DESC_RECORD_TYPE:
124 if (DGifGetImageDesc(gifFile) == GIF_ERROR) {
125 #if GIFLIB_MAJOR < 5
126 PrintGifError();
127 #else /* GIFLIB_MAJOR < 5 */
128 gif_error ("Could not get GIF record information", gifFile->Error);
129 #endif /* GIFLIB_MAJOR < 5 */
130 return;
131 }
132
133 framewidth = gifFile->Image.Width;
134 frameheight = gifFile->Image.Height;
135
136 framedata = g_malloc (framewidth*frameheight);
137
138 if (DGifGetLine(gifFile, framedata, framewidth*frameheight)==GIF_ERROR) {
139 #if GIFLIB_MAJOR < 5
140 PrintGifError();
141 #else /* GIFLIB_MAJOR < 5 */
142 gif_error ("Could not load a block of GIF pixes", gifFile->Error);
143 #endif /* GIFLIB_MAJOR < 5 */
144 return;
145 }
146
147 gd.width = g_strdup_printf ("%d", framewidth);
148 gd.height = g_strdup_printf ("%d", frameheight);
149
150
151 g_free (framedata);
152
153 break;
154 case EXTENSION_RECORD_TYPE:
155 extBlock.bytes = NULL;
156 extBlock.byteCount = 0;
157
158 if ((status = DGifGetExtension (gifFile, &ExtCode, &ExtData)) != GIF_OK) {
159 g_warning ("Problem getting the extension");
160 return;
161 }
162 #if defined(HAVE_EXEMPI)
163 if (ExtData && *ExtData &&
164 strncmp (&ExtData[1],"XMP Data",8) == 0) {
pointer targets in passing argument 1 of 'strlen' differ in signedness
(emitted by gcc)
165 while (ExtData != NULL && status == GIF_OK ) {
166 if ((status = DGifGetExtensionNext (gifFile, &ExtData)) == GIF_OK) {
167 if (ExtData != NULL) {
168 if (ext_block_append (&extBlock, ExtData[0]+1, (char *) &(ExtData[0])) != GIF_OK) {
169 g_warning ("Problem with extension data");
170 return;
171 }
172 }
173 }
174 }
175
176 xd = tracker_xmp_new (extBlock.bytes,
177 extBlock.byteCount-XMP_MAGIC_TRAILER_LENGTH,
178 uri);
179
180 g_free (extBlock.bytes);
181 } else
182 #endif
183 /* See Section 24. Comment Extension. in the GIF format definition */
184 if (ExtCode == EXTENSION_RECORD_COMMENT_BLOCK_CODE &&
185 ExtData && *ExtData) {
186 guint block_count = 0;
187
188 /* Merge all blocks */
189 do {
190 block_count++;
191
192 g_debug ("Comment Extension block found (#%u, %u bytes)",
193 block_count,
194 ExtData[0]);
195 if (ext_block_append (&extBlock, ExtData[0], (char *) &(ExtData[1])) != GIF_OK) {
196 g_warning ("Problem with Comment extension data");
197 return;
198 }
199 } while (((status = DGifGetExtensionNext(gifFile, &ExtData)) == GIF_OK) &&
200 ExtData != NULL);
201
202 /* Add last NUL byte */
203 g_debug ("Comment Extension blocks found (%u) with %u bytes",
204 block_count,
205 extBlock.byteCount);
206 extBlock.bytes = g_realloc (extBlock.bytes, extBlock.byteCount + 1);
207 extBlock.bytes[extBlock.byteCount] = '\0';
208
209 /* Set commentt */
210 gd.comment = extBlock.bytes;
211 } else {
212 do {
213 status = DGifGetExtensionNext(gifFile, &ExtData);
214 } while ( status == GIF_OK && ExtData != NULL);
215 }
216 break;
217 case TERMINATE_RECORD_TYPE:
218 break;
219 default:
220 break;
221 }
222 } while (RecordType != TERMINATE_RECORD_TYPE);
223
224
225 if (!xd) {
226 xd = g_new0 (TrackerXmpData, 1);
227 }
228
229 md.title = tracker_coalesce_strip (3, xd->title, xd->title2, xd->pdf_title);
230 md.date = tracker_coalesce_strip (2, xd->date, xd->time_original);
231 md.artist = tracker_coalesce_strip (2, xd->artist, xd->contributor);
232
233 if (xd->license) {
234 tracker_sparql_builder_predicate (metadata, "nie:license");
235 tracker_sparql_builder_object_unvalidated (metadata, xd->license);
236 }
237
238 if (xd->creator) {
239 gchar *uri = tracker_sparql_escape_uri_printf ("urn:contact:%s", xd->creator);
240
241 tracker_sparql_builder_insert_open (preupdate, NULL);
242 if (graph) {
243 tracker_sparql_builder_graph_open (preupdate, graph);
244 }
245
246 tracker_sparql_builder_subject_iri (preupdate, uri);
247 tracker_sparql_builder_predicate (preupdate, "a");
248 tracker_sparql_builder_object (preupdate, "nco:Contact");
249 tracker_sparql_builder_predicate (preupdate, "nco:fullname");
250 tracker_sparql_builder_object_unvalidated (preupdate, xd->creator);
251
252 if (graph) {
253 tracker_sparql_builder_graph_close (preupdate);
254 }
255 tracker_sparql_builder_insert_close (preupdate);
256
257 tracker_sparql_builder_predicate (metadata, "nco:creator");
258 tracker_sparql_builder_object_iri (metadata, uri);
259 g_free (uri);
260 }
261
262 tracker_guarantee_date_from_file_mtime (metadata,
263 "nie:contentCreated",
264 md.date,
265 uri);
266
267 if (xd->description) {
268 tracker_sparql_builder_predicate (metadata, "nie:description");
269 tracker_sparql_builder_object_unvalidated (metadata, xd->description);
270 }
271
272 if (xd->copyright) {
273 tracker_sparql_builder_predicate (metadata, "nie:copyright");
274 tracker_sparql_builder_object_unvalidated (metadata, xd->copyright);
275 }
276
277 if (xd->make || xd->model) {
278 gchar *equip_uri;
279
280 equip_uri = tracker_sparql_escape_uri_printf ("urn:equipment:%s:%s:",
281 xd->make ? xd->make : "",
282 xd->model ? xd->model : "");
283
284 tracker_sparql_builder_insert_open (preupdate, NULL);
285 if (graph) {
286 tracker_sparql_builder_graph_open (preupdate, graph);
287 }
288
289 tracker_sparql_builder_subject_iri (preupdate, equip_uri);
290 tracker_sparql_builder_predicate (preupdate, "a");
291 tracker_sparql_builder_object (preupdate, "nfo:Equipment");
292
293 if (xd->make) {
294 tracker_sparql_builder_predicate (preupdate, "nfo:manufacturer");
295 tracker_sparql_builder_object_unvalidated (preupdate, xd->make);
296 }
297 if (xd->model) {
298 tracker_sparql_builder_predicate (preupdate, "nfo:model");
299 tracker_sparql_builder_object_unvalidated (preupdate, xd->model);
300 }
301
302 if (graph) {
303 tracker_sparql_builder_graph_close (preupdate);
304 }
305 tracker_sparql_builder_insert_close (preupdate);
306
307 tracker_sparql_builder_predicate (metadata, "nfo:equipment");
308 tracker_sparql_builder_object_iri (metadata, equip_uri);
309 g_free (equip_uri);
310 }
311
312 tracker_guarantee_title_from_file (metadata,
313 "nie:title",
314 md.title,
315 uri,
316 NULL);
317
318 if (md.artist) {
319 gchar *uri = tracker_sparql_escape_uri_printf ("urn:contact:%s", md.artist);
320
321 tracker_sparql_builder_insert_open (preupdate, NULL);
322 if (graph) {
323 tracker_sparql_builder_graph_open (preupdate, graph);
324 }
325
326 tracker_sparql_builder_subject_iri (preupdate, uri);
327 tracker_sparql_builder_predicate (preupdate, "a");
328 tracker_sparql_builder_object (preupdate, "nco:Contact");
329 tracker_sparql_builder_predicate (preupdate, "nco:fullname");
330 tracker_sparql_builder_object_unvalidated (preupdate, md.artist);
331
332 if (graph) {
333 tracker_sparql_builder_graph_close (preupdate);
334 }
335 tracker_sparql_builder_insert_close (preupdate);
336
337 tracker_sparql_builder_predicate (metadata, "nco:contributor");
338 tracker_sparql_builder_object_iri (metadata, uri);
339 g_free (uri);
340 }
341
342 if (xd->orientation) {
343 tracker_sparql_builder_predicate (metadata, "nfo:orientation");
344 tracker_sparql_builder_object_unvalidated (metadata, xd->orientation);
345 }
346
347 if (xd->exposure_time) {
348 tracker_sparql_builder_predicate (metadata, "nmm:exposureTime");
349 tracker_sparql_builder_object_unvalidated (metadata, xd->exposure_time);
350 }
351
352 if (xd->iso_speed_ratings) {
353 tracker_sparql_builder_predicate (metadata, "nmm:isoSpeed");
354 tracker_sparql_builder_object_unvalidated (metadata, xd->iso_speed_ratings);
355 }
356
357 if (xd->white_balance) {
358 tracker_sparql_builder_predicate (metadata, "nmm:whiteBalance");
359 tracker_sparql_builder_object_unvalidated (metadata, xd->white_balance);
360 }
361
362 if (xd->fnumber) {
363 tracker_sparql_builder_predicate (metadata, "nmm:fnumber");
364 tracker_sparql_builder_object_unvalidated (metadata, xd->fnumber);
365 }
366
367 if (xd->flash) {
368 tracker_sparql_builder_predicate (metadata, "nmm:flash");
369 tracker_sparql_builder_object_unvalidated (metadata, xd->flash);
370 }
371
372 if (xd->focal_length) {
373 tracker_sparql_builder_predicate (metadata, "nmm:focalLength");
374 tracker_sparql_builder_object_unvalidated (metadata, xd->focal_length);
375 }
376
377 if (xd->metering_mode) {
378 tracker_sparql_builder_predicate (metadata, "nmm:meteringMode");
379 tracker_sparql_builder_object_unvalidated (metadata, xd->metering_mode);
380 }
381
382 keywords = g_ptr_array_new ();
383
384 if (xd->keywords) {
385 tracker_keywords_parse (keywords, xd->keywords);
386 }
387
388 if (xd->pdf_keywords) {
389 tracker_keywords_parse (keywords, xd->pdf_keywords);
390 }
391
392 if (xd->rating) {
393 tracker_sparql_builder_predicate (metadata, "nao:numericRating");
394 tracker_sparql_builder_object_unvalidated (metadata, xd->rating);
395 }
396
397 if (xd->subject) {
398 tracker_keywords_parse (keywords, xd->subject);
399 }
400
401 if (xd->regions) {
402 tracker_xmp_apply_regions (preupdate, metadata, graph, xd);
403 }
404
405 for (i = 0; i < keywords->len; i++) {
406 gchar *p, *escaped, *var;
407
408 p = g_ptr_array_index (keywords, i);
409 escaped = tracker_sparql_escape_string (p);
410 var = g_strdup_printf ("tag%d", i + 1);
411
412 /* ensure tag with specified label exists */
413 tracker_sparql_builder_append (preupdate, "INSERT { ");
414
415 if (graph) {
416 tracker_sparql_builder_append (preupdate, "GRAPH <");
417 tracker_sparql_builder_append (preupdate, graph);
418 tracker_sparql_builder_append (preupdate, "> { ");
419 }
420
421 tracker_sparql_builder_append (preupdate,
422 "_:tag a nao:Tag ; nao:prefLabel \"");
423 tracker_sparql_builder_append (preupdate, escaped);
424 tracker_sparql_builder_append (preupdate, "\"");
425
426 if (graph) {
427 tracker_sparql_builder_append (preupdate, " } ");
428 }
429
430 tracker_sparql_builder_append (preupdate, " }\n");
431 tracker_sparql_builder_append (preupdate,
432 "WHERE { FILTER (NOT EXISTS { "
433 "?tag a nao:Tag ; nao:prefLabel \"");
434 tracker_sparql_builder_append (preupdate, escaped);
435 tracker_sparql_builder_append (preupdate,
436 "\" }) }\n");
437
438 /* associate file with tag */
439 tracker_sparql_builder_predicate (metadata, "nao:hasTag");
440 tracker_sparql_builder_object_variable (metadata, var);
441
442 g_string_append_printf (where, "?%s a nao:Tag ; nao:prefLabel \"%s\" .\n", var, escaped);
443
444 g_free (var);
445 g_free (escaped);
446 g_free (p);
447 }
448 g_ptr_array_free (keywords, TRUE);
449
450 if (xd->publisher) {
451 gchar *uri = tracker_sparql_escape_uri_printf ("urn:contact:%s", xd->publisher);
452
453 tracker_sparql_builder_insert_open (preupdate, NULL);
454 if (graph) {
455 tracker_sparql_builder_graph_open (preupdate, graph);
456 }
457
458 tracker_sparql_builder_subject_iri (preupdate, uri);
459 tracker_sparql_builder_predicate (preupdate, "a");
460 tracker_sparql_builder_object (preupdate, "nco:Contact");
461 tracker_sparql_builder_predicate (preupdate, "nco:fullname");
462 tracker_sparql_builder_object_unvalidated (preupdate, xd->publisher);
463
464 if (graph) {
465 tracker_sparql_builder_graph_close (preupdate);
466 }
467 tracker_sparql_builder_insert_close (preupdate);
468
469 tracker_sparql_builder_predicate (metadata, "nco:creator");
470 tracker_sparql_builder_object_iri (metadata, uri);
471 g_free (uri);
472 }
473
474 if (xd->type) {
475 tracker_sparql_builder_predicate (metadata, "dc:type");
476 tracker_sparql_builder_object_unvalidated (metadata, xd->type);
477 }
478
479 if (xd->format) {
480 tracker_sparql_builder_predicate (metadata, "dc:format");
481 tracker_sparql_builder_object_unvalidated (metadata, xd->format);
482 }
483
484 if (xd->identifier) {
485 tracker_sparql_builder_predicate (metadata, "dc:identifier");
486 tracker_sparql_builder_object_unvalidated (metadata, xd->identifier);
487 }
488
489 if (xd->source) {
490 tracker_sparql_builder_predicate (metadata, "dc:source");
491 tracker_sparql_builder_object_unvalidated (metadata, xd->source);
492 }
493
494 if (xd->language) {
495 tracker_sparql_builder_predicate (metadata, "dc:language");
496 tracker_sparql_builder_object_unvalidated (metadata, xd->language);
497 }
498
499 if (xd->relation) {
500 tracker_sparql_builder_predicate (metadata, "dc:relation");
501 tracker_sparql_builder_object_unvalidated (metadata, xd->relation);
502 }
503
504 if (xd->coverage) {
505 tracker_sparql_builder_predicate (metadata, "dc:coverage");
506 tracker_sparql_builder_object_unvalidated (metadata, xd->coverage);
507 }
508
509 if (xd->address || xd->state || xd->country || xd->city ||
510 xd->gps_altitude || xd->gps_latitude || xd-> gps_longitude) {
511
512 tracker_sparql_builder_predicate (metadata, "slo:location");
513
514 tracker_sparql_builder_object_blank_open (metadata); /* GeoLocation */
515 tracker_sparql_builder_predicate (metadata, "a");
516 tracker_sparql_builder_object (metadata, "slo:GeoLocation");
517
518 if (xd->address || xd->state || xd->country || xd->city) {
519 gchar *addruri;
520 addruri = tracker_sparql_get_uuid_urn ();
521
522 tracker_sparql_builder_predicate (metadata, "slo:postalAddress");
523 tracker_sparql_builder_object_iri (metadata, addruri);
524
525 tracker_sparql_builder_insert_open (preupdate, NULL);
526 if (graph) {
527 tracker_sparql_builder_graph_open (preupdate, graph);
528 }
529
530 tracker_sparql_builder_subject_iri (preupdate, addruri);
531
532 g_free (addruri);
533
534 tracker_sparql_builder_predicate (preupdate, "a");
535 tracker_sparql_builder_object (preupdate, "nco:PostalAddress");
536
537 if (xd->address) {
538 tracker_sparql_builder_predicate (preupdate, "nco:streetAddress");
539 tracker_sparql_builder_object_unvalidated (preupdate, xd->address);
540 }
541
542 if (xd->state) {
543 tracker_sparql_builder_predicate (preupdate, "nco:region");
544 tracker_sparql_builder_object_unvalidated (preupdate, xd->state);
545 }
546
547 if (xd->city) {
548 tracker_sparql_builder_predicate (preupdate, "nco:locality");
549 tracker_sparql_builder_object_unvalidated (preupdate, xd->city);
550 }
551
552 if (xd->country) {
553 tracker_sparql_builder_predicate (preupdate, "nco:country");
554 tracker_sparql_builder_object_unvalidated (preupdate, xd->country);
555 }
556
557 if (graph) {
558 tracker_sparql_builder_graph_close (preupdate);
559 }
560 tracker_sparql_builder_insert_close (preupdate);
561 }
562
563 if (xd->gps_altitude) {
564 tracker_sparql_builder_predicate (metadata, "slo:altitude");
565 tracker_sparql_builder_object_unvalidated (metadata, xd->gps_altitude);
566 }
567
568 if (xd->gps_latitude) {
569 tracker_sparql_builder_predicate (metadata, "slo:latitude");
570 tracker_sparql_builder_object_unvalidated (metadata, xd->gps_latitude);
571 }
572
573 if (xd->gps_longitude) {
574 tracker_sparql_builder_predicate (metadata, "slo:longitude");
575 tracker_sparql_builder_object_unvalidated (metadata, xd->gps_longitude);
576 }
577
578 tracker_sparql_builder_object_blank_close (metadata); /* GeoLocation */
579 }
580
581 if (xd->gps_direction) {
582 tracker_sparql_builder_predicate (metadata, "nfo:heading");
583 tracker_sparql_builder_object_unvalidated (metadata, xd->gps_direction);
584 }
585
586 if (gd.width) {
587 tracker_sparql_builder_predicate (metadata, "nfo:width");
588 tracker_sparql_builder_object_unvalidated (metadata, gd.width);
589 g_free (gd.width);
590 }
591
592 if (gd.height) {
593 tracker_sparql_builder_predicate (metadata, "nfo:height");
594 tracker_sparql_builder_object_unvalidated (metadata, gd.height);
595 g_free (gd.height);
596 }
597
598 if (gd.comment) {
599 tracker_sparql_builder_predicate (metadata, "nie:comment");
600 tracker_sparql_builder_object_unvalidated (metadata, gd.comment);
601 g_free (gd.comment);
602 }
603
604 tracker_xmp_free (xd);
605 }
606
607
608 G_MODULE_EXPORT gboolean
609 tracker_extract_get_metadata (TrackerExtractInfo *info)
610 {
611 TrackerSparqlBuilder *preupdate, *metadata;
612 goffset size;
613 GifFileType *gifFile = NULL;
614 GString *where;
615 const gchar *graph;
616 gchar *filename, *uri;
617 GFile *file;
618 int fd;
619 #if GIFLIB_MAJOR >= 5
620 int err;
621 #endif
622
623 preupdate = tracker_extract_info_get_preupdate_builder (info);
624 metadata = tracker_extract_info_get_metadata_builder (info);
625 graph = tracker_extract_info_get_graph (info);
626
627 file = tracker_extract_info_get_file (info);
628 filename = g_file_get_path (file);
629 size = tracker_file_get_size (filename);
630
631 if (size < 64) {
632 g_free (filename);
633 return FALSE;
634 }
635
636 fd = tracker_file_open_fd (filename);
637
638 if (fd == -1) {
639 g_warning ("Could not open GIF file '%s': %s\n",
640 filename,
641 g_strerror (errno));
642 g_free (filename);
643 return FALSE;
644 }
645
646 #if GIFLIB_MAJOR < 5
647 if ((gifFile = DGifOpenFileHandle (fd)) == NULL) {
648 PrintGifError ();
649 #else /* GIFLIB_MAJOR < 5 */
650 if ((gifFile = DGifOpenFileHandle (fd, &err)) == NULL) {
651 gif_error ("Could not open GIF file with handle", err);
652 #endif /* GIFLIB_MAJOR < 5 */
653 close (fd);
654 return FALSE;
655 }
656
657 g_free (filename);
658
659 tracker_sparql_builder_predicate (metadata, "a");
660 tracker_sparql_builder_object (metadata, "nfo:Image");
661 tracker_sparql_builder_object (metadata, "nmm:Photo");
662
663 where = g_string_new ("");
664 uri = g_file_get_uri (file);
665
666 read_metadata (preupdate, metadata, where, gifFile, uri, graph);
667 tracker_extract_info_set_where_clause (info, where->str);
668 g_string_free (where, TRUE);
669
670 g_free (uri);
671
672 if (DGifCloseFile (gifFile) != GIF_OK) {
673 #if GIFLIB_MAJOR < 5
674 PrintGifError ();
675 #else /* GIFLIB_MAJOR < 5 */
676 gif_error ("Could not close GIF file", gifFile->Error);
677 #endif /* GIFLIB_MAJOR < 5 */
678 }
679
680 return TRUE;
681 }