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 +190/−0
- Graphics/HsExif.hs +25/−263
- Graphics/PrettyPrinters.hs +198/−0
- Graphics/Types.hs +108/−0
- hsexif.cabal +2/−2
- tests/Tests.hs +60/−1
+ 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"