IfdBuilder: Added AddFromConfigWithName().

- IfdBuilder: Added NewBuilderTagFromConfigWithName().
- TagIndex: Added GetWithName().
- IfdByteEncoder: Updated example to use names.
pull/3/head
Dustin Oprea 2018-05-01 12:22:48 -04:00
parent b78ca39e2b
commit 54e4cb73eb
6 changed files with 148 additions and 162 deletions

View File

@ -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() {

View File

@ -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
}

View File

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

View File

@ -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
View File

@ -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 {

View File

@ -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")
}
}