packages feed

hsexif 0.5.0.2 → 0.5.0.3

raw patch · 6 files changed

+583/−266 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Graphics.HsExif: instance Eq ExifTag
- Graphics.HsExif: instance Eq ExifValue
- Graphics.HsExif: instance Eq TagLocation
- Graphics.HsExif: instance Ord ExifTag
- Graphics.HsExif: instance Ord TagLocation
- Graphics.HsExif: instance Show ExifTag
- Graphics.HsExif: instance Show ExifValue
- Graphics.HsExif: instance Show TagLocation
+ Graphics.HsExif: prettyPrinter :: ExifTag -> ExifValue -> String
+ Graphics.HsExif: wasFlashFired :: Map ExifTag ExifValue -> Maybe Bool
- Graphics.HsExif: ExifTag :: TagLocation -> Maybe String -> Word16 -> ExifTag
+ Graphics.HsExif: ExifTag :: TagLocation -> Maybe String -> Word16 -> (ExifValue -> String) -> ExifTag

Files

+ Graphics/ExifTags.hs view
@@ -0,0 +1,190 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+module Graphics.ExifTags where++import Data.Word+import Text.Printf (printf)+import qualified Data.Map as Map+import Data.Map (Map)++import Graphics.Types+import Graphics.PrettyPrinters++exifSubIfdTag :: String -> Word16 -> (ExifValue -> String)-> ExifTag+exifSubIfdTag d = ExifTag ExifSubIFD (Just d)++exifIfd0Tag :: String -> Word16 -> (ExifValue -> String) -> ExifTag+exifIfd0Tag d = ExifTag IFD0 (Just d)++exifGpsTag :: String -> Word16 -> (ExifValue -> String) -> ExifTag+exifGpsTag d = ExifTag GpsSubIFD (Just d)++withFormat :: String -> ExifValue -> String+withFormat fmt = printf fmt . show++asFpWithFormat :: String -> ExifValue -> String+asFpWithFormat fmt = printf fmt . formatAsFloatingPoint 2++exposureTime		= exifSubIfdTag "exposureTime" 0x829a $ withFormat "%s sec."+fnumber			= exifSubIfdTag "fnumber" 0x829d $ withFormat "f/%s"+exposureProgram		= exifSubIfdTag "exposureProgram" 0x8822 ppExposureProgram+spectralSensitivity	= exifSubIfdTag "spectralSensitivity" 0x8824 show+isoSpeedRatings		= exifSubIfdTag "isoSpeedRatings" 0x8827 show+oecf			= exifSubIfdTag "OECF" 0x8828 show+exifVersion		= exifSubIfdTag "exifVersion" 0x9000 ppExifVersion+dateTimeOriginal	= exifSubIfdTag "dateTimeOriginal" 0x9003 show+dateTimeDigitized	= exifSubIfdTag "dateTimeDigitized" 0x9004 show+componentConfiguration	= exifSubIfdTag "componentConfiguration" 0x9101 ppComponentConfiguration+compressedBitsPerPixel	= exifSubIfdTag "compressedBitsPerPixel" 0x9102 (formatAsFloatingPoint 2)+shutterSpeedValue	= exifSubIfdTag "shutterSpeedValue" 0x9201 $ withFormat "%s sec."+apertureValue		= exifSubIfdTag "apertureValue" 0x9202 show+brightnessValue		= exifSubIfdTag "brightnessValue" 0x9203 show+exposureBiasValue	= exifSubIfdTag "exposureBiasValue" 0x9204 $ asFpWithFormat "%s EV"+maxApertureValue	= exifSubIfdTag "maxApertureValue" 0x9205 $ withFormat "f/%s"+subjectDistance		= exifSubIfdTag "subjectDistance" 0x9206 show+meteringMode		= exifSubIfdTag "meteringMode" 0x9207 ppMeteringMode+lightSource		= exifSubIfdTag "lightSource" 0x9208 ppLightSource+flash			= exifSubIfdTag "flash" 0x9209 ppFlash+focalLength		= exifSubIfdTag "focalLength" 0x920a $ asFpWithFormat "%s mm"+subjectArea		= exifSubIfdTag "subjectArea" 0x9214 show+makerNote		= exifSubIfdTag "makerNote" 0x927c show+userComment		= exifSubIfdTag "userComment" 0x9286 show+subSecTime		= exifSubIfdTag "subSecTime" 0x9290 show+subSecTimeOriginal	= exifSubIfdTag "subSecTimeOriginal" 0x9291 show+subSecTimeDigitized	= exifSubIfdTag "subSecTimeDigitized" 0x9292 show+flashPixVersion		= exifSubIfdTag "flashPixVersion" 0xa000 ppFlashPixVersion+colorSpace		= exifSubIfdTag "colorSpace" 0xa001 ppColorSpace+exifImageWidth		= exifSubIfdTag "exifImageWidth" 0xa002 show+exifImageHeight		= exifSubIfdTag "exifImageHeight" 0xa003 show+relatedSoundFile	= exifSubIfdTag "relatedSoundFile" 0xa004 show+flashEnergy		= exifSubIfdTag "flashEnergy" 0xa20b show+spatialFrequencyResponse= exifSubIfdTag "spatialFrequencyResponse" 0xa20c show+focalPlaneXResolution	= exifSubIfdTag "focalPlaneXResolution" 0xa20e show+focalPlaneYResolution	= exifSubIfdTag "focalPlaneYResolution" 0xa20f show+focalPlaneResolutionUnit= exifSubIfdTag "focalPlaneResolutionUnit" 0xa210 ppFocalPlaneResolutionUnit+subjectLocation		= exifSubIfdTag "subjectLocation" 0xa214 show+exposureIndex		= exifSubIfdTag "exposureIndex" 0xa215 show+sensingMethod		= exifSubIfdTag "sensingMethod" 0xa217 ppSensingMethod+fileSource		= exifSubIfdTag "fileSource" 0xa300 show+sceneType		= exifSubIfdTag "sceneType" 0xa301 ppSceneType+cfaPattern		= exifSubIfdTag "cfaPattern" 0xa302 show+customRendered		= exifSubIfdTag "customRendered" 0xa401 ppCustomRendered+exposureMode		= exifSubIfdTag "exposureMode" 0xa402 ppExposureMode+whiteBalance		= exifSubIfdTag "whiteBalance" 0xa403 ppWhiteBalance+digitalZoomRatio	= exifSubIfdTag "digitalZoomRatio" 0xa404 show+focalLengthIn35mmFilm	= exifSubIfdTag "focalLengthIn35mmFilm" 0xa405 $ asFpWithFormat "%s mm"+sceneCaptureType	= exifSubIfdTag "sceneCaptureType" 0xa406 ppSceneCaptureType+gainControl		= exifSubIfdTag "gainControl" 0xa407 ppGainControl+contrast		= exifSubIfdTag "contrast" 0xa408 ppContrastSharpness+saturation		= exifSubIfdTag "saturation" 0xa409 ppSaturation+sharpness		= exifSubIfdTag "sharpness" 0xa40a ppContrastSharpness+deviceSettingDescription= exifSubIfdTag "deviceSettingDescription" 0xa40b show+subjectDistanceRange	= exifSubIfdTag "subjectDistanceRange" 0xa40c ppSubjectDistanceRange+imageUniqueId		= exifSubIfdTag "imageUniqueId" 0xa420 show+exifInteroperabilityOffset=exifSubIfdTag "exifInteroperabilityOffset" 0xa005 show++imageDescription	= exifIfd0Tag "imageDescription" 0x010e show+make			= exifIfd0Tag "make" 0x010f show+model			= exifIfd0Tag "model" 0x0110 show+orientation		= exifIfd0Tag "orientation" 0x0112 ppOrientation+xResolution		= exifIfd0Tag "xResolution" 0x011a show+yResolution		= exifIfd0Tag "xResolution" 0x011b show+resolutionUnit		= exifIfd0Tag "resolutionUnit" 0x0128 ppResolutionUnit+software		= exifIfd0Tag "software" 0x0131 show+dateTime		= exifIfd0Tag "dateTime" 0x0132 show+artist			= exifIfd0Tag "artist" 0x013b show+whitePoint		= exifIfd0Tag "whitePoint" 0x013e show+primaryChromaticities	= exifIfd0Tag "primaryChromaticities" 0x013f show+yCbCrCoefficients	= exifIfd0Tag "yCbCrCoefficients" 0x0211 show+yCbCrPositioning	= exifIfd0Tag "yCbCrPositioning" 0x0213 ppYCbCrPositioning+referenceBlackWhite	= exifIfd0Tag "referenceBlackWhite" 0x0214 show+copyright		= exifIfd0Tag "copyright" 0x8298 show+exifIfdOffset		= exifIfd0Tag "exifIfdOffset" 0x8769 show+gpsTagOffset		= exifIfd0Tag "gpsTagOffset" 0x8825 show+printImageMatching	= exifIfd0Tag "printImageMatching" 0xc4a5 show++gpsVersionID		= exifGpsTag "gpsVersionID" 0x0000 show+gpsLatitudeRef		= exifGpsTag "gpsLatitudeRef" 0x0001 show+gpsLatitude		= exifGpsTag "gpsLatitude" 0x0002 ppGpsLongLat+gpsLongitudeRef		= exifGpsTag "gpsLongitudeRef" 0x0003 show+gpsLongitude		= exifGpsTag "gpsLongitude" 0x0004 ppGpsLongLat+gpsAltitudeRef		= exifGpsTag "gpsAltitudeRef" 0x0005 ppGpsAltitudeRef+gpsAltitude		= exifGpsTag "gpsAltitude" 0x0006 show+gpsTimeStamp		= exifGpsTag "gpsTimeStamp" 0x0007 show+gpsSatellites		= exifGpsTag "gpsSatellites" 0x0008 show+gpsStatus		= exifGpsTag "gpsStatus" 0x0009 show+gpsMeasureMode		= exifGpsTag "gpsMeasureMode" 0x000a show+gpsDop			= exifGpsTag "gpsDop" 0x000b show+gpsSpeedRef		= exifGpsTag "gpsSpeedRef" 0x000c show+gpsSpeed		= exifGpsTag "gpsSpeed" 0x000d show+gpsTrackRef		= exifGpsTag "gpsTrackRef" 0x000e show+gpsTrack		= exifGpsTag "gpsTrack" 0x000f show+gpsImgDirectionRef	= exifGpsTag "gpsImgDirectionRef" 0x0010 show+gpsImgDirection		= exifGpsTag "gpsImgDirection" 0x0011 show+gpsMapDatum		= exifGpsTag "gpsMapDatum" 0x0012 show+gpsDestLatitudeRef	= exifGpsTag "gpsDestLatitudeRef" 0x0013 show+gpsDestLatitude		= exifGpsTag "gpsDestLatitude" 0x0014 show+gpsDestLongitudeRef	= exifGpsTag "gpsDestLongitudeRef" 0x0015 show+gpsDestLongitude	= exifGpsTag "gpsDestLongitude" 0x0016 show+gpsDestBearingRef	= exifGpsTag "gpsDestBearingRef" 0x0017 show+gpsDestBearing		= exifGpsTag "gpsDestBearing" 0x0018 show+gpsDestDistanceRef	= exifGpsTag "gpsDestDistanceRef" 0x0019 show+gpsDestDistance		= exifGpsTag "gpsDestDistance" 0x001a show+gpsProcessingMethod	= exifGpsTag "gpsProcessingMethod" 0x001b show+gpsAreaInformation	= exifGpsTag "gpsAreaInformation" 0x001c show+gpsDateStamp		= exifGpsTag "gpsDateStamp" 0x001d show+gpsDifferential		= exifGpsTag "gpsDifferential" 0x001e show++allExifTags :: [ExifTag]+allExifTags = [exposureTime, fnumber, exposureProgram, isoSpeedRatings,+	exifVersion, dateTimeOriginal, dateTimeDigitized, componentConfiguration,+	compressedBitsPerPixel, shutterSpeedValue, apertureValue, brightnessValue,+	exposureBiasValue, maxApertureValue, subjectDistance, meteringMode,+	lightSource, flash, focalLength, makerNote, userComment,+	exifImageWidth, exifImageHeight, relatedSoundFile, focalPlaneXResolution,+	focalPlaneYResolution, focalPlaneResolutionUnit, sensingMethod, fileSource,+	sceneType, orientation, make, model, software, copyright,+	spectralSensitivity, oecf, subjectArea, subSecTime, subSecTimeOriginal,+	subSecTimeDigitized, flashPixVersion, colorSpace, flashEnergy,+	spatialFrequencyResponse, subjectLocation, exposureIndex, cfaPattern,+	customRendered, exposureMode, whiteBalance, digitalZoomRatio,+	focalLengthIn35mmFilm, sceneCaptureType, gainControl, contrast,+	saturation, sharpness, deviceSettingDescription, subjectDistanceRange,+	imageUniqueId, exifInteroperabilityOffset, imageDescription,+	xResolution, yResolution, resolutionUnit, dateTime, whitePoint,+	primaryChromaticities, yCbCrPositioning, yCbCrCoefficients, referenceBlackWhite,+	exifIfdOffset, printImageMatching, gpsTagOffset, artist,+	gpsVersionID, gpsLatitudeRef, gpsLatitude, gpsLongitudeRef, gpsLongitude,+	gpsAltitudeRef, gpsAltitude, gpsTimeStamp, gpsSatellites, gpsStatus,+	gpsMeasureMode, gpsDop, gpsSpeedRef, gpsSpeed, gpsTrackRef, gpsTrack,+	gpsImgDirectionRef, gpsImgDirection, gpsMapDatum, gpsDestLatitudeRef,+	gpsDestLatitude, gpsDestLongitudeRef, gpsDestLongitude, gpsDestBearingRef,+	gpsDestBearing, gpsDestDistanceRef, gpsDestDistance, gpsProcessingMethod,+	gpsAreaInformation, gpsDateStamp, gpsDifferential]++-- | Extract the GPS latitude and longitude where the picture was taken+-- (if it is present in the EXIF)+getGpsLatitudeLongitude :: Map ExifTag ExifValue -> Maybe (Double, Double)+getGpsLatitudeLongitude exifData = do+	(ExifText latRef) <- Map.lookup gpsLatitudeRef exifData+	latDec <- Map.lookup gpsLatitude exifData >>= gpsDecodeToDecimalDegrees +	let signedLatDec = case latRef of+		"S" -> -latDec+		_ -> latDec+	(ExifText longRef) <- Map.lookup gpsLongitudeRef exifData+	longDec <- Map.lookup gpsLongitude exifData >>= gpsDecodeToDecimalDegrees +	let signedLongDec = case longRef of+		"W" -> -longDec+		_ -> longDec+	return (signedLatDec, signedLongDec)++gpsDecodeToDecimalDegrees :: ExifValue -> Maybe Double+gpsDecodeToDecimalDegrees (ExifRationalList intPairs) = case fmap intPairToFloating intPairs of+			(degrees:minutes:seconds:[]) -> Just $ degrees + minutes / 60 + seconds / 3600+			_ -> Nothing+	where+		intPairToFloating (n, d) = fromIntegral n / fromIntegral d+gpsDecodeToDecimalDegrees _ = Nothing++ppGpsLongLat :: ExifValue -> String+ppGpsLongLat v = maybe "invalid GPS data" fmt $ gpsDecodeToDecimalDegrees v+	where fmt = printf "%.6f"
Graphics/HsExif.hs view
@@ -14,6 +14,7 @@ 	ImageOrientation(..), 	RotationDirection(..), 	getGpsLatitudeLongitude,+	wasFlashFired, 	formatAsFloatingPoint,  	-- * The ExifValue type@@ -142,7 +143,6 @@ import Data.Binary.Get import Data.Binary.Put import qualified Data.ByteString.Lazy as B-import qualified Data.ByteString as BS import Control.Monad (liftM, unless, replicateM) import qualified Data.ByteString.Char8 as Char8 import Data.Word@@ -154,49 +154,10 @@ import Data.Map (Map) import Data.Time.LocalTime import Data.Time.Calendar-import Numeric (showHex)-import Text.Printf---- | An exif value.--- --- If you want a string describing the contents--- of the value, simply use 'show'.-data ExifValue = ExifNumber !Int-	-- ^ An exif number. Originally it could have been short, int, signed or not.-	| ExifText !String-	-- ^ ASCII text.-	| ExifRational !Int !Int-	-- ^ A rational number (numerator, denominator).-	-- Sometimes we're used to it as rational (exposition time: 1/160),-	-- sometimes as float (exposure compensation, we rather think -0.75)-	-- 'show' will display it as 1/160.-	| ExifNumberList ![Int]-	-- ^ List of numbers. Originally they could have been short, int, signed or not.-	| ExifRationalList ![(Int,Int)]-	-- ^ A list of rational numbers (numerator, denominator).-	-- Sometimes we're used to it as rational (exposition time: 1/160),-	-- sometimes as float (exposure compensation, we rather think -0.75)-	-- 'show' will display it as 1/160.-	| ExifUndefined !BS.ByteString-	-- ^ The undefined type in EXIF means that the contents are not-	-- specified and up to the manufacturer. In effect it's exactly-	-- a bytestring. Sometimes it's text with ASCII or UNICODE at the-	-- beginning, often it's binary in nature.-	| ExifUnknown !Word16 !Int !Int-	-- ^ Unknown exif value type. All EXIF 2.3 types are-	-- supported, it could be a newer file.-	-- The parameters are type, count then value-	deriving Eq+import Data.Bits ((.&.)) -instance Show ExifValue where-	show (ExifNumber v) = show v-	show (ExifText v) = v-	show (ExifRational n d) = show n ++ "/" ++ show d-	show (ExifUnknown t c v) = show "Unknown exif type. Type: " ++ show t -		++ " count: " ++ show c ++ " value: " ++ show v-	show (ExifNumberList l) = show l-	show (ExifRationalList l) = show l-	show (ExifUndefined bs) = show bs+import Graphics.Types (ExifValue(..), ExifTag(..), TagLocation(..), formatAsFloatingPoint)+import Graphics.ExifTags  -- see http://www.media.mit.edu/pia/Research/deepview/exif.html -- and http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf@@ -326,187 +287,9 @@ 			entryContents = value 		} --- | Location of the tag in the JPG file structure.--- Normally you don't need to fiddle with this,--- except maybe if the library doesn't know the particular--- exif tag you're interested in.--- Rather check the list of supported exif tags, like--- 'exposureTime' and so on.-data TagLocation = ExifSubIFD | IFD0 | GpsSubIFD-	deriving (Show, Eq, Ord)---- | An exif tag. Normally you don't need to fiddle with this,--- except maybe if the library doesn't know the particular--- exif tag you're interested in.--- Rather check the list of supported exif tags, like--- 'exposureTime' and so on.-data ExifTag = ExifTag-	{-		tagLocation :: TagLocation,-		-- ^ In which part of the JPEG file the exif tag was found-		tagDesc :: Maybe String,-		-- ^ Description of the exif tag (exposureTime, fnumber...) or if unknown, Nothing.-		-- This should be purely for debugging purposes, to compare tags use == on ExifTag-		-- or compare the tagKey.-		tagKey :: Word16-		-- ^ Exif tag key, the number uniquely identifying this tag.-	}-	deriving (Eq, Ord)--instance Show ExifTag where-	show (ExifTag _ (Just d) _) = d-	show (ExifTag l _ v) = "Unknown tag, location: " ++ show l-		++ ", value: 0x" ++ showHex v ""--exifSubIfdTag :: String -> Word16 -> ExifTag-exifSubIfdTag d = ExifTag ExifSubIFD (Just d)--exifIfd0Tag :: String -> Word16 -> ExifTag-exifIfd0Tag d = ExifTag IFD0 (Just d)--exifGpsTag :: String -> Word16 -> ExifTag-exifGpsTag d = ExifTag GpsSubIFD (Just d)--exposureTime		= exifSubIfdTag "exposureTime" 0x829a-fnumber			= exifSubIfdTag "fnumber" 0x829d-exposureProgram		= exifSubIfdTag "exposureProgram" 0x8822-spectralSensitivity	= exifSubIfdTag "spectralSensitivity" 0x8824-isoSpeedRatings		= exifSubIfdTag "isoSpeedRatings" 0x8827-oecf			= exifSubIfdTag "OECF" 0x8828-exifVersion		= exifSubIfdTag "exifVersion" 0x9000-dateTimeOriginal	= exifSubIfdTag "dateTimeOriginal" 0x9003-dateTimeDigitized	= exifSubIfdTag "dateTimeDigitized" 0x9004-componentConfiguration	= exifSubIfdTag "componentConfiguration" 0x9101-compressedBitsPerPixel	= exifSubIfdTag "compressedBitsPerPixel" 0x9102-shutterSpeedValue	= exifSubIfdTag "shutterSpeedValue" 0x9201-apertureValue		= exifSubIfdTag "apertureValue" 0x9202-brightnessValue		= exifSubIfdTag "brightnessValue" 0x9203-exposureBiasValue	= exifSubIfdTag "exposureBiasValue" 0x9204-maxApertureValue	= exifSubIfdTag "maxApertureValue" 0x9205-subjectDistance		= exifSubIfdTag "subjectDistance" 0x9206-meteringMode		= exifSubIfdTag "meteringMode" 0x9207-lightSource		= exifSubIfdTag "lightSource" 0x9208-flash			= exifSubIfdTag "flash" 0x9209-focalLength		= exifSubIfdTag "focalLength" 0x920a-subjectArea		= exifSubIfdTag "subjectArea" 0x9214-makerNote		= exifSubIfdTag "makerNote" 0x927c-userComment		= exifSubIfdTag "userComment" 0x9286-subSecTime		= exifSubIfdTag "subSecTime" 0x9290-subSecTimeOriginal	= exifSubIfdTag "subSecTimeOriginal" 0x9291-subSecTimeDigitized	= exifSubIfdTag "subSecTimeDigitized" 0x9292-flashPixVersion		= exifSubIfdTag "flashPixVersion" 0xa000-colorSpace		= exifSubIfdTag "colorSpace" 0xa001-exifImageWidth		= exifSubIfdTag "exifImageWidth" 0xa002-exifImageHeight		= exifSubIfdTag "exifImageHeight" 0xa003-relatedSoundFile	= exifSubIfdTag "relatedSoundFile" 0xa004-flashEnergy		= exifSubIfdTag "flashEnergy" 0xa20b-spatialFrequencyResponse= exifSubIfdTag "spatialFrequencyResponse" 0xa20c-focalPlaneXResolution	= exifSubIfdTag "focalPlaneXResolution" 0xa20e-focalPlaneYResolution	= exifSubIfdTag "focalPlaneYResolution" 0xa20f-focalPlaneResolutionUnit= exifSubIfdTag "focalPlaneResolutionUnit" 0xa210-subjectLocation		= exifSubIfdTag "subjectLocation" 0xa214-exposureIndex		= exifSubIfdTag "exposureIndex" 0xa215-sensingMethod		= exifSubIfdTag "sensingMethod" 0xa217-fileSource		= exifSubIfdTag "fileSource" 0xa300-sceneType		= exifSubIfdTag "sceneType" 0xa301-cfaPattern		= exifSubIfdTag "cfaPattern" 0xa302-customRendered		= exifSubIfdTag "customRendered" 0xa401-exposureMode		= exifSubIfdTag "exposureMode" 0xa402-whiteBalance		= exifSubIfdTag "whiteBalance" 0xa403-digitalZoomRatio	= exifSubIfdTag "digitalZoomRatio" 0xa404-focalLengthIn35mmFilm	= exifSubIfdTag "focalLengthIn35mmFilm" 0xa405-sceneCaptureType	= exifSubIfdTag "sceneCaptureType" 0xa406-gainControl		= exifSubIfdTag "gainControl" 0xa407-contrast		= exifSubIfdTag "contrast" 0xa408-saturation		= exifSubIfdTag "saturation" 0xa409-sharpness		= exifSubIfdTag "sharpness" 0xa40a-deviceSettingDescription= exifSubIfdTag "deviceSettingDescription" 0xa40b-subjectDistanceRange	= exifSubIfdTag "subjectDistanceRange" 0xa40c-imageUniqueId		= exifSubIfdTag "imageUniqueId" 0xa420-exifInteroperabilityOffset=exifSubIfdTag "exifInteroperabilityOffset" 0xa005--imageDescription	= exifIfd0Tag "imageDescription" 0x010e-make			= exifIfd0Tag "make" 0x010f-model			= exifIfd0Tag "model" 0x0110-orientation		= exifIfd0Tag "orientation" 0x0112-xResolution		= exifIfd0Tag "xResolution" 0x011a-yResolution		= exifIfd0Tag "xResolution" 0x011b-resolutionUnit		= exifIfd0Tag "resolutionUnit" 0x0128-software		= exifIfd0Tag "software" 0x0131-dateTime		= exifIfd0Tag "dateTime" 0x0132-artist			= exifIfd0Tag "artist" 0x013b-whitePoint		= exifIfd0Tag "whitePoint" 0x013e-primaryChromaticities	= exifIfd0Tag "primaryChromaticities" 0x013f-yCbCrCoefficients	= exifIfd0Tag "yCbCrCoefficients" 0x0211-yCbCrPositioning	= exifIfd0Tag "yCbCrPositioning" 0x0213-referenceBlackWhite	= exifIfd0Tag "referenceBlackWhite" 0x0214-copyright		= exifIfd0Tag "copyright" 0x8298-exifIfdOffset		= exifIfd0Tag "exifIfdOffset" 0x8769-gpsTagOffset		= exifIfd0Tag "gpsTagOffset" 0x8825-printImageMatching	= exifIfd0Tag "printImageMatching" 0xc4a5--gpsVersionID		= exifGpsTag "gpsVersionID" 0x0000-gpsLatitudeRef		= exifGpsTag "gpsLatitudeRef" 0x0001-gpsLatitude		= exifGpsTag "gpsLatitude" 0x0002-gpsLongitudeRef		= exifGpsTag "gpsLongitudeRef" 0x0003-gpsLongitude		= exifGpsTag "gpsLongitude" 0x0004-gpsAltitudeRef		= exifGpsTag "gpsAltitudeRef" 0x0005-gpsAltitude		= exifGpsTag "gpsAltitude" 0x0006-gpsTimeStamp		= exifGpsTag "gpsTimeStamp" 0x0007-gpsSatellites		= exifGpsTag "gpsSatellites" 0x0008-gpsStatus		= exifGpsTag "gpsStatus" 0x0009-gpsMeasureMode		= exifGpsTag "gpsMeasureMode" 0x000a-gpsDop			= exifGpsTag "gpsDop" 0x000b-gpsSpeedRef		= exifGpsTag "gpsSpeedRef" 0x000c-gpsSpeed		= exifGpsTag "gpsSpeed" 0x000d-gpsTrackRef		= exifGpsTag "gpsTrackRef" 0x000e-gpsTrack		= exifGpsTag "gpsTrack" 0x000f-gpsImgDirectionRef	= exifGpsTag "gpsImgDirectionRef" 0x0010-gpsImgDirection		= exifGpsTag "gpsImgDirection" 0x0011-gpsMapDatum		= exifGpsTag "gpsMapDatum" 0x0012-gpsDestLatitudeRef	= exifGpsTag "gpsDestLatitudeRef" 0x0013-gpsDestLatitude		= exifGpsTag "gpsDestLatitude" 0x0014-gpsDestLongitudeRef	= exifGpsTag "gpsDestLongitudeRef" 0x0015-gpsDestLongitude	= exifGpsTag "gpsDestLongitude" 0x0016-gpsDestBearingRef	= exifGpsTag "gpsDestBearingRef" 0x0017-gpsDestBearing		= exifGpsTag "gpsDestBearing" 0x0018-gpsDestDistanceRef	= exifGpsTag "gpsDestDistanceRef" 0x0019-gpsDestDistance		= exifGpsTag "gpsDestDistance" 0x001a-gpsProcessingMethod	= exifGpsTag "gpsProcessingMethod" 0x001b-gpsAreaInformation	= exifGpsTag "gpsAreaInformation" 0x001c-gpsDateStamp		= exifGpsTag "gpsDateStamp" 0x001d-gpsDifferential		= exifGpsTag "gpsDifferential" 0x001e--allExifTags :: [ExifTag]-allExifTags = [exposureTime, fnumber, exposureProgram, isoSpeedRatings,-	exifVersion, dateTimeOriginal, dateTimeDigitized, componentConfiguration,-	compressedBitsPerPixel, shutterSpeedValue, apertureValue, brightnessValue,-	exposureBiasValue, maxApertureValue, subjectDistance, meteringMode,-	lightSource, flash, focalLength, makerNote, userComment,-	exifImageWidth, exifImageHeight, relatedSoundFile, focalPlaneXResolution,-	focalPlaneYResolution, focalPlaneResolutionUnit, sensingMethod, fileSource,-	sceneType, orientation, make, model, software, copyright,-	spectralSensitivity, oecf, subjectArea, subSecTime, subSecTimeOriginal,-	subSecTimeDigitized, flashPixVersion, colorSpace, flashEnergy,-	spatialFrequencyResponse, subjectLocation, exposureIndex, cfaPattern,-	customRendered, exposureMode, whiteBalance, digitalZoomRatio,-	focalLengthIn35mmFilm, sceneCaptureType, gainControl, contrast,-	saturation, sharpness, deviceSettingDescription, subjectDistanceRange,-	imageUniqueId, exifInteroperabilityOffset, imageDescription,-	xResolution, yResolution, resolutionUnit, dateTime, whitePoint,-	primaryChromaticities, yCbCrPositioning, yCbCrCoefficients, referenceBlackWhite,-	exifIfdOffset, printImageMatching, gpsTagOffset, artist,-	gpsVersionID, gpsLatitudeRef, gpsLatitude, gpsLongitudeRef, gpsLongitude,-	gpsAltitudeRef, gpsAltitude, gpsTimeStamp, gpsSatellites, gpsStatus,-	gpsMeasureMode, gpsDop, gpsSpeedRef, gpsSpeed, gpsTrackRef, gpsTrack,-	gpsImgDirectionRef, gpsImgDirection, gpsMapDatum, gpsDestLatitudeRef,-	gpsDestLatitude, gpsDestLongitudeRef, gpsDestLongitude, gpsDestBearingRef,-	gpsDestBearing, gpsDestDistanceRef, gpsDestDistance, gpsProcessingMethod,-	gpsAreaInformation, gpsDateStamp, gpsDifferential]- getExifTag :: TagLocation -> Word16 -> ExifTag-getExifTag l v = fromMaybe (ExifTag l Nothing v) $ find (isSameTag l v) allExifTags-	where isSameTag l1 v1 (ExifTag l2 _ v2) = l1 == l2 && v1 == v2+getExifTag l v = fromMaybe (ExifTag l Nothing v show) $ find (isSameTag l v) allExifTags+	where isSameTag l1 v1 (ExifTag l2 _ v2 _) = l1 == l2 && v1 == v2  data ValueHandler = ValueHandler 	{@@ -698,6 +481,8 @@ 	second <- getCharValue ':' >> readDigit 2 	-- the realToFrac is workaround for a GHC 7.8.0->7.8.2 bug: 	-- https://ghc.haskell.org/trac/ghc/ticket/9231+	-- actually generates a warning and it's good, reminds me to+	-- remove it someday, since 7.8.3 is out and with the fix. 	return $ LocalTime (fromGregorian year month day) (TimeOfDay hour minute $ realToFrac second) 	where 		readDigit x = liftM read $ count x getDigit@@ -752,44 +537,15 @@ 		ExifNumber 8 -> Just $ Rotation Ninety 		_ -> Nothing --- | Extract the GPS latitude and longitude where the picture was taken--- (if it is present in the EXIF)-getGpsLatitudeLongitude :: Map ExifTag ExifValue -> Maybe (Double, Double)-getGpsLatitudeLongitude exifData = do-	(ExifText latRef) <- Map.lookup gpsLatitudeRef exifData-	latDec <- Map.lookup gpsLatitude exifData >>= gpsDecodeToDecimalDegrees -	let signedLatDec = case latRef of-		"S" -> -latDec-		_ -> latDec-	(ExifText longRef) <- Map.lookup gpsLongitudeRef exifData-	longDec <- Map.lookup gpsLongitude exifData >>= gpsDecodeToDecimalDegrees -	let signedLongDec = case longRef of-		"W" -> -longDec-		_ -> longDec-	return (signedLatDec, signedLongDec)--gpsDecodeToDecimalDegrees :: ExifValue -> Maybe Double-gpsDecodeToDecimalDegrees (ExifRationalList intPairs) = case fmap intPairToFloating intPairs of-			(degrees:minutes:seconds:[]) -> Just $ degrees + minutes / 60 + seconds / 3600-			_ -> Nothing-	where-		intPairToFloating (n, d) = fromIntegral n / fromIntegral d-gpsDecodeToDecimalDegrees _ = Nothing---- | Format the exif value as floating-point if it makes sense,--- otherwise use the default 'show' implementation.--- The first parameter lets you specify how many digits after--- the comma to format in the result string.--- The special behaviour applies only for 'ExifRational' and 'ExifRationalList'.-formatAsFloatingPoint :: Int -> ExifValue -> String-formatAsFloatingPoint n (ExifRational num den) = formatNumDenAsString n num den-formatAsFloatingPoint n (ExifRationalList values) = intercalate ", " $ foldl' step [] values-	where step soFar (num,den) = soFar ++ [formatNumDenAsString n num den]-formatAsFloatingPoint _ v = show v--formatNumDenAsString :: Int -> Int -> Int -> String-formatNumDenAsString n num den = printf formatString (fromIntegral num / fromIntegral den :: Double)-	where formatString = "%." ++ show n ++ "f"+-- | Will return Just True if the flash was fired, Just False+-- if it was not, and Nothing if the file does not contain+-- the information.+wasFlashFired :: Map ExifTag ExifValue -> Maybe Bool+wasFlashFired exifData = do+	flashVal <- Map.lookup flash exifData+	case flashVal of+		ExifNumber n -> Just $ n .&. 1 /= 0+		_ -> Nothing  -- $intro --@@ -798,9 +554,15 @@ -- If you use the predefined ExifTag values, you don't care about details -- of the ExifTag type, however you should check out the 'ExifValue' type. ----- You start from a JPEG file, you can parse its exif tags as a 'Map' of+-- Regarding the ExifTag type there is however a field of that type that may+-- interest you: 'prettyPrinter'. It's a function that'll format nicely an exif value+-- for that exif tag as a String.+-- For instance for the 'flash' ExifTag, it'll say whether the flash was+-- fired or not, if there was return light and so on.+--+-- Generally speaking, you start from a JPEG file, you can parse its exif tags as a 'Map' of -- 'ExifTag' to 'ExifValue' using 'parseExif' or 'parseFileExif'. -- You can enumerate the map or 'lookup' the tags that interest you. -- -- There are also a couple of higher-level helpers like 'getOrientation',--- 'getDateTimeOriginal'.+-- 'getDateTimeOriginal', 'wasFlashFired' and 'getGpsLatitudeLongitude'.
+ Graphics/PrettyPrinters.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Graphics.PrettyPrinters where++import Graphics.Types (ExifValue(..))+import Data.List (foldl')+import Text.Printf (printf)+import qualified Data.Map as Map+import Data.Char (chr)+import qualified Data.ByteString as BS+import Control.Arrow (first)++ppResolutionUnit :: ExifValue -> String+ppResolutionUnit = fromNumberMap [(1, "No absolute unit"),+			(2, "Inch"),+			(3, "Centimeter")]++ppYCbCrPositioning :: ExifValue -> String+ppYCbCrPositioning = fromNumberMap [(1, "Centered"), (2, "Co-sited")]++ppExposureProgram :: ExifValue -> String+ppExposureProgram = fromNumberMap [(0, "Not defined"),+			(1, "Manual"),+			(2, "Normal program"),+			(3, "Aperture priority"),+			(4, "Shutter priority"),+			(5, "Creative program (biased toward depth of field)"),+			(6, "Action program (biased toward fast shutter speed)"),+			(7, "Portrait mode (for closeup photos with the background out of focus)"),+			(8, "Landscape mode (for landscape photos with the background in focus)")]++ppMeteringMode :: ExifValue -> String+ppMeteringMode = fromNumberMap [(0, "Unknown"),+			(1, "Average"),+			(2, "Center-weighted average"),+			(3, "Spot"),+			(4, "MultiSpot"),+			(5, "Pattern"),+			(6, "Partial"),+			(255, "other")]++ppLightSource :: ExifValue -> String+ppLightSource = fromNumberMap [(0, "Unknown"),+			(1, "Daylight"),+			(2, "Fluorescent"),+			(3, "Tungsten (incandescent light)"),+			(4, "Flash"),+			(9, "Fine weather"),+			(10, "Cloudy weather"),+			(11, "Shade"),+			(12, "Daylight fluorescent (D 5700 - 7100K)"),+			(13, "Day white fluorescent (N 4600 - 5400K)"),+			(14, "Cool white fluorescent (W 3900 - 4500K)"),+			(15, "White fluorescent (WW 3200 - 3700K)"),+			(17, "Standard light A"),+			(18, "Standard light B"),+			(19, "Standard light C"),+			(20, "D55"),+			(21, "D65"),+			(22, "D75"),+			(23, "D50"),+			(24, "ISO studio tungsten"),+			(255, "Other light source")]++ppFlash :: ExifValue -> String+ppFlash = fromNumberMap [(0, "Flash did not fire"),+			(1, "Flash fired"),+			(5, "Strobe return light not detected"),+			(7, "Strobe return light detected"),+			(9, "Flash fired, compulsory flash mode"),+			(0x0D, "Flash fired, compulsory flash mode, return light not detected"),+			(0x0F, "Flash fired, compulsory flash mode, return light detected"),+			(0x10, "Flash did not fire, compulsory flash mode"),+			(0x18, "Flash did not fire, auto mode"),+			(0x19, "Flash fired, auto mode"),+			(0x1D, "Flash fired, auto mode, return light not detected"),+			(0x1F, "Flash fired, auto mode, return light detected"),+			(0x20, "No flash function"),+			(0x41, "Flash fired, red-eye reduction mode"),+			(0x45, "Flash fired, red-eye reduction mode, return light not detected"),+			(0x47, "Flash fired, red-eye reduction mode, return light detected"),+			(0x49, "Flash fired, compulsory flash mode, red-eye reduction mode"),+			(0x4D, "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected"),+			(0x4F, "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected"),+			(0x59, "Flash fired, auto mode, red-eye reduction mode"),+			(0x5D, "Flash fired, auto mode, return light not detected, red-eye reduction mode"),+			(0x5F, "Flash fired, auto mode, return light detected, red-eye reduction mode")]++ppColorSpace :: ExifValue -> String+ppColorSpace = fromNumberMap [(1, "sRGB"), (65535, "Uncalibrated")]++ppCustomRendered :: ExifValue -> String+ppCustomRendered = fromNumberMap [(0, "Normal process"), (1, "Custom process")]++ppExposureMode :: ExifValue -> String+ppExposureMode = fromNumberMap [(0, "Auto exposure"),+			(1, "Manual exposure"),+			(2, "Auto bracket")]++ppWhiteBalance :: ExifValue -> String+ppWhiteBalance = fromNumberMap [(0, "Auto white balance"),+			(1, "Manual white balance")]++ppSceneCaptureType :: ExifValue -> String+ppSceneCaptureType = fromNumberMap [(0, "Standard"),+			(1, "Landscape"),+			(2, "Portrait"),+			(3, "Night scene")]++ppGainControl :: ExifValue -> String+ppGainControl = fromNumberMap [(0, "Normal"),+			(1, "Low gain up"),+			(2, "High gain up"),+			(3, "Low gain down"),+			(4, "High gain down")]++ppContrastSharpness :: ExifValue -> String+ppContrastSharpness = fromNumberMap [(0, "Normal"), (1, "Soft"), (2, "Hard")]++ppSaturation :: ExifValue -> String+ppSaturation = fromNumberMap [(0, "Normal"),+			(1, "Low saturation"),+			(2, "High saturation")]++ppSensingMethod :: ExifValue -> String+ppSensingMethod = fromNumberMap [(1, "Not defined"),+			(2, "One-chip color area sensor"),+			(3, "Two-chip color area sensor"),+			(4, "Three-chip color area sensor"),+			(5, "Color sequential area sensor"),+			(7, "Trilinear sensor"),+			(8, "Color sequential linear sensor")]++ppSubjectDistanceRange :: ExifValue -> String+ppSubjectDistanceRange = fromNumberMap [(0, "Unknown"),+			(1, "Macro"),+			(2, "Close view"),+			(3, "Distance view")]++ppFocalPlaneResolutionUnit :: ExifValue -> String+ppFocalPlaneResolutionUnit = fromNumberMap [(1, "No absolute unit of measurement"),+			(2, "Inch"),+			(3, "Centimeter")]++ppOrientation :: ExifValue -> String+ppOrientation = fromNumberMap [(1, "Top-left"),+			(2, "Top-right"),+			(3, "Bottom-right"),+			(4, "Bottom-left"),+			(5, "Left-top"),+			(6, "Right-top"),+			(7, "Right-bottom"),+			(8, "Left-bottom")]++ppSceneType :: ExifValue -> String+ppSceneType = fromNumberMap [(1, "Directly photographed")]++ppGpsAltitudeRef :: ExifValue -> String+ppGpsAltitudeRef = fromNumberMap [(0, "Sea level"),+    			(1, "Below sea level")]++ppComponentConfiguration :: ExifValue -> String+ppComponentConfiguration (ExifUndefined bs) = foldl' addComponent [] numbers+	where+		numbers :: [Int] = fmap fromIntegral $ BS.unpack bs+		addComponent soFar c = soFar ++ formatComponent c+		formatComponent c = case c of+			0 -> "-"+			1 -> "Y"+			2 -> "Cb"+			3 -> "Cr"+			4 -> "R"+			5 -> "G"+			6 -> "B"+			_ -> "?"+ppComponentConfiguration v@_ = unknown v++ppFlashPixVersion :: ExifValue -> String+ppFlashPixVersion = formatVersion "FlashPix version %.1f"++ppExifVersion :: ExifValue -> String+ppExifVersion = formatVersion "Exif version %.2f"++formatVersion :: String -> ExifValue -> String+formatVersion fmt (ExifUndefined s) = printf fmt num+	where+		num :: Float = read asStr / 10.0+		asStr = fmap (chr . fromIntegral) $ BS.unpack s+formatVersion _ v@_ = unknown v++fromNumberMap :: [(Int, String)] -> ExifValue -> String+fromNumberMap m = fromMap convertedMap+	where convertedMap = fmap (first ExifNumber) m++fromMap :: [(ExifValue, String)] -> ExifValue -> String+fromMap m v = Map.findWithDefault (unknown v) v $ Map.fromList m++unknown :: Show a => a -> String+unknown v = "Unknown value: " ++ show v
+ Graphics/Types.hs view
@@ -0,0 +1,108 @@+module Graphics.Types where++import qualified Data.ByteString as BS+import Data.Word+import Numeric (showHex)+import Data.Function (on)+import Data.List+import Text.Printf (printf)++-- | An exif value.+-- +-- If you want a string describing the contents+-- of the value, simply use 'show'.+data ExifValue = ExifNumber !Int+	-- ^ An exif number. Originally it could have been short, int, signed or not.+	| ExifText !String+	-- ^ ASCII text.+	| ExifRational !Int !Int+	-- ^ A rational number (numerator, denominator).+	-- Sometimes we're used to it as rational (exposition time: 1/160),+	-- sometimes as float (exposure compensation, we rather think -0.75)+	-- 'show' will display it as 1/160.+	| ExifNumberList ![Int]+	-- ^ List of numbers. Originally they could have been short, int, signed or not.+	| ExifRationalList ![(Int,Int)]+	-- ^ A list of rational numbers (numerator, denominator).+	-- Sometimes we're used to it as rational (exposition time: 1/160),+	-- sometimes as float (exposure compensation, we rather think -0.75)+	-- 'show' will display it as 1/160.+	| ExifUndefined !BS.ByteString+	-- ^ The undefined type in EXIF means that the contents are not+	-- specified and up to the manufacturer. In effect it's exactly+	-- a bytestring. Sometimes it's text with ASCII or UNICODE at the+	-- beginning, often it's binary in nature.+	| ExifUnknown !Word16 !Int !Int+	-- ^ Unknown exif value type. All EXIF 2.3 types are+	-- supported, it could be a newer file.+	-- The parameters are type, count then value+	deriving (Eq, Ord)++instance Show ExifValue where+	show (ExifNumber v) = show v+	show (ExifText v) = v+	show (ExifRational n d) = show n ++ "/" ++ show d+	show (ExifUnknown t c v) = show "Unknown exif type. Type: " ++ show t +		++ " count: " ++ show c ++ " value: " ++ show v+	show (ExifNumberList l) = show l+	show (ExifRationalList l) = show l+	show (ExifUndefined bs) = show bs++-- | Location of the tag in the JPG file structure.+-- Normally you don't need to fiddle with this,+-- except maybe if the library doesn't know the particular+-- exif tag you're interested in.+-- Rather check the list of supported exif tags, like+-- 'exposureTime' and so on.+data TagLocation = ExifSubIFD | IFD0 | GpsSubIFD+	deriving (Show, Eq, Ord)++-- | An exif tag. Normally you don't need to fiddle with this,+-- except maybe if the library doesn't know the particular+-- exif tag you're interested in.+-- Rather check the list of supported exif tags, like+-- 'exposureTime' and so on.+data ExifTag = ExifTag+	{+		tagLocation :: TagLocation,+		-- ^ In which part of the JPEG file the exif tag was found+		tagDesc :: Maybe String,+		-- ^ Description of the exif tag (exposureTime, fnumber...) or if unknown, Nothing.+		-- This should be purely for debugging purposes, to compare tags use == on ExifTag+		-- or compare the tagKey.+		tagKey :: Word16,+		-- ^ Exif tag key, the number uniquely identifying this tag.+		prettyPrinter :: ExifValue -> String+		-- ^ A function that'll display nicely an exif value for that exif tag.+		-- For instance for the 'flash' ExifTag, it'll say whether the flash was+		-- fired or not, if there was return light and so on.+	}++instance Show ExifTag where+	show (ExifTag _ (Just d) _ _) = d+	show (ExifTag l _ v _) = "Unknown tag, location: " ++ show l+		++ ", value: 0x" ++ showHex v ""++instance Eq ExifTag where+	t1 == t2 = tagKey t1 == tagKey t2 && tagLocation t1 == tagLocation t2++instance Ord ExifTag where+	compare t1 t2 = if locCmp /= EQ then locCmp else tagCmp+		where+			locCmp = (compare `on` tagLocation) t1 t2 +			tagCmp = (compare `on` tagKey) t1 t2++-- | Format the exif value as floating-point if it makes sense,+-- otherwise use the default 'show' implementation.+-- The first parameter lets you specify how many digits after+-- the comma to format in the result string.+-- The special behaviour applies only for 'ExifRational' and 'ExifRationalList'.+formatAsFloatingPoint :: Int -> ExifValue -> String+formatAsFloatingPoint n (ExifRational num den) = formatNumDenAsString n num den+formatAsFloatingPoint n (ExifRationalList values) = intercalate ", " $ foldl' step [] values+	where step soFar (num,den) = soFar ++ [formatNumDenAsString n num den]+formatAsFloatingPoint _ v = show v++formatNumDenAsString :: Int -> Int -> Int -> String+formatNumDenAsString n num den = printf formatString (fromIntegral num / fromIntegral den :: Double)+	where formatString = "%." ++ show n ++ "f"
hsexif.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                hsexif-version:             0.5.0.2+version:             0.5.0.3 synopsis:            EXIF handling library in pure Haskell description:         The hsexif library provides functions for working with EXIF data                      contained in JPEG files. Currently it only supports reading the data.@@ -19,7 +19,7 @@  library   exposed-modules:     Graphics.HsExif-  -- other-modules:       +  other-modules:       Graphics.Types, Graphics.PrettyPrinters, Graphics.ExifTags   -- other-extensions:       build-depends:       base >=4.6 && <5,                        binary >=0.7 && <0.8,
tests/Tests.hs view
@@ -10,6 +10,7 @@ import Data.Time.LocalTime import Data.Time.Calendar import Data.Char (chr)+import Control.Monad (liftM)  main :: IO () main = do@@ -29,7 +30,9 @@ 		describe "read exif date time" testReadExifDateTime 		describe "read GPS lat long" $ testReadGpsLatLong gpsExifData 		describe "read GPS lat long -- no data" $ testReadGpsLatLongNoData exifData-		describe "test formatAsFloatingPoint" $ testFormatAsFloatingPoint+		describe "test formatAsFloatingPoint" testFormatAsFloatingPoint+		describe "pretty printing" $ testPrettyPrint gpsExifData+		describe "flash fired" $ testFlashFired exifData  testNotAJpeg :: B.ByteString -> Spec testNotAJpeg imageContents = it "returns empty list if not a JPEG" $@@ -127,6 +130,62 @@ testFormatAsFloatingPoint = it "properly formats as floating point" $ do 	assertEqual' "0.75" $ formatAsFloatingPoint 2 $ ExifRational 3 4 	assertEqual' "0.75, -0.50, 0.25" $ formatAsFloatingPoint 2 $ ExifRationalList [(3,4),(-1,2),(1,4)]++testPrettyPrint :: Map ExifTag ExifValue -> Spec+testPrettyPrint exifData = it "pretty prints many tags properly" $ do+	checkPrettyPrinter orientation "Top-left" exifData+	checkPrettyPrinter flash "Flash did not fire, auto mode" exifData+	checkPrettyPrinter exposureTime "1/200 sec." exifData+	checkPrettyPrinter exposureProgram "Normal program" exifData+	checkPrettyPrinter exifVersion "Exif version 2.30" exifData+	checkPrettyPrinter componentConfiguration "YCbCr" exifData+	checkPrettyPrinter exposureBiasValue "0.00 EV" exifData+	checkPrettyPrinter meteringMode "Pattern" exifData+	checkPrettyPrinter lightSource "Unknown" exifData+	checkPrettyPrinter flashPixVersion "FlashPix version 1.0" exifData+	checkPrettyPrinter colorSpace "sRGB" exifData+	checkPrettyPrinter customRendered "Normal process" exifData+	checkPrettyPrinter exposureMode "Auto exposure" exifData+	checkPrettyPrinter whiteBalance "Auto white balance" exifData+	checkPrettyPrinter sceneCaptureType "Standard" exifData+	checkPrettyPrinter gainControl "Normal" exifData+	checkPrettyPrinter contrast "Normal" exifData+	checkPrettyPrinter saturation "Normal" exifData+	checkPrettyPrinter resolutionUnit "Inch" exifData+	checkPrettyPrinter yCbCrPositioning "Co-sited" exifData+	checkPrettyPrinter gpsLatitude "50.217917" exifData++checkPrettyPrinter :: ExifTag -> String -> Map ExifTag ExifValue -> Assertion+checkPrettyPrinter tag str exifData = assertEqual' (Just str) $ liftM (prettyPrinter tag) $ Map.lookup tag exifData++testFlashFired :: Map ExifTag ExifValue -> Spec+testFlashFired exifData = it "properly reads whether the flash was fired" $ do+	assertEqual' (Just False) $ wasFlashFired exifData+	assertEqual' Nothing $ wasFlashFired Map.empty+	assertEqual' (Just False) $ wasFlashFired $ makeExifMapWithFlash 0+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 1+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 5+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 7+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 9+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x0D+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x0F+	assertEqual' (Just False) $ wasFlashFired $ makeExifMapWithFlash 0x10+	assertEqual' (Just False) $ wasFlashFired $ makeExifMapWithFlash 0x18+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x19+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x1D+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x1F+	assertEqual' (Just False) $ wasFlashFired $ makeExifMapWithFlash 0x20+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x41+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x45+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x47+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x4D+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x4F+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x59+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x5D+	assertEqual' (Just True) $ wasFlashFired $ makeExifMapWithFlash 0x5F++makeExifMapWithFlash :: Int -> Map ExifTag ExifValue+makeExifMapWithFlash flashV = Map.fromList [(flash, ExifNumber flashV)]  assertEqual' :: (Show a, Eq a) => a -> a -> Assertion assertEqual' = assertEqual "doesn't match"