mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-05-31 11:41:57 +00:00
- Updated IfdByteEncoder tests to use it instead of hacking-together their own BT's (makes for more standardized, consistent testing). - Universally refactored all core IFD knowledge implemented upon a single IFD name to instead work with IfdIdentity instances, instead, in order to validate that we only recognize the IFDs only in the context of the correct parents in the hierarchy. - Implemented standard testing byte-order (assigned to TestDefaultByteOrder).
173 lines
3.7 KiB
Go
173 lines
3.7 KiB
Go
package exif
|
|
|
|
import (
|
|
"os"
|
|
"errors"
|
|
"bytes"
|
|
|
|
"io/ioutil"
|
|
"encoding/binary"
|
|
|
|
"github.com/dsoprea/go-logging"
|
|
)
|
|
|
|
const (
|
|
// RootIfdExifOffset is the offset of the first IFD in the block of EXIF
|
|
// data.
|
|
RootIfdExifOffset = uint32(0x0008)
|
|
|
|
// ExifAddressableAreaStart is the start of where all offsets are relative to.
|
|
ExifAddressableAreaStart = uint32(0x6)
|
|
)
|
|
|
|
var (
|
|
exifLogger = log.NewLogger("exif.exif")
|
|
)
|
|
|
|
var (
|
|
ErrNotExif = errors.New("not exif data")
|
|
ErrExifHeaderError = errors.New("exif header error")
|
|
)
|
|
|
|
|
|
func IsExif(data []byte) (ok bool) {
|
|
if bytes.Compare(data[:6], []byte("Exif\000\000")) == 0 {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
|
|
type Exif struct {
|
|
|
|
}
|
|
|
|
func NewExif() *Exif {
|
|
return new(Exif)
|
|
}
|
|
|
|
func (e *Exif) SearchAndExtractExif(filepath string) (rawExif []byte, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err := log.Wrap(state.(error))
|
|
log.Panic(err)
|
|
}
|
|
}()
|
|
|
|
// Open the file.
|
|
|
|
f, err := os.Open(filepath)
|
|
log.PanicIf(err)
|
|
|
|
defer f.Close()
|
|
|
|
data, err := ioutil.ReadAll(f)
|
|
log.PanicIf(err)
|
|
|
|
// Search for the beginning of the EXIF information. The EXIF is near the
|
|
// beginning of our/most JPEGs, so this has a very low cost.
|
|
|
|
foundAt := -1
|
|
for i := 0; i < len(data); i++ {
|
|
if IsExif(data[i:i + 6]) == true {
|
|
foundAt = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundAt == -1 {
|
|
log.Panicf("EXIF start not found")
|
|
}
|
|
|
|
return data[foundAt:], nil
|
|
}
|
|
|
|
|
|
type ExifHeader struct {
|
|
ByteOrder binary.ByteOrder
|
|
FirstIfdOffset uint32
|
|
}
|
|
|
|
func (e *Exif) ParseExifHeader(data []byte) (eh ExifHeader, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
if IsExif(data) == false {
|
|
log.Panic(ErrNotExif)
|
|
}
|
|
|
|
// Good reference:
|
|
//
|
|
// CIPA DC-008-2016; JEITA CP-3451D
|
|
// -> http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
|
|
|
|
byteOrderSignature := data[6:8]
|
|
var byteOrder binary.ByteOrder
|
|
byteOrder = binary.BigEndian
|
|
if string(byteOrderSignature) == "II" {
|
|
byteOrder = binary.LittleEndian
|
|
} else if string(byteOrderSignature) != "MM" {
|
|
log.Panicf("byte-order not recognized: [%v]", byteOrderSignature)
|
|
}
|
|
|
|
fixedBytes := data[8:10]
|
|
if fixedBytes[0] != 0x2a || fixedBytes[1] != 0x00 {
|
|
exifLogger.Warningf(nil, "EXIF header fixed-bytes should be 0x002a but are: [%v]", fixedBytes)
|
|
log.Panic(ErrExifHeaderError)
|
|
}
|
|
|
|
firstIfdOffset := uint32(0)
|
|
if byteOrder == binary.BigEndian {
|
|
firstIfdOffset = binary.BigEndian.Uint32(data[10:14])
|
|
} else {
|
|
firstIfdOffset = binary.LittleEndian.Uint32(data[10:14])
|
|
}
|
|
|
|
eh = ExifHeader{
|
|
ByteOrder: byteOrder,
|
|
FirstIfdOffset: firstIfdOffset,
|
|
}
|
|
|
|
return eh, nil
|
|
}
|
|
|
|
func (e *Exif) Visit(exifData []byte, visitor TagVisitor) (eh ExifHeader, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
eh, err = e.ParseExifHeader(exifData)
|
|
log.PanicIf(err)
|
|
|
|
ie := NewIfdEnumerate(exifData, eh.ByteOrder)
|
|
|
|
err = ie.Scan(eh.FirstIfdOffset, visitor)
|
|
log.PanicIf(err)
|
|
|
|
return eh, nil
|
|
}
|
|
|
|
func (e *Exif) Collect(exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
eh, err = e.ParseExifHeader(exifData)
|
|
log.PanicIf(err)
|
|
|
|
ie := NewIfdEnumerate(exifData, eh.ByteOrder)
|
|
|
|
index, err = ie.Collect(eh.FirstIfdOffset)
|
|
log.PanicIf(err)
|
|
|
|
return eh, index, nil
|
|
}
|