;;; Portable EXIF info parser, based on code written by Kevin Layer
;;; (http://www.lispwire.com/entry-photog-exifinfo-des).
;;;
;;; EXIF info in TIFF files can be parsed and most of EXIF-2.2 and
;;; TIFF-6.0 tags have been added. Exporting EXIF data to files is not
;;; implemeted (yet).
;;;
;;; This code is in the public domain.  You may do with it what you
;;; want.
;;;
;;;
;;; How does this stuff work?
;;; 
;;; CL-USER> (use-package :cl-exif)
;;; T
;;; CL-USER> (setq x (parse-exif-data #p"~/Desktop/IMG_0013.JPG"))
;;; #S(EXIF :FILE #P"/Users/bernd/Desktop/IMG_0013.JPG"...)
;;; CL-USER> (exif-model x)
;;; "Canon PowerShot S80"
;;; CL-USER> (exif-date-time-original x)
;;; "2008:07:17 19:18:35"
;;; CL-USER> (exif-flash x)
;;; 24
;;; CL-USER> (exif-focal-length x)
;;; 207/10
;;; 
;;; $Id: cl-exif.lisp,v 1.5 2008/08/19 16:53:40 bernd Exp $

(defpackage :cl-exif
  (:use :common-lisp)
  (:export
   :parse-exif-data

   :exif
   :make-exif
   :exif-file
   :exif-image-width
   :exif-image-length
   :exif-bits-per-sample
   :exif-compression
   :exif-photometric-interpretation
   :exif-image-description
   :exif-make
   :exif-model
   :exif-strip-offsets
   :exif-orientation
   :exif-samples-per-pixel
   :exif-rows-per-strip
   :exif-strip-byte-counts
   :exif-x-resolution
   :exif-y-resolution
   :exif-planar-configuration
   :exif-resolution-unit
   :exif-transfer-function
   :exif-software
   :exif-date-time
   :exif-artist
   :exif-host-computer
   :exif-white-point
   :exif-primary-chromaticities
   :exif-jpeg-interchange-format
   :exif-jpeg-interchange-format-length
   :exif-y-cb-cr-coefficients
   :exif-y-cb-cr-sub-sampling
   :exif-y-cb-cr-positioning
   :exif-reference-black-white
   :exif-copyright
   :exif-exposure-time
   :exif-f-number
   :exif-exposure-program
   :exif-spectral-sensitivity
   :exif-iso-speed-ratings
   :exif-oecf
   :exif-exif-version
   :exif-date-time-original
   :exif-date-time-digitized
   :exif-components-configuration
   :exif-compressed-bits-per-pixel
   :exif-shutter-speed-value
   :exif-aperture-value
   :exif-brightness-value
   :exif-exposure-bias-value
   :exif-max-aperture-value
   :exif-subject-distance
   :exif-metering-mode
   :exif-light-source
   :exif-flash
   :exif-focal-length
   :exif-subject-area
   :exif-maker-note
   :exif-user-comment
   :exif-sub-sec-time
   :exif-sub-sec-time-original
   :exif-sub-sec-time-digitized
   :exif-flash-pix-version
   :exif-color-space
   :exif-pixel-x-dimension
   :exif-pixel-y-dimension
   :exif-related-sound-file
   :exif-flash-energy
   :exif-spatial-frequency-response
   :exif-focal-plane-x-resolution
   :exif-focal-plane-y-resolution
   :exif-focal-plane-resolution-unit
   :exif-subject-location
   :exif-exposure-index
   :exif-sensing-method
   :exif-file-source
   :exif-scene-type
   :exif-cfa-pattern
   :exif-custom-rendered
   :exif-exposure-method
   :exif-white-balance
   :exif-digital-zoom-ratio
   :exif-focal-length-in-35mm-film
   :exif-scene-capture-type
   :exif-gain-control
   :exif-contrast
   :exif-saturation
   :exif-sharpness
   :exif-device-setting-description
   :exif-subject-distance-range
   :exif-image-unique-id))

(in-package :cl-exif)


(defstruct exif
  file

  ;; Exif tags:
  image-width				; #x0100 image width
  image-length				; #x0101 image height
  (bits-per-sample #(8 8 8))		; #x0102 number of bits per component
  compression				; #x0103 compression scheme
  photometric-interpretation		; #x0106 pixel composition
  image-description			; #x010e image title
  make					; #x010f manufacturer of image input equipment
  model					; #x0110 model of image input equipment
  strip-offsets				; #x0111 image data location
  (orientation 1)			; #x0112 orientation of the image
  (samples-per-pixel 3)			; #x0115 number of components
  rows-per-strip			; #x0116 number of rows per strip
  strip-byte-counts			; #x0117 bytes per compressed strip
  (x-resolution 72)			; #x011a image resolution in width direction
  (y-resolution 72)			; #x011b image resolution in height direction
  (planar-configuration 1)		; #x011c image data arrangement
  (resolution-unit 2)			; #x0128 unit of X and Y resolution 
  transfer-function			; #x012d transfer function
  software				; #x0131 software used
  date-time				; #x0132 file change date and time
  artist				; #x013b person who created the image
  host-computer				; #x013c the computer and/or operating system in use at time of image creation
  white-point				; #x013e white point chromaticity
  primary-chromaticities		; #x013f chromaticities of primaries
  jpeg-interchange-format		; #x0201 offset to JPEG SOI
  jpeg-interchange-format-length	; #x0202 bytes of JPEG data
  y-cb-cr-coefficients			; #x0211 color space transformation matrix
  y-cb-cr-sub-sampling			; #x0212 subsampling ratio of Y to C
  (y-cb-cr-positioning 1)		; #x0213 Y and C positioning
  reference-black-white			; #x0214 pair of black and white reference values

  copyright				; #x8298 copyright holder
  exposure-time				; #x829a exposure time
  f-number				; #x829d F number
  (exposure-program 0)			; #x8822 exposure program
  spectral-sensitivity			; #x8824 spectral sensitivitiy
  iso-speed-ratings			; #x8827 ISO speed ratings
  oecf					; #x8828 optoelectronic coefficient
  
  exif-version				; #x9000 Exif version
  date-time-original			; #x9003 date and time original image was generated
  date-time-digitized			; #x9004 date and time image was made digital data
  components-configuration		; #x9101 meaning of each component
  compressed-bits-per-pixel		; #x9102 image compression mode
  shutter-speed-value			; #x9201 shutter speed
  aperture-value			; #x9202 aperture
  brightness-value			; #x9203 brightness
  exposure-bias-value			; #x9204 exposure bias
  max-aperture-value			; #x9205 maximum lens aperture
  subject-distance			; #x9206 subject distance
  (metering-mode 0)			; #x9207 metering mode
  (light-source 0)			; #x9208 light source
  flash					; #x9209 flash
  focal-length				; #x920a lens focal length
  subject-area				; #x9214 subject area
  maker-note				; #x927c manufacturer notes
  user-comment				; #x9286 user comments
  sub-sec-time				; #x9290 DateTime subseconds
  sub-sec-time-original			; #x9291 DateTimeOriginal subseconds
  sub-sec-time-digitized		; #x9292 DateTimeDigitized subseconds

  flash-pix-version			; #xa000 supported Flashpix version
  color-space				; #xa001 color space information
  pixel-x-dimension			; #xa002 valid image width
  pixel-y-dimension			; #xa003 valid image height
  related-sound-file			; #xa004 related audio file
  flash-energy				; #xa20b flash energy
  spatial-frequency-response		; #xa20c spatial frequency response
  focal-plane-x-resolution		; #xa20e focal plane X resolution
  focal-plane-y-resolution		; #xa20f focal plane Y resolution
  (focal-plane-resolution-unit 2)	; #xa210 focal plane resolution unit
  subject-location			; #xa214 subject location
  exposure-index			; #xa215 exposure index
  sensing-method			; #xa217 sensing method
  file-source				; #xa300 file source
  scene-type				; #xa301 scene type
  cfa-pattern				; #xa302 CFA pattern
  (custom-rendered 0)			; #xa401 custom image processing
  exposure-method			; #xa402 exposure mode
  white-balance				; #xa403 white balance
  digital-zoom-ratio			; #xa404 digital zoom ratio
  focal-length-in-35mm-film		; #xa405 focal length in 35 mm film
  (scene-capture-type 0)		; #xa406 scene capture type
  gain-control				; #xa407 gain control
  (contrast 0)				; #xa408 contrast
  (saturation 0)			; #xa409 saturation
  (sharpness 0)				; #xa40a sharpness
  device-setting-description		; #xa40b device settingss description
  subject-distance-range		; #xa40c subject distance range
  image-unique-id)			; #xa420 Unique image ID


(defun parse-exif-data (file-name)
  "Return EXIF structure from file FILE-NAME. If the file contains
no valid EXIF data return NIL."
  (with-open-file (stream file-name :direction :input :element-type '(unsigned-byte 8))
	 (let ((exif (make-exif :file file-name))
	       (app1 (read-stream-app1 stream)))
	   (process-app1 exif app1))))

;;; Stream
(defun read-stream-app1 (stream)
  "Parse STREAM as TIFF or JPEG file. Return APP1 structure."
  (let ((tag (read-stream-unsigned-byte-16 stream))
	len app1)
    (case tag
      ((#x4949 #x4d4d) ;; TIFF, read complete file because IFD can be
		       ;; stored at the end
                       (file-position stream 0)
                       (setf app1 (make-array (file-length stream) :element-type '(unsigned-byte 8)))
                       (read-sequence app1 stream)
                      app1)
      (#xffd8          ;; JPEG file 
		       ;; search for APP1 tag
		       (loop
			  (setf tag (read-stream-unsigned-byte-16 stream))
			  (setf len (- (read-stream-unsigned-byte-16 stream) 2))
			  (if (= tag #xffe1) ; APP1 found
			      (return)
			      (loop repeat len do (read-byte stream)))) ; jump over directory
			 ;; check for ID code "Exif#\Null" (plus padding byte = 6 bytes)
			 (let ((buf (make-array 6 :element-type '(unsigned-byte 8))))
			   (read-sequence buf stream)
			   (assert (equalp (subseq buf 0 5) #.(map 'vector #'char-code #(#\E #\x #\i #\f #\Null)))
				   nil "No Exif ID code")			   
			   ;; read APP1 structure
			   (setf app1 (make-array (- len 6) :element-type '(unsigned-byte 8)))
			   (read-sequence app1 stream)
			   app1))
      (t                 nil))))

(defun read-stream-unsigned-byte-16 (stream)
  "Read two bytes from STREAM and convert it to a 16-bit unsigned number
\(Big Endian)."
  (let ((n 0))
    (setf (ldb (byte 8 8) n) (read-byte stream))
    (setf (ldb (byte 8 0) n) (read-byte stream))
    n))


;;;; APP1
(defvar *big-endian* nil "Byte order")

(defun process-app1 (exif app1)
  "Fill in EXIF structure from APP1."
  (and app1
       (let ((ifd (process-tiff-header app1)))
	 (process-ifd exif app1 ifd)
	 exif)))

(defun process-tiff-header (app1)
  "Process TIFF header. Set *BIG-ENDIAN* to byte order and return
offset to IFD."
  ;; Little Endian="II", Big Endian="MM"
  (setf *big-endian* (equalp (subseq app1 0 2) #(#.(char-code #\M) #.(char-code #\M))))
  (if (/= (get-unsigned-byte-16 app1 2) 42)
      (warn "TIFF header has not the value 42."))
  (get-unsigned-byte-32 app1 4))

(defun process-ifd (exif app1 ifd)
  "Parse every entry in IFD and update EXIF accordingly."
  (let ((num-interop (get-unsigned-byte-16 app1 ifd)))
    (dotimes (i num-interop)
      (let* ((offset  (+ ifd 2 (* 12 i)))
	     (tag     (get-unsigned-byte-16 app1 offset))
	     (type    (get-unsigned-byte-16 app1 (+ offset 2)))
	     (count   (get-unsigned-byte-32 app1 (+ offset 4)))
	     (val-off (get-unsigned-byte-32 app1 (+ offset 8)))
	     (tag-val (get-value app1 type count val-off)))
	#+or(format t "~&~2D: tag=#X~4,'0X type=~D count=~D val-off=~D: ~S~%"
		i tag type count val-off tag-val)
	(case tag
	  (#x0100 (setf (exif-image-width exif) tag-val))
	  (#x0101 (setf (exif-image-length exif) tag-val))
	  (#x0102 (setf (exif-bits-per-sample exif) tag-val))
	  (#x0103 (setf (exif-compression exif) tag-val))
	  (#x0106 (setf (exif-photometric-interpretation exif) tag-val))
	  (#x010e (setf (exif-image-description exif) tag-val))
	  (#x010f (setf (exif-make exif) tag-val))
	  (#x0110 (setf (exif-model exif) tag-val))
	  (#x0111 (setf (exif-strip-offsets exif) tag-val))
	  (#x0112 (setf (exif-orientation exif) tag-val))
	  (#x0115 (setf (exif-samples-per-pixel exif) tag-val))
	  (#x0116 (setf (exif-rows-per-strip exif) tag-val))
	  (#x0117 (setf (exif-strip-byte-counts exif) tag-val))
	  (#x011a (setf (exif-x-resolution exif) tag-val))
	  (#x011b (setf (exif-y-resolution exif) tag-val))
	  (#x011c (setf (exif-planar-configuration exif) tag-val))
	  (#x0128 (setf (exif-resolution-unit exif) tag-val))
	  (#x012d (setf (exif-transfer-function exif) tag-val))
	  (#x0131 (setf (exif-software exif) tag-val))
	  (#x0132 (setf (exif-date-time exif) tag-val))
	  (#x013b (setf (exif-artist exif) tag-val))
	  (#x013c (setf (exif-host-computer exif) tag-val))
	  (#x013e (setf (exif-white-point exif) tag-val))
	  (#x013f (setf (exif-primary-chromaticities exif) tag-val))
	  (#x0201 (setf (exif-jpeg-interchange-format exif) tag-val))
	  (#x0202 (setf (exif-jpeg-interchange-format-length exif) tag-val))
	  (#x0211 (setf (exif-y-cb-cr-coefficients exif) tag-val))
	  (#x0212 (setf (exif-y-cb-cr-sub-sampling exif) tag-val))
	  (#x0213 (setf (exif-y-cb-cr-positioning exif) tag-val))
	  (#x0214 (setf (exif-reference-black-white exif) tag-val))

	  (#x8298 (setf (exif-copyright exif) tag-val))
	  (#x829a (setf (exif-exposure-time exif) tag-val))
	  (#x829d (setf (exif-f-number exif) tag-val))
	  (#x8822 (setf (exif-exposure-program exif) tag-val))
	  (#x8824 (setf (exif-spectral-sensitivity exif) tag-val))
	  (#x8827 (setf (exif-iso-speed-ratings exif) tag-val))
	  (#x8828 (setf (exif-oecf exif) tag-val))

	  (#x9000 (setf (exif-exif-version exif) tag-val))
	  (#x9003 (setf (exif-date-time-original exif) tag-val))
	  (#x9004 (setf (exif-date-time-digitized exif) tag-val))
	  (#x9101 (setf (exif-components-configuration exif) tag-val))
	  (#x9102 (setf (exif-compressed-bits-per-pixel exif) tag-val))
	  (#x9201 (setf (exif-shutter-speed-value exif) tag-val))
	  (#x9202 (setf (exif-aperture-value exif) tag-val))
	  (#x9203 (setf (exif-brightness-value exif) tag-val))
	  (#x9204 (setf (exif-exposure-bias-value exif) tag-val))
	  (#x9205 (setf (exif-max-aperture-value exif) tag-val))
	  (#x9206 (setf (exif-subject-distance exif) tag-val))
	  (#x9207 (setf (exif-metering-mode exif) tag-val))
	  (#x9208 (setf (exif-light-source exif) tag-val))
	  (#x9209 (setf (exif-flash exif) tag-val))
	  (#x920a (setf (exif-focal-length exif) tag-val))
	  (#x9214 (setf (exif-subject-area exif) tag-val))
	  (#x927c (setf (exif-maker-note exif) tag-val))
	  (#x9286 (setf (exif-user-comment exif) tag-val))
	  (#x9290 (setf (exif-sub-sec-time exif) tag-val))
	  (#x9291 (setf (exif-sub-sec-time-original exif) tag-val))
	  (#x9292 (setf (exif-sub-sec-time-digitized exif) tag-val))

	  (#xa000 (setf (exif-flash-pix-version exif) tag-val))
	  (#xa001 (setf (exif-color-space exif) tag-val))
	  (#xa002 (setf (exif-pixel-x-dimension exif) tag-val))
	  (#xa003 (setf (exif-pixel-y-dimension exif) tag-val))
	  (#xa004 (setf (exif-related-sound-file exif) tag-val))
	  (#xa20b (setf (exif-flash-energy exif) tag-val))
	  (#xa20c (setf (exif-spatial-frequency-response exif) tag-val))
	  (#xa20e (setf (exif-focal-plane-x-resolution exif) tag-val))
	  (#xa20f (setf (exif-focal-plane-y-resolution exif) tag-val))
	  (#xa210 (setf (exif-focal-plane-resolution-unit exif) tag-val))
	  (#xa214 (setf (exif-subject-location exif) tag-val))
	  (#xa215 (setf (exif-exposure-index exif) tag-val))
	  (#xa217 (setf (exif-sensing-method exif) tag-val))
	  (#xa300 (setf (exif-file-source exif) tag-val))
	  (#xa301 (setf (exif-scene-type exif) tag-val))
	  (#xa302 (setf (exif-cfa-pattern exif) tag-val))
	  (#xa401 (setf (exif-custom-rendered exif) tag-val))
	  (#xa402 (setf (exif-exposure-method exif) tag-val))
	  (#xa403 (setf (exif-white-balance exif) tag-val))
	  (#xa404 (setf (exif-digital-zoom-ratio exif) tag-val))
	  (#xa405 (setf (exif-focal-length-in-35mm-film exif) tag-val))
	  (#xa406 (setf (exif-scene-capture-type exif) tag-val))
	  (#xa407 (setf (exif-gain-control exif) tag-val))
	  (#xa408 (setf (exif-contrast exif) tag-val))
	  (#xa409 (setf (exif-saturation exif) tag-val))
	  (#xa40a (setf (exif-sharpness exif) tag-val))
	  (#xa40b (setf (exif-device-setting-description exif) tag-val))
	  (#xa40c (setf (exif-subject-distance-range exif) tag-val))
	  (#xa420 (setf (exif-image-unique-id exif) tag-val))
	  
	  ;; Exif-, GPS Info- and Interoperability IFD Pointer
	  ((#x8769 #x8825 #xa005) (process-ifd exif app1 val-off)))))))

(defun get-value (seq type count val-off)
  "Return tag values (single value or array)."
  (and (> count 0)
       (case type
	 ;; ASCII
	 (2 (let ((s (make-string (1- count))))
	      (if (<= count 4) 
		  (dotimes (i (1- count) s)
		    (setf (aref s i) (code-char (ldb (byte 8 (* (- 3 i) 8)) val-off))))
		  (dotimes (i (1- count) s)
		    (setf (aref s i) (code-char (aref seq (+ val-off i))))))))
	 ;; SHORT
	 (3 (case count 
	      (1 (if *big-endian*
		     (ldb (byte 16 16) val-off)
		     (ldb (byte 16 0) val-off)))
	      (2 (let ((a (make-array 2 :element-type '(unsigned-byte 16))))
		   (if *big-endian*
		       (setf (aref a 0) (ldb (byte 16 16) val-off)
			     (aref a 1) (ldb (byte 16  0) val-off))
		       (setf (aref a 0) (ldb (byte 16  0) val-off)
			     (aref a 1) (ldb (byte 16 16) val-off)))))
	      (t (let ((a (make-array count :element-type '(unsigned-byte 16))))
		   (dotimes (i count a)
		     (setf (aref a i) (get-unsigned-byte-16 seq (+ val-off (* 2 i)))))))))
	 ;; LONG
	 (4 (if (= count 1)
		val-off
		(let ((a (make-array count :element-type '(unsigned-byte 32))))
		  (dotimes (i count a)
		    (setf (aref a i) (get-unsigned-byte-32 seq (+ val-off (* 4 i))))))))
	 ;; RATIONAL
	 (5 (let ((n (get-unsigned-byte-32 seq val-off))
		  (d (get-unsigned-byte-32 seq (+ val-off 4))))
	      (/ n d)))
	 ;; UNDEFINED
	 (7 (if (<= count 4)
		val-off
		(subseq seq val-off (+ val-off count))))
	 ;; LONG
	 (9 (if (= count 1)
		(unsigned-signed-byte-32 val-off)
		(let ((a (make-array count :element-type '(signed-byte 32))))
		  (dotimes (i count a)
		    (setf (aref a i) (get-signed-byte-32 seq (+ val-off (* 4 i))))))))
	 ;; SRATIONAL
	 (10 (let ((n (get-signed-byte-32 seq val-off))
		   (d (get-signed-byte-32 seq (+ val-off 4))))
	       (/ n d))))))

(defun get-unsigned-byte-16 (seq offset)
  "Get two bytes from SEQ and convert it to a 16-bit unsigned number."
  (let ((n 0))
    (if *big-endian*
	(setf
	  (ldb (byte 8 8) n) (aref seq offset)
	  (ldb (byte 8 0) n) (aref seq (1+ offset)))
	(setf
	  (ldb (byte 8 0) n) (aref seq offset)
	  (ldb (byte 8 8) n) (aref seq (1+ offset))))
    n))

(defun get-unsigned-byte-32 (seq offset)
  "Get four bytes from SEQ and convert it to a 32-bit unsigned number."
  (let ((n 0))
    (if *big-endian*
	(setf
	  (ldb (byte 8 24) n) (aref seq offset)
	  (ldb (byte 8 16) n) (aref seq (+ offset 1))
	  (ldb (byte 8  8) n) (aref seq (+ offset 2))
	  (ldb (byte 8  0) n) (aref seq (+ offset 3)))
	(setf
	  (ldb (byte 8  0) n) (aref seq offset)
	  (ldb (byte 8  8) n) (aref seq (+ offset 1))
	  (ldb (byte 8 16) n) (aref seq (+ offset 2))
	  (ldb (byte 8 24) n) (aref seq (+ offset 3))))
    n))

(defun get-signed-byte-32 (seq offset)
  "Get four bytes from SEQ and convert it to a 32-bit signed number."
  (unsigned-signed-byte-32 (get-unsigned-byte-32 seq offset)))


(defun unsigned-signed-byte-32 (n)
  "Convert a unsigned 32-bit integer to signed 32-bit."
  (if (logbitp 31 n)
      (- n #x100000000)
      n))
