Mercurial > geeqie
comparison src/exif.c @ 9:d907d608745f
Sync to GQview 1.5.9 release.
########
DO NOT BASE ENHANCEMENTS OR TRANSLATION UPDATES ON CODE IN THIS CVS!
This CVS is never up to date with current development and is provided
solely for reference purposes, please use the latest official release
package when making any changes or translation updates.
########
author | gqview |
---|---|
date | Sat, 26 Feb 2005 00:13:35 +0000 |
parents | |
children | ee03f36e9e4b |
comparison
equal
deleted
inserted
replaced
8:e0d0593d519e | 9:d907d608745f |
---|---|
1 /* | |
2 * GQView | |
3 * (C) 2004 John Ellis | |
4 * | |
5 * Authors: | |
6 * Support for Exif file format, originally written by Eric Swalens. | |
7 * Modified by Quy Tonthat | |
8 * | |
9 * Reimplemented with generic data storage by John Ellis (Nov 2003) | |
10 * | |
11 * The tags were added with information from the FREE document: | |
12 * http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html | |
13 * | |
14 * For the official Exif Format, please refer to: | |
15 * http://www.exif.org | |
16 * http://www.exif.org/specifications.html (PDF spec sheets) | |
17 * | |
18 * Notes: | |
19 * Additional tag formats should be added to the proper | |
20 * location in ExifKnownMarkersList[]. | |
21 * | |
22 * Human readable ouput (that needs additional processing of data to | |
23 * be useable) can be defined by adding a key to ExifFormattedList[], | |
24 * then handling that tag in the function exif_get_formatted_by_key(). | |
25 * The human readable formatted keys must begin with the character 'f'. | |
26 * | |
27 * Unsupported at this time: | |
28 * IFD1 (thumbnail) | |
29 * MakerNote | |
30 * GPSInfo | |
31 * | |
32 * TODO: | |
33 * Convert data to useable form in the ??_as_text function for: | |
34 * ComponentsConfiguration | |
35 * UserComment (convert this to UTF-8?) | |
36 * | |
37 | |
38 This program is free software; you can redistribute it and/or modify | |
39 it under the terms of the GNU General Public License as published by | |
40 the Free Software Foundation; either version 2 of the License, or | |
41 (at your option) any later version. | |
42 | |
43 This program is distributed in the hope that it will be useful, | |
44 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
45 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
46 GNU General Public License for more details. | |
47 | |
48 You should have received a copy of the GNU General Public License | |
49 along with this program; if not, write to the Free Software | |
50 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
51 */ | |
52 | |
53 #ifdef HAVE_CONFIG_H | |
54 # include "config.h" | |
55 #endif | |
56 | |
57 #include <stdio.h> | |
58 #include <inttypes.h> /* stdint.h is not available on all systems... */ | |
59 #include <string.h> | |
60 #include <fcntl.h> | |
61 #include <unistd.h> | |
62 #include <sys/types.h> | |
63 #include <sys/stat.h> | |
64 #include <sys/mman.h> | |
65 #include <math.h> | |
66 | |
67 #include <glib.h> | |
68 | |
69 #include "intl.h" | |
70 | |
71 #include "exif.h" | |
72 | |
73 #include "ui_fileops.h" | |
74 | |
75 | |
76 /* | |
77 *----------------------------------------------------------------------------- | |
78 * Tag formats | |
79 *----------------------------------------------------------------------------- | |
80 */ | |
81 | |
82 ExifFormatAttrib ExifFormatList[] = { | |
83 { EXIF_FORMAT_UNKNOWN, 1, "unknown", "unknown" }, | |
84 { EXIF_FORMAT_BYTE_UNSIGNED, 1, "ubyte", "unsigned byte" }, | |
85 { EXIF_FORMAT_STRING, 1, "string", "string" }, | |
86 { EXIF_FORMAT_SHORT_UNSIGNED, 2, "ushort", "unsigned short" }, | |
87 { EXIF_FORMAT_LONG_UNSIGNED, 4, "ulong", "unsigned long" }, | |
88 { EXIF_FORMAT_RATIONAL_UNSIGNED,8, "urational", "unsigned rational" }, | |
89 { EXIF_FORMAT_BYTE, 1, "byte", "byte" }, | |
90 { EXIF_FORMAT_UNDEFINED, 1, "undefined", "undefined" }, | |
91 { EXIF_FORMAT_SHORT, 2, "sshort", "signed short" }, | |
92 { EXIF_FORMAT_LONG, 4, "slong", "signed long" }, | |
93 { EXIF_FORMAT_RATIONAL, 8, "srational", "signed rational" }, | |
94 { EXIF_FORMAT_FLOAT, 4, "float", "float" }, | |
95 { EXIF_FORMAT_DOUBLE, 8, "double", "double" }, | |
96 { -1, 0, NULL } | |
97 }; | |
98 | |
99 /* tags that are special, or need special treatment */ | |
100 #define TAG_EXIFOFFSET 0x8769 | |
101 | |
102 | |
103 /* | |
104 *----------------------------------------------------------------------------- | |
105 * Data | |
106 *----------------------------------------------------------------------------- | |
107 */ | |
108 | |
109 #define EXIF_TEXT_LIST_END { -1, NULL } | |
110 | |
111 static ExifTextList ExifOrientationList[] = { | |
112 { EXIF_ORIENTATION_UNKNOWN, N_("unknown") }, | |
113 { EXIF_ORIENTATION_TOP_LEFT, N_("top left") }, | |
114 { EXIF_ORIENTATION_TOP_RIGHT, N_("top right") }, | |
115 { EXIF_ORIENTATION_BOTTOM_RIGHT,N_("bottom right") }, | |
116 { EXIF_ORIENTATION_BOTTOM_LEFT, N_("bottom left") }, | |
117 { EXIF_ORIENTATION_LEFT_TOP, N_("left top") }, | |
118 { EXIF_ORIENTATION_RIGHT_TOP, N_("right top") }, | |
119 { EXIF_ORIENTATION_RIGHT_BOTTOM,N_("right bottom") }, | |
120 { EXIF_ORIENTATION_LEFT_BOTTOM, N_("left bottom") }, | |
121 EXIF_TEXT_LIST_END | |
122 }; | |
123 | |
124 static ExifTextList ExifUnitList[] = { | |
125 { EXIF_UNIT_UNKNOWN, N_("unknown") }, | |
126 { EXIF_UNIT_NOUNIT, "" }, | |
127 { EXIF_UNIT_INCH, N_("inch") }, | |
128 { EXIF_UNIT_CENTIMETER, N_("centimeter") }, | |
129 EXIF_TEXT_LIST_END | |
130 }; | |
131 | |
132 static ExifTextList ExifYCbCrPosList[] = { | |
133 { 1, "center" }, | |
134 { 2, "datum" }, | |
135 EXIF_TEXT_LIST_END | |
136 }; | |
137 | |
138 static ExifTextList ExifMeteringModeList[] = { | |
139 { 0, N_("unknown") }, | |
140 { 1, N_("average") }, | |
141 { 2, N_("center weighted") }, | |
142 { 3, N_("spot") }, | |
143 { 4, N_("multi-spot") }, | |
144 { 5, N_("multi-segment") }, | |
145 { 6, N_("partial") }, | |
146 { 255, N_("other") }, | |
147 EXIF_TEXT_LIST_END | |
148 }; | |
149 | |
150 static ExifTextList ExifExposureProgramList[] = { | |
151 { 0, N_("not defined") }, | |
152 { 1, N_("manual") }, | |
153 { 2, N_("normal") }, | |
154 { 3, N_("aperture") }, | |
155 { 4, N_("shutter") }, | |
156 { 5, N_("creative") }, | |
157 { 6, N_("action") }, | |
158 { 7, N_("portrait") }, | |
159 { 8, N_("landscape") }, | |
160 EXIF_TEXT_LIST_END | |
161 }; | |
162 | |
163 static ExifTextList ExifLightSourceList[] = { | |
164 { 0, N_("unknown") }, | |
165 { 1, N_("daylight") }, | |
166 { 2, N_("fluorescent") }, | |
167 { 3, N_("tungsten (incandescent)") }, | |
168 { 4, N_("flash") }, | |
169 { 9, "fine weather" }, | |
170 { 10, "cloudy weather" }, | |
171 { 11, "shade" }, | |
172 { 12, "daylight fluorescent" }, | |
173 { 13, "day white fluorescent" }, | |
174 { 14, "cool white fluorescent" }, | |
175 { 15, "while fluorescent" }, | |
176 { 17, "standard light A" }, | |
177 { 18, "standard light B" }, | |
178 { 19, "standard light C" }, | |
179 { 20, "D55" }, | |
180 { 21, "D65" }, | |
181 { 22, "D75" }, | |
182 { 23, "D50" }, | |
183 { 24, "ISO studio tungsten" }, | |
184 { 255, N_("other") }, | |
185 EXIF_TEXT_LIST_END | |
186 }; | |
187 | |
188 static ExifTextList ExifFlashList[] = { | |
189 { 0, N_("no") }, | |
190 { 1, N_("yes") }, | |
191 { 5, N_("yes, not detected by strobe") }, | |
192 { 7, N_("yes, detected by strobe") }, | |
193 EXIF_TEXT_LIST_END | |
194 }; | |
195 | |
196 static ExifTextList ExifColorSpaceList[] = { | |
197 { 1, "sRGB" }, | |
198 { 65535,"uncalibrated" }, | |
199 EXIF_TEXT_LIST_END | |
200 }; | |
201 | |
202 static ExifTextList ExifSensorList[] = { | |
203 { 1, "not defined" }, | |
204 { 2, "1 chip color area" }, | |
205 { 2, "2 chip color area" }, | |
206 { 4, "3 chip color area" }, | |
207 { 5, "color sequential area" }, | |
208 { 7, "trilinear" }, | |
209 { 8, "color sequential linear" }, | |
210 EXIF_TEXT_LIST_END | |
211 }; | |
212 | |
213 static ExifTextList ExifSourceList[] = { | |
214 { 3, "digital still camera" }, | |
215 EXIF_TEXT_LIST_END | |
216 }; | |
217 | |
218 static ExifTextList ExifSceneList[] = { | |
219 { 1, "direct photo" }, | |
220 EXIF_TEXT_LIST_END | |
221 }; | |
222 | |
223 static ExifTextList ExifCustRenderList[] = { | |
224 { 0, "normal" }, | |
225 { 1, "custom" }, | |
226 EXIF_TEXT_LIST_END | |
227 }; | |
228 | |
229 static ExifTextList ExifExposureModeList[] = { | |
230 { 0, "auto" }, | |
231 { 1, "manual" }, | |
232 { 2, "auto bracket" }, | |
233 EXIF_TEXT_LIST_END | |
234 }; | |
235 | |
236 static ExifTextList ExifWhiteBalanceList[] = { | |
237 { 0, "auto" }, | |
238 { 1, "manual" }, | |
239 EXIF_TEXT_LIST_END | |
240 }; | |
241 | |
242 static ExifTextList ExifSceneCaptureList[] = { | |
243 { 0, "standard" }, | |
244 { 1, "landscape" }, | |
245 { 2, "portrait" }, | |
246 { 3, "night scene" }, | |
247 EXIF_TEXT_LIST_END | |
248 }; | |
249 | |
250 static ExifTextList ExifGainControlList[] = { | |
251 { 0, "none" }, | |
252 { 1, "low gain up" }, | |
253 { 2, "high gain up" }, | |
254 { 3, "low gain down" }, | |
255 { 4, "high gain down" }, | |
256 EXIF_TEXT_LIST_END | |
257 }; | |
258 | |
259 static ExifTextList ExifContrastList[] = { | |
260 { 0, "normal" }, | |
261 { 1, "soft" }, | |
262 { 2, "hard" }, | |
263 EXIF_TEXT_LIST_END | |
264 }; | |
265 | |
266 static ExifTextList ExifSaturationList[] = { | |
267 { 0, "normal" }, | |
268 { 1, "low" }, | |
269 { 2, "high" }, | |
270 EXIF_TEXT_LIST_END | |
271 }; | |
272 | |
273 static ExifTextList ExifSharpnessList[] = { | |
274 { 0, "normal" }, | |
275 { 1, "soft" }, | |
276 { 2, "hard" }, | |
277 EXIF_TEXT_LIST_END | |
278 }; | |
279 | |
280 static ExifTextList ExifSubjectRangeList[] = { | |
281 { 0, "unknown" }, | |
282 { 1, "macro" }, | |
283 { 2, "close" }, | |
284 { 3, "distant" }, | |
285 EXIF_TEXT_LIST_END | |
286 }; | |
287 | |
288 ExifMarker ExifKnownMarkersList[] = { | |
289 { 0x010e, EXIF_FORMAT_STRING, -1, "ImageDescription", N_("Image description"), NULL }, | |
290 { 0x010f, EXIF_FORMAT_STRING, -1, "Make", "Camera make", NULL }, | |
291 { 0x0110, EXIF_FORMAT_STRING, -1, "Model", "Camera model", NULL }, | |
292 { 0x0112, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Orientation", N_("Orientation"), ExifOrientationList }, | |
293 { 0x011a, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "XResolution", "X resolution", NULL }, | |
294 { 0x011b, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "YResolution", "Y Resolution", NULL }, | |
295 { 0x0128, EXIF_FORMAT_SHORT_UNSIGNED, 1, "ResolutionUnit", "Resolution units", ExifUnitList }, | |
296 { 0x0131, EXIF_FORMAT_STRING, -1, "Software", "Firmware", NULL }, | |
297 { 0x0132, EXIF_FORMAT_STRING, 20, "DateTime", N_("Date"), NULL }, | |
298 { 0x013e, EXIF_FORMAT_RATIONAL_UNSIGNED, 2, "WhitePoint", "White point", NULL }, | |
299 { 0x013f, EXIF_FORMAT_RATIONAL_UNSIGNED, 6, "PrimaryChromaticities","Primary chromaticities", NULL }, | |
300 { 0x0211, EXIF_FORMAT_RATIONAL_UNSIGNED, 3, "YCbCrCoefficients", "YCbCy coefficients", NULL }, | |
301 { 0x0213, EXIF_FORMAT_SHORT_UNSIGNED, 1, "YCbCrPositioning", "YCbCr positioning", ExifYCbCrPosList }, | |
302 { 0x0214, EXIF_FORMAT_RATIONAL_UNSIGNED, 6, "ReferenceBlackWhite", "Black white reference", NULL }, | |
303 { 0x8298, EXIF_FORMAT_STRING, -1, "Copyright", N_("Copyright"), NULL }, | |
304 { 0x8769, EXIF_FORMAT_LONG_UNSIGNED, 1, "ExifOffset", "SubIFD Exif offset", NULL }, | |
305 /* subIFD follows */ | |
306 { 0x829a, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "ExposureTime", "Exposure time (seconds)", NULL }, | |
307 { 0x829d, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "FNumber", "FNumber", NULL }, | |
308 { 0x8822, EXIF_FORMAT_SHORT_UNSIGNED, 1, "ExposureProgram", N_("Exposure program"), ExifExposureProgramList }, | |
309 { 0x8824, EXIF_FORMAT_STRING, -1, "SpectralSensitivity", "Spectral Sensitivity", NULL }, | |
310 { 0x8827, EXIF_FORMAT_SHORT_UNSIGNED, -1, "ISOSpeedRatings", N_("ISO sensitivity"), NULL }, | |
311 { 0x8828, EXIF_FORMAT_UNDEFINED, -1, "OECF", "Optoelectric conversion factor", NULL }, | |
312 { 0x9000, EXIF_FORMAT_UNDEFINED, 4, "ExifVersion", "Exif version", NULL }, | |
313 { 0x9003, EXIF_FORMAT_STRING, 20, "DateTimeOriginal", N_("Date original"), NULL }, | |
314 { 0x9004, EXIF_FORMAT_STRING, 20, "DateTimeDigitized", N_("Date digitized"), NULL }, | |
315 { 0x9101, EXIF_FORMAT_UNDEFINED, -1, "ComponentsConfiguration","Pixel format", NULL }, | |
316 { 0x9102, EXIF_FORMAT_RATIONAL_UNSIGNED,1, "CompressedBitsPerPixel","Compression ratio", NULL }, | |
317 { 0x9201, EXIF_FORMAT_RATIONAL, 1, "ShutterSpeedValue", N_("Shutter speed"), NULL }, | |
318 { 0x9202, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "ApertureValue", N_("Aperture"), NULL }, | |
319 { 0x9203, EXIF_FORMAT_RATIONAL, 1, "BrightnessValue", "Brightness", NULL }, | |
320 { 0x9204, EXIF_FORMAT_RATIONAL, 1, "ExposureBiasValue", N_("Exposure bias"), NULL }, | |
321 { 0x9205, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "MaxApertureValue", "Maximum aperture", NULL }, | |
322 { 0x9206, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "SubjectDistance", N_("Subject distance"), NULL }, | |
323 { 0x9207, EXIF_FORMAT_SHORT_UNSIGNED, 1, "MeteringMode", N_("Metering mode"), ExifMeteringModeList }, | |
324 { 0x9208, EXIF_FORMAT_SHORT_UNSIGNED, 1, "LightSource", N_("Light source"), ExifLightSourceList }, | |
325 { 0x9209, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Flash", N_("Flash"), ExifFlashList }, | |
326 { 0x920a, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "FocalLength", N_("Focal length"), NULL }, | |
327 { 0x9214, EXIF_FORMAT_SHORT_UNSIGNED, -1, "SubjectArea", "Subject area", NULL }, | |
328 { 0x927c, EXIF_FORMAT_UNDEFINED, -1, "MakerNote", "MakerNote", NULL }, | |
329 { 0x9286, EXIF_FORMAT_UNDEFINED, -1, "UserComment", "UserComment", NULL }, | |
330 { 0x9290, EXIF_FORMAT_STRING, -1, "SubsecTime", "Subsecond time", NULL }, | |
331 { 0x9291, EXIF_FORMAT_STRING, -1, "SubsecTimeOriginal", "Subsecond time original", NULL }, | |
332 { 0x9292, EXIF_FORMAT_STRING, -1, "SubsecTimeDigitized", "Subsecond time digitized", NULL }, | |
333 { 0xa000, EXIF_FORMAT_UNDEFINED, 4, "FlashPixVersion", "FlashPix version", NULL }, | |
334 { 0xa001, EXIF_FORMAT_SHORT_UNSIGNED, 1, "ColorSpace", "Colorspace", ExifColorSpaceList }, | |
335 /* ExifImageWidth, ExifImageHeight can also be unsigned short */ | |
336 { 0xa002, EXIF_FORMAT_LONG_UNSIGNED, 1, "ExifImageWidth", N_("Width"), NULL }, | |
337 { 0xa003, EXIF_FORMAT_LONG_UNSIGNED, 1, "ExifImageHeight", N_("Height"), NULL }, | |
338 { 0xa004, EXIF_FORMAT_STRING, -1, "RelatedSoundFile", "Audio data", NULL }, | |
339 { 0xa005, EXIF_FORMAT_LONG_UNSIGNED, 1, "ExifInteroperabilityOffset", "ExifR98 extension", NULL }, | |
340 { 0xa20b, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "FlashEnergy", "Flash strength", NULL }, | |
341 { 0xa20c, EXIF_FORMAT_SHORT_UNSIGNED, 1, "SpatialFrequencyResponse","Spatial frequency response", NULL }, | |
342 { 0xa20e, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "FocalPlaneXResolution", "X Pixel density", NULL }, | |
343 { 0xa20f, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "FocalPlaneYResolution", "Y Pixel density", NULL }, | |
344 { 0xa210, EXIF_FORMAT_SHORT_UNSIGNED, 1, "FocalPlaneResolutionUnit", "Pixel density units", ExifUnitList }, | |
345 { 0x0214, EXIF_FORMAT_SHORT_UNSIGNED, 2, "SubjectLocation", "Subject location", NULL }, | |
346 { 0xa215, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "ExposureIndex", N_("ISO sensitivity"), NULL }, | |
347 { 0xa217, EXIF_FORMAT_SHORT_UNSIGNED, -1, "SensingMethod", "Sensor type", ExifSensorList }, | |
348 { 0xa300, EXIF_FORMAT_UNDEFINED, 1, "FileSource", "Source type", ExifSourceList }, | |
349 { 0xa301, EXIF_FORMAT_UNDEFINED, 1, "SceneType", "Scene type", ExifSceneList }, | |
350 { 0xa302, EXIF_FORMAT_UNDEFINED, -1, "CFAPattern", "Color filter array pattern", NULL }, | |
351 /* tags a4xx were added for Exif 2.2 (not just these - some above, as well) */ | |
352 { 0xa401, EXIF_FORMAT_SHORT_UNSIGNED, 1, "CustomRendered", "Render process", ExifCustRenderList }, | |
353 { 0xa402, EXIF_FORMAT_SHORT_UNSIGNED, 1, "ExposureMode", "Exposure mode", ExifExposureModeList }, | |
354 { 0xa403, EXIF_FORMAT_SHORT_UNSIGNED, 1, "WhiteBalance", "White balance", ExifWhiteBalanceList }, | |
355 { 0xa404, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "DigitalZoomRatio", "Digital zoom ratio", NULL }, | |
356 { 0xa405, EXIF_FORMAT_SHORT_UNSIGNED, 1, "FocalLength35mmFilm", "Focal length (35mm)", NULL }, | |
357 { 0xa406, EXIF_FORMAT_SHORT_UNSIGNED, 1, "SceneCapturetype", "Scene capture type", ExifSceneCaptureList }, | |
358 { 0xa407, EXIF_FORMAT_SHORT_UNSIGNED, 1, "GainControl", "Gain control", ExifGainControlList }, | |
359 { 0xa408, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Contrast", "Contrast", ExifContrastList }, | |
360 { 0xa409, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Saturation", "Saturation", ExifSaturationList }, | |
361 { 0xa40a, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Sharpness", "Sharpness", ExifSharpnessList }, | |
362 { 0xa40b, EXIF_FORMAT_UNDEFINED, -1, "DeviceSettingDescription","Device setting", NULL }, | |
363 { 0xa40c, EXIF_FORMAT_SHORT_UNSIGNED, 1, "SubjectDistanceRange", "Subject range", ExifSubjectRangeList }, | |
364 { 0xa420, EXIF_FORMAT_STRING, -1, "ImageUniqueID", "Image serial number", NULL }, | |
365 /* place known, but undocumented or lesser used tags here */ | |
366 { 0x00fe, EXIF_FORMAT_LONG_UNSIGNED, 1, "NewSubfileType", NULL, NULL }, | |
367 { 0x00ff, EXIF_FORMAT_SHORT_UNSIGNED, 1, "SubfileType", NULL, NULL }, | |
368 { 0x012d, EXIF_FORMAT_SHORT_UNSIGNED, 3, "TransferFunction", NULL, NULL }, | |
369 { 0x013b, EXIF_FORMAT_STRING, -1, "Artist", "Artist", NULL }, | |
370 { 0x013d, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Predictor", NULL, NULL }, | |
371 { 0x0142, EXIF_FORMAT_SHORT_UNSIGNED, 1, "TileWidth", NULL, NULL }, | |
372 { 0x0143, EXIF_FORMAT_SHORT_UNSIGNED, 1, "TileLength", NULL, NULL }, | |
373 { 0x0144, EXIF_FORMAT_LONG_UNSIGNED, -1, "TileOffsets", NULL, NULL }, | |
374 { 0x0145, EXIF_FORMAT_SHORT_UNSIGNED, -1, "TileByteCounts", NULL, NULL }, | |
375 { 0x014a, EXIF_FORMAT_LONG_UNSIGNED, -1, "SubIFDs", NULL, NULL }, | |
376 { 0x015b, EXIF_FORMAT_UNDEFINED, -1, "JPEGTables", NULL, NULL }, | |
377 { 0x828d, EXIF_FORMAT_SHORT_UNSIGNED, 2, "CFARepeatPatternDim", NULL, NULL }, | |
378 { 0x828e, EXIF_FORMAT_BYTE_UNSIGNED, -1, "CFAPattern", NULL, NULL }, | |
379 { 0x828f, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "BatteryLevel", NULL, NULL }, | |
380 { 0x83bb, EXIF_FORMAT_LONG_UNSIGNED, -1, "IPTC/NAA", NULL, NULL }, | |
381 { 0x8773, EXIF_FORMAT_UNDEFINED, -1, "InterColorProfile", NULL, NULL }, | |
382 { 0x8825, EXIF_FORMAT_LONG_UNSIGNED, 1, "GPSInfo", "SubIFD GPS offset", NULL }, | |
383 { 0x8829, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Interlace", NULL, NULL }, | |
384 { 0x882a, EXIF_FORMAT_SHORT, 1, "TimeZoneOffset", NULL, NULL }, | |
385 { 0x882b, EXIF_FORMAT_SHORT_UNSIGNED, 1, "SelfTimerMode", NULL, NULL }, | |
386 { 0x920b, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "FlashEnergy", NULL, NULL }, | |
387 { 0x920c, EXIF_FORMAT_UNDEFINED, -1, "SpatialFrequencyResponse", NULL, NULL }, | |
388 { 0x920d, EXIF_FORMAT_UNDEFINED, -1, "Noise", NULL, NULL }, | |
389 { 0x9211, EXIF_FORMAT_LONG_UNSIGNED, 1, "ImageNumber", NULL, NULL }, | |
390 { 0x9212, EXIF_FORMAT_STRING, 1, "SecurityClassification", NULL, NULL }, | |
391 { 0x9213, EXIF_FORMAT_STRING, -1, "ImageHistory", NULL, NULL }, | |
392 { 0x9215, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "ExposureIndex", NULL, NULL }, | |
393 { 0x9216, EXIF_FORMAT_BYTE_UNSIGNED, 4, "TIFF/EPStandardID", NULL, NULL }, | |
394 | |
395 /* end is marked by 0 tag */ | |
396 { 0x0000, EXIF_FORMAT_UNKNOWN, 0, NULL, NULL, NULL } | |
397 }; | |
398 | |
399 ExifMarker ExifUnknownMarkersList[] = { | |
400 { 0x0000, EXIF_FORMAT_UNKNOWN, 0, "unknown", NULL, NULL }, | |
401 { 0x0000, EXIF_FORMAT_BYTE_UNSIGNED, -1, "unknown", NULL, NULL }, | |
402 { 0x0000, EXIF_FORMAT_STRING, -1, "unknown", NULL, NULL }, | |
403 { 0x0000, EXIF_FORMAT_SHORT_UNSIGNED, -1, "unknown", NULL, NULL }, | |
404 { 0x0000, EXIF_FORMAT_LONG_UNSIGNED, -1, "unknown", NULL, NULL }, | |
405 { 0x0000, EXIF_FORMAT_RATIONAL_UNSIGNED, -1, "unknown", NULL, NULL }, | |
406 { 0x0000, EXIF_FORMAT_BYTE, -1, "unknown", NULL, NULL }, | |
407 { 0x0000, EXIF_FORMAT_UNDEFINED, -1, "unknown", NULL, NULL }, | |
408 { 0x0000, EXIF_FORMAT_SHORT, -1, "unknown", NULL, NULL }, | |
409 { 0x0000, EXIF_FORMAT_LONG, -1, "unknown", NULL, NULL }, | |
410 { 0x0000, EXIF_FORMAT_RATIONAL, -1, "unknown", NULL, NULL }, | |
411 { 0x0000, EXIF_FORMAT_FLOAT, -1, "unknown", NULL, NULL }, | |
412 { 0x0000, EXIF_FORMAT_DOUBLE, -1, "unknown", NULL, NULL }, | |
413 }; | |
414 | |
415 /* human readable key list */ | |
416 | |
417 ExifFormattedText ExifFormattedList[] = { | |
418 { "fCamera", N_("Camera") }, | |
419 { "fDateTime", N_("Date") }, | |
420 { "fShutterSpeed", N_("Shutter speed") }, | |
421 { "fAperture", N_("Aperture") }, | |
422 { "fExposureBias", N_("Exposure bias") }, | |
423 { "fISOSpeedRating", N_("ISO sensitivity") }, | |
424 { "fFocalLength", N_("Focal length") }, | |
425 { "fSubjectDistance", N_("Subject distance") }, | |
426 { "fFlash", N_("Flash") }, | |
427 { "fResolution", N_("Resolution") }, | |
428 { NULL, NULL } | |
429 }; | |
430 | |
431 | |
432 /* | |
433 *----------------------------------------------------------------------------- | |
434 * misc | |
435 *----------------------------------------------------------------------------- | |
436 */ | |
437 | |
438 #define BYTE_ORDER_INTEL 1 | |
439 #define BYTE_ORDER_MOTOROLA 2 | |
440 | |
441 #define MARKER_UNKNOWN 0x00 | |
442 #define MARKER_SOI 0xD8 | |
443 #define MARKER_APP1 0xE1 | |
444 | |
445 typedef struct { | |
446 char byte_order[2]; | |
447 uint16_t magic; | |
448 uint32_t IFD_offset; | |
449 } TIFFHeader; | |
450 | |
451 typedef struct { | |
452 uint16_t tag; | |
453 uint16_t format; | |
454 uint32_t nb; | |
455 uint32_t data; | |
456 } IFDEntry; | |
457 | |
458 | |
459 static ExifMarker *exif_marker_from_tag(uint16_t tag); | |
460 static int parse_IFD_table(ExifData *exif, unsigned char *tiff, int offset, | |
461 int size, int byte_order); | |
462 | |
463 /* | |
464 *----------------------------------------------------------------------------- | |
465 * ExifItem | |
466 *----------------------------------------------------------------------------- | |
467 */ | |
468 | |
469 static ExifItem *exif_item_new(ExifFormatType format, unsigned int tag, unsigned int elements, ExifMarker *marker) | |
470 { | |
471 ExifItem *item; | |
472 | |
473 item = g_new0(ExifItem, 1); | |
474 item->format = format; | |
475 item->tag = tag; | |
476 item->marker = marker; | |
477 item->elements = elements; | |
478 item->data = NULL; | |
479 item->data_len = 0; | |
480 | |
481 switch (format) | |
482 { | |
483 case EXIF_FORMAT_UNKNOWN: | |
484 /* unknown, data is NULL */ | |
485 return item; | |
486 break; | |
487 case EXIF_FORMAT_BYTE_UNSIGNED: | |
488 item->data_len = sizeof(char) * elements; | |
489 break; | |
490 case EXIF_FORMAT_STRING: | |
491 item->data_len = sizeof(char) * elements; | |
492 break; | |
493 case EXIF_FORMAT_SHORT_UNSIGNED: | |
494 item->data_len = sizeof(unsigned short int) * elements; | |
495 break; | |
496 case EXIF_FORMAT_LONG_UNSIGNED: | |
497 item->data_len = sizeof(unsigned long int) * elements; | |
498 break; | |
499 case EXIF_FORMAT_RATIONAL_UNSIGNED: | |
500 item->data_len = sizeof(ExifRational) * elements; | |
501 break; | |
502 case EXIF_FORMAT_BYTE: | |
503 item->data_len = sizeof(char) * elements; | |
504 break; | |
505 case EXIF_FORMAT_UNDEFINED: | |
506 item->data_len = sizeof(char) * elements; | |
507 break; | |
508 case EXIF_FORMAT_SHORT: | |
509 item->data_len = sizeof(short int) * elements; | |
510 break; | |
511 case EXIF_FORMAT_LONG: | |
512 item->data_len = sizeof(long int) * elements; | |
513 break; | |
514 case EXIF_FORMAT_RATIONAL: | |
515 item->data_len = sizeof(ExifRational) * elements; | |
516 break; | |
517 case EXIF_FORMAT_FLOAT: | |
518 item->data_len = sizeof(float) * elements; | |
519 break; | |
520 case EXIF_FORMAT_DOUBLE: | |
521 item->data_len = sizeof(double) * elements; | |
522 break; | |
523 } | |
524 | |
525 item->data = g_malloc0(item->data_len); | |
526 | |
527 return item; | |
528 } | |
529 | |
530 static void exif_item_free(ExifItem *item) | |
531 { | |
532 if (!item) return; | |
533 | |
534 g_free(item->data); | |
535 g_free(item); | |
536 } | |
537 | |
538 const char *exif_item_get_tag_name(ExifItem *item) | |
539 { | |
540 if (!item || !item->marker) return NULL; | |
541 return item->marker->key; | |
542 } | |
543 | |
544 const char *exif_item_get_description(ExifItem *item) | |
545 { | |
546 if (!item || !item->marker) return NULL; | |
547 return _(item->marker->description); | |
548 } | |
549 | |
550 const char *exif_item_get_format_name(ExifItem *item, gint brief) | |
551 { | |
552 if (!item || !item->marker) return NULL; | |
553 return (brief) ? ExifFormatList[item->format].short_name : ExifFormatList[item->format].description; | |
554 } | |
555 | |
556 | |
557 #define UNDEFINED_TEXT_BYTE_COUNT 16 | |
558 | |
559 static GString *string_append_raw_bytes(GString *string, gpointer data, gint ne) | |
560 { | |
561 gint i; | |
562 | |
563 for (i = 0 ; i < ne && i < UNDEFINED_TEXT_BYTE_COUNT; i++) | |
564 { | |
565 unsigned char c = ((char *)data)[i]; | |
566 if (c < 32 || c > 127) c = '.'; | |
567 g_string_append_printf(string, "%c", c); | |
568 } | |
569 string = g_string_append(string, " : "); | |
570 for (i = 0 ; i < ne && i < UNDEFINED_TEXT_BYTE_COUNT; i++) | |
571 { | |
572 const gchar *spacer; | |
573 if (i > 0) | |
574 { | |
575 if (i%8 == 0) | |
576 { | |
577 spacer = " - "; | |
578 } | |
579 else | |
580 { | |
581 spacer = " "; | |
582 } | |
583 } | |
584 else | |
585 { | |
586 spacer = ""; | |
587 } | |
588 g_string_append_printf(string, "%s%02x", spacer, ((char *)data)[i]); | |
589 } | |
590 if (i >= UNDEFINED_TEXT_BYTE_COUNT) g_string_append_printf(string, " (%d bytes)", ne); | |
591 | |
592 return string; | |
593 } | |
594 | |
595 static gchar *text_list_find_value(ExifTextList *list, gint value) | |
596 { | |
597 gchar *result = NULL; | |
598 gint i; | |
599 | |
600 i = 0; | |
601 while (!result && list[i].value >= 0) | |
602 { | |
603 if (value == list[i].value) result = g_strdup(_(list[i].description)); | |
604 i++; | |
605 } | |
606 if (!result) result = g_strdup_printf("%d (%s)", value, _("unknown")); | |
607 | |
608 return result; | |
609 } | |
610 | |
611 /* | |
612 *------------------------------------------------------------------- | |
613 * byte size utils | |
614 *------------------------------------------------------------------- | |
615 */ | |
616 | |
617 static uint16_t get_int16(unsigned char *f, int bo) | |
618 { | |
619 if (bo == BYTE_ORDER_INTEL) | |
620 return *f + (*(f+1)<<8); | |
621 else | |
622 return ((*f)<<8) + *(f+1); | |
623 } | |
624 | |
625 #if 0 | |
626 /* not used ? */ | |
627 static uint32_t get_int32(unsigned char *f, int bo) | |
628 { | |
629 if (bo == BYTE_ORDER_INTEL) | |
630 return get_int16(f, BYTE_ORDER_INTEL) + (get_int16(f+2, BYTE_ORDER_INTEL)<<16); | |
631 else | |
632 return (get_int16(f, BYTE_ORDER_MOTOROLA)<<16) + get_int16(f+2, BYTE_ORDER_MOTOROLA); | |
633 } | |
634 #endif | |
635 | |
636 static uint16_t swab_int16(uint16_t n, int bo) | |
637 { | |
638 #if BYTE_ORDER == LITTLE_ENDIAN | |
639 if (bo == BYTE_ORDER_MOTOROLA) | |
640 #else | |
641 if (bo == BYTE_ORDER_INTEL) | |
642 #endif | |
643 return n>>8 | n<<8 ; | |
644 else | |
645 return n; | |
646 } | |
647 | |
648 static uint32_t swab_int32(uint32_t n, int bo) | |
649 { | |
650 #if BYTE_ORDER == LITTLE_ENDIAN | |
651 if (bo == BYTE_ORDER_MOTOROLA) | |
652 #else | |
653 if (bo == BYTE_ORDER_INTEL) | |
654 #endif | |
655 return n<<24 | n>>24 | (n & 0xFF0000)>>8 | (n & 0xFF00)<<8; | |
656 else | |
657 return n; | |
658 } | |
659 | |
660 /* | |
661 *------------------------------------------------------------------- | |
662 * marker utils | |
663 *------------------------------------------------------------------- | |
664 */ | |
665 | |
666 static int get_marker_size(unsigned char *f) | |
667 { | |
668 /* Size is always in Motorola byte order */ | |
669 return get_int16(f+2, BYTE_ORDER_MOTOROLA); | |
670 } | |
671 | |
672 static int goto_next_marker(unsigned char **f, int *size, int *marker) | |
673 { | |
674 int marker_size = 2; | |
675 | |
676 *marker = MARKER_UNKNOWN; | |
677 | |
678 /* It is safe to access the marker and its size since we have checked | |
679 * the SOI and this function guaranties the whole next marker is | |
680 * available | |
681 */ | |
682 if (*(*f+1) != MARKER_SOI) | |
683 { | |
684 marker_size += get_marker_size(*f); | |
685 } | |
686 | |
687 *size -= marker_size; | |
688 | |
689 /* size should be at least 4, so we can read the marker and its size | |
690 * and check data are actually available | |
691 */ | |
692 if (*size < 4) return -1; | |
693 | |
694 /* Jump to the next marker and be sure it begins with 0xFF | |
695 */ | |
696 *f += marker_size; | |
697 if (**f != 0xFF) return -1; | |
698 | |
699 if (get_marker_size(*f)+2 > *size) return -1; | |
700 | |
701 *marker = *(*f+1); | |
702 | |
703 return 0; | |
704 } | |
705 | |
706 /* | |
707 *------------------------------------------------------------------- | |
708 * IFD utils | |
709 *------------------------------------------------------------------- | |
710 */ | |
711 | |
712 static ExifMarker *exif_marker_from_tag(uint16_t tag) | |
713 { | |
714 static int len = sizeof(ExifKnownMarkersList)/sizeof(ExifMarker) - 1; | |
715 int i = 0; | |
716 | |
717 while (i < len && ExifKnownMarkersList[i].tag != tag) | |
718 { | |
719 i++; | |
720 } | |
721 | |
722 return (i >= len ? NULL : &ExifKnownMarkersList[i]); | |
723 } | |
724 | |
725 static void rational_from_data(ExifRational *r, void *src, int byte_order) | |
726 { | |
727 r->num = swab_int32(*(uint32_t*)src, byte_order); | |
728 r->den = swab_int32(*(uint32_t*)(src + sizeof(uint32_t)), byte_order); | |
729 } | |
730 | |
731 static void exif_item_copy_data(ExifItem *item, void *src, int len, ExifFormatType src_format, int byte_order) | |
732 { | |
733 int bs; | |
734 int ne; | |
735 gpointer dest; | |
736 int i; | |
737 | |
738 bs = ExifFormatList[item->format].size; | |
739 ne = item->elements; | |
740 dest = item->data; | |
741 | |
742 if (!dest || len > item->data_len) | |
743 { | |
744 printf("exif tag %s data size mismatch\n", exif_item_get_tag_name(item)); | |
745 return; | |
746 } | |
747 | |
748 switch (item->format) | |
749 { | |
750 case EXIF_FORMAT_UNKNOWN: | |
751 break; | |
752 case EXIF_FORMAT_BYTE_UNSIGNED: | |
753 case EXIF_FORMAT_BYTE: | |
754 case EXIF_FORMAT_UNDEFINED: | |
755 memcpy(dest, src, len); | |
756 break; | |
757 case EXIF_FORMAT_STRING: | |
758 memcpy(dest, src, len); | |
759 /* string is NULL terminated, make sure this is true */ | |
760 if (((char *)dest)[len - 1] != '\0') ((char *)dest)[len - 1] = '\0'; | |
761 break; | |
762 case EXIF_FORMAT_SHORT_UNSIGNED: | |
763 case EXIF_FORMAT_SHORT: | |
764 for (i = 0; i < ne; i++) | |
765 { | |
766 ((short *)dest)[i] = swab_int16(*(uint16_t*)(src + i * bs), byte_order); | |
767 } | |
768 break; | |
769 case EXIF_FORMAT_LONG_UNSIGNED: | |
770 case EXIF_FORMAT_LONG: | |
771 if (src_format == EXIF_FORMAT_SHORT_UNSIGNED || | |
772 src_format == EXIF_FORMAT_SHORT) | |
773 { | |
774 /* a short fits into a long, so allow it */ | |
775 int ss; | |
776 | |
777 ss = ExifFormatList[src_format].size; | |
778 for (i = 0; i < ne; i++) | |
779 { | |
780 ((long *)dest)[i] = (long)swab_int16(*(uint16_t*)(src + i * ss), byte_order); | |
781 } | |
782 } | |
783 else | |
784 { | |
785 for (i = 0; i < ne; i++) | |
786 { | |
787 ((long *)dest)[i] = swab_int32(*(uint32_t*)(src + i * bs), byte_order); | |
788 } | |
789 } | |
790 break; | |
791 case EXIF_FORMAT_RATIONAL_UNSIGNED: | |
792 case EXIF_FORMAT_RATIONAL: | |
793 for (i = 0; i < ne; i++) | |
794 { | |
795 rational_from_data(&((ExifRational *)dest)[i], src + i * bs, byte_order); | |
796 } | |
797 break; | |
798 case EXIF_FORMAT_FLOAT: | |
799 for (i = 0; i < ne; i++) | |
800 { | |
801 ((float *)dest)[i] = swab_int32(*(uint32_t*)(src + i * bs), byte_order); | |
802 } | |
803 break; | |
804 case EXIF_FORMAT_DOUBLE: | |
805 for (i = 0; i < ne; i++) | |
806 { | |
807 ExifRational r; | |
808 | |
809 rational_from_data(&r, src + i * bs, byte_order); | |
810 if (r.den) ((double *)dest)[i] = (double)r.num / r.den; | |
811 } | |
812 break; | |
813 } | |
814 } | |
815 | |
816 static int parse_IFD_entry(ExifData *exif, unsigned char *tiff, int offset, | |
817 int size, int byte_order) | |
818 { | |
819 IFDEntry *ent = (IFDEntry*)(tiff+offset); | |
820 uint32_t swabed_data; | |
821 void *data; | |
822 int data_len; | |
823 ExifMarker *marker; | |
824 ExifItem *item; | |
825 | |
826 ent->tag = swab_int16(ent->tag, byte_order); | |
827 ent->format = swab_int16(ent->format, byte_order); | |
828 ent->nb = swab_int32(ent->nb, byte_order); | |
829 swabed_data = swab_int32(ent->data, byte_order); | |
830 | |
831 /* Check tag type. If it does not match, either the format is wrong, | |
832 * either it is a unknown tag; so it is not really an error. | |
833 */ | |
834 marker = exif_marker_from_tag(ent->tag); | |
835 if (!marker) | |
836 { | |
837 if (ent->format > EXIF_FORMAT_DOUBLE) | |
838 { | |
839 printf("warning: exif tag 0x%4x has invalid format %d\n", ent->tag, ent->format); | |
840 return 0; | |
841 } | |
842 /* allow non recognized tags to be displayed */ | |
843 marker = &ExifUnknownMarkersList[ent->format]; | |
844 } | |
845 if (marker->format != ent->format) | |
846 { | |
847 /* Some cameras got mixed up signed/unsigned_rational | |
848 * eg KODAK DC4800 on object_distance tag | |
849 * | |
850 * FIXME: what exactly is this test trying to do? | |
851 * ok, so this test is to allow the case of swapped signed/unsigned mismatch to leak through? | |
852 */ | |
853 if ( !(marker->format == EXIF_FORMAT_RATIONAL_UNSIGNED && ent->format == EXIF_FORMAT_RATIONAL) && | |
854 !(marker->format == EXIF_FORMAT_RATIONAL && ent->format == EXIF_FORMAT_RATIONAL_UNSIGNED) && | |
855 /* short fits into a long so allow this mismatch | |
856 * as well (some tags allowed to be unsigned short _or_ unsigned long) | |
857 */ | |
858 !(marker->format == EXIF_FORMAT_LONG_UNSIGNED && ent->format == EXIF_FORMAT_SHORT_UNSIGNED) ) | |
859 { | |
860 if (ent->format <= EXIF_FORMAT_DOUBLE) | |
861 { | |
862 printf("warning: exif tag %s format mismatch, found %s exif spec requests %s\n", | |
863 marker->key, ExifFormatList[ent->format].short_name, ExifFormatList[marker->format].short_name); | |
864 } | |
865 else | |
866 { | |
867 printf("warning: exif tag %s format mismatch, found unknown id %d exif spec requests %d (%s)\n", | |
868 marker->key, ent->format, marker->format, ExifFormatList[marker->format].short_name); | |
869 } | |
870 return 0; | |
871 } | |
872 } | |
873 | |
874 /* Where is the data, is it available? | |
875 */ | |
876 if (marker->components > 0 && marker->components != ent->nb) | |
877 { | |
878 printf("warning: exif tag %s has %d elements, exif spec requests %d\n", marker->key, ent->nb, marker->components); | |
879 } | |
880 data_len = ExifFormatList[marker->format].size * ent->nb; | |
881 if (data_len > sizeof(ent->data)) | |
882 { | |
883 if (size < swabed_data+data_len) | |
884 { | |
885 printf("warning: exif tag %s will overrun IFD segment, ignored.\n", marker->key); | |
886 return -1; | |
887 } | |
888 data = (void*)tiff + swabed_data; | |
889 } | |
890 else | |
891 { | |
892 data = (void*)(&(ent->data)); | |
893 } | |
894 | |
895 item = exif_item_new(marker->format, ent->tag, ent->nb, marker); | |
896 exif_item_copy_data(item, data, data_len, ent->format, byte_order); | |
897 exif->items = g_list_prepend(exif->items, item); | |
898 | |
899 if (item->tag == TAG_EXIFOFFSET) | |
900 { | |
901 parse_IFD_table(exif, tiff, swabed_data, size, byte_order); | |
902 } | |
903 | |
904 return 0; | |
905 } | |
906 | |
907 static int parse_IFD_table(ExifData *exif, unsigned char *tiff, int offset, | |
908 int size, int byte_order) | |
909 { | |
910 int i, nb_entries; | |
911 | |
912 /* We should be able to read number of entries in IFD0) */ | |
913 if (size < offset+2) return -1; | |
914 | |
915 nb_entries = get_int16(tiff+offset, byte_order); | |
916 | |
917 /* Entries and next IFD offset must be readable */ | |
918 if (size < offset+nb_entries*12+4) return -1; | |
919 | |
920 for (i=0; i<nb_entries; ++i) | |
921 { | |
922 parse_IFD_entry(exif, tiff, offset+2+i*sizeof(IFDEntry), size, byte_order); | |
923 } | |
924 | |
925 return 0; | |
926 } | |
927 | |
928 /* | |
929 *------------------------------------------------------------------- | |
930 * file formats | |
931 *------------------------------------------------------------------- | |
932 */ | |
933 | |
934 static int parse_TIFF(ExifData *exif, unsigned char *tiff, int size) | |
935 { | |
936 int byte_order, offset=0; | |
937 | |
938 if (size < sizeof(TIFFHeader)) | |
939 { | |
940 return -1; | |
941 } | |
942 | |
943 if (strncmp(((TIFFHeader*)tiff)->byte_order, "II", 2) == 0) | |
944 { | |
945 byte_order = BYTE_ORDER_INTEL; | |
946 } | |
947 else if (strncmp(((TIFFHeader*)tiff)->byte_order, "MM", 2) == 0) | |
948 { | |
949 byte_order = BYTE_ORDER_MOTOROLA; | |
950 } | |
951 else | |
952 { | |
953 return -1; | |
954 } | |
955 | |
956 if (swab_int16(((TIFFHeader*)tiff)->magic, byte_order) != 0x002A) | |
957 { | |
958 return -1; | |
959 } | |
960 | |
961 offset = swab_int32(((TIFFHeader*)tiff)->IFD_offset, byte_order); | |
962 | |
963 return parse_IFD_table(exif, tiff, offset, size, byte_order); | |
964 } | |
965 | |
966 static int parse_JPEG(ExifData *exif, unsigned char *f, int size) | |
967 { | |
968 int marker, marker_size; | |
969 | |
970 if (size<2 || *f!=0xFF || *(f+1)!=MARKER_SOI) | |
971 { | |
972 return -2; | |
973 } | |
974 | |
975 do { | |
976 if (goto_next_marker(&f, &size, &marker) == -1) | |
977 { | |
978 break; | |
979 } | |
980 } while (marker != MARKER_APP1); | |
981 | |
982 if (marker != MARKER_APP1) | |
983 { | |
984 return -2; | |
985 } | |
986 | |
987 marker_size = get_marker_size(f)-2; | |
988 | |
989 if (marker_size<6 || strncmp((char*)f+4, "Exif\0\0", 6)!=0) | |
990 { | |
991 return -2; | |
992 } | |
993 | |
994 return parse_TIFF(exif, f+10, marker_size-6); | |
995 } | |
996 | |
997 static gint map_file(const gchar *path, void **mapping, int *size) | |
998 { | |
999 int fd; | |
1000 struct stat fs; | |
1001 | |
1002 if ((fd = open(path, O_RDONLY)) == -1) | |
1003 { | |
1004 perror(path); | |
1005 return -1; | |
1006 } | |
1007 | |
1008 if (fstat(fd, &fs) == -1) | |
1009 { | |
1010 perror(path); | |
1011 close(fd); | |
1012 return -1; | |
1013 } | |
1014 | |
1015 *size = fs.st_size; | |
1016 | |
1017 if ((*mapping = mmap(0, *size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0)) == MAP_FAILED) | |
1018 { | |
1019 perror(path); | |
1020 close(fd); | |
1021 return -1; | |
1022 } | |
1023 | |
1024 close(fd); | |
1025 return 0; | |
1026 } | |
1027 | |
1028 static gint unmap_file(void *mapping, int size) | |
1029 { | |
1030 if (munmap(mapping, size) == -1) | |
1031 { | |
1032 perror("munmap"); | |
1033 return -1; | |
1034 } | |
1035 | |
1036 return 0; | |
1037 } | |
1038 | |
1039 void exif_free(ExifData *exif) | |
1040 { | |
1041 GList *work; | |
1042 | |
1043 if (!exif) return; | |
1044 | |
1045 work = exif->items; | |
1046 while (work) | |
1047 { | |
1048 ExifItem *item = work->data; | |
1049 work = work->next; | |
1050 exif_item_free(item); | |
1051 } | |
1052 | |
1053 g_list_free(exif->items); | |
1054 g_free(exif); | |
1055 } | |
1056 | |
1057 ExifData *exif_read(const gchar *path) | |
1058 { | |
1059 ExifData *exif; | |
1060 void *f; | |
1061 int size, res; | |
1062 gchar *pathl; | |
1063 | |
1064 if (!path) return NULL; | |
1065 | |
1066 pathl = path_from_utf8(path); | |
1067 if (map_file(pathl, &f, &size) == -1) | |
1068 { | |
1069 g_free(pathl); | |
1070 return NULL; | |
1071 } | |
1072 g_free(pathl); | |
1073 | |
1074 exif = g_new0(ExifData, 1); | |
1075 exif->items = NULL; | |
1076 | |
1077 if ((res = parse_JPEG(exif, (unsigned char *)f, size)) == -2) | |
1078 { | |
1079 res = parse_TIFF(exif, (unsigned char *)f, size); | |
1080 } | |
1081 | |
1082 if (res != 0) | |
1083 { | |
1084 exif_free(exif); | |
1085 exif = NULL; | |
1086 } | |
1087 | |
1088 unmap_file(f, size); | |
1089 | |
1090 if (exif) exif->items = g_list_reverse(exif->items); | |
1091 | |
1092 #if 0 | |
1093 exif_write_data_list(exif, stdout, TRUE); | |
1094 exif_write_data_list(exif, stdout, FALSE); | |
1095 #endif | |
1096 | |
1097 return exif; | |
1098 } | |
1099 | |
1100 ExifItem *exif_get_item(ExifData *exif, const gchar *key) | |
1101 { | |
1102 GList *work; | |
1103 | |
1104 if (!key) return NULL; | |
1105 | |
1106 work = exif->items; | |
1107 while (work) | |
1108 { | |
1109 ExifItem *item; | |
1110 | |
1111 item = work->data; | |
1112 work = work->next; | |
1113 if (item->marker->key && strcmp(key, item->marker->key) == 0) return item; | |
1114 } | |
1115 return NULL; | |
1116 } | |
1117 | |
1118 gchar *exif_item_get_data_as_text(ExifItem *item) | |
1119 { | |
1120 ExifMarker *marker; | |
1121 gpointer data; | |
1122 GString *string; | |
1123 gchar *text; | |
1124 gint ne; | |
1125 gint i; | |
1126 | |
1127 if (!item) return NULL; | |
1128 | |
1129 marker = item->marker; | |
1130 if (!marker) return NULL; | |
1131 | |
1132 data = item->data; | |
1133 ne = item->elements; | |
1134 string = g_string_new(""); | |
1135 switch (item->format) | |
1136 { | |
1137 case EXIF_FORMAT_UNKNOWN: | |
1138 break; | |
1139 case EXIF_FORMAT_BYTE_UNSIGNED: | |
1140 case EXIF_FORMAT_BYTE: | |
1141 case EXIF_FORMAT_UNDEFINED: | |
1142 if (ne == 1 && marker->list) | |
1143 { | |
1144 gchar *result; | |
1145 unsigned char val; | |
1146 | |
1147 if (item->format == EXIF_FORMAT_BYTE_UNSIGNED || | |
1148 item->format == EXIF_FORMAT_UNDEFINED) | |
1149 { | |
1150 val = ((unsigned char *)data)[0]; | |
1151 } | |
1152 else | |
1153 { | |
1154 val = (unsigned char)(((signed char *)data)[0]); | |
1155 } | |
1156 | |
1157 result = text_list_find_value(marker->list, (unsigned short)val); | |
1158 string = g_string_append(string, result); | |
1159 g_free(result); | |
1160 } | |
1161 else | |
1162 { | |
1163 string = string_append_raw_bytes(string, data, ne); | |
1164 } | |
1165 break; | |
1166 case EXIF_FORMAT_STRING: | |
1167 string = g_string_append(string, (gchar *)(item->data)); | |
1168 break; | |
1169 case EXIF_FORMAT_SHORT_UNSIGNED: | |
1170 if (ne == 1 && marker->list) | |
1171 { | |
1172 gchar *result; | |
1173 | |
1174 result = text_list_find_value(marker->list, ((unsigned short *)data)[0]); | |
1175 string = g_string_append(string, result); | |
1176 g_free(result); | |
1177 } | |
1178 else for (i = 0; i < ne; i++) | |
1179 { | |
1180 g_string_append_printf(string, "%s%hd", (i > 0) ? ", " : "", | |
1181 ((unsigned short *)data)[i]); | |
1182 } | |
1183 break; | |
1184 case EXIF_FORMAT_LONG_UNSIGNED: | |
1185 for (i = 0; i < ne; i++) | |
1186 { | |
1187 g_string_append_printf(string, "%s%ld", (i > 0) ? ", " : "", | |
1188 ((unsigned long *)data)[i]); | |
1189 } | |
1190 break; | |
1191 case EXIF_FORMAT_RATIONAL_UNSIGNED: | |
1192 for (i = 0; i < ne; i++) | |
1193 { | |
1194 ExifRational *r; | |
1195 | |
1196 r = &((ExifRational *)data)[i]; | |
1197 g_string_append_printf(string, "%s%ld/%ld", (i > 0) ? ", " : "", | |
1198 (unsigned long)r->num, (unsigned long)r->den); | |
1199 } | |
1200 break; | |
1201 case EXIF_FORMAT_SHORT: | |
1202 for (i = 0; i < ne; i++) | |
1203 { | |
1204 g_string_append_printf(string, "%s%hd", (i > 0) ? ", " : "", | |
1205 ((short *)data)[i]); | |
1206 } | |
1207 break; | |
1208 case EXIF_FORMAT_LONG: | |
1209 for (i = 0; i < ne; i++) | |
1210 { | |
1211 g_string_append_printf(string, "%s%ld", (i > 0) ? ", " : "", | |
1212 ((long *)data)[i]); | |
1213 } | |
1214 break; | |
1215 case EXIF_FORMAT_RATIONAL: | |
1216 for (i = 0; i < ne; i++) | |
1217 { | |
1218 ExifRational *r; | |
1219 | |
1220 r = &((ExifRational *)data)[i]; | |
1221 g_string_append_printf(string, "%s%ld/%ld", (i > 0) ? ", " : "", | |
1222 (long)r->num, (long)r->den); | |
1223 } | |
1224 break; | |
1225 case EXIF_FORMAT_FLOAT: | |
1226 for (i = 0; i < ne; i++) | |
1227 { | |
1228 g_string_append_printf(string, "%s%f", (i > 0) ? ", " : "", | |
1229 ((float *)data)[i]); | |
1230 } | |
1231 break; | |
1232 case EXIF_FORMAT_DOUBLE: | |
1233 for (i = 0; i < ne; i++) | |
1234 { | |
1235 g_string_append_printf(string, "%s%f", (i > 0) ? ", " : "", | |
1236 ((double *)data)[i]); | |
1237 } | |
1238 break; | |
1239 } | |
1240 | |
1241 text = g_strdup(string->str); | |
1242 g_string_free(string, TRUE); | |
1243 | |
1244 return text; | |
1245 } | |
1246 | |
1247 gint exif_item_get_integer(ExifItem *item, gint *value) | |
1248 { | |
1249 if (!item) return FALSE; | |
1250 | |
1251 switch (item->format) | |
1252 { | |
1253 case EXIF_FORMAT_SHORT: | |
1254 *value = (gint)(((short *)(item->data))[0]); | |
1255 return TRUE; | |
1256 break; | |
1257 case EXIF_FORMAT_SHORT_UNSIGNED: | |
1258 *value = (gint)(((unsigned short *)(item->data))[0]); | |
1259 return TRUE; | |
1260 break; | |
1261 case EXIF_FORMAT_LONG: | |
1262 *value = (gint)(((long *)(item->data))[0]); | |
1263 return TRUE; | |
1264 break; | |
1265 case EXIF_FORMAT_LONG_UNSIGNED: | |
1266 /* FIXME: overflow possible */ | |
1267 *value = (gint)(((unsigned long *)(item->data))[0]); | |
1268 return TRUE; | |
1269 default: | |
1270 /* all other type return FALSE */ | |
1271 break; | |
1272 } | |
1273 return FALSE; | |
1274 } | |
1275 | |
1276 gint exif_get_integer(ExifData *exif, const gchar *key, gint *value) | |
1277 { | |
1278 ExifItem *item; | |
1279 | |
1280 item = exif_get_item(exif, key); | |
1281 return exif_item_get_integer(item, value); | |
1282 } | |
1283 | |
1284 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign) | |
1285 { | |
1286 if (!item) return NULL; | |
1287 | |
1288 if (item->format == EXIF_FORMAT_RATIONAL || | |
1289 item->format == EXIF_FORMAT_RATIONAL_UNSIGNED) | |
1290 { | |
1291 if (sign) *sign = (item->format == EXIF_FORMAT_RATIONAL); | |
1292 return &((ExifRational *)(item->data))[0]; | |
1293 } | |
1294 | |
1295 return NULL; | |
1296 } | |
1297 | |
1298 ExifRational *exif_get_rational(ExifData *exif, const gchar *key, gint *sign) | |
1299 { | |
1300 ExifItem *item; | |
1301 | |
1302 item = exif_get_item(exif, key); | |
1303 return exif_item_get_rational(item, sign); | |
1304 } | |
1305 | |
1306 double exif_rational_to_double(ExifRational *r, gint sign) | |
1307 { | |
1308 if (!r || r->den == 0.0) return 0.0; | |
1309 | |
1310 if (sign) return (double)((int)r->num) / (double)((int)r->den); | |
1311 return (double)r->num / r->den; | |
1312 } | |
1313 | |
1314 static double exif_get_rational_as_double(ExifData *exif, const gchar *key) | |
1315 { | |
1316 ExifRational *r; | |
1317 gint sign; | |
1318 | |
1319 r = exif_get_rational(exif, key, &sign); | |
1320 return exif_rational_to_double(r, sign); | |
1321 } | |
1322 | |
1323 static GString *append_comma_text(GString *string, const gchar *text) | |
1324 { | |
1325 string = g_string_append(string, ", "); | |
1326 string = g_string_append(string, text); | |
1327 | |
1328 return string; | |
1329 } | |
1330 | |
1331 static gchar *exif_get_formatted_by_key(ExifData *exif, const gchar *key, gint *key_valid) | |
1332 { | |
1333 /* must begin with f, else not formatted */ | |
1334 if (key[0] != 'f') | |
1335 { | |
1336 if (key_valid) *key_valid = FALSE; | |
1337 return NULL; | |
1338 } | |
1339 | |
1340 if (key_valid) *key_valid = TRUE; | |
1341 | |
1342 if (strcmp(key, "fCamera") == 0) | |
1343 { | |
1344 gchar *text; | |
1345 gchar *make = exif_get_data_as_text(exif, "Make"); | |
1346 gchar *model = exif_get_data_as_text(exif, "Model"); | |
1347 gchar *software = exif_get_data_as_text(exif, "Software"); | |
1348 | |
1349 text = g_strdup_printf("%s%s%s%s%s%s", (make) ? make : "", ((make) && (model)) ? " " : "", | |
1350 (model) ? model : "", | |
1351 (software) ? " (" : "", | |
1352 (software) ? software : "", | |
1353 (software) ? ")" : ""); | |
1354 | |
1355 g_free(make); | |
1356 g_free(model); | |
1357 g_free(software); | |
1358 return text; | |
1359 } | |
1360 if (strcmp(key, "fDateTime") == 0) | |
1361 { | |
1362 gchar *text = exif_get_data_as_text(exif, "DateTimeOriginal"); | |
1363 gchar *subsec = NULL; | |
1364 if (text) subsec = exif_get_data_as_text(exif, "SubsecTimeOriginal"); | |
1365 if (!text) | |
1366 { | |
1367 text = exif_get_data_as_text(exif, "DateTime"); | |
1368 if (text) subsec = exif_get_data_as_text(exif, "SubsecTime"); | |
1369 } | |
1370 if (subsec) | |
1371 { | |
1372 gchar *tmp = text; | |
1373 text = g_strconcat(tmp, ".", subsec, NULL); | |
1374 g_free(tmp); | |
1375 g_free(subsec); | |
1376 } | |
1377 return text; | |
1378 } | |
1379 if (strcmp(key, "fShutterSpeed") == 0) | |
1380 { | |
1381 ExifRational *r; | |
1382 | |
1383 r = exif_get_rational(exif, "ExposureTime", NULL); | |
1384 if (r && r->num && r->den) | |
1385 { | |
1386 double n = (double)r->den / (double)r->num; | |
1387 return g_strdup_printf("%s%.0fs", n > 1.0 ? "1/" : "", | |
1388 n > 1.0 ? n : 1.0 / n); | |
1389 } | |
1390 r = exif_get_rational(exif, "ShutterSpeedValue", NULL); | |
1391 if (r && r->num && r->den) | |
1392 { | |
1393 double n = pow(2.0, exif_rational_to_double(r, TRUE)); | |
1394 | |
1395 /* Correct exposure time to avoid values like 1/91s (seen on Minolta DImage 7) */ | |
1396 if (n > 1.0 && (int)n - ((int)(n/10))*10 == 1) n--; | |
1397 | |
1398 return g_strdup_printf("%s%.0fs", n > 1.0 ? "1/" : "", | |
1399 n > 1.0 ? floor(n) : 1.0 / n); | |
1400 } | |
1401 return NULL; | |
1402 } | |
1403 if (strcmp(key, "fAperture") == 0) | |
1404 { | |
1405 double n; | |
1406 | |
1407 n = exif_get_rational_as_double(exif, "FNumber"); | |
1408 if (n == 0.0) n = exif_get_rational_as_double(exif, "ApertureValue"); | |
1409 if (n == 0.0) return NULL; | |
1410 | |
1411 return g_strdup_printf("f/%.1f", n); | |
1412 } | |
1413 if (strcmp(key, "fExposureBias") == 0) | |
1414 { | |
1415 ExifRational *r; | |
1416 gint sign; | |
1417 double n; | |
1418 | |
1419 r = exif_get_rational(exif, "ExposureBiasValue", &sign); | |
1420 if (!r) return NULL; | |
1421 | |
1422 n = exif_rational_to_double(r, sign); | |
1423 return g_strdup_printf("%+.1f", n); | |
1424 } | |
1425 if (strcmp(key, "fFocalLength") == 0) | |
1426 { | |
1427 double n; | |
1428 | |
1429 n = exif_get_rational_as_double(exif, "FocalLength"); | |
1430 if (n == 0.0) return NULL; | |
1431 return g_strdup_printf("%.2f mm", n); | |
1432 } | |
1433 if (strcmp(key, "fISOSpeedRating") == 0) | |
1434 { | |
1435 gchar *text; | |
1436 | |
1437 text = exif_get_data_as_text(exif, "ISOSpeedRatings"); | |
1438 /* kodak may set this instead */ | |
1439 if (!text) text = exif_get_data_as_text(exif, "ExposureIndex"); | |
1440 return text; | |
1441 } | |
1442 if (strcmp(key, "fSubjectDistance") == 0) | |
1443 { | |
1444 ExifRational *r; | |
1445 gint sign; | |
1446 double n; | |
1447 | |
1448 r = exif_get_rational(exif, "SubjectDistance", &sign); | |
1449 if (!r) return NULL; | |
1450 | |
1451 if ((long)r->num == 0xffffffff) return g_strdup(_("infinity")); | |
1452 if ((long)r->num == 0) return g_strdup(_("unknown")); | |
1453 | |
1454 n = exif_rational_to_double(r, sign); | |
1455 if (n == 0.0) return _("unknown"); | |
1456 return g_strdup_printf("%.3f m", n); | |
1457 } | |
1458 if (strcmp(key, "fFlash") == 0) | |
1459 { | |
1460 /* grr, flash is a bitmask... */ | |
1461 GString *string; | |
1462 gchar *text; | |
1463 gint n; | |
1464 gint v; | |
1465 | |
1466 if (!exif_get_integer(exif, "Flash", &n)) return NULL; | |
1467 | |
1468 /* Exif 2.1 only defines first 3 bits */ | |
1469 if (n <= 0x07) return g_strdup(text_list_find_value(ExifFlashList, n)); | |
1470 | |
1471 /* must be Exif 2.2 */ | |
1472 string = g_string_new(""); | |
1473 | |
1474 /* flash fired (bit 0) */ | |
1475 string = g_string_append(string, (n & 0x01) ? _("yes") : _("no")); | |
1476 | |
1477 /* flash mode (bits 3, 4) */ | |
1478 v = (n >> 3) & 0x03; | |
1479 if (v) string = append_comma_text(string, _("mode:")); | |
1480 switch (v) | |
1481 { | |
1482 case 1: | |
1483 string = g_string_append(string, _("on")); | |
1484 break; | |
1485 case 2: | |
1486 string = g_string_append(string, _("off")); | |
1487 break; | |
1488 case 3: | |
1489 string = g_string_append(string, _("auto")); | |
1490 break; | |
1491 } | |
1492 | |
1493 /* return light (bits 1, 2) */ | |
1494 v = (n >> 1) & 0x03; | |
1495 if (v == 2) string = append_comma_text(string, _("not detected by strobe")); | |
1496 if (v == 3) string = append_comma_text(string, _("detected by strobe")); | |
1497 | |
1498 /* we ignore flash function (bit 5) */ | |
1499 | |
1500 /* red-eye (bit 6) */ | |
1501 if ((n >> 5) & 0x01) string = append_comma_text(string, _("red-eye reduction")); | |
1502 | |
1503 text = string->str; | |
1504 g_string_free(string, FALSE); | |
1505 return text; | |
1506 } | |
1507 if (strcmp(key, "fResolution") == 0) | |
1508 { | |
1509 ExifRational *rx, *ry; | |
1510 gchar *units; | |
1511 gchar *text; | |
1512 | |
1513 rx = exif_get_rational(exif, "XResolution", NULL); | |
1514 ry = exif_get_rational(exif, "YResolution", NULL); | |
1515 if (!rx || !ry) return NULL; | |
1516 | |
1517 units = exif_get_data_as_text(exif, "ResolutionUnit"); | |
1518 text = g_strdup_printf("%0.f x %0.f (%s/%s)", rx->den ? (double)rx->num / rx->den : 1.0, | |
1519 ry->den ? (double)ry->num / ry->den : 1.0, | |
1520 _("dot"), (units) ? units : _("unknown")); | |
1521 | |
1522 g_free(units); | |
1523 return text; | |
1524 } | |
1525 | |
1526 if (key_valid) *key_valid = FALSE; | |
1527 return NULL; | |
1528 } | |
1529 | |
1530 gchar *exif_get_data_as_text(ExifData *exif, const gchar *key) | |
1531 { | |
1532 ExifItem *item; | |
1533 gchar *text; | |
1534 gint key_valid; | |
1535 | |
1536 if (!key) return NULL; | |
1537 | |
1538 text = exif_get_formatted_by_key(exif, key, &key_valid); | |
1539 if (key_valid) return text; | |
1540 | |
1541 item = exif_get_item(exif, key); | |
1542 if (item) return exif_item_get_data_as_text(item); | |
1543 | |
1544 return NULL; | |
1545 } | |
1546 | |
1547 const gchar *exif_get_description_by_key(const gchar *key) | |
1548 { | |
1549 gint i; | |
1550 | |
1551 if (!key) return NULL; | |
1552 | |
1553 i = 0; | |
1554 while (ExifFormattedList[i].key != NULL) | |
1555 { | |
1556 if (strcmp(key, ExifFormattedList[i].key) == 0) return _(ExifFormattedList[i].description); | |
1557 i++; | |
1558 } | |
1559 | |
1560 i = 0; | |
1561 while (ExifKnownMarkersList[i].tag > 0) | |
1562 { | |
1563 if (strcmp(key, ExifKnownMarkersList[i].key) == 0) return _(ExifKnownMarkersList[i].description); | |
1564 i++; | |
1565 } | |
1566 | |
1567 return NULL; | |
1568 } | |
1569 | |
1570 static void exif_write_item(FILE *f, ExifItem *item) | |
1571 { | |
1572 gchar *text; | |
1573 | |
1574 text = exif_item_get_data_as_text(item); | |
1575 if (text) | |
1576 { | |
1577 fprintf(f, "%4x %9s %30s %s\n", item->tag, ExifFormatList[item->format].short_name, | |
1578 exif_item_get_tag_name(item), text); | |
1579 } | |
1580 g_free(text); | |
1581 } | |
1582 | |
1583 void exif_write_data_list(ExifData *exif, FILE *f, gint human_readable_list) | |
1584 { | |
1585 if (!f || !exif) return; | |
1586 | |
1587 fprintf(f, " tag format key value\n"); | |
1588 fprintf(f, "----------------------------------------------------\n"); | |
1589 | |
1590 if (human_readable_list) | |
1591 { | |
1592 gint i; | |
1593 | |
1594 i = 0; | |
1595 while (ExifFormattedList[i].key) | |
1596 { | |
1597 gchar *text; | |
1598 | |
1599 text = exif_get_formatted_by_key(exif, ExifFormattedList[i].key, NULL); | |
1600 if (text) | |
1601 { | |
1602 fprintf(f, " %9s %30s %s\n", "string", ExifFormattedList[i].key, text); | |
1603 } | |
1604 i++; | |
1605 } | |
1606 } | |
1607 else | |
1608 { | |
1609 GList *work; | |
1610 | |
1611 work = exif->items; | |
1612 while (work) | |
1613 { | |
1614 ExifItem *item; | |
1615 | |
1616 item = work->data; | |
1617 work = work->next; | |
1618 | |
1619 exif_write_item(f, item); | |
1620 } | |
1621 } | |
1622 fprintf(f, "----------------------------------------------------\n"); | |
1623 } | |
1624 |