EXIF

14 views
Skip to first unread message

Ryan

unread,
Apr 28, 2025, 1:26:36 PMApr 28
to hakyll
Hi all,

I'm using HsExif to add EXIF fields for templates with:

```
photoContext :: Context a
photoContext =
   ...
    `mappend` exifField "make" make show
    `mappend` exifField "model" model show

exifField :: String -> ExifTag -> (ExifValue -> String) -> Context a
exifField key tag print =
  field key $ \item -> do
    metadata <- exifMetadata item
    case M.lookup tag metadata of
      Nothing -> noResult ""
      Just value -> return $ print value

-- TODO don't load metadata individually for each field
exifMetadata :: Item a -> Compiler (M.Map ExifTag ExifValue)
exifMetadata item = do
  let identifier = itemIdentifier item
  exifData <- unsafeCompiler (parseFileExif (toFilePath identifier))
  return $ fromRight M.empty exifData
```

However, you might notice that the EXIF metadata is parsed for every field.

I've been thinking about how to avoid and  can't come up with anything other than some kind of caching, since we only get an `item` which we can parse the metadata from in the lambda of the `field` function which expects a `Compiler String`.

Is there a more elegant way that I'm missing?

Many thanks in advance!

The complete example can be found at: https://ryan.freumh.org/how-this-site-is-built.html

Yoo Chung

unread,
May 2, 2025, 9:32:43 PMMay 2
to hak...@googlegroups.com, ryang...@gmail.com
Consider passing in the metadata to photoContext instead of having field parse the metadata.  For example, something like

photoContext :: Map ExifTag ExifValue -> Context b
photoContext md = constField "make" (getValue md make) <>
                  constField "model" (getValue md model) <>
                  ...

match "..." $ do
  item <- getResourceLBS
  metadata <- exifMetadata item
  let photoCtx = photoContext metadata
  ...

You could also use the Compiler monad to get the item body and return the context like in the following example.  It also uses parseExif instead of parseFileExif, which allows deriving the context from items that are created, not just items that are matched.

photoContext :: Compiler (Item a)
photoContext = do
  body <- getResourceLBS
  let metadata = fromRight empty $ parseExif body
  return $ constField "make" (getValue metadata make) <>
           constField "model" (getValue metadata model) <>
           ...

match "..." $ do
  ctx <- photoContext
  ...

create ["..."] $ do
  ctx <- photoContext
  ...





--
You received this message because you are subscribed to the Google Groups "hakyll" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hakyll+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/hakyll/d7f0929a-dd4f-4972-9745-0432d08999edn%40googlegroups.com.


--

Ryan Gibb

unread,
May 4, 2025, 8:42:29 AMMay 4
to Yoo Chung, hak...@googlegroups.com
Hi Yoo Chung,

Many thanks for the response, it's very helpful and much appreciated.

I'm actually using this photoContext in a listField,

photosContext :: [Item a] -> Context String
photosContext photos =
listField "photos" photoContext (return photos)
`mappend` defaultContext

Would you have any pointers on how this could be adapted to be used in
this situation?

Best,
-- Ryan

Yoo Chung

unread,
May 27, 2025, 6:04:52 AMMay 27
to Ryan Gibb, hak...@googlegroups.com
In case you're still interested in this issue, I would still be partial to parsing the metadata in the Compiler monad.  For example, something like

match "*.jpg" $ do
  route ...
  
  -- So that the loadAll later is a Compiler (Item ByteString)
  -- I'm not actually sure if we could use copyFileCompiler here,
  -- but the loadAll would be able to be a Compiler (Item ByteString)
  -- to make the types consistent.
  getResourceLBS

match "..." $ do
  route ...
  
  metadata <- fmap (map (fmap exifMetadata)) . recentFirst =<< loadAll "*.jpg"
  -- Or equivalently,
  --   images <- loadAll "*.jpg" :: Compiler [Item ByteString]
  --   sortedImages <- recentFirst images :: Compiler [Item ByteString]
  --   let metadata = map (fmap exifMetadata) sortedImages :: [Item (Map ExifTag ExifValue)]
  -- in case this is easier to understand.
  
  let ctx = listField "photos" photoContext (return metadata) <> defaultContext
  ...

photoContext :: Map ExifTag ExifValue -> Context b
photoContext md = 
constField "make" (getValue md make) <>
                  constField "model" (getValue md model) <>
                  ...

Not having tried this out myself, I'm sure there are multiple bugs ...


Reply all
Reply to author
Forward
0 new messages