Skip to content

Instantly share code, notes, and snippets.

@rorycl
Last active March 6, 2025 17:54
Show Gist options
  • Save rorycl/cee0a2e4ebe4d3ee7713a3442e6e8ffb to your computer and use it in GitHub Desktop.
Save rorycl/cee0a2e4ebe4d3ee7713a3442e6e8ffb to your computer and use it in GitHub Desktop.
Example of using bep/imagemeta (https://github.com/bep/imagemeta)
package main
import (
"fmt"
"maps"
"os"
"regexp"
"slices"
"strings"
"time"
"github.com/bep/imagemeta"
)
// ExifInfo holds the decoded Exif data for an Image. The main fields
// are picked out by name, with other residual fields that match the
// package (private) filtering regex held in Tags.
type ExifInfo struct {
// GPS latitude in degrees.
Lat float64
// GPS longitude in degrees.
Long float64
// Image creation date/time.
Date time.Time
// Aperture
Aperture float64
// F Number
FNumber float64
// LensModel
LensModel string
// Original Image Width
Width uint32
// Original Image Height
Height uint32
// Original orientation
Orientation uint16
// Shutterspeed
ShutterSpeed float64
// A collection of tags for this Image matching the
// interestingTagsRegexp, GPS and Time matches.
Tags imagemeta.Tags
}
// Map returns a map[string]any of fields of ExifInfo.
func (e *ExifInfo) Map() map[string]any {
m := map[string]any{}
m["Lat"] = e.Lat
m["Long"] = e.Long
m["Date"] = e.Date
m["Aperture"] = e.Aperture
m["FNumber"] = e.FNumber
m["LensModel"] = e.LensModel
m["Width"] = e.Width
m["Height"] = e.Height
m["Orientation"] = e.Orientation
m["ShutterSpeed"] = e.ShutterSpeed
return m
}
// Keys returns the sorted keys of ExifInfo.
func (e *ExifInfo) Keys() []string {
return slices.Sorted(maps.Keys(e.Map()))
}
func main() {
// see hugo:resources/images/exif/exif.go
interestingTagsRegexp := regexp.MustCompile(
`Aperture|ShutterSpeed|Lens|FNumber|Height|Width|Orientation`,
)
img, err := os.Open("IMG_4708.JPG")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
shouldInclude := func(ti imagemeta.TagInfo) bool {
if ti.Source == imagemeta.EXIF {
if strings.Contains(ti.Tag, "Time") {
return true
}
// GPS tags are needed for lat/long.
if strings.HasPrefix(ti.Tag, "GPS") {
return true
}
// Drop thumbnail tags.
if !strings.HasPrefix(ti.Namespace, "IFD0") {
return false
}
}
if interestingTagsRegexp.MatchString(ti.Tag) {
return true
}
return false // all other tags will be exlcuded
}
var ei ExifInfo
handleTag := func(ti imagemeta.TagInfo) error {
ei.Tags.Add(ti)
return nil
}
err = imagemeta.Decode(
imagemeta.Options{
R: img,
ImageFormat: imagemeta.JPEG,
ShouldHandleTag: shouldInclude,
HandleTag: handleTag,
Sources: imagemeta.EXIF,
},
)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
ei.Date, _ = ei.Tags.GetDateTime()
ei.Lat, ei.Long, _ = ei.Tags.GetLatLong()
if tag, ok := ei.Tags.EXIF()["ApertureValue"]; ok {
ei.Aperture = tag.Value.(float64)
}
if tag, ok := ei.Tags.EXIF()["LensModel"]; ok {
ei.LensModel = tag.Value.(string)
}
if tag, ok := ei.Tags.EXIF()["ExifImageWidth"]; ok {
ei.Width = tag.Value.(uint32)
}
if tag, ok := ei.Tags.EXIF()["ExifImageHeight"]; ok {
ei.Height = tag.Value.(uint32)
}
if tag, ok := ei.Tags.EXIF()["Orientation"]; ok {
ei.Orientation = tag.Value.(uint16)
}
if tag, ok := ei.Tags.EXIF()["FNumber"]; ok {
if n, ok := tag.Value.(imagemeta.Rat[uint32]); ok {
ei.FNumber = n.Float64()
}
}
if tag, ok := ei.Tags.EXIF()["ShutterSpeedValue"]; ok {
ei.ShutterSpeed = tag.Value.(float64)
}
for _, k := range ei.Keys() {
fmt.Printf("%12s : %v\n", k, ei.asMap()[k])
}
// ei.Tags = imagemeta.Tags{} // Remove all tags for printing
fmt.Printf("%#v\n", ei)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment