exif

package module
v2.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 4, 2020 License: MIT Imports: 17 Imported by: 0

README

Build Status Coverage Status GoDoc

Overview

This package provides native Go functionality to parse an existing EXIF block, update an existing EXIF block, or add a new EXIF block.

NOTICE

This project is modulized and v2 is now available (in v2/). This features a heavily reflowed interface that makes usage much simpler. The undefined-type tag-processing (which affects most photographic images) has also been overhauled and streamlined. It is now complete and stable. Adoption is strongly encouraged.

Getting

To get the project and dependencies:

$ go get -t github.com/dsoprea/go-exif/v2

Scope

This project is concerned only with parsing and encoding raw EXIF data. It does not understand specific file-formats. This package assumes you know how to extract the raw EXIF data from a file, such as a JPEG, and, if you want to update it, know how to write it back. File-specific formats are not the concern of go-exif, though we provide exif.SearchAndExtractExif and exif.SearchFileAndExtractExif as brute-force search mechanisms that will help you explore the EXIF information for newer formats that you might not yet have any way to parse.

That said, the author also provides go-jpeg-image-structure and go-png-image-structure to support properly reading and writing JPEG and PNG images. See the SetExif example in go-jpeg-image-structure for practical information on getting started with JPEG files.

Usage

The package provides a set of working examples and is covered by unit-tests. Please look to these for getting familiar with how to read and write EXIF.

Create an instance of the Exif type and call Scan() with a byte-slice, where the first byte is the beginning of the raw EXIF data. You may pass a callback that will be invoked for every tag or nil if you do not want one. If no callback is given, you are effectively just validating the structure or parsing of the image.

Obviously, it is most efficient to properly parse the media file and then provide the specific EXIF data to be parsed, but there is also a heuristic for finding the EXIF data within the media blob, directly. This means that, at least for testing or curiosity, you do not have to parse or even understand the format of image or audio file in order to find and decode the EXIF information inside of it. See the usage of the SearchAndExtractExif method in the example.

The library often refers to an IFD with an "IFD path" (e.g. IFD/Exif, IFD/GPSInfo). A "fully-qualified" IFD-path is one that includes an index describing which specific sibling IFD is being referred to if not the first one (e.g. IFD1, the IFD where the thumbnail is expressed per the TIFF standard).

There is an "IFD mapping" and a "tag index" that must be created and passed to the library from the top. These contain all of the knowledge of the IFD hierarchies and their tag-IDs (the IFD mapping) and the tags that they are allowed to host (the tag index). There are convenience functions to load them with the standard TIFF information, but you, alternatively, may choose something totally different (to support parsing any kind of EXIF data that does not follow or is not relevant to TIFF at all).

Reader Tool

There is a runnable reading/dumping tool included:

$ go get github.com/dsoprea/go-exif/v2/exif-read-tool
$ exif-read-tool -filepath "<media file-path>"

Example output:

IFD-PATH=[IFD] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]
IFD-PATH=[IFD] ID=(0x0110) NAME=[Model] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]
IFD-PATH=[IFD] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[1]
IFD-PATH=[IFD] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]
IFD-PATH=[IFD] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]
IFD-PATH=[IFD] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]
IFD-PATH=[IFD] ID=(0x0132) NAME=[DateTime] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]
...

You can also print the raw, parsed data as JSON:

$ exif-read-tool -filepath "<media file-path>" -json

Example output:

[
    {
        "ifd_path": "IFD",
        "fq_ifd_path": "IFD",
        "ifd_index": 0,
        "tag_id": 271,
        "tag_name": "Make",
        "tag_type_id": 2,
        "tag_type_name": "ASCII",
        "unit_count": 6,
        "value": "Canon",
        "value_string": "Canon"
    },
    {
        "ifd_path": "IFD",
...

Testing

The traditional method:

$ go test github.com/dsoprea/go-exif/v2/...

Contributing

EXIF has an excellently-documented structure but there are a lot of devices and manufacturers out there. There are only so many files that we can personally find to test against, and most of these are images that have been generated only in the past few years. JPEG, being the largest implementor of EXIF, has been around for even longer (but not much). Therefore, there is a lot of compatibility to test for.

If you are able to help by running the included reader-tool against all of the EXIF-compatible files you have, it would be deeply appreciated. This is mostly going to be JPEG files (but not all variations). If you are able to test a large number of files (thousands or millions), please post an issue mentioning how many files you tried, whether there were any failures, and, if you would be willing, give us access to the failed files.

If you are able to test 100K+ files, I will give you credit on the project. The further back in time your images reach, the higher in the list your name/company will go.

Contributors/Testing

Thank you to the following users for providing their non-trivial time or corpus to test go-exif.

Documentation

Overview

Package exif parses raw EXIF information given a block of raw EXIF data. It can also construct new EXIF information, and provides tools for doing so. This package is not involved with the parsing of particular file-formats.

The EXIF data must first be extracted and then provided to us. Conversely, when constructing new EXIF data, the caller is responsible for packaging this in whichever format they require.

Index

Examples

Constants

View Source
const (
	// ExifAddressableAreaStart is the absolute offset in the file that all
	// offsets are relative to.
	ExifAddressableAreaStart = uint32(0x0)

	// ExifDefaultFirstIfdOffset is essentially the number of bytes in addition
	// to `ExifAddressableAreaStart` that you have to move in order to escape
	// the rest of the header and get to the earliest point where we can put
	// stuff (which has to be the first IFD). This is the size of the header
	// sequence containing the two-character byte-order, two-character fixed-
	// bytes, and the four bytes describing the first-IFD offset.
	ExifDefaultFirstIfdOffset = uint32(2 + 2 + 4)
)
View Source
const (
	ThumbnailOffsetTagId = 0x0201
	ThumbnailSizeTagId   = 0x0202

	TagGpsVersionId = 0x0000

	TagLatitudeId     = 0x0002
	TagLatitudeRefId  = 0x0001
	TagLongitudeId    = 0x0004
	TagLongitudeRefId = 0x0003

	TagTimestampId = 0x0007
	TagDatestampId = 0x001d

	TagAltitudeId    = 0x0006
	TagAltitudeRefId = 0x0005
)
View Source
const (
	// Tag-ID + Tag-Type + Unit-Count + Value/Offset.
	IfdTagEntrySize = uint32(2 + 2 + 4 + 4)
)

Variables

View Source
var (
	ErrTagNotFound    = errors.New("tag not found")
	ErrTagNotStandard = errors.New("tag not a standard tag")
)
View Source
var (
	BigEndianBoBytes    = [2]byte{'M', 'M'}
	LittleEndianBoBytes = [2]byte{'I', 'I'}

	ByteOrderLookup = map[[2]byte]binary.ByteOrder{
		BigEndianBoBytes:    binary.BigEndian,
		LittleEndianBoBytes: binary.LittleEndian,
	}

	ByteOrderLookupR = map[binary.ByteOrder][2]byte{
		binary.BigEndian:    BigEndianBoBytes,
		binary.LittleEndian: LittleEndianBoBytes,
	}

	ExifFixedBytesLookup = map[binary.ByteOrder][2]byte{
		binary.LittleEndian: [2]byte{0x2a, 0x00},
		binary.BigEndian:    [2]byte{0x00, 0x2a},
	}
)
View Source
var (
	ErrNoExif          = errors.New("no exif data")
	ErrExifHeaderError = errors.New("exif header error")
)
View Source
var (
	ErrTagEntryNotFound = errors.New("tag entry not found")
	ErrChildIbNotFound  = errors.New("child IB not found")
)
View Source
var (
	ErrNoThumbnail     = errors.New("no thumbnail")
	ErrNoGpsTags       = errors.New("no gps tags")
	ErrTagTypeNotValid = errors.New("tag type invalid")
)
View Source
var (
	ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
)
View Source
var (
	ErrGpsCoordinatesNotValid = errors.New("GPS coordinates not valid")
)
View Source
var (
	ValidGpsVersions = [][4]byte{
		{2, 2, 0, 0},

		{2, 3, 0, 0},
	}
)

Functions

func BuildExifHeader

func BuildExifHeader(byteOrder binary.ByteOrder, firstIfdOffset uint32) (headerBytes []byte, err error)

BuildExifHeader constructs the bytes that go in the very beginning.

Example
headerBytes, err := BuildExifHeader(exifcommon.TestDefaultByteOrder, 0x11223344)
log.PanicIf(err)

eh, err := ParseExifHeader(headerBytes)
log.PanicIf(err)

fmt.Printf("%v\n", eh)
Output:

ExifHeader<BYTE-ORDER=[BigEndian] FIRST-IFD-OFFSET=(0x11223344)>

func Collect

func Collect(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte) (eh ExifHeader, index IfdIndex, err error)

Collect recursively builds a static structure of all IFDs and tags.

func ExifFullTimestampString

func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string)

ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a `time.Time` struct. It will attempt to convert to UTC first.

Example
originalPhrase := "2018:11:30 13:01:49"

timestamp, err := ParseExifFullTimestamp(originalPhrase)
log.PanicIf(err)

restoredPhrase := ExifFullTimestampString(timestamp)
fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase)
Output:

To EXIF timestamp: [2018:11:30 13:01:49]

func LoadStandardIfds

func LoadStandardIfds(im *IfdMapping) (err error)

func LoadStandardTags

func LoadStandardTags(ti *TagIndex) (err error)

LoadStandardTags registers the tags that all devices/applications should support.

func ParseExifFullTimestamp

func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error)

ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC `time.Time` struct.

Example
originalPhrase := "2018:11:30 13:01:49"

timestamp, err := ParseExifFullTimestamp(originalPhrase)
log.PanicIf(err)

fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339))
Output:

To Go timestamp: [2018-11-30T13:01:49Z]

func SearchAndExtractExif

func SearchAndExtractExif(data []byte) (rawExif []byte, err error)

SearchAndExtractExif returns a slice from the beginning of the EXIF data to end of the file (it's not practical to try and calculate where the data actually ends; it needs to be formally parsed).

func SearchFileAndExtractExif

func SearchFileAndExtractExif(filepath string) (rawExif []byte, err error)

SearchFileAndExtractExif returns a slice from the beginning of the EXIF data to the end of the file (it's not practical to try and calculate where the data actually ends).

Types

type BuilderTag

type BuilderTag struct {
	// contains filtered or unexported fields
}

func NewBuilderTag

func NewBuilderTag(ifdPath string, tagId uint16, typeId exifcommon.TagTypePrimitive, value *IfdBuilderTagValue, byteOrder binary.ByteOrder) *BuilderTag

func NewChildIfdBuilderTag

func NewChildIfdBuilderTag(ifdPath string, tagId uint16, value *IfdBuilderTagValue) *BuilderTag

func NewStandardBuilderTag

func NewStandardBuilderTag(ifdPath string, it *IndexedTag, byteOrder binary.ByteOrder, value interface{}) *BuilderTag

NewStandardBuilderTag constructs a `BuilderTag` instance. The type is looked up. `ii` is the type of IFD that owns this tag.

func (*BuilderTag) SetValue

func (bt *BuilderTag) SetValue(byteOrder binary.ByteOrder, value interface{}) (err error)
Example
testImageFilepath := getTestImageFilepath()

rawExif, err := SearchFileAndExtractExif(testImageFilepath)
log.PanicIf(err)

im := NewIfdMapping()

err = LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()

_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)

// Create builder.

rootIb := NewIfdBuilderFromExistingChain(index.RootIfd)

// Find tag to update.

exifBt, err := rootIb.FindTagWithName("ExifTag")
log.PanicIf(err)

ucBt, err := exifBt.value.Ib().FindTagWithName("UserComment")
log.PanicIf(err)

// Update the value. Since this is an "undefined"-type tag, we have to use
// its type-specific struct.

// TODO(dustin): !! Add an example for setting a non-unknown value, too.
uc := exifundefined.Tag9286UserComment{
	EncodingType:  exifundefined.TagUndefinedType_9286_UserComment_Encoding_ASCII,
	EncodingBytes: []byte("TEST COMMENT"),
}

err = ucBt.SetValue(rootIb.byteOrder, uc)
log.PanicIf(err)

// Encode.

ibe := NewIfdByteEncoder()
updatedExif, err := ibe.EncodeToExif(rootIb)
log.PanicIf(err)

updatedExif = updatedExif
Output:

func (*BuilderTag) String

func (bt *BuilderTag) String() string

func (*BuilderTag) Value

func (bt *BuilderTag) Value() (value *IfdBuilderTagValue)

type ByteWriter

type ByteWriter struct {
	// contains filtered or unexported fields
}

func NewByteWriter

func NewByteWriter(b *bytes.Buffer, byteOrder binary.ByteOrder) (bw *ByteWriter)

func (ByteWriter) WriteFourBytes

func (bw ByteWriter) WriteFourBytes(value []byte) (err error)

func (ByteWriter) WriteUint16

func (bw ByteWriter) WriteUint16(value uint16) (err error)

func (ByteWriter) WriteUint32

func (bw ByteWriter) WriteUint32(value uint32) (err error)

type ExifHeader

type ExifHeader struct {
	ByteOrder      binary.ByteOrder
	FirstIfdOffset uint32
}

func ParseExifHeader

func ParseExifHeader(data []byte) (eh ExifHeader, err error)

ParseExifHeader parses the bytes at the very top of the header.

This will panic with ErrNoExif on any data errors so that we can double as an EXIF-detection routine.

func Visit

func Visit(rootIfdName string, ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, visitor TagVisitorFn) (eh ExifHeader, err error)

Visit recursively invokes a callback for every tag.

func (ExifHeader) String

func (eh ExifHeader) String() string

type ExifTag

type ExifTag struct {
	IfdPath string `json:"ifd_path"`

	TagId   uint16 `json:"id"`
	TagName string `json:"name"`

	TagTypeId   exifcommon.TagTypePrimitive `json:"type_id"`
	TagTypeName string                      `json:"type_name"`
	Value       interface{}                 `json:"value"`
	ValueBytes  []byte                      `json:"value_bytes"`

	ChildIfdPath string `json:"child_ifd_path"`
}

ExifTag is one simple representation of a tag in a flat list of all of them.

func GetFlatExifData

func GetFlatExifData(exifData []byte) (exifTags []ExifTag, err error)

GetFlatExifData returns a simple, flat representation of all tags.

func (ExifTag) String

func (et ExifTag) String() string

String returns a string representation.

type GpsDegrees

type GpsDegrees struct {
	Orientation               byte
	Degrees, Minutes, Seconds float64
}

func (GpsDegrees) Decimal

func (d GpsDegrees) Decimal() float64

func (GpsDegrees) String

func (d GpsDegrees) String() string

type GpsInfo

type GpsInfo struct {
	Latitude, Longitude GpsDegrees
	Altitude            int
	Timestamp           time.Time
}

func (*GpsInfo) S2CellId

func (gi *GpsInfo) S2CellId() s2.CellID

func (*GpsInfo) String

func (gi *GpsInfo) String() string

type Ifd

type Ifd struct {
	ByteOrder binary.ByteOrder

	// Name is the name of the IFD (the rightmost name in the path, sans any
	// indices).
	Name string

	// IfdPath is a simple IFD path (e.g. IFD/GPSInfo). No indices.
	IfdPath string

	// FqIfdPath is a fully-qualified IFD path (e.g. IFD0/GPSInfo0). With
	// indices.
	FqIfdPath string

	TagId uint16

	Id int

	ParentIfd *Ifd

	// ParentTagIndex is our tag position in the parent IFD, if we had a parent
	// (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
	// instead of as a child).
	ParentTagIndex int

	// Name   string
	Index  int
	Offset uint32

	Entries        []*IfdTagEntry
	EntriesByTagId map[uint16][]*IfdTagEntry

	Children []*Ifd

	ChildIfdIndex map[string]*Ifd

	NextIfdOffset uint32
	NextIfd       *Ifd
	// contains filtered or unexported fields
}

Ifd represents a single parsed IFD.

func FindIfdFromRootIfd

func FindIfdFromRootIfd(rootIfd *Ifd, ifdPath string) (ifd *Ifd, err error)

func (*Ifd) ChildWithIfdPath

func (ifd *Ifd) ChildWithIfdPath(ifdPath string) (childIfd *Ifd, err error)

func (*Ifd) DumpTags

func (ifd *Ifd) DumpTags() []*IfdTagEntry

DumpTags prints the IFD hierarchy.

func (*Ifd) DumpTree

func (ifd *Ifd) DumpTree() []string

DumpTree returns a list of strings describing the IFD hierarchy.

func (*Ifd) EnumerateTagsRecursively

func (ifd *Ifd) EnumerateTagsRecursively(visitor ParsedTagVisitor) (err error)
Example
testImageFilepath := getTestImageFilepath()

rawExif, err := SearchFileAndExtractExif(testImageFilepath)
log.PanicIf(err)

im := NewIfdMapping()

err = LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()

_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)

cb := func(ifd *Ifd, ite *IfdTagEntry) error {

	// Something useful.

	return nil
}

err = index.RootIfd.EnumerateTagsRecursively(cb)
log.PanicIf(err)
Output:

func (*Ifd) FindTagWithId

func (ifd *Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error)

FindTagWithId returns a list of tags (usually just zero or one) that match the given tag ID. This is efficient.

func (*Ifd) FindTagWithName

func (ifd *Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err error)

FindTagWithName returns a list of tags (usually just zero or one) that match the given tag name. This is not efficient (though the labor is trivial).

Example
testImageFilepath := getTestImageFilepath()

rawExif, err := SearchFileAndExtractExif(testImageFilepath)
log.PanicIf(err)

im := NewIfdMapping()

err = LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()

_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)

tagName := "Model"

rootIfd := index.RootIfd

// We know the tag we want is on IFD0 (the first/root IFD).
results, err := rootIfd.FindTagWithName(tagName)
log.PanicIf(err)

// This should never happen.
if len(results) != 1 {
	log.Panicf("there wasn't exactly one result")
}

ite := results[0]

valueRaw, err := ite.Value()
log.PanicIf(err)

value := valueRaw.(string)
fmt.Println(value)
Output:

Canon EOS 5D Mark III

func (*Ifd) GpsInfo

func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error)

GpsInfo parses and consolidates the GPS info. This can only be called on the GPS IFD.

Example
filepath := path.Join(assetsPath, "gps.jpg")

rawExif, err := SearchFileAndExtractExif(filepath)
log.PanicIf(err)

im := NewIfdMapping()

err = LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()

_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)

ifd, err := index.RootIfd.ChildWithIfdPath(exifcommon.IfdPathStandardGps)
log.PanicIf(err)

gi, err := ifd.GpsInfo()
log.PanicIf(err)

fmt.Printf("%s\n", gi)
Output:

GpsInfo<LAT=(26.58667) LON=(-80.05361) ALT=(0) TIME=[2018-04-29 01:22:57 +0000 UTC]>

func (*Ifd) PrintIfdTree

func (ifd *Ifd) PrintIfdTree()

PrintIfdTree prints the IFD hierarchy.

func (*Ifd) PrintTagTree

func (ifd *Ifd) PrintTagTree(populateValues bool)

PrintTagTree prints the IFD hierarchy.

func (*Ifd) String

func (ifd *Ifd) String() string

func (*Ifd) Thumbnail

func (ifd *Ifd) Thumbnail() (data []byte, err error)
Example
testImageFilepath := getTestImageFilepath()

rawExif, err := SearchFileAndExtractExif(testImageFilepath)
log.PanicIf(err)

im := NewIfdMapping()

err = LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()

_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)

thumbnailData, err := index.RootIfd.NextIfd.Thumbnail()
log.PanicIf(err)

thumbnailData = thumbnailData
Output:

type IfdBuilder

type IfdBuilder struct {
	// contains filtered or unexported fields
}

func GetOrCreateIbFromRootIb

func GetOrCreateIbFromRootIb(rootIb *IfdBuilder, fqIfdPath string) (ib *IfdBuilder, err error)

GetOrCreateIbFromRootIb returns an IB representing the requested IFD, even if an IB doesn't already exist for it. This function may call itself recursively.

func NewIfdBuilder

func NewIfdBuilder(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath string, byteOrder binary.ByteOrder) (ib *IfdBuilder)

func NewIfdBuilderFromExistingChain

func NewIfdBuilderFromExistingChain(rootIfd *Ifd) (firstIb *IfdBuilder)

NewIfdBuilderFromExistingChain creates a chain of IB instances from an IFD chain generated from real data.

func NewIfdBuilderWithExistingIfd

func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder)

NewIfdBuilderWithExistingIfd creates a new IB using the same header type information as the given IFD.

func (*IfdBuilder) Add

func (ib *IfdBuilder) Add(bt *BuilderTag) (err error)

func (*IfdBuilder) AddChildIb

func (ib *IfdBuilder) AddChildIb(childIb *IfdBuilder) (err error)

AddChildIb adds a tag that branches to a new IFD.

func (*IfdBuilder) AddStandard

func (ib *IfdBuilder) AddStandard(tagId uint16, value interface{}) (err error)

AddStandard quickly and easily composes and adds the tag using the information already known about a tag. Only works with standard tags.

func (*IfdBuilder) AddStandardWithName

func (ib *IfdBuilder) AddStandardWithName(tagName string, value interface{}) (err error)

AddStandardWithName quickly and easily composes and adds the tag using the information already known about a tag (using the name). Only works with standard tags.

func (*IfdBuilder) AddTagsFromExisting

func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excludeTagIds []uint16) (err error)

AddTagsFromExisting does a verbatim copy of the entries in `ifd` to this builder. It excludes child IFDs. These must be added explicitly via `AddChildIb()`.

func (*IfdBuilder) ChildWithTagId

func (ib *IfdBuilder) ChildWithTagId(childIfdTagId uint16) (childIb *IfdBuilder, err error)

func (*IfdBuilder) DeleteAll

func (ib *IfdBuilder) DeleteAll(tagId uint16) (n int, err error)

func (*IfdBuilder) DeleteFirst

func (ib *IfdBuilder) DeleteFirst(tagId uint16) (err error)

func (*IfdBuilder) DeleteN

func (ib *IfdBuilder) DeleteN(tagId uint16, n int) (err error)

func (*IfdBuilder) DumpToStrings

func (ib *IfdBuilder) DumpToStrings() (lines []string)

func (*IfdBuilder) Find

func (ib *IfdBuilder) Find(tagId uint16) (position int, err error)

func (*IfdBuilder) FindN

func (ib *IfdBuilder) FindN(tagId uint16, maxFound int) (found []int, err error)

func (*IfdBuilder) FindTag

func (ib *IfdBuilder) FindTag(tagId uint16) (bt *BuilderTag, err error)

func (*IfdBuilder) FindTagWithName

func (ib *IfdBuilder) FindTagWithName(tagName string) (bt *BuilderTag, err error)

func (*IfdBuilder) NewBuilderTagFromBuilder

func (ib *IfdBuilder) NewBuilderTagFromBuilder(childIb *IfdBuilder) (bt *BuilderTag)

func (*IfdBuilder) NextIb

func (ib *IfdBuilder) NextIb() (nextIb *IfdBuilder, err error)

func (*IfdBuilder) PrintIfdTree

func (ib *IfdBuilder) PrintIfdTree()

func (*IfdBuilder) PrintTagTree

func (ib *IfdBuilder) PrintTagTree()

func (*IfdBuilder) Replace

func (ib *IfdBuilder) Replace(tagId uint16, bt *BuilderTag) (err error)

func (*IfdBuilder) ReplaceAt

func (ib *IfdBuilder) ReplaceAt(position int, bt *BuilderTag) (err error)

func (*IfdBuilder) Set

func (ib *IfdBuilder) Set(bt *BuilderTag) (err error)

Set will add a new entry or update an existing entry.

func (*IfdBuilder) SetNextIb

func (ib *IfdBuilder) SetNextIb(nextIb *IfdBuilder) (err error)

func (*IfdBuilder) SetStandard

func (ib *IfdBuilder) SetStandard(tagId uint16, value interface{}) (err error)

SetStandard quickly and easily composes and adds or replaces the tag using the information already known about a tag. Only works with standard tags.

func (*IfdBuilder) SetStandardWithName

func (ib *IfdBuilder) SetStandardWithName(tagName string, value interface{}) (err error)

SetStandardWithName quickly and easily composes and adds or replaces the tag using the information already known about a tag (using the name). Only works with standard tags.

Example

ExampleIfdBuilder_SetStandardWithName establishes a chain of `IfdBuilder` structs from an existing chain of `Ifd` structs, navigates to the IB representing IFD0, updates the ProcessingSoftware tag to a different value, encodes down to a new EXIF block, reparses, and validates that the value for that tag is what we set it to.

testImageFilepath := getTestImageFilepath()

rawExif, err := SearchFileAndExtractExif(testImageFilepath)
log.PanicIf(err)

// Boilerplate.

im := NewIfdMapping()

err = LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()

// Load current IFDs.

_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)

ib := NewIfdBuilderFromExistingChain(index.RootIfd)

// Read the IFD whose tag we want to change.

// Standard:
// - "IFD0"
// - "IFD0/Exif0"
// - "IFD0/Exif0/Iop0"
// - "IFD0/GPSInfo0"
//
// If the numeric indices are not included, (0) is the default. Note that
// this isn't strictly necessary in our case since IFD0 is the first IFD anyway, but we're putting it here to show usage.
ifdPath := "IFD0"

childIb, err := GetOrCreateIbFromRootIb(ib, ifdPath)
log.PanicIf(err)

// There are a few functions that allow you to surgically change the tags in an
// IFD, but we're just gonna overwrite a tag that has an ASCII value.

tagName := "ProcessingSoftware"

err = childIb.SetStandardWithName(tagName, "alternative software")
log.PanicIf(err)

// Encode the in-memory representation back down to bytes.

ibe := NewIfdByteEncoder()

updatedRawExif, err := ibe.EncodeToExif(ib)
log.PanicIf(err)

// Reparse the EXIF to confirm that our value is there.

_, index, err = Collect(im, ti, updatedRawExif)
log.PanicIf(err)

// This isn't strictly necessary for the same reason as above, but it's here
// for documentation.
childIfd, err := FindIfdFromRootIfd(index.RootIfd, ifdPath)
log.PanicIf(err)

results, err := childIfd.FindTagWithName(tagName)
log.PanicIf(err)

for _, ite := range results {
	valueRaw, err := ite.Value()
	log.PanicIf(err)

	stringValue := valueRaw.(string)
	fmt.Println(stringValue)
}
Output:

alternative software

func (*IfdBuilder) SetThumbnail

func (ib *IfdBuilder) SetThumbnail(data []byte) (err error)

SetThumbnail sets thumbnail data.

NOTES:

  • We don't manage any facet of the thumbnail data. This is the responsibility of the user/developer.
  • This method will fail unless the thumbnail is set on a the root IFD. However, in order to be valid, it must be set on the second one, linked to by the first, as per the EXIF/TIFF specification.
  • We set the offset to (0) now but will allocate the data and properly assign the offset when the IB is encoded (later).

func (*IfdBuilder) String

func (ib *IfdBuilder) String() string

func (*IfdBuilder) Tags

func (ib *IfdBuilder) Tags() (tags []*BuilderTag)

func (*IfdBuilder) Thumbnail

func (ib *IfdBuilder) Thumbnail() []byte

type IfdBuilderTagValue

type IfdBuilderTagValue struct {
	// contains filtered or unexported fields
}

func NewIfdBuilderTagValueFromBytes

func NewIfdBuilderTagValueFromBytes(valueBytes []byte) *IfdBuilderTagValue

func NewIfdBuilderTagValueFromIfdBuilder

func NewIfdBuilderTagValueFromIfdBuilder(ib *IfdBuilder) *IfdBuilderTagValue

func (IfdBuilderTagValue) Bytes

func (ibtv IfdBuilderTagValue) Bytes() []byte

func (IfdBuilderTagValue) Ib

func (ibtv IfdBuilderTagValue) Ib() *IfdBuilder

func (IfdBuilderTagValue) IsBytes

func (ibtv IfdBuilderTagValue) IsBytes() bool

IsBytes returns true if the bytes are populated. This is always the case when we're loaded from a tag in an existing IFD.

func (IfdBuilderTagValue) IsIb

func (ibtv IfdBuilderTagValue) IsIb() bool

func (IfdBuilderTagValue) String

func (ibtv IfdBuilderTagValue) String() string

type IfdByteEncoder

type IfdByteEncoder struct {
	// contains filtered or unexported fields
}

IfdByteEncoder converts an IB to raw bytes (for writing) while also figuring out all of the allocations and indirection that is required for extended data.

func NewIfdByteEncoder

func NewIfdByteEncoder() (ibe *IfdByteEncoder)

func (*IfdByteEncoder) EncodeToExif

func (ibe *IfdByteEncoder) EncodeToExif(ib *IfdBuilder) (data []byte, err error)

EncodeToExif calls EncodeToExifPayload and then packages the result into a complete EXIF block.

Example
// Construct an IFD.

im := NewIfdMapping()

err := LoadStandardIfds(im)
log.PanicIf(err)

ti := NewTagIndex()
ib := NewIfdBuilder(im, ti, exifcommon.IfdPathStandard, exifcommon.TestDefaultByteOrder)

err = ib.AddStandardWithName("ProcessingSoftware", "asciivalue")
log.PanicIf(err)

err = ib.AddStandardWithName("DotRange", []uint8{0x11})
log.PanicIf(err)

err = ib.AddStandardWithName("SubfileType", []uint16{0x2233})
log.PanicIf(err)

err = ib.AddStandardWithName("ImageWidth", []uint32{0x44556677})
log.PanicIf(err)

err = ib.AddStandardWithName("WhitePoint", []exifcommon.Rational{{Numerator: 0x11112222, Denominator: 0x33334444}})
log.PanicIf(err)

err = ib.AddStandardWithName("ShutterSpeedValue", []exifcommon.SignedRational{{Numerator: 0x11112222, Denominator: 0x33334444}})
log.PanicIf(err)

// Encode it.

ibe := NewIfdByteEncoder()

exifData, err := ibe.EncodeToExif(ib)
log.PanicIf(err)

// Parse it so we can see it.

_, index, err := Collect(im, ti, exifData)
log.PanicIf(err)

for i, ite := range index.RootIfd.Entries {
	value, err := ite.Value()
	log.PanicIf(err)

	fmt.Printf("%d: %s [%v]\n", i, ite, value)
}
Output:


0: IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x000b) TAG-TYPE=[ASCII] UNIT-COUNT=(11)> [asciivalue]
1: IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x0150) TAG-TYPE=[BYTE] UNIT-COUNT=(1)> [[17]]
2: IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x00ff) TAG-TYPE=[SHORT] UNIT-COUNT=(1)> [[8755]]
3: IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x0100) TAG-TYPE=[LONG] UNIT-COUNT=(1)> [[1146447479]]
4: IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x013e) TAG-TYPE=[RATIONAL] UNIT-COUNT=(1)> [[{286335522 858997828}]]
5: IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x9201) TAG-TYPE=[SRATIONAL] UNIT-COUNT=(1)> [[{286335522 858997828}]]

func (*IfdByteEncoder) EncodeToExifPayload

func (ibe *IfdByteEncoder) EncodeToExifPayload(ib *IfdBuilder) (data []byte, err error)

EncodeToExifPayload is the base encoding step that transcribes the entire IB structure to its on-disk layout.

func (*IfdByteEncoder) Journal

func (ibe *IfdByteEncoder) Journal() [][3]string

func (*IfdByteEncoder) PrintJournal

func (ibe *IfdByteEncoder) PrintJournal()

PrintJournal prints a hierarchical representation of the steps taken during encoding.

func (*IfdByteEncoder) TableSize

func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32

type IfdEnumerate

type IfdEnumerate struct {
	// contains filtered or unexported fields
}

func NewIfdEnumerate

func NewIfdEnumerate(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate

func (*IfdEnumerate) Collect

func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error)

Scan enumerates the different EXIF blocks (called IFDs).

func (*IfdEnumerate) ParseIfd

func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, enumerator *IfdTagEnumerator, visitor TagVisitorFn, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error)

ParseIfd decodes the IFD block that we're currently sitting on the first byte of.

func (*IfdEnumerate) Scan

func (ie *IfdEnumerate) Scan(rootIfdName string, ifdOffset uint32, visitor TagVisitorFn) (err error)

Scan enumerates the different EXIF blocks (called IFDs). `rootIfdName` will be "IFD" in the TIFF standard.

type IfdIndex

type IfdIndex struct {
	RootIfd *Ifd
	Ifds    []*Ifd
	Tree    map[int]*Ifd
	Lookup  map[string][]*Ifd
}

type IfdMapping

type IfdMapping struct {
	// contains filtered or unexported fields
}

IfdMapping describes all of the IFDs that we currently recognize.

func NewIfdMapping

func NewIfdMapping() (ifdMapping *IfdMapping)

func NewIfdMappingWithStandard

func NewIfdMappingWithStandard() (ifdMapping *IfdMapping)

func (*IfdMapping) Add

func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error)

Add puts the given IFD at the given position of the tree. The position of the tree is referred to as the placement and is represented by a set of tag-IDs, where the leftmost is the root tag and the tags going to the right are progressive descendants.

func (*IfdMapping) DumpLineages

func (im *IfdMapping) DumpLineages() (output []string, err error)

func (*IfdMapping) FqPathPhraseFromLineage

func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string)

func (*IfdMapping) Get

func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error)

func (*IfdMapping) GetChild

func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error)

GetChild is a convenience function to get the child path for a given parent placement and child tag-ID.

func (*IfdMapping) GetWithPath

func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error)

func (*IfdMapping) PathPhraseFromLineage

func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string)

func (*IfdMapping) ResolvePath

func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error)

ResolvePath takes a list of names, which can also be suffixed with indices (to identify the second, third, etc.. sibling IFD) and returns a list of tag-IDs and those indices.

Example:

- IFD/Exif/Iop - IFD0/Exif/Iop

This is the only call that supports adding the numeric indices.

func (*IfdMapping) StripPathPhraseIndices

func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error)

StripPathPhraseIndices returns a non-fully-qualified path-phrase (no indices).

type IfdTagEntry

type IfdTagEntry struct {
	// contains filtered or unexported fields
}

IfdTagEntry refers to a tag in the loaded EXIF block.

func ParseOneIfd

func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitorFn) (nextIfdOffset uint32, entries []*IfdTagEntry, err error)

ParseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for testing.

func ParseOneTag

func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, tagBlock []byte) (tag *IfdTagEntry, err error)

ParseOneTag is a hack to use an IE to parse a raw tag block.

func (*IfdTagEntry) ChildFqIfdPath

func (ite *IfdTagEntry) ChildFqIfdPath() string

ChildFqIfdPath returns the complete path of the child IFD along with the numeric suffixes differentiating sibling occurrences of the same type. "0" indices are omitted.

func (*IfdTagEntry) ChildIfdName

func (ite *IfdTagEntry) ChildIfdName() string

ChildIfdName returns the name of the child IFD

func (*IfdTagEntry) ChildIfdPath

func (ite *IfdTagEntry) ChildIfdPath() string

ChildIfdPath returns the path of the child IFD.

func (*IfdTagEntry) Format

func (ite *IfdTagEntry) Format() (phrase string, err error)

Format returns the tag's value as a string.

func (*IfdTagEntry) FormatFirst

func (ite *IfdTagEntry) FormatFirst() (phrase string, err error)

FormatFirst returns the same as Format() but only the first item.

func (*IfdTagEntry) GetRawBytes

func (ite *IfdTagEntry) GetRawBytes() (rawBytes []byte, err error)

RawBytes renders a specific list of bytes from the value in this tag.

func (*IfdTagEntry) IfdPath

func (ite *IfdTagEntry) IfdPath() string

IfdPath returns the path of the IFD that owns this tag.

func (*IfdTagEntry) SetChildIfd

func (ite *IfdTagEntry) SetChildIfd(childFqIfdPath, childIfdPath, childIfdName string)

SetChildIfd sets child-IFD information (if we represent a child IFD).

func (*IfdTagEntry) String

func (ite *IfdTagEntry) String() string

String returns a stringified representation of the struct.

func (*IfdTagEntry) TagId

func (ite *IfdTagEntry) TagId() uint16

TagId returns the ID of the tag that we represent. The combination of (IfdPath(), TagId()) is unique.

func (*IfdTagEntry) TagType

func (ite *IfdTagEntry) TagType() exifcommon.TagTypePrimitive

TagType is the type of value for this tag.

func (*IfdTagEntry) UnitCount

func (ite *IfdTagEntry) UnitCount() uint32

UnitCount returns the unit-count of the tag's value.

func (*IfdTagEntry) Value

func (ite *IfdTagEntry) Value() (value interface{}, err error)

Value returns the specific, parsed, typed value from the tag.

type IfdTagEnumerator

type IfdTagEnumerator struct {
	// contains filtered or unexported fields
}

IfdTagEnumerator knows how to decode an IFD and all of the tags it describes.

The IFDs and the actual values can float throughout the EXIF block, but the IFD itself is just a minor header followed by a set of repeating, statically-sized records. So, the tags (though notnecessarily their values) are fairly simple to enumerate.

func NewIfdTagEnumerator

func NewIfdTagEnumerator(addressableData []byte, byteOrder binary.ByteOrder, ifdOffset uint32) (enumerator *IfdTagEnumerator)

type IfdTagIdAndIndex

type IfdTagIdAndIndex struct {
	Name  string
	TagId uint16
	Index int
}

func (IfdTagIdAndIndex) String

func (itii IfdTagIdAndIndex) String() string

type IndexedTag

type IndexedTag struct {
	Id      uint16
	Name    string
	IfdPath string
	Type    exifcommon.TagTypePrimitive
}

func (*IndexedTag) Is

func (it *IndexedTag) Is(ifdPath string, id uint16) bool

func (*IndexedTag) IsName

func (it *IndexedTag) IsName(ifdPath, name string) bool

func (*IndexedTag) String

func (it *IndexedTag) String() string

type MappedIfd

type MappedIfd struct {
	ParentTagId uint16
	Placement   []uint16
	Path        []string

	Name     string
	TagId    uint16
	Children map[uint16]*MappedIfd
}

func (*MappedIfd) PathPhrase

func (mi *MappedIfd) PathPhrase() string

func (*MappedIfd) String

func (mi *MappedIfd) String() string

type ParsedTagVisitor

type ParsedTagVisitor func(*Ifd, *IfdTagEntry) error

type QueuedIfd

type QueuedIfd struct {
	Name      string
	IfdPath   string
	FqIfdPath string

	TagId uint16

	Index  int
	Offset uint32
	Parent *Ifd

	// ParentTagIndex is our tag position in the parent IFD, if we had a parent
	// (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
	// instead of as a child).
	ParentTagIndex int
}

type TagIndex

type TagIndex struct {
	// contains filtered or unexported fields
}

func NewTagIndex

func NewTagIndex() *TagIndex

func (*TagIndex) Add

func (ti *TagIndex) Add(it *IndexedTag) (err error)

func (*TagIndex) Get

func (ti *TagIndex) Get(ifdPath string, id uint16) (it *IndexedTag, err error)

Get returns information about the non-IFD tag.

func (*TagIndex) GetWithName

func (ti *TagIndex) GetWithName(ifdPath string, name string) (it *IndexedTag, err error)

Get returns information about the non-IFD tag.

type TagVisitorFn

type TagVisitorFn func(fqIfdPath string, ifdIndex int, ite *IfdTagEntry) (err error)

TagVisitorFn is called for each tag when enumerating through the EXIF.

Directories

Path Synopsis
This tool dumps EXIF information from images.
This tool dumps EXIF information from images.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL