packages feed

hsexif 0.3.0.0 → 0.4.0.0

raw patch · 3 files changed

+506/−164 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Graphics.HsExif: ExifNumberList :: ![Int] -> ExifValue
+ Graphics.HsExif: ExifRationalList :: ![(Int, Int)] -> ExifValue
+ Graphics.HsExif: ExifUndefined :: !ByteString -> ExifValue
+ Graphics.HsExif: GpsSubIFD :: TagLocation
+ Graphics.HsExif: artist :: ExifTag
+ Graphics.HsExif: cfaPattern :: ExifTag
+ Graphics.HsExif: contrast :: ExifTag
+ Graphics.HsExif: customRendered :: ExifTag
+ Graphics.HsExif: dateTime :: ExifTag
+ Graphics.HsExif: deviceSettingDescription :: ExifTag
+ Graphics.HsExif: digitalZoomRatio :: ExifTag
+ Graphics.HsExif: exifIfdOffset :: ExifTag
+ Graphics.HsExif: exifInteroperabilityOffset :: ExifTag
+ Graphics.HsExif: exposureIndex :: ExifTag
+ Graphics.HsExif: exposureMode :: ExifTag
+ Graphics.HsExif: flashEnergy :: ExifTag
+ Graphics.HsExif: flashPixVersion :: ExifTag
+ Graphics.HsExif: focalLengthIn35mmFilm :: ExifTag
+ Graphics.HsExif: gainControl :: ExifTag
+ Graphics.HsExif: gpsAltitude :: ExifTag
+ Graphics.HsExif: gpsAltitudeRef :: ExifTag
+ Graphics.HsExif: gpsAreaInformation :: ExifTag
+ Graphics.HsExif: gpsDateStamp :: ExifTag
+ Graphics.HsExif: gpsDestBearing :: ExifTag
+ Graphics.HsExif: gpsDestBearingRef :: ExifTag
+ Graphics.HsExif: gpsDestDistance :: ExifTag
+ Graphics.HsExif: gpsDestDistanceRef :: ExifTag
+ Graphics.HsExif: gpsDestLatitude :: ExifTag
+ Graphics.HsExif: gpsDestLatitudeRef :: ExifTag
+ Graphics.HsExif: gpsDestLongitude :: ExifTag
+ Graphics.HsExif: gpsDestLongitudeRef :: ExifTag
+ Graphics.HsExif: gpsDifferential :: ExifTag
+ Graphics.HsExif: gpsDop :: ExifTag
+ Graphics.HsExif: gpsImgDirection :: ExifTag
+ Graphics.HsExif: gpsImgDirectionRef :: ExifTag
+ Graphics.HsExif: gpsLatitude :: ExifTag
+ Graphics.HsExif: gpsLatitudeRef :: ExifTag
+ Graphics.HsExif: gpsLongitude :: ExifTag
+ Graphics.HsExif: gpsLongitudeRef :: ExifTag
+ Graphics.HsExif: gpsMapDatum :: ExifTag
+ Graphics.HsExif: gpsMeasureMode :: ExifTag
+ Graphics.HsExif: gpsProcessingMethod :: ExifTag
+ Graphics.HsExif: gpsSatellites :: ExifTag
+ Graphics.HsExif: gpsSpeed :: ExifTag
+ Graphics.HsExif: gpsSpeedRef :: ExifTag
+ Graphics.HsExif: gpsStatus :: ExifTag
+ Graphics.HsExif: gpsTagOffset :: ExifTag
+ Graphics.HsExif: gpsTimeStamp :: ExifTag
+ Graphics.HsExif: gpsTrack :: ExifTag
+ Graphics.HsExif: gpsTrackRef :: ExifTag
+ Graphics.HsExif: gpsVersionID :: ExifTag
+ Graphics.HsExif: imageDescription :: ExifTag
+ Graphics.HsExif: imageUniqueId :: ExifTag
+ Graphics.HsExif: oecf :: ExifTag
+ Graphics.HsExif: primaryChromaticities :: ExifTag
+ Graphics.HsExif: printImageMatching :: ExifTag
+ Graphics.HsExif: referenceBlackWhite :: ExifTag
+ Graphics.HsExif: resolutionUnit :: ExifTag
+ Graphics.HsExif: saturation :: ExifTag
+ Graphics.HsExif: sceneCaptureType :: ExifTag
+ Graphics.HsExif: sharpness :: ExifTag
+ Graphics.HsExif: spatialFrequencyResponse :: ExifTag
+ Graphics.HsExif: spectralSensitivity :: ExifTag
+ Graphics.HsExif: subSecTime :: ExifTag
+ Graphics.HsExif: subSecTimeDigitized :: ExifTag
+ Graphics.HsExif: subSecTimeOriginal :: ExifTag
+ Graphics.HsExif: subjectArea :: ExifTag
+ Graphics.HsExif: subjectDistanceRange :: ExifTag
+ Graphics.HsExif: subjectLocation :: ExifTag
+ Graphics.HsExif: whiteBalance :: ExifTag
+ Graphics.HsExif: whitePoint :: ExifTag
+ Graphics.HsExif: xResolution :: ExifTag
+ Graphics.HsExif: yCbCrCoefficients :: ExifTag
+ Graphics.HsExif: yCbCrPositioning :: ExifTag
+ Graphics.HsExif: yResolution :: ExifTag
- Graphics.HsExif: ExifNumber :: Int -> ExifValue
+ Graphics.HsExif: ExifNumber :: !Int -> ExifValue
- Graphics.HsExif: ExifUnknown :: !Word16 -> !Int -> ExifValue
+ Graphics.HsExif: ExifUnknown :: !Word16 -> !Int -> !Int -> ExifValue

Files

Graphics/HsExif.hs view
@@ -17,43 +17,120 @@ 	-- * The ExifValue type 	ExifValue(..), -	-- * All known exif tags+	-- * Most useful exif tags 	exposureTime, 	fnumber,-	exposureProgram, 	isoSpeedRatings,-	exifVersion, 	dateTimeOriginal,-	dateTimeDigitized,-	componentConfiguration,-	compressedBitsPerPixel, 	shutterSpeedValue, 	apertureValue, 	brightnessValue, 	exposureBiasValue, 	maxApertureValue,-	subjectDistance,-	meteringMode,-	lightSource, 	flash, 	focalLength,-	makerNote, 	userComment,-	colorSpace,+	orientation,+	make,+	model,+	software,+	copyright,+	digitalZoomRatio,+	focalLengthIn35mmFilm, +	artist,++	-- * GPS related exif tags++	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,++	-- * Less useful exif tags++	exifVersion,+	sensingMethod,+	fileSource,+	sceneType,+	makerNote,+	subjectDistance,+	meteringMode,+	lightSource, 	exifImageWidth, 	exifImageHeight, 	relatedSoundFile, 	focalPlaneXResolution, 	focalPlaneYResolution, 	focalPlaneResolutionUnit,-	sensingMethod,-	fileSource,-	sceneType,-	orientation,-	make,-	model,-	software,-	copyright,+	dateTimeDigitized,+	componentConfiguration,+	compressedBitsPerPixel,+	exposureProgram,+	spectralSensitivity,+	oecf,+	subjectArea,+	subSecTime,+	subSecTimeOriginal,+	subSecTimeDigitized,+	flashPixVersion,+	colorSpace,+	flashEnergy,+	spatialFrequencyResponse,+	subjectLocation,+	exposureIndex,+	cfaPattern,+	customRendered,+	exposureMode,+	whiteBalance,+	sceneCaptureType,+	gainControl,+	contrast,+	saturation,+	sharpness,+	deviceSettingDescription,+	subjectDistanceRange,+	imageUniqueId,+	exifInteroperabilityOffset,+	imageDescription,+	xResolution,+	yResolution,+	resolutionUnit,+	dateTime,+	whitePoint,+	primaryChromaticities,+	yCbCrPositioning,+	yCbCrCoefficients,+	referenceBlackWhite,+	exifIfdOffset,+	printImageMatching,+	gpsTagOffset,  	-- * If you need to declare your own exif tags 	ExifTag(..),@@ -61,29 +138,28 @@ ) where  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) import qualified Data.ByteString.Char8 as Char8 import Data.Word import Data.Char (isDigit, ord, chr)-import Data.Int (Int32)+import Data.Int (Int32, Int16, Int8) import Data.List-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, fromJust) -- TODO try to get rid of fromJust import qualified Data.Map as Map import Data.Map (Map) import Data.Time.LocalTime import Data.Time.Calendar import Numeric (showHex) --- | An exif value. Exif values can also be float,--- but this library doesn't support it yet. If you--- have JPG files containing float exif values,--- please send it!---+-- | An exif value.+--  -- If you want a string describing the contents -- of the value, simply use 'show'.-data ExifValue = ExifNumber Int-	-- ^ An exif number. Could be short, int, signed or not.+data ExifValue = ExifNumber !Int+	-- ^ An exif number. Originally it could have been short, int, signed or not. 	| ExifText !String 	-- ^ ASCII text. 	| ExifRational !Int !Int@@ -91,18 +167,38 @@ 	-- 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.-	| ExifUnknown !Word16 !Int -- type then value-	-- ^ Unknown exif value type. Maybe float? If the JPEG file is not-	-- corrupted, please send it to me.+	| 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  instance Show ExifValue where 	show (ExifNumber v) = show v 	show (ExifText v) = v 	show (ExifRational n d) = show n ++ "/" ++ show d-	show (ExifUnknown t v) = show "Unknown exif type. Type: " ++ show t ++ " value: " ++ show v+	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  -- see http://www.media.mit.edu/pia/Research/deepview/exif.html+-- and http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf+-- and http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html+-- and http://www.exiv2.org/tags.html  -- | Read EXIF data from the file you give. It's a key-value map. parseFileExif :: FilePath -> IO (Either String (Map ExifTag ExifValue))@@ -142,6 +238,10 @@ getWord32 Intel = getWord32le getWord32 Motorola = getWord32be +putWord32 :: ByteAlign -> (Word32 -> Put)+putWord32 Intel = putWord32le+putWord32 Motorola = putWord32be+ parseExifBlock :: Get (Map ExifTag ExifValue) parseExifBlock = do 	header <- getByteString 4@@ -150,14 +250,24 @@ 		$ fail "invalid EXIF header" 	tiffHeaderStart <- liftM fromIntegral bytesRead 	byteAlign <- parseTiffHeader-	(exifSubIfdOffsetW, ifdEntries) <- parseIfd byteAlign tiffHeaderStart+	(exifSubIfdOffsetW, mGpsOffsetW, ifdEntries) <- parseIfd byteAlign tiffHeaderStart 	let exifSubIfdOffset = fromIntegral $ toInteger exifSubIfdOffsetW+	gpsData <- case mGpsOffsetW of+		Nothing -> return []+		Just gpsOffsetW -> lookAhead $ parseGps gpsOffsetW byteAlign tiffHeaderStart 	-- skip to the exif offset 	bytesReadNow <- liftM fromIntegral bytesRead 	skip $ (exifSubIfdOffset + tiffHeaderStart) - bytesReadNow-	exifSubEntries <- parseExifSubIfd byteAlign tiffHeaderStart-	return $ Map.fromList $ ifdEntries ++ exifSubEntries+	exifSubEntries <- parseSubIfd byteAlign tiffHeaderStart ExifSubIFD+	return $ Map.fromList $ ifdEntries ++ exifSubEntries ++ gpsData +parseGps :: Word32 -> ByteAlign -> Int -> Get [(ExifTag, ExifValue)]+parseGps gpsOffsetW byteAlign tiffHeaderStart = do+	let gpsOffset = fromIntegral $ toInteger gpsOffsetW+	bytesReadNow <- liftM fromIntegral bytesRead+	skip $ (gpsOffset + tiffHeaderStart) - bytesReadNow+	parseSubIfd byteAlign tiffHeaderStart GpsSubIFD+ parseTiffHeader :: Get ByteAlign parseTiffHeader = do 	byteAlignV <- getByteString 2@@ -172,27 +282,30 @@ 	skip $ ifdOffset - 8 	return byteAlign -parseIfd :: ByteAlign -> Int -> Get (Word32, [(ExifTag, ExifValue)])+parseIfd :: ByteAlign -> Int -> Get (Word32, Maybe Word32, [(ExifTag, ExifValue)]) parseIfd byteAlign tiffHeaderStart = do 	dirEntriesCount <- liftM toInteger (getWord16 byteAlign) 	ifdEntries <- mapM (\_ -> parseIfEntry byteAlign) [1..dirEntriesCount]-	let exifOffsetEntry = fromMaybe (error "Can't find the exif offset in the IFD")-		(find (\ e -> entryTag e == 0x8769) ifdEntries)+	let exifOffsetEntry = fromMaybe (error "Can't find the exif ifd offset")+		(find (\ e -> entryTag e == tagKey exifIfdOffset) ifdEntries) 	let exifOffset = entryContents exifOffsetEntry++	let gpsOffsetEntry = find (\ e -> entryTag e == tagKey gpsTagOffset) ifdEntries+	let gpsOffset = fmap entryContents gpsOffsetEntry 	entries <- mapM (decodeEntry byteAlign tiffHeaderStart IFD0) ifdEntries-	return (exifOffset, entries)+	return (exifOffset, gpsOffset, entries) -parseExifSubIfd :: ByteAlign -> Int -> Get [(ExifTag, ExifValue)]-parseExifSubIfd byteAlign tiffHeaderStart = do+parseSubIfd :: ByteAlign -> Int -> TagLocation -> Get [(ExifTag, ExifValue)]+parseSubIfd byteAlign tiffHeaderStart location = do 	dirEntriesCount <- liftM toInteger (getWord16 byteAlign) 	ifdEntries <- mapM (\_ -> parseIfEntry byteAlign) [1..dirEntriesCount]-	mapM (decodeEntry byteAlign tiffHeaderStart ExifSubIFD) ifdEntries+	mapM (decodeEntry byteAlign tiffHeaderStart location) ifdEntries  data IfEntry = IfEntry 	{ 		entryTag :: !Word16, 		entryFormat :: !Word16,-		entryNoComponents :: !Word32,+		entryNoComponents :: !Int, 		entryContents :: !Word32 	} deriving Show @@ -206,7 +319,7 @@ 		{ 			entryTag = tagNumber, 			entryFormat = dataFormat,-			entryNoComponents = numComponents,+			entryNoComponents = fromIntegral $ toInteger numComponents, 			entryContents = value 		} @@ -216,7 +329,7 @@ -- exif tag you're interested in. -- Rather check the list of supported exif tags, like -- 'exposureTime' and so on.-data TagLocation = ExifSubIFD | IFD0+data TagLocation = ExifSubIFD | IFD0 | GpsSubIFD 	deriving (Show, Eq, Ord)  -- | An exif tag. Normally you don't need to fiddle with this,@@ -248,95 +361,319 @@ 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-isoSpeedRatings		= exifSubIfdTag "isoSpeedRatings" 0x8827 -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 -makerNote		= exifSubIfdTag "makerNote" 0x927c -userComment		= exifSubIfdTag "userComment" 0x9286 -colorSpace		= exifSubIfdTag "colorSpace" 0xa001 -exifImageWidth		= exifSubIfdTag "exifImageWidth" 0xa002 -exifImageHeight		= exifSubIfdTag "exifImageHeight" 0xa003 -relatedSoundFile	= exifSubIfdTag "relatedSoundFile" 0xa004 -focalPlaneXResolution	= exifSubIfdTag "focalPlaneXResolution" 0xa20e -focalPlaneYResolution	= exifSubIfdTag "focalPlaneYResolution" 0xa20f -focalPlaneResolutionUnit= exifSubIfdTag "focalPlaneResolutionUnit" 0xa210 -sensingMethod		= exifSubIfdTag "sensingMethod" 0xa217 -fileSource		= exifSubIfdTag "fileSource" 0xa300 -sceneType		= exifSubIfdTag "sceneType" 0xa301 -orientation		= exifIfd0Tag "orientation" 0x0112 -make			= exifIfd0Tag "make" 0x010f -model			= exifIfd0Tag "model" 0x0110 -software		= exifIfd0Tag "software" 0x0131 -copyright		= exifIfd0Tag "copyright" 0x8298 +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, colorSpace,+	lightSource, flash, focalLength, makerNote, userComment, 	exifImageWidth, exifImageHeight, relatedSoundFile, focalPlaneXResolution, 	focalPlaneYResolution, focalPlaneResolutionUnit, sensingMethod, fileSource,-	sceneType, orientation, make, model, software, copyright]+	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 +data ValueHandler = ValueHandler+	{+		dataTypeId :: Word16,+		dataLength :: Int,+		readSingle :: ByteAlign -> Get ExifValue,+		readMany :: ByteAlign -> Int -> Get ExifValue+	}++readNumberList decoder = \byteAlign components -> liftM (ExifNumberList . fmap fromIntegral)+			$ readManyInternal byteAlign components+			where readManyInternal byteAlign components = count components (decoder byteAlign)++unsignedByteValueHandler = ValueHandler+	{+		dataTypeId = 1,+		dataLength = 1,+		readSingle = \_ -> liftM (ExifNumber . fromIntegral) getWord8,+		readMany = readNumberList (\_ -> getWord8)+	}++asciiStringValueHandler = ValueHandler+	{+		dataTypeId = 2,+		dataLength = 1,+		readSingle = \ba -> readMany asciiStringValueHandler ba 1,+		readMany = \_ components -> liftM (ExifText . Char8.unpack) (getByteString (components-1))+	}++unsignedShortValueHandler = ValueHandler+	{+		dataTypeId = 3,+		dataLength = 2,+		readSingle = \byteAlign -> liftM (ExifNumber . fromIntegral) $ getWord16 byteAlign,+		readMany = readNumberList getWord16+	}++unsignedLongValueHandler = ValueHandler+	{+		dataTypeId = 4,+		dataLength = 4,+		readSingle = \byteAlign -> liftM (ExifNumber . fromIntegral) $ getWord32 byteAlign,+		readMany = readNumberList getWord32+	}++readRationalContents :: (Int -> Int -> a) -> ByteAlign -> Get a+readRationalContents c byteAlign = do+	numerator <- liftM fromIntegral $ getWord32 byteAlign+	denominator <- liftM fromIntegral $ getWord32 byteAlign+	return $ c numerator denominator++unsignedRationalValueHandler = ValueHandler+	{+		dataTypeId = 5,+		dataLength = 8,+		readSingle = readRationalContents ExifRational,+		readMany = let readManyInternal byteAlign components = count components (readRationalContents (,) byteAlign) in+			\byteAlign components -> liftM ExifRationalList $ readManyInternal byteAlign components+	}++signedByteValueHandler = ValueHandler+	{+		dataTypeId = 6,+		dataLength = 1,+		readSingle = \_ -> liftM (ExifNumber . signedInt8ToInt) getWord8,+		readMany = readNumberList (\_ -> liftM signedInt8ToInt getWord8)+	}++undefinedValueHandler = ValueHandler+	{+		dataTypeId = 7,+		dataLength = 1,+		readSingle = \ba -> readMany undefinedValueHandler ba 1,+		readMany = \_ components -> liftM ExifUndefined (getByteString (components-1))+	}++signedShortValueHandler = ValueHandler+	{+		dataTypeId = 8,+		dataLength = 2,+		readSingle = \byteAlign -> liftM (ExifNumber . signedInt16ToInt) $ getWord16 byteAlign,+		readMany = readNumberList (\b -> liftM signedInt16ToInt $ getWord16 b)+	}++signedLongValueHandler = ValueHandler+	{+		dataTypeId = 9,+		dataLength = 4,+		readSingle = \byteAlign -> liftM (ExifNumber . signedInt32ToInt) $ getWord32 byteAlign,+		readMany = readNumberList (\b -> liftM signedInt32ToInt $ getWord32 b)+	}++readSignedRationalContents :: (Int -> Int -> a) -> ByteAlign -> Get a+readSignedRationalContents c byteAlign = do+	numerator <- liftM signedInt32ToInt $ getWord32 byteAlign+	denominator <- liftM signedInt32ToInt $ getWord32 byteAlign+	return $ c numerator denominator++signedRationalValueHandler = ValueHandler+	{+		dataTypeId = 10,+		dataLength = 8,+		readSingle = readSignedRationalContents ExifRational,+		readMany = let readManyInternal byteAlign components = count components (readSignedRationalContents (,) byteAlign) in+			\byteAlign components -> liftM ExifRationalList $ readManyInternal byteAlign components+	}++-- ascii string is special for now.+valueHandlers :: [ValueHandler]+valueHandlers =+	[+		unsignedByteValueHandler,+		asciiStringValueHandler,+		unsignedShortValueHandler,+		unsignedLongValueHandler,+		unsignedRationalValueHandler,+		signedByteValueHandler,+		signedShortValueHandler,+		signedLongValueHandler,+		signedRationalValueHandler,+		undefinedValueHandler+	]+ decodeEntry :: ByteAlign -> Int -> TagLocation -> IfEntry -> Get (ExifTag, ExifValue) decodeEntry byteAlign tiffHeaderStart location entry = do 	let exifTag = getExifTag location $ entryTag entry 	let contentsInt = fromIntegral $ toInteger $ entryContents entry-	let componentsInt = fromIntegral $ toInteger $ entryNoComponents entry 	-- because I only know how to skip ahead, I hope the entries 	-- are always sorted in order of the offsets to their values... 	-- (maybe lookAhead could help here?)-	tagValue <- case entryFormat entry of-		1 -> return $ ExifNumber contentsInt -- unsigned byte-		2 -> do -- ascii string-			curPos <- liftM fromIntegral bytesRead-			skip $ contentsInt + tiffHeaderStart - curPos-			liftM (ExifText . Char8.unpack) (getByteString (componentsInt-1))-		3 -> return $ ExifNumber contentsInt -- unsigned short-		4 -> return $ ExifNumber contentsInt -- unsigned long-		5 -> do -- unsigned rational-			curPos <- liftM fromIntegral bytesRead-			skip $ contentsInt + tiffHeaderStart - curPos-			numerator <- liftM fromIntegral $ getWord32 byteAlign-			denominator <- liftM fromIntegral $ getWord32 byteAlign-			return $ ExifRational numerator denominator-		6 -> return $ ExifNumber $ signedInt32ToInt $ entryContents entry -- signed byte-		7 -> return $ ExifNumber contentsInt -- undefined-		8 -> return $ ExifNumber $ signedInt32ToInt $ entryContents entry -- signed short-		9 -> return $ ExifNumber $ signedInt32ToInt $ entryContents entry -- signed long-		10 -> do -- signed rational-			curPos <- liftM fromIntegral bytesRead-			skip $ contentsInt + tiffHeaderStart - curPos-			numerator <- liftM signedInt32ToInt (getWord32 byteAlign)-			denominator <- liftM signedInt32ToInt (getWord32 byteAlign)-			return $ ExifRational numerator denominator-		-- TODO decode float values, 11 single float and 12 double float but I'd like tests-		_ -> return $ ExifUnknown (entryFormat entry) contentsInt+	tagValue <- case getHandler $ entryFormat entry of+			Just handler -> decodeEntryWithHandler byteAlign tiffHeaderStart handler entry+			Nothing -> return $ ExifUnknown (entryFormat entry) (entryNoComponents entry) contentsInt 	return (exifTag, tagValue) +getHandler :: Word16 -> Maybe ValueHandler+getHandler typeId = find ((==typeId) . dataTypeId) valueHandlers++decodeEntryWithHandler :: ByteAlign -> Int -> ValueHandler -> IfEntry -> Get ExifValue+decodeEntryWithHandler byteAlign tiffHeaderStart handler entry = do+	if dataLength handler * (entryNoComponents entry) <= 4+		then return $ parseInline byteAlign handler entry inlineBs+		else parseOffset byteAlign tiffHeaderStart handler entry+	where+		inlineBs = runPut $ putWord32 byteAlign $ entryContents entry++parseInline :: ByteAlign -> ValueHandler -> IfEntry -> B.ByteString -> ExifValue+parseInline byteAlign handler entry bytestring =+	fromJust $ runMaybeGet getter bytestring+	where+		getter = case entryNoComponents entry of+			1 -> readSingle handler byteAlign+			_ -> readMany handler byteAlign $ entryNoComponents entry++parseOffset :: ByteAlign -> Int -> ValueHandler -> IfEntry -> Get ExifValue+parseOffset byteAlign tiffHeaderStart handler entry = do+	let contentsInt = fromIntegral $ toInteger $ entryContents entry+	curPos <- liftM fromIntegral bytesRead+	skip $ contentsInt + tiffHeaderStart - curPos+	bytestring <- getLazyByteString (fromIntegral $ entryNoComponents entry * dataLength handler)+	return $ parseInline byteAlign handler entry bytestring+ signedInt32ToInt :: Word32 -> Int signedInt32ToInt w = fromIntegral (fromIntegral w :: Int32)++signedInt16ToInt :: Word16 -> Int+signedInt16ToInt w = fromIntegral (fromIntegral w :: Int16)++signedInt8ToInt :: Word8 -> Int+signedInt8ToInt w = fromIntegral (fromIntegral w :: Int8)  -- i had this as runGetM and reusing in parseExif, -- sadly fail is not implemented for Either.
hsexif.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                hsexif-version:             0.3.0.0+version:             0.4.0.0 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.
tests/Tests.hs view
@@ -1,13 +1,15 @@+{-# LANGUAGE OverloadedStrings #-} import Test.Hspec import Test.HUnit  import Graphics.HsExif import qualified Data.ByteString.Lazy as B+import qualified Data.ByteString as BS import qualified Data.Map as Map import Data.Map (Map) import Data.Time.LocalTime import Data.Time.Calendar-import Data.Word+import Data.Char (chr)  main :: IO () main = do@@ -33,61 +35,64 @@ 	assertEqual' (Left "No EXIF in JPEG") (parseExif imageContents)  testBasic :: B.ByteString -> Spec-testBasic imageContents = it "parses a simple JPEG" $-	assertEqual' (Right $ Map.fromList [-		(exposureTime, ExifRational 1 160),-		(fnumber, ExifRational 0 10),-		(exposureProgram, ExifNumber 2),-		(isoSpeedRatings, ExifNumber 1600),-		(exifVersion, ExifNumber 808661552),-		(dateTimeOriginal, ExifText "2013:10:02 20:33:33"),-		(dateTimeDigitized, ExifText "2013:10:02 20:33:33"),-		(componentConfiguration, ExifNumber 197121),-		(compressedBitsPerPixel, ExifRational 2 1),-		(brightnessValue, ExifRational (-4226) 2560),-		(exposureBiasValue, ExifRational 0 10),-		(maxApertureValue, ExifRational 0 10),-		(meteringMode, ExifNumber 5),-		(lightSource, ExifNumber 0),-		(fileSource, ExifNumber 3),-		(flash, ExifNumber 24),-		(focalLength, ExifRational 0 10),-		(makerNote, ExifNumber 868),-		(userComment, ExifNumber 36568),-		(unknownExifSubIfdTag 40960, ExifNumber 808464688),-		(colorSpace, ExifNumber 1),-		(sceneType, ExifNumber 1),-		(exifImageWidth, ExifNumber 1),-		(exifImageHeight, ExifNumber 1),-		(unknownExifSubIfdTag 40965, ExifNumber 36640),-		(unknownExifSubIfdTag 41985, ExifNumber 0),-		(unknownExifSubIfdTag 41986, ExifNumber 0),-		(unknownExifSubIfdTag 41987, ExifNumber 0),-		(unknownExifSubIfdTag 41988, ExifRational 16 16),-		(unknownExifSubIfdTag 41989, ExifNumber 0),-		(unknownExifSubIfdTag 41990, ExifNumber 0),-		(unknownExifSubIfdTag 41992, ExifNumber 0),-		(unknownExifSubIfdTag 41993, ExifNumber 0),-		(unknownExifSubIfdTag 41994, ExifNumber 0),-		(make, ExifText "SONY"),-		(model, ExifText "NEX-3N"),-		(software, ExifText "GIMP 2.8.10"),-		(orientation, ExifNumber 1),-		(unknownExifIfd0Tag 0x10e, ExifText "                               "),-		(unknownExifIfd0Tag 0x11a, ExifRational 350 1),-		(unknownExifIfd0Tag 0x11b, ExifRational 350 1),-		(unknownExifIfd0Tag 0x128, ExifNumber 2),-		(unknownExifIfd0Tag 0x132, ExifText "2014:04:10 20:14:20"),-		(unknownExifIfd0Tag 0x213, ExifNumber 2),-		(unknownExifIfd0Tag 0x8769, ExifNumber 358),-		(unknownExifIfd0Tag 0xc4a5, ExifNumber 252)-	]) (parseExif imageContents)--unknownExifSubIfdTag :: Word16 -> ExifTag-unknownExifSubIfdTag = ExifTag ExifSubIFD Nothing--unknownExifIfd0Tag :: Word16 -> ExifTag-unknownExifIfd0Tag = ExifTag IFD0 Nothing+testBasic imageContents = it "parses a simple JPEG" $ do+	assertEqual' +		(Map.fromList+		[+			(exposureTime, ExifRational 1 160),+			(fnumber, ExifRational 0 10),+			(exposureProgram, ExifNumber 2),+			(isoSpeedRatings, ExifNumber 1600),+			(exifVersion, ExifUndefined "023"),+			(dateTimeOriginal, ExifText "2013:10:02 20:33:33"),+			(dateTimeDigitized, ExifText "2013:10:02 20:33:33"),+			(componentConfiguration, ExifUndefined "\SOH\STX\ETX"),+			(compressedBitsPerPixel, ExifRational 2 1),+			(brightnessValue, ExifRational (-4226) 2560),+			(exposureBiasValue, ExifRational 0 10),+			(maxApertureValue, ExifRational 0 10),+			(meteringMode, ExifNumber 5),+			(lightSource, ExifNumber 0),+			(fileSource, ExifUndefined ""),+			(flash, ExifNumber 24),+			(focalLength, ExifRational 0 10),+			(userComment, ExifUndefined "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL"),+			(flashPixVersion, ExifUndefined "010"),+			(colorSpace, ExifNumber 1),+			(sceneType, ExifUndefined ""),+			(exifImageWidth, ExifNumber 1),+			(exifImageHeight, ExifNumber 1),+			(exifInteroperabilityOffset, ExifNumber 36640),+			(customRendered, ExifNumber 0),+			(exposureMode, ExifNumber 0),+			(whiteBalance, ExifNumber 0),+			(digitalZoomRatio, ExifRational 16 16),+			(focalLengthIn35mmFilm, ExifNumber 0),+			(contrast, ExifNumber 0),+			(saturation, ExifNumber 0),+			(sceneCaptureType, ExifNumber 0),+			(sharpness, ExifNumber 0),+			(make, ExifText "SONY"),+			(model, ExifText "NEX-3N"),+			(software, ExifText "GIMP 2.8.10"),+			(orientation, ExifNumber 1),+			(imageDescription, ExifText "                               "),+			(xResolution, ExifRational 350 1),+			(yResolution, ExifRational 350 1),+			(resolutionUnit, ExifNumber 2),+			(dateTime, ExifText "2014:04:10 20:14:20"),+			(yCbCrPositioning, ExifNumber 2),+			(exifIfdOffset, ExifNumber 358),+			(printImageMatching, ExifUndefined "PrintIM\NUL0300\NUL\NUL\ETX\NUL\STX\NUL\SOH\NUL\NUL\NUL\ETX\NUL\"\NUL\NUL\NUL\SOH\SOH\NUL\NUL\NUL\NUL\t\DC1\NUL\NUL\DLE'\NUL\NUL\v\SI\NUL\NUL\DLE'\NUL\NUL\151\ENQ\NUL\NUL\DLE'\NUL\NUL\176\b\NUL\NUL\DLE'\NUL\NUL\SOH\FS\NUL\NUL\DLE'\NUL\NUL^\STX\NUL\NUL\DLE'\NUL\NUL\139\NUL\NUL\NUL\DLE'\NUL\NUL\203\ETX\NUL\NUL\DLE'\NUL\NUL\229\ESC\NUL\NUL\DLE'\NUL")+		]) cleanedParsed+	-- the sony maker note is 35k!! Just test its size and that it starts with "SONY DSC".+	assertEqual' 35698 (BS.length makerNoteV)+	assertEqual' "SONY DSC" (take 8 $ fmap (chr . fromIntegral) $ BS.unpack makerNoteV)+	where+		-- the makerNote is HUGE. so test it separately.+		parsed = (\(Right x) -> x) $ parseExif imageContents+		cleanedParsed = Map.fromList $ filter (\(a,_) -> not (a==makerNote)) $ Map.toList parsed+		makerNoteV = (\(Just (ExifUndefined x)) -> x) $ Map.lookup makerNote parsed  testDate :: Map ExifTag ExifValue -> Spec testDate exifData = it "extracts the date correctly" $