4 ;;; Created: 2005-12-08 by Zach Beane <xach@xach.com>
6 ;;; Copyright (c) 2005 Zachary Beane, All Rights Reserved
8 ;;; Redistribution and use in source and binary forms, with or without
9 ;;; modification, are permitted provided that the following conditions
12 ;;; * Redistributions of source code must retain the above copyright
13 ;;; notice, this list of conditions and the following disclaimer.
15 ;;; * Redistributions in binary form must reproduce the above
16 ;;; copyright notice, this list of conditions and the following
17 ;;; disclaimer in the documentation and/or other materials
18 ;;; provided with the distribution.
20 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
21 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
24 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 ;;; $Id: exif.lisp,v 1.7 2009/09/08 17:14:22 xach Exp $
43 ;; Pulling values from IFDs and entries
62 (in-package :zpb-exif
)
67 (define-condition invalid-stream
(error) ())
69 (define-condition invalid-jpeg-stream
(invalid-stream) ())
71 (define-condition invalid-exif-stream
(invalid-stream) ())
73 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
74 (defparameter *optimizations
*
75 '(optimize (speed 3) (safety 0)))
77 (defparameter *optimizations
* '(optimize)))
82 (defun get-32/lsb
(pos buf
)
83 (declare (cl:type
(simple-array (unsigned-byte 8) (*)) buf
)
87 (+ (ash (aref buf pos
) 0)
88 (ash (aref buf
(incf pos
)) 8)
89 (ash (aref buf
(incf pos
)) 16)
90 (ash (aref buf
(incf pos
)) 24))))
92 (defun get-32/msb
(pos buf
)
93 (declare (cl:type
(simple-array (unsigned-byte 8) (*)) buf
)
97 (+ (ash (aref buf pos
) 24)
98 (ash (aref buf
(incf pos
)) 16)
99 (ash (aref buf
(incf pos
)) 8)
100 (ash (aref buf
(incf pos
)) 0))))
102 (defun get-16/lsb
(pos buf
)
103 (declare (cl:type
(simple-array (unsigned-byte 8) (*)) buf
)
107 (+ (ash (aref buf pos
) 0)
108 (ash (aref buf
(incf pos
)) 8))))
110 (defun get-16/msb
(pos buf
)
111 (declare (cl:type
(simple-array (unsigned-byte 8) (*)) buf
)
115 (+ (ash (aref buf pos
) 8)
116 (ash (aref buf
(incf pos
)) 0))))
118 (defun get-8/* (pos buf
)
119 (declare (cl:type
(simple-array (unsigned-byte 8) (*)) buf
)
122 (logand #xFF
(aref buf pos
)))
139 "The position in the input stream from where the Exif data was read.")
141 :initarg
:get-32-function
142 :reader get-32-function
)
144 :initarg
:get-16-function
145 :reader get-16-function
)
154 :initarg
:thumbnail-ifd
156 :accessor thumbnail-ifd
)
165 (interoperability-ifd
166 :initarg
:interoperability-ifd
168 :accessor interoperability-ifd
)))
170 (defun exif-data (exif)
173 (defun get-8 (pos exif
)
174 (get-8/* pos
(data exif
)))
176 (defun get-16 (pos exif
)
177 (funcall (get-16-function exif
) pos
(data exif
)))
179 (defun get-32 (pos exif
)
180 (funcall (get-32-function exif
) pos
(data exif
)))
182 (defun get-string (pos length exif
)
183 (let ((data (data exif
))
184 (string (make-string (1- length
))))
188 do
(setf (char string j
) (code-char (aref data i
))))
192 ;;; Reading the various data types
194 (defun long->slong
(long)
195 (if (logbitp 31 long
)
196 (dpb long
(byte 32 0) -
1)
199 (defmacro do-gets
((j start step count
) &body body
)
200 "Evaluate BODY COUNT times, binding J to START initially, then
201 incrementing it by STEP. The results of the evaluation are returned as
203 (let ((result (gensym))
205 `(do ((,result
(make-array ,count
))
207 (,j
,start
(+ ,j
,step
)))
208 ((= ,i
,count
) ,result
)
209 (setf (aref ,result
,i
)
212 (defun get-byte (type count pos exif
)
213 (declare (ignore type
))
216 (subseq (data exif
) pos
(+ pos count
))))
218 (defun get-ascii (type count pos exif
)
219 (declare (ignore type
))
221 (get-string pos count exif
)
224 (defun get-short (type count pos exif
)
225 (declare (ignore type
))
228 (do-gets (i pos
2 count
)
231 (defun get-long (type count pos exif
)
232 (declare (ignore type
))
235 (do-gets (i pos
4 count
)
238 (defun get-rational (type count pos exif
)
239 (declare (ignore type
))
241 (let ((numerator (get-32 pos exif
))
242 (denominator (get-32 (+ 4 pos
) exif
)))
243 (if (zerop denominator
)
245 (/ numerator denominator
)))
246 (do-gets (i pos
8 count
)
247 (let ((numerator (get-32 i exif
))
248 (denominator (get-32 (+ 4 i
) exif
)))
249 (if (zerop denominator
)
251 (/ numerator denominator
))))))
253 (defun get-undefined (type count pos exif
)
254 (declare (ignore type
))
255 (subseq (data exif
) pos
(+ pos count
)))
257 (defun get-slong (type count pos exif
)
258 (declare (ignore type
))
260 (long->slong
(get-32 pos exif
))
261 (do-gets (i pos
4 count
)
262 (long->slong
(get-32 i exif
)))))
264 (defun get-srational (type count pos exif
)
265 (declare (ignore type
))
267 (let ((numerator (long->slong
(get-32 pos exif
)))
268 (denominator (get-32 (+ pos
4) exif
)))
269 (if (zerop denominator
)
271 (/ numerator denominator
)))
272 (do-gets (i pos
8 count
)
273 (let ((numerator (long->slong
(get-32 i exif
)))
274 (denominator (get-32 (+ i
4) exif
)))
275 (if (zerop denominator
)
277 (/ numerator denominator
))))))
279 (defun get-unknown-type (type &rest args
)
280 (declare (ignore args
))
281 (warn "Encountered unknown data type ~D, ignoring" type
)
284 (defparameter *type-readers
*
285 #(get-unknown-type ; 0
314 :initarg
:next-ifd-offset
316 :accessor next-ifd-offset
)))
318 (defmethod print-object ((ifd ifd
) stream
)
319 (print-unreadable-object (ifd stream
:type t
:identity t
)
320 (format stream
"(~D entries)" (hash-table-count (entries ifd
)))))
322 (defclass ifd-entry
()
339 :initarg
:value-offset
340 :reader value-offset
)
344 :writer
(setf value
))))
346 (defun immediate-value-p (type count
)
347 "Can COUNT items of TYPE be stored in the 32-bit IFD entry value
351 ((1 2 7) (<= count
4))
355 ((4 9) (= count
1))))
357 (defun read-ifd-value (type count pos exif
)
358 (funcall (aref *type-readers
* type
) type count pos exif
))
360 (defun read-ifd-entry (pos ifd exif
)
361 (let ((tag (get-16 pos exif
))
362 (type (get-16 (+ pos
2) exif
))
363 (count (get-32 (+ pos
4) exif
))
364 (value-offset (get-32 (+ pos
8) exif
)))
365 (let ((ifd-entry (make-instance 'ifd-entry
371 :value-offset value-offset
)))
372 (when (immediate-value-p type count
)
373 (setf (value ifd-entry
) (read-ifd-value type count
(+ pos
8) exif
)))
376 (defun read-ifd (pos tagset exif
)
377 (let* ((entry-count (get-16 pos exif
))
378 (entries (make-hash-table))
379 (next-ifd-offset (get-32 (+ pos
2 (* 12 entry-count
)) exif
))
380 (ifd (make-instance 'ifd
383 :next-ifd-offset next-ifd-offset
)))
384 (loop for i below entry-count
386 for entry
= (read-ifd-entry (+ pos j
) ifd exif
)
387 do
(setf (gethash (tag entry
) entries
) entry
))
390 (defun initialize-ifd-entry (ifd-entry)
391 (with-slots (exif type count value-offset
)
393 (setf (value ifd-entry
) (read-ifd-value type count value-offset exif
))))
395 (defun entry-value (ifd-entry)
397 (if (slot-boundp ifd-entry
'value
)
399 (initialize-ifd-entry ifd-entry
))))
401 (defun bisect-find (object vector
&key
(key #'identity
))
408 (setf mid
(ash (+ hi lo
) -
1))
409 (let ((candidate (funcall key
(aref vector mid
))))
410 (cond ((= candidate object
)
411 (return (aref vector mid
)))
412 ((< object candidate
)
415 (setf lo
(1+ mid
))))))))
417 (defun %ifd-entry
(tag ifd
)
419 (gethash tag
(entries ifd
))))
423 ;;; Initializing IFDs in the exif
425 ;;; An IFD is a directory of tag/value structures. Exif defines
426 ;;; separate IFD tags for image information, exif-specific
427 ;;; information, GPS information, and interoperability
428 ;;; information. There may be image, exif, and GPS IFDs for both the
429 ;;; primary image and the thumbnail image.
431 ;;; The image information IFD has pointers to exif and GPS IFDs. The
432 ;;; exif IFD has a pointer to the interoperability IFD.
434 ;;; FIXME: GPS and Exif IFDs for the thumbnail image are ignored.
437 (defun initialize-exif-ifds (exif)
438 (let ((image-ifd-offset (get-32 4 exif
)))
439 (with-slots (image-ifd thumbnail-ifd exif-ifd
440 gps-ifd interoperability-ifd
)
442 (setf image-ifd
(read-ifd image-ifd-offset
'image exif
))
443 (unless (zerop (next-ifd-offset image-ifd
))
444 (setf thumbnail-ifd
(read-ifd (next-ifd-offset image-ifd
)
447 (let ((exif-ifd-offset (entry-value (%ifd-entry
#x8769 image-ifd
)))
448 (gps-ifd-offset (entry-value (%ifd-entry
#x8825 image-ifd
))))
449 (when exif-ifd-offset
450 (setf exif-ifd
(read-ifd exif-ifd-offset
'exif exif
))
451 (let ((interoperability-ifd-offset (entry-value (%ifd-entry
454 (when interoperability-ifd-offset
455 (setf interoperability-ifd
(read-ifd interoperability-ifd-offset
459 (setf gps-ifd
(read-ifd gps-ifd-offset
'gps exif
)))
465 ;;; Creating an exif object from a stream
467 (defparameter *reader-functions
/msb
*
471 (defparameter *reader-functions
/lsb
*
475 (defun read-jpeg-uint16 (stream)
477 (+ (ash (read-byte stream
) 8)
478 (read-byte stream
))))
480 (defun check-bytes (stream &rest bytes
)
481 "Return true if next bytes of STREAM match the list BYTES."
482 (loop for byte in bytes
483 always
(= (read-byte stream
) byte
)))
485 (defun seek-to-app1 (stream)
486 "Position STREAM after the #xFF, #xE1 marker in the JPEG stream
487 representing the APP1 segment. Raise an INVALID-EXIF-STREAM error if
488 no APP1 segment can be found."
489 (do ((first-byte (read-byte stream nil
) next-byte
)
490 (next-byte (read-byte stream nil
) (read-byte stream nil
)))
491 ((not (and first-byte next-byte
)))
492 (cond ((and (= first-byte
#xFF
) (= next-byte
#xE1
))
494 ((and (= first-byte
#xFF
) (<= #xE0 next-byte
#xEF
))
495 (let ((appn-size (read-jpeg-uint16 stream
)))
496 (file-position stream
(+ (file-position stream
)
499 ;; padding -- do nothing
502 (error 'invalid-exif-stream
)))))
504 (defun parse-exif-octets (data &key file offset
)
505 "Parse exif data from (unsigned-byte 8) simple-array DATA, storing
506 FILE and OFFSET in resulting Exif object if provided. Data should
507 contain the exif data starting after the \"Exif#\NUL#\NUL\" header. If
508 the vector does not contain Exif data, raise INVALID-EXIF-STREAM."
510 (let ((endianness (if (= (aref data
0) (aref data
1) #x49
)
513 (destructuring-bind (get-32 get-16
)
514 (if (eql endianness
:lsb
)
515 *reader-functions
/lsb
*
516 *reader-functions
/msb
*)
517 (initialize-exif-ifds
522 :endianness endianness
523 :get-32-function get-32
524 :get-16-function get-16
)))))
526 (defun make-exif-from-stream (stream)
527 "Extract an Exif object from the open (unsigned-byte 8) STREAM. The
528 stream must be positioned at the beginning of JPEG data. If the stream
529 is not a JPEG stream, raise INVALID-JPEG-STREAM. If the stream does
530 not contain Exif data, raise INVALID-EXIF-STREAM."
531 (unless (check-bytes stream
#xFF
#xD8
)
532 (error 'invalid-jpeg-stream
))
533 (seek-to-app1 stream
)
534 (let ((size (read-jpeg-uint16 stream
)))
536 (unless (check-bytes stream
#x45
#x78
#x69
#x66
#x00
#x00
)
537 (error 'invalid-exif-stream
))
538 (let ((data (make-array size
:element-type
'(unsigned-byte 8)))
539 (offset (file-position stream
)))
540 (read-sequence data stream
)
541 (parse-exif-octets data
:file
(ignore-errors (truename stream
))
544 (defun make-exif-from-file (file)
545 (with-open-file (stream file
547 :element-type
'(unsigned-byte 8))
548 (make-exif-from-stream stream
)))
550 (defun make-exif (object)
551 "Read and create an exif object from OBJECT, which may be a pathname
552 designator or a stream."
554 ((or string pathname
) (make-exif-from-file object
))
555 (stream (make-exif-from-stream object
))))
560 ;;; This is necessary because, unfortunately, tags are only unique
561 ;;; within a particular IFD. The GPS and interoperability IFDs, for
562 ;;; example, have conflicting tags.
564 (defclass tagset-entry
()
575 (defun tag-name (code tagset
)
576 (let ((table (get tagset
'tagset-code-table
)))
578 (let ((entry (gethash code table
)))
581 (error "~A is not a known tagset" tagset
))))
583 (defun tag-type (code tagset
)
584 (let ((table (get tagset
'tagset-code-table
)))
586 (let ((entry (gethash code table
)))
589 (error "~A is not a known tagset" tagset
))))
591 (defun tag-code (name tagset
)
592 (let ((table (get tagset
'tagset-name-table
)))
594 (nth-value 0 (gethash name table
))
595 (error "~A is not a known tagset" tagset
))))
597 (defmacro define-tagset
(name &body tag-definitions
)
598 (let ((name-table (gensym))
599 (code-table (gensym))
601 `(let ((,name-table
(make-hash-table :test
'equalp
))
602 (,code-table
(make-hash-table)))
603 (setf (get ',name
'tagset-name-table
) ,name-table
604 (get ',name
'tagset-code-table
) ,code-table
)
605 ,@(dolist (definition tag-definitions setfs
)
606 (destructuring-bind (&key tag name type
)
608 (push `(setf (gethash ,name
,name-table
) ,tag
609 (gethash ,tag
,code-table
) (make-instance 'tagset-entry
616 ;;; Converting some Exif values to somewhat more parsed values
618 (defun exif-type-parser-fun (exif-type)
619 (or (get exif-type
'exif-type-parser-fun
) 'identity
))
621 (defmacro define-exif-type
(name (base-type) &rest options
)
622 (declare (ignore base-type
))
624 (loop for
((type . parameters
)) on options
625 when
(eql type
:parser
)
626 collect
(destructuring-bind (lambda-list &body body
)
628 `(setf (get ',name
'exif-type-parser-fun
)
629 (lambda ,lambda-list
,@body
))))))
633 (defun parse-datetime (string)
634 "Convert an Exif datetime string in the form \"YYYY:MM:DD hh:mm:ss\"
635 to a universal time."
636 ;; YYYY:MM:DD hh:mm:ss
637 ;; 0123456789012345678
638 (flet ((integer-at (start end
)
639 (parse-integer string
:start start
:end end
)))
640 (encode-universal-time (integer-at 17 19)
648 ;;; It was no fun to type all these in from JEITA CP-3451
651 ;;; Generic types, used multiple times
653 (define-exif-type datetime
(ascii)
655 (parse-datetime value
)))
657 (define-exif-type floatable-rational
(rational)
661 ;;; One-off types; used for a specific tag in a tagset
663 (define-exif-type orientation
(short)
667 (2 :flipped-horizontally
)
669 (4 :flipped-vertically
)
670 (5 :rotated-270-and-flipped-horizontally
)
672 (7 :rotated-90-and-flipped-vertically
)
674 (otherwise :reserved
))))
676 (define-exif-type exposure-program
(short)
682 (3 :aperture-priority
)
683 (4 :shutter-priority
)
684 (5 :creative-program
)
688 (otherwise :reserved
))))
690 (define-exif-type sensing-method
(short)
694 (2 :one-chip-color-area-sensor
)
695 (3 :two-chip-color-area-sensor
)
696 (4 :three-chip-color-area-sensor
)
697 (5 :color-sequential-area-sensor
)
698 (6 :trilinear-sensor
)
699 (7 :color-sequential-linear-sensor
)
700 (otherwise :reserved
))))
702 (define-exif-type metering-mode
(short)
707 (2 :center-weighted-average
)
713 (otherwise :reserved
))))
715 (define-exif-type light-source
(short)
726 (12 :daylight-flourescent
)
727 (13 :day-white-flourescent
)
728 (14 :cool-white-flourescent
)
729 (15 :white-flourescent
)
730 (17 :standard-light-a
)
731 (18 :standard-light-b
)
732 (19 :standard-light-c
)
737 (24 :iso-studio-tungsten
)
738 (255 :other-light-source
)
739 (otherwise :reserved
))))
741 (define-exif-type flash
(short)
743 ;; FIXME: a gross simplification
746 (define-exif-type exposure-mode
(short)
752 (otherwise :reserved
))))
754 (define-exif-type white-balance
(short)
759 (otherwise :reserved
))))
761 (define-exif-type scene-capture-type
(short)
768 (otherwise :reserved
))))
770 (define-exif-type gain-control
(short)
778 (otherwise :reserved
))))
780 (define-exif-type contrast
(short)
786 (otherwise :reserved
))))
788 (define-exif-type saturation
(short)
794 (otherwise :reserved
))))
796 (define-exif-type sharpness
(short)
802 (otherwise :reserved
))))
804 (define-exif-type subject-distance-range
(short)
811 (otherwise :reserved
))))
813 (define-exif-type exif-version
(unknown)
815 ;; FIXME: ASCII-centric
816 (let ((version-string (make-array 5 :initial-element
(char-code #\.
))))
817 (replace version-string value
:start2
0 :end2
2)
818 (replace version-string value
:start1
3 :start2
2)
819 (string-trim '(#\
0) (map 'string
#'code-char version-string
)))))
821 (define-exif-type color-space
(short)
825 (#xFFFF
:uncalibrated
)
826 (otherwise :reserved
))))
828 (define-exif-type user-comment
(undefined)
830 (flet ((starts-with (prefix)
831 (loop for i across prefix
834 (cond ((or (starts-with #(0 0 0 0 0 0 0 0))
835 (starts-with #(#x41
#x53
#x43
#x49
#x49
)))
837 (let ((first-null (or (position 0 value
:start
8)
839 (map 'string
#'code-char
(subseq value
8 first-null
))))
842 (define-exif-type ycbcr-positioning
(short)
847 (otherwise :reserved
))))
850 ;; Tags relating to image data structure
851 (:tag
#x0100
:name
"ImageWidth" :type short
/long
)
852 (:tag
#x0101
:name
"ImageHeight" :type short
/long
)
853 (:tag
#x0102
:name
"BitsPerSample" :type short
)
854 (:tag
#x0103
:name
"Compression" :type short
)
855 (:tag
#x0106
:name
"PhotometricInterpretation" :type short
)
856 (:tag
#x0112
:name
"Orientation" :type orientation
)
857 (:tag
#x0115
:name
"SamplesPerPixel" :type short
)
858 (:tag
#x011C
:name
"PlanarConfiguration" :type short
)
859 (:tag
#x0212
:name
"YCbCrSubSampling" :type short
)
860 (:tag
#x0213
:name
"YCbCrPositioning" :type ycbcr-positioning
)
861 (:tag
#x011A
:name
"XResolution" :type rational
)
862 (:tag
#x011B
:name
"YResolution" :type rational
)
863 (:tag
#x0128
:name
"ResolutionUnit" :type short
)
864 ;; Tags relating to recording offset
865 (:tag
#x0111
:name
"StripOffsets" :type short
/long
)
866 (:tag
#x0116
:name
"RowsPerStrip" :type short
/long
)
867 (:tag
#x0117
:name
"StripByteCounts" :type short
/long
)
868 (:tag
#x0201
:name
"JPEGInterchangeFormat" :type long
)
869 (:tag
#x0202
:name
"JPEGInterchangeFormatLength" :type long
)
870 ;; Tags relating to image data characteristics
871 (:tag
#x012D
:name
"TransferFunction" :type short
)
872 (:tag
#x013E
:name
"WhitePoint" :type rational
)
873 (:tag
#x013F
:name
"PrimaryChromaticities" :type rational
)
874 (:tag
#x0211
:name
"YCbCrCoefficients" :type rational
)
875 (:tag
#x0214
:name
"ReferenceBlackWhite" :type rational
)
877 (:tag
#x0132
:name
"DateTime" :type datetime
)
878 (:tag
#x010E
:name
"ImageDescription" :type ascii
)
879 (:tag
#x010F
:name
"Make" :type ascii
)
880 (:tag
#x0110
:name
"Model" :type ascii
)
881 (:tag
#x0131
:name
"Software" :type ascii
)
882 (:tag
#x013B
:name
"Artist" :type ascii
)
883 (:tag
#x8298
:name
"Copyright" :type ascii
))
886 ;; Tags Relating to Version
887 (:tag
#x9000
:name
"ExifVersion" :type exif-version
)
888 (:tag
#xA000
:name
"FlashpixVersion" :type undefined
)
889 ;; Tag Relating to Image Data Characteristics
890 (:tag
#xA001
:name
"ColorSpace" :type color-space
)
891 (:tag
#xA500
:name
"Gamma" :type floatable-rational
)
892 ;; Tags Relating to Image Configuration
893 (:tag
#x9101
:name
"ComponentsConfiguration" :type undefined
)
894 (:tag
#x9102
:name
"CompressedBitsPerPixel" :type rational
)
895 (:tag
#xA002
:name
"PixelXDimension" :type short
/long
)
896 (:tag
#xA003
:name
"PixelYDimension" :type short
/long
)
897 ;; Tags Relating to User Information
898 (:tag
#x927C
:name
"MakerNote" :type undefined
)
899 (:tag
#x9286
:name
"UserComment" :type user-comment
)
900 ;; Tag Relating to Related File Information
901 (:tag
#xA004
:name
"RelatedSoundFile" :type ascii
)
902 ;; Tags Relating to Date and Time
903 (:tag
#x9003
:name
"DateTimeOriginal" :type datetime
)
904 (:tag
#x9004
:name
"DateTimeDigitized" :type datetime
)
905 (:tag
#x9290
:name
"SubSecTime" :type ascii
)
906 (:tag
#x9291
:name
"SubSecTimeOriginal" :type ascii
)
907 (:tag
#x9292
:name
"SubSecTimeDigitized" :type ascii
)
908 ;; Tags Relating to Picture-Taking Conditions
909 (:tag
#x829A
:name
"ExposureTime" :type floatable-rational
)
910 (:tag
#x829D
:name
"FNumber" :type floatable-rational
)
911 (:tag
#x8822
:name
"ExposureProgram" :type exposure-program
)
912 (:tag
#x8824
:name
"SpectralSensitivity" :type ascii
)
913 (:tag
#x8827
:name
"ISOSpeedRatings" :type short
)
914 (:tag
#x8828
:name
"OECF" :type undefined
)
915 (:tag
#x9201
:name
"ShutterSpeedValue" :type floatable-rational
)
916 (:tag
#x9202
:name
"ApertureValue" :type floatable-rational
)
917 (:tag
#x9203
:name
"BrightnessValue" :type srational
)
918 (:tag
#x9204
:name
"ExposureBiasValue" :type srational
)
919 (:tag
#x9205
:name
"MaxApertureValue" :type floatable-rational
)
920 (:tag
#x9206
:name
"SubjectDistance" :type floatable-rational
)
921 (:tag
#x9207
:name
"MeteringMode" :type metering-mode
)
922 (:tag
#x9208
:name
"LightSource" :type light-source
)
923 (:tag
#x9209
:name
"Flash" :type flash
)
924 (:tag
#x920A
:name
"FocalLength" :type floatable-rational
)
925 (:tag
#x9214
:name
"SubjectArea" :type short
)
926 (:tag
#xA20B
:name
"FlashEngergy" :type rational
)
927 (:tag
#xA20C
:name
"SpatialFrquencyResponse" :type undefined
)
928 (:tag
#xA20E
:name
"FocalPlaneXResolution" :type floatable-rational
)
929 (:tag
#xA20F
:name
"FocalPlaneYResolution" :type floatable-rational
)
930 (:tag
#xA210
:name
"FocalPlaneResolutionUnit" :type short
)
931 (:tag
#xA214
:name
"SubjectLocation" :type short
)
932 (:tag
#xA215
:name
"ExposureIndex" :type rational
)
933 (:tag
#xA217
:name
"SensingMethod" :type sensing-method
)
934 (:tag
#xA300
:name
"FileSource" :type undefined
)
935 (:tag
#xA301
:name
"SceneType" :type undefined
)
936 (:tag
#xA302
:name
"CFAPattern" :type undefined
)
937 (:tag
#xA401
:name
"CustomRendered" :type short
)
938 (:tag
#xA402
:name
"ExposureMode" :type exposure-mode
)
939 (:tag
#xA403
:name
"WhiteBalance" :type white-balance
)
940 (:tag
#xA404
:name
"DigitalZoomRatio" :type rational
)
941 (:tag
#xA405
:name
"FocalLengthIn35mmFilm" :type short
)
942 (:tag
#xA406
:name
"SceneCaptureType" :type scene-capture-type
)
943 (:tag
#xA407
:name
"GainControl" :type rational
)
944 (:tag
#xA408
:name
"Contrast" :type contrast
)
945 (:tag
#xA409
:name
"Saturation" :type saturation
)
946 (:tag
#xA40A
:name
"Sharpness" :type sharpness
)
947 (:tag
#xA40B
:name
"DeviceSettingDescription" :type undefined
)
948 (:tag
#xA40C
:name
"SubjectDistanceRange" :type subject-distance-range
)
950 (:tag
#xA420
:name
"ImageUniqueID" :type ascii
))
953 ;; Tags Relating to GPS
954 (:tag
#x0000
:name
"GPSVersionID" :type byte
)
955 (:tag
#x0001
:name
"GPSLatitudeRef" :type ascii
)
956 (:tag
#x0002
:name
"GPSLatitude" :type rational
)
957 (:tag
#x0003
:name
"GPSLongitudeRef" :type ascii
)
958 (:tag
#x0004
:name
"GPSLongitude" :type ascii
)
959 (:tag
#x0005
:name
"GPSAltitudeRef" :type byte
)
960 (:tag
#x0006
:name
"GPSAltitude" :type rational
)
961 (:tag
#x0007
:name
"GPSTimeStamp" :type rational
)
962 (:tag
#x0008
:name
"GPSSatellites" :type ascii
)
963 (:tag
#x0009
:name
"GPSStatus" :type ascii
)
964 (:tag
#x000A
:name
"GPSMeasureMode" :type ascii
)
965 (:tag
#x000B
:name
"GPSDOP" :type rational
)
966 (:tag
#x000C
:name
"GPSSpeedRef" :type ascii
)
967 (:tag
#x000D
:name
"GPSSpeed" :type rational
)
968 (:tag
#x000E
:name
"GPSTrackRef" :type ascii
)
969 (:tag
#x000F
:name
"GPSTrack" :type rational
)
970 (:tag
#x0010
:name
"GPSImgDirectionRef" :type ascii
)
971 (:tag
#x0011
:name
"GPSImgDirection" :type rational
)
972 (:tag
#x0012
:name
"GPSMapDatum" :type ascii
)
973 (:tag
#x0013
:name
"GPSDestLatitudeRef" :type ascii
)
974 (:tag
#x0014
:name
"GPSDestLatitude" :type rational
)
975 (:tag
#x0015
:name
"GPSDestLongitudeRef" :type ascii
)
976 (:tag
#x0016
:name
"GPSDestLongitude" :type rational
)
977 (:tag
#x0017
:name
"GPSDestBearingRef" :type ascii
)
978 (:tag
#x0018
:name
"GPSDestBearing" :type rational
)
979 (:tag
#x0019
:name
"GPSDestDistanceRef" :type ascii
)
980 (:tag
#x001A
:name
"GPSDestDistance" :type rational
)
981 (:tag
#x001B
:name
"GPSProcessingMethod" :type undefined
)
982 (:tag
#x001C
:name
"GPSAreaInformation" :type undefined
)
983 (:tag
#x001D
:name
"GPSDateStamp" :type ascii
)
984 (:tag
#x001E
:name
"GPSDifferential" :type short
))
986 (define-tagset interoperability
987 (:tag
#x0001
:name
"InteroperabilityIndex" :type ascii
))
989 (defun tag-designator (tag tagset
)
991 (string (tag-code tag tagset
))
992 (symbol (tag-code (symbol-name tag
) tagset
))
995 (defun ifd-entry (tag ifd
)
997 (let ((tag (tag-designator tag
(tagset ifd
))))
999 (gethash tag
(entries ifd
))))))
1001 (defun find-ifd-entry (tag exif
)
1002 (or (ifd-entry tag
(image-ifd exif
))
1003 (ifd-entry tag
(exif-ifd exif
))
1004 (ifd-entry tag
(gps-ifd exif
))))
1006 (defun exif-value (tag exif
)
1007 (entry-value (find-ifd-entry tag exif
)))
1009 (defun parsed-entry-value (ifd-entry)
1011 (let* ((type (tag-type (tag ifd-entry
) (tagset (ifd ifd-entry
))))
1012 (fun (exif-type-parser-fun type
)))
1013 (funcall fun
(entry-value ifd-entry
)))))
1015 (defun parsed-exif-value (tag exif
)
1016 (let ((entry (find-ifd-entry tag exif
)))
1018 (parsed-entry-value entry
))))
1021 (defmethod print-object ((ifd-entry ifd-entry
) stream
)
1022 (print-unreadable-object (ifd-entry stream
:type t
:identity t
)
1023 (let ((name (or (tag-name (tag ifd-entry
) (tagset (ifd ifd-entry
)))
1025 (format stream
"~S #x~X/~D/~D"
1029 (count ifd-entry
)))))
1032 ;;; Miscellaneous utility functions
1034 (defun thumbnail-image (exif)
1035 "Return an (UNSIGNED-BYTE 8) vector containing the JPEG data for the
1036 thumbnail image in EXIF, if present. If there is no thumbnail, return
1038 (let ((ifd (thumbnail-ifd exif
)))
1040 (entry-value (ifd-entry tag ifd
))))
1042 (let ((offset (val "JPEGInterchangeFormat"))
1043 (length (val "JPEGInterchangeFormatLength")))
1044 (when (and offset length
)
1045 (let ((data (make-array length
1046 :element-type
'(unsigned-byte 8))))
1047 (replace data
(exif-data exif
)
1051 ;;; Similar to exifinfo.cl's parse-exif-data
1053 (defun parse-exif-data (file)
1054 (let ((exif (make-exif file
)))
1056 (exif-value tag exif
))
1058 (let ((value (val tag
)))
1059 (and value
(float value
)))))
1060 (let* ((flash-value (val "Flash"))
1061 (flash-fired-p (and flash-value
(not (logand flash-value
1)))))
1062 (list :file
(file exif
)
1064 :model
(val "Model")
1065 :date
(val "DateTime")
1066 :comment nil
; This isn't actually useful
1067 :orientation
(val "Orientation")
1068 :exposure
(float-val "ExposureTime")
1069 :f-number
(float-val "FNumber")
1070 :iso-rating
(val "IsoSpeedRatings")
1071 :exposure-bias-value
(float-val "ExposureBiasValue")
1072 :subject-distance
(float-val "SubjectDistance")
1073 :flash flash-fired-p
1074 :focal-length
(float-val "FocalLength")
1075 :image-width
(val "PixelXDimension")
1076 :image-length
(val "PixelYDimension"))))))
1079 (defun ifd-alist (ifd &key parsedp
)
1080 "Return all the values in IFD as an alist."
1082 (loop for entry being the hash-value of
(entries ifd
)
1083 for name
= (or (tag-name (tag entry
) (tagset ifd
))
1084 (format nil
"Unknown Tag #x~4,'0X" (tag entry
)))
1087 (parsed-entry-value entry
)
1088 (entry-value entry
))))))
1090 (defun exif-alist (exif &key parsedp
)
1091 "Return the keys and values from the Image, Exif, and GPS IFDs of EXIF."
1092 (with-slots (image-ifd exif-ifd gps-ifd
)
1094 (nconc (ifd-alist image-ifd
:parsedp parsedp
)
1095 (ifd-alist exif-ifd
:parsedp parsedp
)
1096 (ifd-alist gps-ifd
:parsedp parsedp
))))