Sitelet https://github.com/danielsz/beeld
Skip to content

Latest commit

 

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

[[https://clojars.org/com.github.danielsz/beeld][https://img.shields.io/clojars/v/com.github.danielsz/beeld.svg]]


* Beeld

Beeld is a Clojure library for reading and writing image metadata. It
also provides image scaling (with EXIF orientation awareness), format
detection, and I/O utilities. It works transparently with local files,
remote URLs, and in-memory byte arrays.

*Highlights:*

- Read EXIF, IPTC, XMP, ICC, and GPS metadata from JPEG and other
  image formats.
- Write IPTC captions, keywords, EXIF descriptions, and XMP data.
- Detect image format and MIME type.
- Convert images to byte arrays or Base64-encoded strings.

This enables use cases such as: 

- Scale images respecting the EXIF orientation tag (something Java's
  ImageIO does not do).
- Create a naming scheme for a photo library where parts of the name
  gets extracted from metadata.
- Detect the format of an image so that the correct mime-type can be
  appended to the headers in a web service.

** Metadata

Image formats embed standardized information about themselves.
Multiple information standards may coexist in the same image, for
example EXIF, IPTC, ICC and XMP. The EXIF standard specifies a special
tag called /Makernote/ that allows camera manufacturers to add their
proprietary tags.

Over the years, software solutions for retrieving and preserving
metadata have coalesced around a handful of flagship projects, with
Phil Harvey's [[https://exiftool.org/][Exiftool]] reigning supreme. For Java, the go-to
solution is Drew Noakes's [[https://github.com/drewnoakes/metadata-extractor][metadata extractor]]. This library depends
on the latter for reading and on Apache Commons Imaging for writing.

Most users will only need the ~beeld.core~ namespace.

** beeld.core

~beeld.core~ defines the =Beeld= protocol with methods that accept the
same arguments as ~clojure.java.io/input-stream~: ~File~, ~URI~,
~URL~, ~byte-array~, ~InputStream~, and ~String~. A string argument is
resolved first as a URI, then as a local file name. This means you can
query metadata of images over the wire.

*** Reading metadata

#+begin_src clojure
(require '[beeld.core :as beeld])
;; All metadata, returned as a map of directories → maps of tag-name → value
(beeld/exif-tags "path/to/your/image.jpg")
;;or
(beeld/exif-tags "https://somewhere.com/your/image.jpg")

;; A single tag by name
(beeld/exif-tag "path/to/your/image.jpg" "Image Width")

;; Returns a map of maps
#+end_src

The data structure returned by ~exif-tags~ is a map of maps organized
around standardized directories — thematic groupings of information
such as ~Exif SubIFD~, ~File Type~, ~GPS~, etc.

*** Image properties

#+begin_src clojure
(beeld/image-width "photo.jpg")   ;; => 4032
(beeld/image-height "photo.jpg")  ;; => 3024
(beeld/aspect-ratio "photo.jpg")  ;; => 4/3
(beeld/filesize "photo.jpg")      ;; => 2847631 (bytes, works on URLs too)
(beeld/filename "photo.jpg")      ;; => "photo.jpg"
#+end_src

*** Scaling

~scale~ resizes an image and returns a byte array. It reads the EXIF
orientation tag and rotates the image accordingly.

#+begin_src clojure
;; Default: 750×750 pixels
(beeld/scale "photo.jpg")

;; Explicit dimensions
(beeld/scale "photo.jpg" 1024 768)

;; With quality setting (defaults to :speed)
(beeld/scale "photo.jpg" 1024 768 :ultra-quality)
#+end_src

*** Format and MIME type

#+begin_src clojure
(beeld/detect-image-format "photo.jpg")   ;; => ["JPEG"]
(beeld/mime-type "photo.jpg")             ;; => "image/jpeg"
#+end_src

*** I/O utilities

#+begin_src clojure
;; Convert image to byte array
(beeld/->bytes "photo.jpg")

;; Convert image to Base64 string (e.g. for HTML data URIs)
(beeld/->base64 "photo.jpg")

;; Write to a destination
(beeld/write image-bytes "/tmp/copy.jpg")             ;; 2-arity: exact path
(beeld/write "photo.jpg" "copy.jpg" "/tmp/")          ;; 3-arity: name + dir
(beeld/write "photo.jpg")                             ;; 1-arity: writes to system tmpdir
#+end_src

*** Full protocol reference

All methods of the =Beeld= protocol:

| Method              |   Arity | Description                                                   |
|---------------------+---------+---------------------------------------------------------------|
| ~filename~            |       1 | Returns filename as a string                                  |
| ~filesize~            |       1 | Returns file size in bytes (Content-Length for URLs)          |
| ~image-width~         |       1 | Image width in pixels                                         |
| ~image-height~        |       1 | Image height in pixels                                        |
| ~aspect-ratio~        |       1 | Width divided by height                                       |
| ~exif-tags~           |       1 | All metadata as a nested map                                  |
| ~exif-tag~            |       2 | Single tag value by name                                      |
| ~->bytes~             |       1 | Image content as a byte array                                 |
| ~->base64~            |       1 | Image content as a Base64-encoded string                      |
| ~detect-image-format~ |       1 | Returns list of detected format names (e.g. ["JPEG"])         |
| ~mime-type~           |       1 | Returns MIME type string                                      |
| ~write~               | 1, 2, 3 | Writes image to a file or directory                           |
| ~scale~               | 1, 2, 3 | Scales image with orientation correction                      |
| ~clone~               |       2 | Creates n independent InputStreams from a BufferedInputStream |


** beeld.metadata

~beeld.metadata~ offers targeted convenience functions that are more
performant than searching by tag name. They read directly from known
metadata directories using Java class references.

*** Camera info

#+begin_src clojure
(require '[beeld.metadata :as m])

(m/make "photo.jpg")        ;; => "FUJIFILM"
(m/model "photo.jpg")       ;; => "X-T3"
#+end_src

*** Lens info

#+begin_src clojure
(m/lens "photo.jpg")               ;; => "XF35mmF1.4 R"
(m/lens-make "photo.jpg")          ;; => "FUJIFILM"
(m/lens-model "photo.jpg")         ;; => "XF35mmF1.4 R"
(m/lens-specification "photo.jpg") ;; => "35mm f/1.4"
(m/focal-length "photo.jpg")       ;; => "35.0 mm"
#+end_src

*** Exposure

#+begin_src clojure
(m/aperture "photo.jpg")       ;; => "f/5.6"
(m/fnumber "photo.jpg")        ;; => "f/5.6"
(m/shutter-speed "photo.jpg")  ;; => "1/500"
(m/exposure-time "photo.jpg")  ;; => "1/500"
(m/iso "photo.jpg")            ;; => "ISO 200"
(m/compression "photo.jpg")    ;; => "JPEG (old-style)"
#+end_src

*** Orientation and image properties

#+begin_src clojure
(m/orientation "photo.jpg")             ;; => "Top, left side (Horizontal / normal)"
(m/image-width "photo.jpg")             ;; => "4032"
(m/image-height "photo.jpg")            ;; => "3024"
(m/mime-type "photo.jpg")               ;; => "image/jpeg"
(m/compression-type "photo.jpg")        ;; => "Baseline"
(m/data-precision "photo.jpg")          ;; => "8 bits"
(m/number-of-tags "photo.jpg")          ;; => 142
#+end_src

*** Dates

#+begin_src clojure
(m/original-date "photo.jpg")  ;; => #inst "2024-01-15T10:30:00"
#+end_src

*** Descriptions and captions

#+begin_src clojure
(m/description-exif "photo.jpg")   ;; EXIF image description
(m/description-xmp "photo.jpg")    ;; XMP dc:description
(m/caption "photo.jpg")            ;; IPTC caption
(m/filename "photo.jpg")           ;; filename from filesystem metadata
#+end_src

*** GPS

#+begin_src clojure
(m/geolocation "photo.jpg")  ;; => GeoLocation object with latitude/longitude
(m/gps-date "photo.jpg")     ;; => GPS timestamp as java.util.Date
#+end_src

*** Fujifilm film simulation

#+begin_src clojure
(m/simulation "photo.jpg")   ;; => "Classic Chrome"
#+end_src

*** Tag access at different levels

#+begin_src clojure
(m/tags "photo.jpg")    ;; Map of maps: {"Directory" {"Tag" "Value"}}
(m/tags* "photo.jpg")   ;; Vector of maps, grouped by directory
(m/tags** "photo.jpg")  ;; Sequence of raw Tag objects (metadata-extractor)
#+end_src

*** Complete function list

| Function           | Description                                   |
|--------------------+-----------------------------------------------|
| ~aperture~           | Lens aperture (e.g. "f/5.6")                  |
| ~caption~            | IPTC caption                                  |
| ~compression~        | EXIF compression type                         |
| ~compression-type~   | JPEG compression type                         |
| ~data-precision~     | JPEG data precision (bits)                    |
| ~description-exif~   | EXIF image description                        |
| ~description-xmp~    | XMP dc:description                            |
| ~exif-tag~ (macro)   | Low-level: tag by directory class + tag const |
| ~exposure-time~      | Exposure time (e.g. "1/500")                  |
| ~filename~           | Filename from filesystem metadata             |
| ~fnumber~            | F-number (e.g. "f/5.6")                       |
| ~focal-length~       | Focal length (e.g. "35.0 mm")                 |
| ~geolocation~        | GPS GeoLocation object                        |
| ~get-tag-by-name~    | Search all tags by name                       |
| ~gps-date~           | GPS timestamp                                 |
| ~image-height~       | Image height                                  |
| ~image-width~        | Image width                                   |
| ~iso~                | ISO speed rating                              |
| ~lens~               | Lens name                                     |
| ~lens-make~          | Lens manufacturer                             |
| ~lens-model~         | Lens model                                    |
| ~lens-specification~ | Lens specification (e.g. "35mm f/1.4")        |
| ~make~               | Camera make                                   |
| ~mime-type~          | Detected MIME type                            |
| ~model~              | Camera model                                  |
| ~number-of-tags~     | Total tag count across all directories        |
| ~orientation~        | EXIF orientation                              |
| ~original-date~      | Original date/time as java.util.Date          |
| ~shutter-speed~      | Shutter speed (e.g. "1/500")                  |
| ~simulation~         | Fujifilm film simulation mode                 |
| ~tags~               | All metadata as map of maps                   |
| ~tags*~              | All metadata as vector of maps                |
| ~tags**~             | All metadata as seq of Tag objects            |

** Metadata Writing

~beeld.metadata-extractor.writer~ writes metadata to JPEG files using
Apache Commons Imaging. All functions write to a /new file/ rather
than modifying the original.

#+begin_src clojure
(require '[beeld.metadata-extractor.writer :as w])

;; Write an IPTC caption
(w/write-iptc-caption "photo.jpg" "photo-tagged.jpg" "A beautiful sunset over the harbor")

;; Write IPTC caption and keywords
(w/write-iptc-caption-and-keywords "photo.jpg" "photo-tagged.jpg"
  "A beautiful sunset" "#sunset, #harbor, #golden-hour")

;; Write arbitrary IPTC fields
(w/write-iptc-fields "photo.jpg" "photo-tagged.jpg"
  [[IptcTypes/CAPTION_ABSTRACT "A caption"]
   [IptcTypes/COPYRIGHT_NOTICE "© 2024 Jane Doe"]])

;; Write an EXIF image description
(w/write-exif-description "photo.jpg" "photo-tagged.jpg" "Sunset over the harbor at low tide")

;; Preserve XMP data when rewriting
(w/write-xmp "photo.jpg" "photo-tagged.jpg")
#+end_src


| Function                        | Description                                      |
|---------------------------------+--------------------------------------------------|
| ~write-iptc~                      | Write IPTC records and raw blocks                |
| ~write-iptc-fields~               | Append IPTC fields, preserving existing metadata |
| ~write-iptc-caption~              | Convenience: write CAPTION_ABSTRACT               |
| ~write-iptc-caption-and-keywords~ | Write caption and keywords in one call           |
| ~write-exif-description~          | Write ImageDescription EXIF tag                  |
| ~write-xmp~                       | Preserve XMP XML data to new file                |


** Contributing

Additional convenience functions in the ~beeld.metadata~ namespace are
welcome. Anything else requires preliminary discussion and vetting.

About

Get the metadata associated with an image. Also contains image utilities: filesize, scale, etc.

Topics

Resources

Stars

31 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages