exif

package module
v0.0.0-...-6579e82 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2023 License: MIT Imports: 14 Imported by: 11

README

Build Status codecov Go Report Card 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.

Getting

To get the project and dependencies:

$ git clone https://github.com/dsoprea/go-exif.git
$ cd v3

Requirements

  • Go >= 1.17: Due to a breakage with "go test", we only officially support 1.17 for testing/CI reasons. It may still work in earlier versions if such a need is critically required, however.

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 the following projects to support the efficient processing of the corresponding image formats:

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).

Standards and Customization

This project is configuration driven. By default, it has no knowledge of tags and IDs until you load them prior to using (which is incorporated in the examples). You are just as easily able to add additional custom IFDs and custom tags for them. If desired, you could completely ignore the standard information and load totally non-standard IFDs and tags.

This would be useful for divergent implementations that add non-standard information to images. It would also be useful if there is some need to just store a flat list of tags in an image for simplified, proprietary usage.

Reader Tool

There is a runnable reading/dumping tool included:

$ go install github.com/dsoprea/go-exif/v3/command/exif-read-tool@latest
$ 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/v3/...

Release Notes

v3 Release

This release primarily introduces an interchangeable data-layer, where any io.ReadSeeker can be used to read EXIF data rather than necessarily loading the EXIF blob into memory first.

Several backwards-incompatible clean-ups were also included in this release. See releases for more information.

v2 Release

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.

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) then please post an issue mentioning how many files you have processed. If you had failures, then please share them and try to support efforts to understand them.

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 solving non-trivial issues, supporting the project with solving edge-case problems in specific images, or otherwise providing their non-trivial time or image corpus to test go-exif:

In addition to these, it has been tested on my own collection, north of 560K images.

Documentation

Overview

exif parses raw EXIF information given a block of raw EXIF data.

v1 of go-exif is now deprecated. Please use v2.

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 (
	IfdStandard = "IFD"
	IfdExif     = "Exif"
	IfdGps      = "GPSInfo"
	IfdIop      = "Iop"

	IfdExifId = 0x8769
	IfdGpsId  = 0x8825
	IfdIopId  = 0xA005

	IfdRootId = 0x0000

	IfdPathStandard        = "IFD"
	IfdPathStandardExif    = "IFD/Exif"
	IfdPathStandardExifIop = "IFD/Exif/Iop"
	IfdPathStandardGps     = "IFD/GPSInfo"
)
View Source
const (
	ThumbnailOffsetTagId = 0x0201
	ThumbnailSizeTagId   = 0x0202

	TagVersionId = 0x0000

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

	TagTimestampId = 0x0007
	TagDatestampId = 0x001d

	TagAltitudeId    = 0x0006
	TagAltitudeRefId = 0x0005
)
View Source
const (
	TagUnknownType_9298_UserComment_Encoding_ASCII     = iota
	TagUnknownType_9298_UserComment_Encoding_JIS       = iota
	TagUnknownType_9298_UserComment_Encoding_UNICODE   = iota
	TagUnknownType_9298_UserComment_Encoding_UNDEFINED = iota
)
View Source
const (
	TagUnknownType_9101_ComponentsConfiguration_Channel_Y  = 0x1
	TagUnknownType_9101_ComponentsConfiguration_Channel_Cb = 0x2
	TagUnknownType_9101_ComponentsConfiguration_Channel_Cr = 0x3
	TagUnknownType_9101_ComponentsConfiguration_Channel_R  = 0x4
	TagUnknownType_9101_ComponentsConfiguration_Channel_G  = 0x5
	TagUnknownType_9101_ComponentsConfiguration_Channel_B  = 0x6
)
View Source
const (
	TagUnknownType_9101_ComponentsConfiguration_OTHER = iota
	TagUnknownType_9101_ComponentsConfiguration_RGB   = iota
	TagUnknownType_9101_ComponentsConfiguration_YCBCR = iota
)
View Source
const (
	// Tag-ID + Tag-Type + Unit-Count + Value/Offset.
	IfdTagEntrySize = uint32(2 + 2 + 4 + 4)
)
View Source
const (
	UnparseableUnknownTagValuePlaceholder = "!UNKNOWN"
)

Variables

View Source
var (
	ErrTagNotFound    = errors.New("tag not found")
	ErrTagNotStandard = errors.New("tag not a standard tag")
)
View Source
var (

	// EncodeDefaultByteOrder is the default byte-order for encoding operations.
	EncodeDefaultByteOrder = binary.BigEndian

	// Default byte order for tests.
	TestDefaultByteOrder = binary.BigEndian

	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: {0x2a, 0x00},
		binary.BigEndian:    {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 (
	// TODO(dustin): Rename TypeNames() to typeNames() and add getter.
	TypeNames = map[TagTypePrimitive]string{
		TypeByte:           "BYTE",
		TypeAscii:          "ASCII",
		TypeShort:          "SHORT",
		TypeLong:           "LONG",
		TypeRational:       "RATIONAL",
		TypeUndefined:      "UNDEFINED",
		TypeSignedLong:     "SLONG",
		TypeSignedRational: "SRATIONAL",

		TypeAsciiNoNul: "_ASCII_NO_NUL",
	}

	TypeNamesR = map[string]TagTypePrimitive{}
)
View Source
var (
	// ErrNotEnoughData is used when there isn't enough data to accomodate what
	// we're trying to parse (sizeof(type) * unit_count).
	ErrNotEnoughData = errors.New("not enough data for type")

	// ErrWrongType is used when we try to parse anything other than the
	// current type.
	ErrWrongType = errors.New("wrong type, can not parse")

	// ErrUnhandledUnknownTag is used when we try to parse a tag that's
	// recorded as an "unknown" type but not a documented tag (therefore
	// leaving us not knowning how to read it).
	ErrUnhandledUnknownTypedTag = errors.New("not a standard unknown-typed tag")
)
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(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 DumpBytes

func DumpBytes(data []byte)

func DumpBytesClause

func DumpBytesClause(data []byte)

func DumpBytesClauseToString

func DumpBytesClauseToString(data []byte) string

func DumpBytesToString

func DumpBytesToString(data []byte) string

func EncodeStringToBytes

func EncodeStringToBytes(tagType TagTypePrimitive, valueString string) (value interface{}, err error)

func EncodeUnknown_9286

func EncodeUnknown_9286(uc TagUnknownType_9298_UserComment) (encoded []byte, err error)

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 Format

func Format(rawBytes []byte, tagType TagTypePrimitive, justFirst bool, byteOrder binary.ByteOrder) (value string, err error)

Format returns a stringified value for the given bytes. Automatically calculates count based on type size.

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).

func TagTypeSize

func TagTypeSize(tagType TagTypePrimitive) int

func UndefinedValue

func UndefinedValue(ifdPath string, tagId uint16, valueContext interface{}, byteOrder binary.ByteOrder) (value interface{}, err error)

UndefinedValue knows how to resolve the value for most unknown-type tags.

Types

type BuilderTag

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

func NewBuilderTag

func NewBuilderTag(ifdPath string, tagId uint16, typeId 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
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, nil)

// 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 := TagUnknownType_9298_UserComment{
	EncodingType:  TagUnknownType_9298_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 EncodeableUndefinedValue

type EncodeableUndefinedValue struct {
	IfdPath    string
	TagId      uint16
	Parameters interface{}
}

type EncodedData

type EncodedData struct {
	Type    TagTypePrimitive
	Encoded []byte

	// TODO(dustin): Is this really necessary? We might have this just to correlate to the incoming stream format (raw bytes and a unit-count both for incoming and outgoing).
	UnitCount uint32
}

EncodedData encapsulates the compound output of an encoding operation.

func EncodeUndefined

func EncodeUndefined(ifdPath string, tagId uint16, value interface{}) (ed EncodedData, 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 RawTagVisitor) (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   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
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
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"

// We know the tag we want is on IFD0 (the first/root IFD).
results, err := index.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 := index.RootIfd.TagValue(ite)
log.PanicIf(err)

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

Canon EOS 5D Mark III

func (*Ifd) GetValueContext

func (ifd *Ifd) GetValueContext(ite *IfdTagEntry) *ValueContext

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(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) TagValue

func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error)

func (*Ifd) TagValueBytes

func (ifd *Ifd) TagValueBytes(ite *IfdTagEntry) (value []byte, err error)

func (*Ifd) Thumbnail

func (ifd *Ifd) Thumbnail() (data []byte, err error)
Example
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, itevr *IfdTagEntryValueResolver) (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, itevr *IfdTagEntryValueResolver, 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.

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, nil)

// 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 {
	value, err := childIfd.TagValue(ite)
	log.PanicIf(err)

	stringValue := value.(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, IfdPathStandard, 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", []Rational{{Numerator: 0x11112222, Denominator: 0x33334444}})
log.PanicIf(err)

err = ib.AddStandardWithName("ShutterSpeedValue", []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)

// addressableData is the byte-slice where the allocated data can be
// resolved (where position 0x0 will correlate with offset 0x0).
addressableData := exifData[ExifAddressableAreaStart:]

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

	fmt.Printf("%d: %s [%v]\n", i, e, 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, resolveValues bool) (index IfdIndex, err error)

Scan enumerates the different EXIF blocks (called IFDs).

func (*IfdEnumerate) GetValueContext

func (ie *IfdEnumerate) GetValueContext(ite *IfdTagEntry) *ValueContext

func (*IfdEnumerate) ParseIfd

func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor interface{}, doDescend bool, resolveValues 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 RawTagVisitor, resolveValue bool) (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 {
	TagId          uint16
	TagIndex       int
	TagType        TagTypePrimitive
	UnitCount      uint32
	ValueOffset    uint32
	RawValueOffset []byte

	// ChildIfdName is the right most atom in the IFD-path. We need this to
	// construct the fully-qualified IFD-path.
	ChildIfdName string

	// ChildIfdPath is the IFD-path of the child if this tag represents a child
	// IFD.
	ChildIfdPath string

	// ChildFqIfdPath is the IFD-path of the child if this tag represents a
	// child IFD. Includes indices.
	ChildFqIfdPath string

	// IfdPath is the IFD that this tag belongs to.
	IfdPath string
	// contains filtered or unexported fields
}

func ParseOneIfd

func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, ifdBlock []byte, visitor RawTagVisitor, resolveValues bool) (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, resolveValue bool) (tag *IfdTagEntry, err error)

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

func (*IfdTagEntry) String

func (ite *IfdTagEntry) String() string

func (*IfdTagEntry) Value

func (ite *IfdTagEntry) Value(addressableData []byte, byteOrder binary.ByteOrder) (value interface{}, err error)

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

func (*IfdTagEntry) ValueBytes

func (ite *IfdTagEntry) ValueBytes(addressableData []byte, byteOrder binary.ByteOrder) (value []byte, err error)

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

func (*IfdTagEntry) ValueString

func (ite *IfdTagEntry) ValueString(addressableData []byte, byteOrder binary.ByteOrder) (value string, err error)

ValueString renders a string from whatever the value in this tag is.

type IfdTagEntryValueResolver

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

IfdTagEntryValueResolver instances know how to resolve the values for any tag for a particular EXIF block.

func NewIfdTagEntryValueResolver

func NewIfdTagEntryValueResolver(exifData []byte, byteOrder binary.ByteOrder) (itevr *IfdTagEntryValueResolver)

func (*IfdTagEntryValueResolver) Value

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

func (*IfdTagEntryValueResolver) ValueBytes

func (itevr *IfdTagEntryValueResolver) ValueBytes(ite *IfdTagEntry) (value []byte, err error)

ValueBytes will resolve embedded or allocated data from the tag and return the raw bytes.

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) (ite *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    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 Parser

type Parser struct {
}

func (*Parser) ParseAscii

func (p *Parser) ParseAscii(data []byte, unitCount uint32) (value string, err error)

ParseAscii returns a string and auto-strips the trailing NUL character.

func (*Parser) ParseAsciiNoNul

func (p *Parser) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error)

ParseAsciiNoNul returns a string without any consideration for a trailing NUL character.

func (*Parser) ParseBytes

func (p *Parser) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error)

func (*Parser) ParseLongs

func (p *Parser) ParseLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint32, err error)

func (*Parser) ParseRationals

func (p *Parser) ParseRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []Rational, err error)

func (*Parser) ParseShorts

func (p *Parser) ParseShorts(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint16, err error)

func (*Parser) ParseSignedLongs

func (p *Parser) ParseSignedLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []int32, err error)

func (*Parser) ParseSignedRationals

func (p *Parser) ParseSignedRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []SignedRational, err 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 Rational

type Rational struct {
	Numerator   uint32
	Denominator uint32
}

type RawTagVisitor

type RawTagVisitor func(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)

RawTagVisitor is an optional callback that can get hit for every tag we parse through. `addressableData` is the byte array startign after the EXIF header (where the offsets of all IFDs and values are calculated from).

DEPRECATED(dustin): Use a RawTagWalk instead.

type RawTagWalk

type RawTagWalk interface {
	Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error)
}

RawTagVisitorPtr is an optional callback that can get hit for every tag we parse through. `addressableData` is the byte array startign after the EXIF header (where the offsets of all IFDs and values are calculated from).

This was reimplemented as an interface to allow for simpler change management in the future.

type RawTagWalkLegacyWrapper

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

func (RawTagWalkLegacyWrapper) Visit

func (rtwlw RawTagWalkLegacyWrapper) Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error)

type SignedRational

type SignedRational struct {
	Numerator   int32
	Denominator int32
}

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 TagType

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

func NewTagType

func NewTagType(tagType TagTypePrimitive, byteOrder binary.ByteOrder) TagType

func (TagType) ByteOrder

func (tt TagType) ByteOrder() binary.ByteOrder

func (TagType) Encode

func (tt TagType) Encode(value interface{}) (encoded []byte, err error)

Encode knows how to encode the given value to a byte slice.

func (TagType) FromString

func (tt TagType) FromString(valueString string) (value interface{}, err error)

func (TagType) Name

func (tt TagType) Name() string

func (TagType) ParseAscii

func (tt TagType) ParseAscii(data []byte, unitCount uint32) (value string, err error)

ParseAscii returns a string and auto-strips the trailing NUL character.

func (TagType) ParseAsciiNoNul

func (tt TagType) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error)

ParseAsciiNoNul returns a string without any consideration for a trailing NUL character.

func (TagType) ParseBytes

func (tt TagType) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error)

func (TagType) ParseLongs

func (tt TagType) ParseLongs(data []byte, unitCount uint32) (value []uint32, err error)

func (TagType) ParseRationals

func (tt TagType) ParseRationals(data []byte, unitCount uint32) (value []Rational, err error)

func (TagType) ParseShorts

func (tt TagType) ParseShorts(data []byte, unitCount uint32) (value []uint16, err error)

func (TagType) ParseSignedLongs

func (tt TagType) ParseSignedLongs(data []byte, unitCount uint32) (value []int32, err error)

func (TagType) ParseSignedRationals

func (tt TagType) ParseSignedRationals(data []byte, unitCount uint32) (value []SignedRational, err error)

func (TagType) ReadAsciiNoNulValue

func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, err error)

func (TagType) ReadAsciiValue

func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err error)

func (TagType) ReadByteValues

func (tt TagType) ReadByteValues(valueContext ValueContext) (value []byte, err error)

func (TagType) ReadLongValues

func (tt TagType) ReadLongValues(valueContext ValueContext) (value []uint32, err error)

func (TagType) ReadRationalValues

func (tt TagType) ReadRationalValues(valueContext ValueContext) (value []Rational, err error)

func (TagType) ReadShortValues

func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, err error)

func (TagType) ReadSignedLongValues

func (tt TagType) ReadSignedLongValues(valueContext ValueContext) (value []int32, err error)

func (TagType) ReadSignedRationalValues

func (tt TagType) ReadSignedRationalValues(valueContext ValueContext) (value []SignedRational, err error)

func (TagType) Resolve

func (tt TagType) Resolve(valueContext *ValueContext) (values interface{}, err error)

Resolve knows how to resolve the given value.

Since this method lacks the information to process unknown-type tags (e.g. byte-order, tag-ID, IFD type), it will return an error if attempted. See `Undefined()`.

func (TagType) ResolveAsString

func (tt TagType) ResolveAsString(valueContext ValueContext, justFirst bool) (value string, err error)

ResolveAsString resolves the given value and returns a flat string.

Where the type is not ASCII, `justFirst` indicates whether to just stringify the first item in the slice (or return an empty string if the slice is empty).

Since this method lacks the information to process unknown-type tags (e.g. byte-order, tag-ID, IFD type), it will return an error if attempted. See `Undefined()`.

func (TagType) Size

func (tt TagType) Size() int

func (TagType) String

func (tt TagType) String() string

func (TagType) Type

func (tt TagType) Type() TagTypePrimitive

type TagTypePrimitive

type TagTypePrimitive uint16
const (
	TypeByte           TagTypePrimitive = 1
	TypeAscii          TagTypePrimitive = 2
	TypeShort          TagTypePrimitive = 3
	TypeLong           TagTypePrimitive = 4
	TypeRational       TagTypePrimitive = 5
	TypeUndefined      TagTypePrimitive = 7
	TypeSignedLong     TagTypePrimitive = 9
	TypeSignedRational TagTypePrimitive = 10

	// TypeAsciiNoNul is just a pseudo-type, for our own purposes.
	TypeAsciiNoNul TagTypePrimitive = 0xf0
)

func (TagTypePrimitive) Size

func (tagType TagTypePrimitive) Size() int

func (TagTypePrimitive) String

func (typeType TagTypePrimitive) String() string

type TagUnknownType_9101_ComponentsConfiguration

type TagUnknownType_9101_ComponentsConfiguration struct {
	ConfigurationId    int
	ConfigurationBytes []byte
}

func (TagUnknownType_9101_ComponentsConfiguration) String

func (TagUnknownType_9101_ComponentsConfiguration) ValueBytes

func (uc TagUnknownType_9101_ComponentsConfiguration) ValueBytes() (value []byte, err error)

type TagUnknownType_927C_MakerNote

type TagUnknownType_927C_MakerNote struct {
	MakerNoteType  []byte
	MakerNoteBytes []byte
}

func (TagUnknownType_927C_MakerNote) String

func (TagUnknownType_927C_MakerNote) ValueBytes

func (uc TagUnknownType_927C_MakerNote) ValueBytes() (value []byte, err error)

type TagUnknownType_9298_UserComment

type TagUnknownType_9298_UserComment struct {
	EncodingType  int
	EncodingBytes []byte
}

func (TagUnknownType_9298_UserComment) String

func (TagUnknownType_9298_UserComment) ValueBytes

func (uc TagUnknownType_9298_UserComment) ValueBytes() (value []byte, err error)

type TagUnknownType_GeneralString

type TagUnknownType_GeneralString string

func (TagUnknownType_GeneralString) ValueBytes

func (gs TagUnknownType_GeneralString) ValueBytes() (value []byte, err error)

type TagUnknownType_UnknownValue

type TagUnknownType_UnknownValue []byte

func (TagUnknownType_UnknownValue) String

func (tutuv TagUnknownType_UnknownValue) String() string

type UnknownTagValue

type UnknownTagValue interface {
	ValueBytes() ([]byte, error)
}

type ValueContext

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

ValueContext describes all of the parameters required to find and extract the actual tag value.

func (*ValueContext) AddressableData

func (vc *ValueContext) AddressableData() []byte

func (*ValueContext) Format

func (vc *ValueContext) Format() (value string, err error)

Format returns a string representation for the value.

Where the type is not ASCII, `justFirst` indicates whether to just stringify the first item in the slice (or return an empty string if the slice is empty).

Since this method lacks the information to process undefined-type tags (e.g. byte-order, tag-ID, IFD type), it will return an error if attempted. See `Undefined()`.

func (*ValueContext) FormatFirst

func (vc *ValueContext) FormatFirst() (value string, err error)

FormatOne is similar to `Format` but only gets and stringifies the first item.

func (*ValueContext) RawValueOffset

func (vc *ValueContext) RawValueOffset() []byte

func (*ValueContext) ReadAscii

func (vc *ValueContext) ReadAscii() (value string, err error)

func (*ValueContext) ReadAsciiNoNul

func (vc *ValueContext) ReadAsciiNoNul() (value string, err error)

func (*ValueContext) ReadBytes

func (vc *ValueContext) ReadBytes() (value []byte, err error)

func (*ValueContext) ReadLongs

func (vc *ValueContext) ReadLongs() (value []uint32, err error)

func (*ValueContext) ReadRationals

func (vc *ValueContext) ReadRationals() (value []Rational, err error)

func (*ValueContext) ReadShorts

func (vc *ValueContext) ReadShorts() (value []uint16, err error)

func (*ValueContext) ReadSignedLongs

func (vc *ValueContext) ReadSignedLongs() (value []int32, err error)

func (*ValueContext) ReadSignedRationals

func (vc *ValueContext) ReadSignedRationals() (value []SignedRational, err error)

func (*ValueContext) SetUnknownValueType

func (vc *ValueContext) SetUnknownValueType(tagType TagTypePrimitive)

func (*ValueContext) Undefined

func (vc *ValueContext) Undefined() (value interface{}, err error)

Undefined attempts to identify and decode supported undefined-type fields. This is the primary, preferred interface to reading undefined values.

func (*ValueContext) UnitCount

func (vc *ValueContext) UnitCount() uint32

func (*ValueContext) ValueOffset

func (vc *ValueContext) ValueOffset() uint32

func (*ValueContext) Values

func (vc *ValueContext) Values() (values interface{}, err error)

Values knows how to resolve the given value. This value is always a list (undefined-values aside), so we're named accordingly.

Since this method lacks the information to process unknown-type tags (e.g. byte-order, tag-ID, IFD type), it will return an error if attempted. See `Undefined()`.

type ValueEncoder

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

func NewValueEncoder

func NewValueEncoder(byteOrder binary.ByteOrder) *ValueEncoder

func (*ValueEncoder) Encode

func (ve *ValueEncoder) Encode(value interface{}) (ed EncodedData, err error)

Encode returns bytes for the given value, infering type from the actual value. This does not support `TypeAsciiNoNull` (all strings are encoded as `TypeAscii`).

func (*ValueEncoder) EncodeWithType

func (ve *ValueEncoder) EncodeWithType(tt TagType, value interface{}) (ed EncodedData, err error)

EncodeWithType returns bytes for the given value, using the given `TagType` value to determine how to encode. This supports `TypeAsciiNoNul`.

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