mirror of https://github.com/dsoprea/go-exif.git
IfdBuilder: Added AddFromConfigWithName().
- IfdBuilder: Added NewBuilderTagFromConfigWithName(). - TagIndex: Added GetWithName(). - IfdByteEncoder: Updated example to use names.pull/3/head
parent
b78ca39e2b
commit
54e4cb73eb
|
@ -333,8 +333,6 @@ func TestBuildAndParseExifHeader(t *testing.T) {
|
|||
} else if eh.FirstIfdOffset != 0x11223344 {
|
||||
t.Fatalf("First IFD offset not correct.")
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", eh)
|
||||
}
|
||||
|
||||
func ExampleBuildExifHeader() {
|
||||
|
|
193
ifd_builder.go
193
ifd_builder.go
|
@ -122,6 +122,31 @@ func NewBuilderTagFromConfig(ii IfdIdentity, tagId uint16, byteOrder binary.Byte
|
|||
}
|
||||
}
|
||||
|
||||
// NewBuilderTagFromConfigWithName allows us to easily generate solid, consistent tags
|
||||
// for testing with. `ii` is the tpye of IFD that owns this tag. This can not be
|
||||
// an IFD (IFDs are not associated with standardized, official names).
|
||||
func NewBuilderTagFromConfigWithName(ii IfdIdentity, tagName string, byteOrder binary.ByteOrder, value interface{}) builderTag {
|
||||
ti := NewTagIndex()
|
||||
|
||||
it, err := ti.GetWithName(ii, tagName)
|
||||
log.PanicIf(err)
|
||||
|
||||
tt := NewTagType(it.Type, byteOrder)
|
||||
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
ed, err := ve.EncodeWithType(tt, value)
|
||||
log.PanicIf(err)
|
||||
|
||||
tagValue := NewIfdBuilderTagValueFromBytes(ed.Encoded)
|
||||
|
||||
return builderTag{
|
||||
ii: ii,
|
||||
tagId: it.Id,
|
||||
value: tagValue,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type IfdBuilder struct {
|
||||
// ifd is the IfdIdentity instance of the IFD that owns the current tag.
|
||||
|
@ -259,8 +284,7 @@ func (ib *IfdBuilder) dump(levels int) {
|
|||
fmt.Printf("\n")
|
||||
|
||||
for i, tag := range ib.tags {
|
||||
// TODO(dustin): Pre-supposes that IFDs are uniquely named, which is fince since we're in charge of their actual naming (only the tag-IDs are important in the spec). However, we're referring to them by name which is inconsistent with our implementation where we take the name as non-unique and our self-imposed unique IDs as the better choice. Reimplement using these.
|
||||
_, isChildIb := IfdTagNames[ib.ii.IfdName][tag.tagId]
|
||||
_, isChildIb := IfdTagNameWithId(ib.ii.IfdName, tag.tagId)
|
||||
|
||||
tagName := ""
|
||||
|
||||
|
@ -334,153 +358,6 @@ func (ib *IfdBuilder) DumpToStrings() (lines []string) {
|
|||
return ib.dumpToStrings(ib, "", lines)
|
||||
}
|
||||
|
||||
// // calculateRawTableSize returns the number of bytes required just to store the
|
||||
// // basic IFD header and tags. This needs to be called before we can even write
|
||||
// // the tags so that we can know where the data starts and can calculate offsets.
|
||||
// func (ib *IfdBuilder) calculateTableSize() (size uint32, err error) {
|
||||
// defer func() {
|
||||
// if state := recover(); state != nil {
|
||||
// err = log.Wrap(state.(error))
|
||||
// }
|
||||
// }()
|
||||
|
||||
|
||||
// // TODO(dustin): !! Finish.
|
||||
|
||||
|
||||
// return 0, nil
|
||||
// }
|
||||
|
||||
// // calculateDataSize returns the number of bytes required the offset-based data
|
||||
// // of the IFD.
|
||||
// func (ib *IfdBuilder) calculateDataSize(tableSize uint32) (size uint32, err error) {
|
||||
// defer func() {
|
||||
// if state := recover(); state != nil {
|
||||
// err = log.Wrap(state.(error))
|
||||
// }
|
||||
// }()
|
||||
|
||||
|
||||
// // TODO(dustin): !! Finish.
|
||||
|
||||
|
||||
// return 0, nil
|
||||
// }
|
||||
|
||||
// // generateBytes populates the given table and data byte-arrays. `dataOffset`
|
||||
// // is the distance from the beginning of the IFD to the beginning of the IFD's
|
||||
// // data (following the IFD's table). It may be used to calculate the final
|
||||
// // offset of the data we store there so that we can reference it from the IFD
|
||||
// // table. The `ioi` is used to know where to insert child IFDs at.
|
||||
// //
|
||||
// // len(ifdTableRaw) == calculateTableSize()
|
||||
// // len(ifdDataRaw) == calculateDataSize()
|
||||
// func (ib *IfdBuilder) generateBytes(dataOffset uint32, ifdTableRaw, ifdDataRaw []byte, ioi *ifdOffsetIterator) (err error) {
|
||||
// defer func() {
|
||||
// if state := recover(); state != nil {
|
||||
// err = log.Wrap(state.(error))
|
||||
// }
|
||||
// }()
|
||||
|
||||
|
||||
// // TODO(dustin): !! Finish.
|
||||
|
||||
// // TODO(dustin): !! Some offsets of existing IFDs will have to be reallocated if there are any updates. We'll need to be able to resolve the original value against the original EXIF data for that, which we currently don't have access to, yet, from here.
|
||||
// // TODO(dustin): !! Test that the offsets are identical if there are no changes (on principle).
|
||||
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // allocateIfd will produce the two byte-arrays for every IFD and bump the IOI
|
||||
// // for the next IFD. This is the foundation of how offsets are calculated.
|
||||
// func (ib *IfdBuilder) allocateIfd(tableSize, dataSize uint32, ioi *ifdOffsetIterator) (tableRaw []byte, dataRaw []byte, dataOffset uint32, err error) {
|
||||
// defer func() {
|
||||
// if state := recover(); state != nil {
|
||||
// err = log.Wrap(state.(error))
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // Allocate the size required and iterate our offset marker
|
||||
// // appropriately so the IFD-build knows where it can calculate its
|
||||
// // offsets from.
|
||||
|
||||
// tableRaw = make([]byte, tableSize)
|
||||
// dataRaw = make([]byte, dataSize)
|
||||
|
||||
// dataOffset = ioi.Offset() + tableSize
|
||||
// ioi.Step(tableSize + dataSize)
|
||||
|
||||
// return tableRaw, dataRaw, dataOffset, nil
|
||||
// }
|
||||
|
||||
// // BuildExif returns a new byte array of EXIF data.
|
||||
// func (ib *IfdBuilder) BuildExif() (new []byte, err error) {
|
||||
// defer func() {
|
||||
// if state := recover(); state != nil {
|
||||
// err = log.Wrap(state.(error))
|
||||
// }
|
||||
// }()
|
||||
|
||||
// b := bytes.Buffer{}
|
||||
|
||||
// ioi := &ifdOffsetIterator{
|
||||
// offset: RootIfdExifOffset,
|
||||
// }
|
||||
|
||||
// ptr := ib
|
||||
|
||||
// for ; ptr != nil ; {
|
||||
// // Figure out the size requirements.
|
||||
|
||||
// tableSize, err := ptr.calculateTableSize()
|
||||
// log.PanicIf(err)
|
||||
|
||||
// dataSize, err := ptr.calculateDataSize(tableSize)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// // Allocate the size required and iterate our offset marker
|
||||
// // appropriately so the IFD-build knows where it can calculate its
|
||||
// // offsets from.
|
||||
|
||||
// tableRaw, dataRaw, dataOffset, err := ib.allocateIfd(tableSize, dataSize, ioi)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// // Build.
|
||||
|
||||
// err = ptr.generateBytes(dataOffset, tableRaw, dataRaw, ioi)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// // Attach the new data to the stream.
|
||||
|
||||
// _, err = b.Write(tableRaw)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// _, err = b.Write(dataRaw)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// ptr = ptr.nextIb
|
||||
|
||||
// // Write the offset of the next IFD (or 0x0 for none).
|
||||
|
||||
// nextIfdOffset := uint32(0)
|
||||
|
||||
// if ptr != nil {
|
||||
// // This might've been iterated by `generateBytes()`. It'll also
|
||||
// // point at the next offset that we can install an IFD to.
|
||||
// nextIfdOffset = ioi.Offset()
|
||||
// }
|
||||
|
||||
// nextIfdOffsetBytes := make([]byte, 4)
|
||||
// ib.byteOrder.PutUint32(nextIfdOffsetBytes, nextIfdOffset)
|
||||
|
||||
// _, err = b.Write(nextIfdOffsetBytes)
|
||||
// log.PanicIf(err)
|
||||
// }
|
||||
|
||||
// return b.Bytes(), nil
|
||||
// }
|
||||
|
||||
func (ib *IfdBuilder) SetNextIfd(nextIb *IfdBuilder) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
|
@ -777,3 +654,21 @@ func (ib *IfdBuilder) AddFromConfig(tagId uint16, value interface{}) (err error)
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddFromConfigWithName 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 (ib *IfdBuilder) AddFromConfigWithName(tagName string, value interface{}) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
bt := NewBuilderTagFromConfigWithName(ib.ii, tagName, ib.byteOrder, value)
|
||||
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -748,22 +748,22 @@ func ExampleIfdByteEncoder_EncodeToExif() {
|
|||
|
||||
ib := NewIfdBuilder(RootIi, EncodeDefaultByteOrder)
|
||||
|
||||
err := ib.AddFromConfig(0x000b, "asciivalue")
|
||||
err := ib.AddFromConfigWithName("ProcessingSoftware", "asciivalue")
|
||||
log.PanicIf(err)
|
||||
|
||||
err = ib.AddFromConfig(0x0150, []uint8 { 0x11 })
|
||||
err = ib.AddFromConfigWithName("DotRange", []uint8 { 0x11 })
|
||||
log.PanicIf(err)
|
||||
|
||||
err = ib.AddFromConfig(0x00ff, []uint16 { 0x2233 })
|
||||
err = ib.AddFromConfigWithName("SubfileType", []uint16 { 0x2233 })
|
||||
log.PanicIf(err)
|
||||
|
||||
err = ib.AddFromConfig(0x0100, []uint32 { 0x44556677 })
|
||||
err = ib.AddFromConfigWithName("ImageWidth", []uint32 { 0x44556677 })
|
||||
log.PanicIf(err)
|
||||
|
||||
err = ib.AddFromConfig(0x013e, []Rational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
|
||||
err = ib.AddFromConfigWithName("WhitePoint", []Rational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
|
||||
log.PanicIf(err)
|
||||
|
||||
err = ib.AddFromConfig(0x9201, []SignedRational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
|
||||
err = ib.AddFromConfigWithName("ShutterSpeedValue", []SignedRational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
|
||||
log.PanicIf(err)
|
||||
|
||||
|
||||
|
|
|
@ -1218,3 +1218,42 @@ func TestNewBuilderTagFromConfig_TwoUnits(t *testing.T) {
|
|||
t.Fatalf("value not correct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBuilderTagFromConfigWithName(t *testing.T) {
|
||||
bt := NewBuilderTagFromConfigWithName(ExifIi, "ISOSpeed", TestDefaultByteOrder, []uint32 { uint32(0x1234), uint32(0x5678) })
|
||||
|
||||
if bt.ii != ExifIi {
|
||||
t.Fatalf("II in builderTag not correct")
|
||||
} else if bt.tagId != 0x8833 {
|
||||
t.Fatalf("tag-ID not correct")
|
||||
} else if bytes.Compare(bt.value.Bytes(), []byte {
|
||||
0x0, 0x0, 0x12, 0x34,
|
||||
0x0, 0x0, 0x56, 0x78, }) != 0 {
|
||||
t.Fatalf("value not correct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddFromConfigWithName(t *testing.T) {
|
||||
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
|
||||
|
||||
err := ib.AddFromConfigWithName("ProcessingSoftware", "some software")
|
||||
log.PanicIf(err)
|
||||
|
||||
if len(ib.tags) != 1 {
|
||||
t.Fatalf("Exactly one tag was not found: (%d)", len(ib.tags))
|
||||
}
|
||||
|
||||
bt := ib.tags[0]
|
||||
|
||||
if bt.ii != RootIi {
|
||||
t.Fatalf("II not correct: %s", bt.ii)
|
||||
} else if bt.tagId != 0x000b {
|
||||
t.Fatalf("Tag-ID not correct: (0x%02x)", bt.tagId)
|
||||
}
|
||||
|
||||
s := string(bt.value.Bytes())
|
||||
|
||||
if s != "some software\000" {
|
||||
t.Fatalf("Value not correct: (%d) [%s]", len(s), s)
|
||||
}
|
||||
}
|
||||
|
|
51
tags.go
51
tags.go
|
@ -51,7 +51,8 @@ var (
|
|||
},
|
||||
}
|
||||
|
||||
// IfdTagNames is populated in the init(), below.
|
||||
// IfdTagNames contains the tag ID-to-name mappings and is populated by
|
||||
// init().
|
||||
IfdTagNames = map[string]map[uint16]string {}
|
||||
|
||||
// IFD Identities. These are often how we refer to IFDs, from call to call.
|
||||
|
@ -126,12 +127,13 @@ func (it IndexedTag) IsName(ifd, name string) bool {
|
|||
return it.Name == name && it.Ifd == ifd
|
||||
}
|
||||
|
||||
func (it IndexedTag) Is(id uint16) bool {
|
||||
return it.Id == id
|
||||
func (it IndexedTag) Is(ifd string, id uint16) bool {
|
||||
return it.Id == id && it.Ifd == ifd
|
||||
}
|
||||
|
||||
type TagIndex struct {
|
||||
tagsByIfd map[string]map[uint16]*IndexedTag
|
||||
tagsByIfdR map[string]map[string]*IndexedTag
|
||||
}
|
||||
|
||||
func NewTagIndex() *TagIndex {
|
||||
|
@ -153,6 +155,7 @@ func (ti *TagIndex) load() (err error) {
|
|||
|
||||
// Read static data.
|
||||
|
||||
// TODO(dustin): !! Load this from init() so we're not doing it every time.
|
||||
f, err := os.Open(tagDataFilepath)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
@ -167,6 +170,7 @@ func (ti *TagIndex) load() (err error) {
|
|||
// Load structure.
|
||||
|
||||
tagsByIfd := make(map[string]map[uint16]*IndexedTag)
|
||||
tagsByIfdR := make(map[string]map[string]*IndexedTag)
|
||||
|
||||
count := 0
|
||||
for ifdName, tags := range encodedIfds {
|
||||
|
@ -175,7 +179,7 @@ func (ti *TagIndex) load() (err error) {
|
|||
tagName := tagInfo.Name
|
||||
tagTypeName := tagInfo.TypeName
|
||||
|
||||
// TODO(dustin): !! Non-standard types but present types. Ignore for right now.
|
||||
// TODO(dustin): !! Non-standard types, but found in real data. Ignore for right now.
|
||||
if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE" {
|
||||
continue
|
||||
}
|
||||
|
@ -193,6 +197,9 @@ if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE"
|
|||
Type: tagTypeId,
|
||||
}
|
||||
|
||||
|
||||
// Store by ID.
|
||||
|
||||
family, found := tagsByIfd[ifdName]
|
||||
if found == false {
|
||||
family = make(map[uint16]*IndexedTag)
|
||||
|
@ -205,17 +212,34 @@ if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE"
|
|||
|
||||
family[tagId] = tag
|
||||
|
||||
|
||||
// Store by name.
|
||||
|
||||
familyR, found := tagsByIfdR[ifdName]
|
||||
if found == false {
|
||||
familyR = make(map[string]*IndexedTag)
|
||||
tagsByIfdR[ifdName] = familyR
|
||||
}
|
||||
|
||||
if _, found := familyR[tagName]; found == true {
|
||||
log.Panicf("tag-name defined more than once for IFD [%s]: (%s)", ifdName, tagName)
|
||||
}
|
||||
|
||||
familyR[tagName] = tag
|
||||
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
ti.tagsByIfd = tagsByIfd
|
||||
ti.tagsByIfdR = tagsByIfdR
|
||||
|
||||
tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns information about the non-IFD tag.
|
||||
func (ti *TagIndex) Get(ii IfdIdentity, id uint16) (it *IndexedTag, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
|
@ -236,6 +260,22 @@ func (ti *TagIndex) Get(ii IfdIdentity, id uint16) (it *IndexedTag, err error) {
|
|||
return it, nil
|
||||
}
|
||||
|
||||
// Get returns information about the non-IFD tag.
|
||||
func (ti *TagIndex) GetWithName(ii IfdIdentity, name string) (it *IndexedTag, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
it, found := ti.tagsByIfdR[ii.IfdName][name]
|
||||
if found != true {
|
||||
log.Panicf("tag with IFD [%s] and name [%s] not known", ii.IfdName, name)
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
// IfdTagWithId returns true if the given tag points to a child IFD block.
|
||||
func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string {
|
||||
name, found := IfdTagNameWithId(parentIfdName, tagId)
|
||||
|
@ -247,6 +287,9 @@ func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string {
|
|||
}
|
||||
|
||||
// IfdTagWithId returns true if the given tag points to a child IFD block.
|
||||
|
||||
// TODO(dustin): !! Rewrite to take an IfdIdentity, instead. We shouldn't expect that IFD names are globally unique.
|
||||
|
||||
func IfdTagNameWithId(parentIfdName string, tagId uint16) (name string, found bool) {
|
||||
if tags, found := IfdTagNames[parentIfdName]; found == true {
|
||||
if name, found = tags[tagId]; found == true {
|
||||
|
|
13
tags_test.go
13
tags_test.go
|
@ -19,7 +19,18 @@ func TestGet(t *testing.T) {
|
|||
it, err := ti.Get(RootIi, 0x10f)
|
||||
log.PanicIf(err)
|
||||
|
||||
if it.Is(0x10f) == false || it.IsName("IFD", "Make") == false {
|
||||
if it.Is("IFD", 0x10f) == false || it.IsName("IFD", "Make") == false {
|
||||
t.Fatalf("tag info not correct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWithName(t *testing.T) {
|
||||
ti := NewTagIndex()
|
||||
|
||||
it, err := ti.GetWithName(RootIi, "Make")
|
||||
log.PanicIf(err)
|
||||
|
||||
if it.Is("IFD", 0x10f) == false || it.Is("IFD", 0x10f) == false {
|
||||
t.Fatalf("tag info not correct")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue