hsexif 0.2.0.1 → 0.3.0.0
raw patch · 3 files changed
+90/−23 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Graphics.HsExif: ExifRational :: Int -> Int -> ExifValue
+ Graphics.HsExif: ExifRational :: !Int -> !Int -> ExifValue
- Graphics.HsExif: ExifText :: String -> ExifValue
+ Graphics.HsExif: ExifText :: !String -> ExifValue
- Graphics.HsExif: ExifUnknown :: Word16 -> Int -> ExifValue
+ Graphics.HsExif: ExifUnknown :: !Word16 -> !Int -> ExifValue
- Graphics.HsExif: readExifDateTime :: String -> LocalTime
+ Graphics.HsExif: readExifDateTime :: String -> Maybe LocalTime
Files
- Graphics/HsExif.hs +75/−17
- hsexif.cabal +1/−1
- tests/Tests.hs +14/−5
Graphics/HsExif.hs view
@@ -1,18 +1,23 @@ {-# OPTIONS_GHC -fno-warn-missing-signatures #-} -- | Ability to work with the EXIF data contained in JPEG files. module Graphics.HsExif (- ExifTag(..),- TagLocation(..),- ExifValue(..),+ -- $intro++ -- * Main functions parseFileExif, parseExif,++ -- * Higher-level helper functions readExifDateTime, getDateTimeOriginal, getOrientation, ImageOrientation(..), RotationDirection(..), - -- now all known exif tags+ -- * The ExifValue type+ ExifValue(..),++ -- * All known exif tags exposureTime, fnumber, exposureProgram,@@ -49,6 +54,10 @@ model, software, copyright,++ -- * If you need to declare your own exif tags+ ExifTag(..),+ TagLocation(..), ) where import Data.Binary.Get@@ -56,6 +65,7 @@ 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.List import Data.Maybe (fromMaybe)@@ -74,14 +84,14 @@ -- of the value, simply use 'show'. data ExifValue = ExifNumber Int -- ^ An exif number. Could be short, int, signed or not.- | ExifText String+ | ExifText !String -- ^ ASCII text.- | ExifRational Int Int+ | 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.- | ExifUnknown Word16 Int -- type then value+ | ExifUnknown !Word16 !Int -- type then value -- ^ Unknown exif value type. Maybe float? If the JPEG file is not -- corrupted, please send it to me. deriving Eq@@ -328,21 +338,54 @@ signedInt32ToInt :: Word32 -> Int signedInt32ToInt w = fromIntegral (fromIntegral w :: Int32) +-- i had this as runGetM and reusing in parseExif,+-- sadly fail is not implemented for Either.+-- will do for now.+runMaybeGet :: Get a -> B.ByteString -> Maybe a+runMaybeGet get bs = case runGetOrFail get bs of+ Left _ -> Nothing+ Right (_,_,x) -> Just x+ -- | Decode an EXIF date time value.-readExifDateTime :: String -> LocalTime-readExifDateTime dateStr = - -- i know more elegant ways to code this.. parsec, regex, text..- -- but i don't want to bring in too many dependencies to this library.- -- the date is like "YYYY:MM:DD HH:MM:SS"- LocalTime- (fromGregorian (read $ take 4 dateStr) (read $ take 2 . drop 5 $ dateStr) (read $ take 2 . drop 8 $ dateStr))- (TimeOfDay (read $ take 2 . drop 11 $ dateStr) (read $ take 2 . drop 14 $ dateStr) (read $ take 2 . drop 17 $ dateStr))+-- Will return 'Nothing' in case parsing fails.+readExifDateTime :: String -> Maybe LocalTime+readExifDateTime dateStr = runMaybeGet getExifDateTime $ B.pack $ map (fromIntegral . ord) dateStr +getExifDateTime :: Get LocalTime+getExifDateTime = do+ year <- readDigit 4+ month <- getCharValue ':' >> readDigit 2+ day <- getCharValue ':' >> readDigit 2+ hour <- getCharValue ' ' >> readDigit 2+ minute <- getCharValue ':' >> readDigit 2+ second <- getCharValue ':' >> readDigit 2+ return $ LocalTime (fromGregorian year month day) (TimeOfDay hour minute second)+ where+ readDigit x = liftM read $ count x getDigit++count :: Int -> Get a -> Get [a]+count n p | n <= 0 = return []+ | otherwise = sequence (replicate n p)+ + -- | Extract the date and time when the picture was taken -- from the EXIF information. getDateTimeOriginal :: Map ExifTag ExifValue -> Maybe LocalTime-getDateTimeOriginal exifData = liftM (readExifDateTime . show) $ Map.lookup dateTimeOriginal exifData+getDateTimeOriginal exifData = Map.lookup dateTimeOriginal exifData >>= readExifDateTime . show +getCharWhere :: (Char->Bool) -> Get Char+getCharWhere wher = do+ char <- liftM (chr . fromIntegral) getWord8+ if wher char+ then return char+ else fail "no parse"++getDigit :: Get Char+getDigit = getCharWhere isDigit++getCharValue :: Char -> Get Char+getCharValue char = getCharWhere (==char)+ data RotationDirection = MinusNinety | Ninety | HundredAndEighty@@ -355,6 +398,7 @@ deriving (Show, Eq) -- | Extract the image orientation from the EXIF information.+-- Will return 'Nothing' on parse error. getOrientation :: Map ExifTag ExifValue -> Maybe ImageOrientation getOrientation exifData = do rotationVal <- Map.lookup orientation exifData@@ -367,4 +411,18 @@ ExifNumber 6 -> Just $ Rotation MinusNinety ExifNumber 7 -> Just $ MirrorRotation Ninety ExifNumber 8 -> Just $ Rotation Ninety- _ -> error $ show rotationVal+ _ -> Nothing++-- $intro+--+-- EXIF parsing from JPEG files.+-- EXIF tags are enumerated as ExifTag values, check 'exposureTime' for instance.+-- 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+-- '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'.
hsexif.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: hsexif-version: 0.2.0.1+version: 0.3.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
@@ -22,18 +22,19 @@ describe "basic parsing" $ testBasic imageContents describe "extract picture date" $ testDate exifData describe "image orientation" $ testOrientation exifData+ describe "read exif date time" $ testReadExifDateTime testNotAJpeg :: B.ByteString -> Spec testNotAJpeg imageContents = it "returns empty list if not a JPEG" $- assertEqual "doesn't match" (Left "Not a JPEG file") (parseExif imageContents)+ assertEqual' (Left "Not a JPEG file") (parseExif imageContents) testNoExif :: B.ByteString -> Spec testNoExif imageContents = it "returns empty list if no EXIF" $- assertEqual "doesn't match" (Left "No EXIF in JPEG") (parseExif imageContents)+ assertEqual' (Left "No EXIF in JPEG") (parseExif imageContents) testBasic :: B.ByteString -> Spec testBasic imageContents = it "parses a simple JPEG" $- assertEqual "doesn't match" (Right $ Map.fromList [+ assertEqual' (Right $ Map.fromList [ (exposureTime, ExifRational 1 160), (fnumber, ExifRational 0 10), (exposureProgram, ExifNumber 2),@@ -90,9 +91,17 @@ testDate :: Map ExifTag ExifValue -> Spec testDate exifData = it "extracts the date correctly" $- assertEqual "doesn't match" (Just $ LocalTime (fromGregorian 2013 10 2) (TimeOfDay 20 33 33))+ assertEqual' (Just $ LocalTime (fromGregorian 2013 10 2) (TimeOfDay 20 33 33)) (getDateTimeOriginal exifData) testOrientation :: Map ExifTag ExifValue -> Spec testOrientation exifData = it "reads exif orientation" $- assertEqual "doesn't match" (Just Normal) $ getOrientation exifData+ assertEqual' (Just Normal) $ getOrientation exifData++testReadExifDateTime :: Spec+testReadExifDateTime = it "reads exif date time" $ do+ assertEqual' (Just $ LocalTime (fromGregorian 2013 10 2) (TimeOfDay 20 33 33)) (readExifDateTime "2013:10:02 20:33:33")+ assertEqual' Nothing (readExifDateTime "2013:10:02 20:33:3")++assertEqual' :: (Show a, Eq a) => a -> a -> Assertion+assertEqual' = assertEqual "doesn't match"