package exif import ( "bytes" "fmt" "reflect" "sort" "strings" "testing" "time" "github.com/dsoprea/go-exif/v3/common" "github.com/dsoprea/go-exif/v3/undefined" "github.com/dsoprea/go-logging" ) func TestIfdBuilder_Add(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) originalBytes := []byte{0x11, 0x22, 0x33} bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x44, value: NewIfdBuilderTagValueFromBytes([]byte(originalBytes)), } err = ib.Add(bt) log.PanicIf(err) if ib.ifdIdentity.UnindexedString() != exifcommon.IfdStandardIfdIdentity.UnindexedString() { t.Fatalf("IFD name not correct.") } else if ib.IfdIdentity().TagId() != 0 { t.Fatalf("IFD tag-ID not correct.") } else if ib.byteOrder != exifcommon.TestDefaultByteOrder { t.Fatalf("IFD byte-order not correct.") } else if len(ib.tags) != 4 { t.Fatalf("IFD tag-count not correct.") } else if ib.existingOffset != 0 { t.Fatalf("IFD offset not correct.") } else if ib.nextIb != nil { t.Fatalf("Next-IFD not correct.") } tags := ib.Tags() if tags[0].tagId != 0x11 { t.Fatalf("tag (0) tag-ID not correct") } else if bytes.Compare(tags[0].value.Bytes(), []byte("test string")) != 0 { t.Fatalf("tag (0) value not correct") } if tags[1].tagId != 0x22 { t.Fatalf("tag (1) tag-ID not correct") } else if bytes.Compare(tags[1].value.Bytes(), []byte("test string2")) != 0 { t.Fatalf("tag (1) value not correct") } if tags[2].tagId != 0x33 { t.Fatalf("tag (2) tag-ID not correct") } else if bytes.Compare(tags[2].value.Bytes(), []byte("test string3")) != 0 { t.Fatalf("tag (2) value not correct") } if tags[3].tagId != 0x44 { t.Fatalf("tag (3) tag-ID not correct") } else if bytes.Compare(tags[3].value.Bytes(), originalBytes) != 0 { t.Fatalf("tag (3) value not correct") } } func TestIfdBuilder_SetNextIb(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib1 := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) ib2 := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) if ib1.nextIb != nil { t.Fatalf("Next-IFD for IB1 not initially terminal.") } err = ib1.SetNextIb(ib2) log.PanicIf(err) if ib1.nextIb != ib2 { t.Fatalf("Next-IFD for IB1 not correct.") } else if ib2.nextIb != nil { t.Fatalf("Next-IFD for IB2 terminal.") } } func TestIfdBuilder_AddChildIb(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) ibChild := NewIfdBuilder(im, ti, exifcommon.IfdExifStandardIfdIdentity, exifcommon.TestDefaultByteOrder) err = ib.AddChildIb(ibChild) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) if ib.tags[0].tagId != 0x11 { t.Fatalf("first tag not correct") } else if ib.tags[1].tagId != ibChild.IfdIdentity().TagId() { t.Fatalf("second tag ID does not match child-IFD tag-ID: (0x%04x) != (0x%04x)", ib.tags[1].tagId, ibChild.IfdIdentity().TagId()) } else if ib.tags[1].value.Ib() != ibChild { t.Fatalf("second tagvalue does not match child-IFD") } else if ib.tags[2].tagId != 0x22 { t.Fatalf("third tag not correct") } } func TestIfdBuilder_AddTagsFromExisting(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) log.PrintError(err) t.Fatalf("Test failure.") } }() exifData := getExifSimpleTestIbBytes() im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, exifData) log.PanicIf(err) ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) err = ib.AddTagsFromExisting(index.RootIfd, nil, nil) log.PanicIf(err) expected := []uint16{ 0x000b, 0x00ff, 0x0100, 0x013e, } if len(ib.tags) != len(expected) { t.Fatalf("Tag count not correct: (%d) != (%d)", len(ib.tags), len(expected)) } for i, tag := range ib.tags { if tag.tagId != expected[i] { t.Fatalf("Tag (%d) not correct: (0x%04x) != (0x%04x)", i, tag.tagId, expected[i]) } } } func TestIfdBuilder_AddTagsFromExisting__Includes(t *testing.T) { exifData := getExifSimpleTestIbBytes() im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, exifData) log.PanicIf(err) ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) err = ib.AddTagsFromExisting(index.RootIfd, []uint16{0x00ff}, nil) log.PanicIf(err) expected := []uint16{ 0x00ff, } if len(ib.tags) != len(expected) { t.Fatalf("Tag count not correct: (%d) != (%d)", len(ib.tags), len(expected)) } for i, tag := range ib.tags { if tag.tagId != expected[i] { t.Fatalf("Tag (%d) not correct: (0x%04x) != (0x%04x)", i, tag.tagId, expected[i]) } } } func TestIfdBuilder_AddTagsFromExisting__Excludes(t *testing.T) { exifData := getExifSimpleTestIbBytes() im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, exifData) log.PanicIf(err) ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) err = ib.AddTagsFromExisting(index.RootIfd, nil, []uint16{0xff}) log.PanicIf(err) expected := []uint16{ 0x000b, 0x0100, 0x013e, } if len(ib.tags) != len(expected) { t.Fatalf("Tag count not correct: (%d) != (%d)", len(ib.tags), len(expected)) } for i, tag := range ib.tags { if tag.tagId != expected[i] { t.Fatalf("Tag (%d) not correct: (0x%04x) != (0x%04x)", i, tag.tagId, expected[i]) } } } func TestIfdBuilder_FindN__First_1(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x11, 1) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 0 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x11 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestIfdBuilder_FindN__First_2_1Returned(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x11, 2) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 0 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x11 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestIfdBuilder_FindN__First_2_2Returned(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string5")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x11, 2) log.PanicIf(err) if len(found) != 2 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 0 { log.Panicf("First result was not in the right place: (%d)", found[0]) } else if found[1] != 3 { log.Panicf("Second result was not in the right place: (%d)", found[1]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x11 || bytes.Compare(bt.value.Bytes(), []byte("test string")) != 0 { log.Panicf("Found entry 0 is not correct: (0x%04x) [%s]", bt.tagId, bt.value) } bt = tags[found[1]] if bt.tagId != 0x11 || bytes.Compare(bt.value.Bytes(), []byte("test string4")) != 0 { log.Panicf("Found entry 1 is not correct: (0x%04x) [%s]", bt.tagId, bt.value) } } func TestIfdBuilder_FindN__Middle_WithDuplicates(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string5")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string6")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x33, 1) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 2 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x33 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestIfdBuilder_FindN__Middle_NoDuplicates(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) found, err := ib.FindN(0x33, 1) log.PanicIf(err) if len(found) != 1 { log.Panicf("Exactly one result was not found: (%d)", len(found)) } else if found[0] != 2 { log.Panicf("Result was not in the right place: (%d)", found[0]) } tags := ib.Tags() bt = tags[found[0]] if bt.tagId != 0x33 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestIfdBuilder_FindN__Miss(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) found, err := ib.FindN(0x11, 1) log.PanicIf(err) if len(found) != 0 { t.Fatalf("Expected empty results.") } } func TestIfdBuilder_Find__Hit(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) position, err := ib.Find(0x33) log.PanicIf(err) if position != 2 { log.Panicf("Result was not in the right place: (%d)", position) } tags := ib.Tags() bt = tags[position] if bt.tagId != 0x33 { log.Panicf("Found entry is not correct: (0x%04x)", bt.tagId) } } func TestIfdBuilder_Find__Miss(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) _, err = ib.Find(0x99) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestIfdBuilder_Replace(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) currentIds := make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x33}, currentIds) == false { t.Fatalf("Pre-replace tags are not correct.") } bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x99, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Replace(0x22, bt) log.PanicIf(err) currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x99, 0x33}, currentIds) == false { t.Fatalf("Post-replace tags are not correct.") } } func TestIfdBuilder_ReplaceN(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) currentIds := make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x33}, currentIds) == false { t.Fatalf("Pre-replace tags are not correct.") } bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0xA9, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.ReplaceAt(1, bt) log.PanicIf(err) currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0xA9, 0x33}, currentIds) == false { t.Fatalf("Post-replace tags are not correct.") } } func TestIfdBuilder_DeleteFirst(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x22, 0x33}, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } err = ib.DeleteFirst(0x22) log.PanicIf(err) if len(ib.Tags()) != 3 { t.Fatalf("Post-delete (1) tag count not correct.") } currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x33}, currentIds) == false { t.Fatalf("Post-delete (1) tags not correct.") } err = ib.DeleteFirst(0x22) log.PanicIf(err) if len(ib.Tags()) != 2 { t.Fatalf("Post-delete (2) tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x33}, currentIds) == false { t.Fatalf("Post-delete (2) tags not correct.") } err = ib.DeleteFirst(0x22) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestIfdBuilder_DeleteN(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x22, 0x33}, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } err = ib.DeleteN(0x22, 1) log.PanicIf(err) if len(ib.Tags()) != 3 { t.Fatalf("Post-delete (1) tag count not correct.") } currentIds = make([]uint16, 3) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x33}, currentIds) == false { t.Fatalf("Post-delete (1) tags not correct.") } err = ib.DeleteN(0x22, 1) log.PanicIf(err) if len(ib.Tags()) != 2 { t.Fatalf("Post-delete (2) tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x33}, currentIds) == false { t.Fatalf("Post-delete (2) tags not correct.") } err = ib.DeleteN(0x22, 1) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestIfdBuilder_DeleteN_Two(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x22, 0x33}, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } err = ib.DeleteN(0x22, 2) log.PanicIf(err) if len(ib.Tags()) != 2 { t.Fatalf("Post-delete tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x33}, currentIds) == false { t.Fatalf("Post-delete tags not correct.") } err = ib.DeleteFirst(0x22) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestIfdBuilder_DeleteAll(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) bt := &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x11, value: NewIfdBuilderTagValueFromBytes([]byte("test string")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string2")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x22, value: NewIfdBuilderTagValueFromBytes([]byte("test string3")), } err = ib.Add(bt) log.PanicIf(err) bt = &BuilderTag{ ifdPath: exifcommon.IfdStandardIfdIdentity.UnindexedString(), typeId: exifcommon.TypeByte, tagId: 0x33, value: NewIfdBuilderTagValueFromBytes([]byte("test string4")), } err = ib.Add(bt) log.PanicIf(err) if len(ib.Tags()) != 4 { t.Fatalf("Pre-delete tag count not correct.") } currentIds := make([]uint16, 4) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x22, 0x22, 0x33}, currentIds) == false { t.Fatalf("Pre-delete tags not correct.") } n, err := ib.DeleteAll(0x22) log.PanicIf(err) if n != 2 { t.Fatalf("Returned delete tag count not correct.") } else if len(ib.Tags()) != 2 { t.Fatalf("Post-delete tag count not correct.") } currentIds = make([]uint16, 2) for i, bt := range ib.Tags() { currentIds[i] = bt.tagId } if reflect.DeepEqual([]uint16{0x11, 0x33}, currentIds) == false { t.Fatalf("Post-delete tags not correct.") } err = ib.DeleteFirst(0x22) if err == nil { t.Fatalf("Expected an error.") } else if log.Is(err, ErrTagEntryNotFound) == false { log.Panic(err) } } func TestIfdBuilder_NewIfdBuilderFromExistingChain(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) log.PrintErrorf(err, "Test failure.") } }() testImageFilepath := getTestImageFilepath() rawExif, err := SearchFileAndExtractExif(testImageFilepath) log.PanicIf(err) im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, rawExif) log.PanicIf(err) ib := NewIfdBuilderFromExistingChain(index.RootIfd) actual := ib.DumpToStrings() expected := []string{ "IFD", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "IFD", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "IFDIFD/Exif] FQ-IFD-PATH=[IFD/Exif/Iop] IFD-INDEX=(0) IFD-TAG-ID=(0xa005) TAG=[0xa005]>", "TAGIFD/Exif] FQ-IFD-PATH=[IFD/Exif/Iop] IFD-TAG-ID=(0xa005) CHILD-IFD=[] TAG-INDEX=(0) TAG=[0x0001]>", "TAGIFD/Exif] FQ-IFD-PATH=[IFD/Exif/Iop] IFD-TAG-ID=(0xa005) CHILD-IFD=[] TAG-INDEX=(1) TAG=[0x0002]>", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", "IFD", "TAG", "IFD", "TAG", "TAG", "TAG", "TAG", "TAG", "TAG", } if reflect.DeepEqual(actual, expected) == false { fmt.Printf("ACTUAL:\n%s\n\nEXPECTED:\n%s\n", strings.Join(actual, "\n"), strings.Join(expected, "\n")) t.Fatalf("IB did not [correctly] duplicate the IFD structure.") } } func TestIfdBuilder_SetStandardWithName_UpdateGps(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) log.PrintErrorf(err, "Test failure.") } }() // Check initial value. filepath := getTestGpsImageFilepath() rawExif, err := SearchFileAndExtractExif(filepath) log.PanicIf(err) im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, rawExif) log.PanicIf(err) rootIfd := index.RootIfd gpsIfd, err := rootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) log.PanicIf(err) initialGi, err := gpsIfd.GpsInfo() log.PanicIf(err) initialGpsLatitudePhrase := "Degrees" if initialGi.Latitude.String() != initialGpsLatitudePhrase { t.Fatalf("Initial GPS latitude not correct: [%s]", initialGi.Latitude) } // Update the value. rootIb := NewIfdBuilderFromExistingChain(rootIfd) gpsIb, err := rootIb.ChildWithTagId(exifcommon.IfdGpsInfoStandardIfdIdentity.TagId()) log.PanicIf(err) updatedGi := GpsDegrees{ Degrees: 11, Minutes: 22, Seconds: 33, } raw := updatedGi.Raw() err = gpsIb.SetStandardWithName("GPSLatitude", raw) log.PanicIf(err) // Encode to bytes. ibe := NewIfdByteEncoder() updatedRawExif, err := ibe.EncodeToExif(rootIb) log.PanicIf(err) // Decode from bytes. _, updatedIndex, err := Collect(im, ti, updatedRawExif) log.PanicIf(err) updatedRootIfd := updatedIndex.RootIfd // Test. updatedGpsIfd, err := updatedRootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) log.PanicIf(err) recoveredUpdatedGi, err := updatedGpsIfd.GpsInfo() log.PanicIf(err) updatedGpsLatitudePhrase := "Degrees" if recoveredUpdatedGi.Latitude.String() != updatedGpsLatitudePhrase { t.Fatalf("Updated GPS latitude not set or recovered correctly: [%s]", recoveredUpdatedGi.Latitude) } } func ExampleIfdBuilder_SetStandardWithName_updateGps() { // Check initial value. filepath := getTestGpsImageFilepath() rawExif, err := SearchFileAndExtractExif(filepath) log.PanicIf(err) im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, rawExif) log.PanicIf(err) rootIfd := index.RootIfd gpsIfd, err := rootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) log.PanicIf(err) initialGi, err := gpsIfd.GpsInfo() log.PanicIf(err) fmt.Printf("Original:\n%s\n\n", initialGi.Latitude.String()) // Update the value. rootIb := NewIfdBuilderFromExistingChain(rootIfd) gpsIb, err := rootIb.ChildWithTagId(exifcommon.IfdGpsInfoStandardIfdIdentity.TagId()) log.PanicIf(err) updatedGi := GpsDegrees{ Degrees: 11, Minutes: 22, Seconds: 33, } raw := updatedGi.Raw() err = gpsIb.SetStandardWithName("GPSLatitude", raw) log.PanicIf(err) // Encode to bytes. ibe := NewIfdByteEncoder() updatedRawExif, err := ibe.EncodeToExif(rootIb) log.PanicIf(err) // Decode from bytes. _, updatedIndex, err := Collect(im, ti, updatedRawExif) log.PanicIf(err) updatedRootIfd := updatedIndex.RootIfd // Test. updatedGpsIfd, err := updatedRootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) log.PanicIf(err) recoveredUpdatedGi, err := updatedGpsIfd.GpsInfo() log.PanicIf(err) fmt.Printf("Updated, written, and re-read:\n%s\n", recoveredUpdatedGi.Latitude.String()) // Output: // Original: // Degrees // // Updated, written, and re-read: // Degrees } func ExampleIfdBuilder_SetStandardWithName_timestamp() { // Check initial value. filepath := getTestGpsImageFilepath() rawExif, err := SearchFileAndExtractExif(filepath) log.PanicIf(err) im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, rawExif) log.PanicIf(err) rootIfd := index.RootIfd // Update the value. rootIb := NewIfdBuilderFromExistingChain(rootIfd) exifIb, err := rootIb.ChildWithTagId(exifcommon.IfdExifStandardIfdIdentity.TagId()) log.PanicIf(err) t := time.Date(2020, 06, 7, 1, 30, 0, 0, time.UTC) err = exifIb.SetStandardWithName("DateTimeDigitized", t) log.PanicIf(err) // Encode to bytes. ibe := NewIfdByteEncoder() updatedRawExif, err := ibe.EncodeToExif(rootIb) log.PanicIf(err) // Decode from bytes. _, updatedIndex, err := Collect(im, ti, updatedRawExif) log.PanicIf(err) updatedRootIfd := updatedIndex.RootIfd // Test. updatedExifIfd, err := updatedRootIfd.ChildWithIfdPath(exifcommon.IfdExifStandardIfdIdentity) log.PanicIf(err) results, err := updatedExifIfd.FindTagWithName("DateTimeDigitized") log.PanicIf(err) ite := results[0] phrase, err := ite.FormatFirst() log.PanicIf(err) fmt.Printf("%s\n", phrase) // Output: // 2020:06:07 01:30:00 } func TestIfdBuilder_NewIfdBuilderFromExistingChain_RealData(t *testing.T) { testImageFilepath := getTestImageFilepath() rawExif, err := SearchFileAndExtractExif(testImageFilepath) log.PanicIf(err) // Decode from binary. im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, originalIndex, err := Collect(im, ti, rawExif) log.PanicIf(err) originalThumbnailData, err := originalIndex.RootIfd.nextIfd.Thumbnail() log.PanicIf(err) originalTags := originalIndex.RootIfd.DumpTags() // Encode back to binary. ibe := NewIfdByteEncoder() rootIb := NewIfdBuilderFromExistingChain(originalIndex.RootIfd) updatedExif, err := ibe.EncodeToExif(rootIb) log.PanicIf(err) // Parse again. _, recoveredIndex, err := Collect(im, ti, updatedExif) log.PanicIf(err) recoveredTags := recoveredIndex.RootIfd.DumpTags() recoveredThumbnailData, err := recoveredIndex.RootIfd.nextIfd.Thumbnail() log.PanicIf(err) // Check the thumbnail. if bytes.Compare(recoveredThumbnailData, originalThumbnailData) != 0 { t.Fatalf("recovered thumbnail does not match original") } // Validate that all of the same IFDs were presented. originalIfdTags := make([][2]interface{}, 0) for _, ite := range originalTags { if ite.ChildIfdPath() != "" { originalIfdTags = append(originalIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()}) } } recoveredIfdTags := make([][2]interface{}, 0) for _, ite := range recoveredTags { if ite.ChildIfdPath() != "" { recoveredIfdTags = append(recoveredIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()}) } } if reflect.DeepEqual(recoveredIfdTags, originalIfdTags) != true { fmt.Printf("Original IFD tags:\n\n") for i, x := range originalIfdTags { fmt.Printf(" %02d %v\n", i, x) } fmt.Printf("\nRecovered IFD tags:\n\n") for i, x := range recoveredIfdTags { fmt.Printf(" %02d %v\n", i, x) } fmt.Printf("\n") t.Fatalf("Recovered IFD tags are not correct.") } // Validate that all of the tags owned by the IFDs were presented. Note // that the thumbnail tags are not kept but only produced on the fly, which // is why we check it above. if len(recoveredTags) != len(originalTags) { t.Fatalf("Recovered tag-count does not match original.") } originalTagPhrases := make([]string, 0) for _, ite := range originalTags { // Adds a lot of noise if/when debugging, and we're already checking the // thumbnail bytes separately. if ite.IsThumbnailOffset() == true || ite.IsThumbnailSize() == true { continue } phrase := ite.String() // The value (the offset) of IFDs will almost never be the same after // reconstruction (by design). if ite.ChildIfdName() == "" { valuePhrase, err := ite.FormatFirst() log.PanicIf(err) phrase += " " + valuePhrase } originalTagPhrases = append(originalTagPhrases, phrase) } sort.Strings(originalTagPhrases) recoveredTagPhrases := make([]string, 0) for _, ite := range recoveredTags { // Adds a lot of noise if/when debugging, and we're already checking the // thumbnail bytes separately. if ite.IsThumbnailOffset() == true || ite.IsThumbnailSize() == true { continue } phrase := ite.String() // The value (the offset) of IFDs will almost never be the same after // reconstruction (by design). if ite.ChildIfdName() == "" { valuePhrase, err := ite.FormatFirst() log.PanicIf(err) phrase += " " + valuePhrase } recoveredTagPhrases = append(recoveredTagPhrases, phrase) } sort.Strings(recoveredTagPhrases) if reflect.DeepEqual(recoveredTagPhrases, originalTagPhrases) != true { fmt.Printf("ORIGINAL:\n") fmt.Printf("\n") for _, tag := range originalTagPhrases { fmt.Printf("%s\n", tag) } fmt.Printf("\n") fmt.Printf("RECOVERED:\n") fmt.Printf("\n") for _, tag := range recoveredTagPhrases { fmt.Printf("%s\n", tag) } fmt.Printf("\n") t.Fatalf("Recovered tags do not equal original tags.") } } // func TestIfdBuilder_NewIfdBuilderFromExistingChain_RealData_WithUpdate(t *testing.T) { // testImageFilepath := getTestImageFilepath() // rawExif, err := SearchFileAndExtractExif(testImageFilepath) // log.PanicIf(err) // // Decode from binary. // ti := NewTagIndex() // _, originalIndex, err := Collect(im, ti, rawExif) // log.PanicIf(err) // originalThumbnailData, err := originalIndex.RootIfd.nextIfd.Thumbnail() // log.PanicIf(err) // originalTags := originalIndex.RootIfd.DumpTags() // // Encode back to binary. // ibe := NewIfdByteEncoder() // rootIb := NewIfdBuilderFromExistingChain(originalIndex.RootIfd) // // Update a tag,. // exifBt, err := rootIb.FindTagWithName("ExifTag") // log.PanicIf(err) // ucBt, err := exifBt.value.Ib().FindTagWithName("UserComment") // log.PanicIf(err) // uc := exifundefined.Tag9286UserComment{ // EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, // EncodingBytes: []byte("TEST COMMENT"), // } // err = ucBt.SetValue(rootIb.byteOrder, uc) // log.PanicIf(err) // // Encode. // updatedExif, err := ibe.EncodeToExif(rootIb) // log.PanicIf(err) // // Parse again. // _, recoveredIndex, err := Collect(im, ti, updatedExif) // log.PanicIf(err) // recoveredTags := recoveredIndex.RootIfd.DumpTags() // recoveredThumbnailData, err := recoveredIndex.RootIfd.nextIfd.Thumbnail() // log.PanicIf(err) // // Check the thumbnail. // if bytes.Compare(recoveredThumbnailData, originalThumbnailData) != 0 { // t.Fatalf("recovered thumbnail does not match original") // } // // Validate that all of the same IFDs were presented. // originalIfdTags := make([][2]interface{}, 0) // for _, ite := range originalTags { // if ite.ChildIfdPath() != "" { // originalIfdTags = append(originalIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()}) // } // } // recoveredIfdTags := make([][2]interface{}, 0) // for _, ite := range recoveredTags { // if ite.ChildIfdPath() != "" { // recoveredIfdTags = append(recoveredIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()}) // } // } // if reflect.DeepEqual(recoveredIfdTags, originalIfdTags) != true { // fmt.Printf("Original IFD tags:\n\n") // for i, x := range originalIfdTags { // fmt.Printf(" %02d %v\n", i, x) // } // fmt.Printf("\nRecovered IFD tags:\n\n") // for i, x := range recoveredIfdTags { // fmt.Printf(" %02d %v\n", i, x) // } // fmt.Printf("\n") // t.Fatalf("Recovered IFD tags are not correct.") // } // // Validate that all of the tags owned by the IFDs were presented. Note // // that the thumbnail tags are not kept but only produced on the fly, which // // is why we check it above. // if len(recoveredTags) != len(originalTags) { // t.Fatalf("Recovered tag-count does not match original.") // } // for i, recoveredIte := range recoveredTags { // if recoveredIte.ChildIfdPath() != "" { // continue // } // originalIte := originalTags[i] // if recoveredIte.IfdPath() != originalIte.IfdPath() { // t.Fatalf("IfdIdentity not as expected: %s != %s ITE=%s", recoveredIte.IfdPath(), originalIte.IfdPath(), recoveredIte) // } else if recoveredIte.TagId() != originalIte.TagId() { // t.Fatalf("Tag-ID not as expected: %d != %d ITE=%s", recoveredIte.TagId(), originalIte.TagId(), recoveredIte) // } else if recoveredIte.TagType() != originalIte.TagType() { // t.Fatalf("Tag-type not as expected: %d != %d ITE=%s", recoveredIte.TagType(), originalIte.TagType(), recoveredIte) // } // originalValueBytes, err := originalIte.ValueBytes(originalIndex.RootIfd.addressableData, originalIndex.RootIfd.ByteOrder()) // log.PanicIf(err) // recoveredValueBytes, err := recoveredIte.ValueBytes(recoveredIndex.RootIfd.addressableData, recoveredIndex.RootIfd.ByteOrder()) // log.PanicIf(err) // if recoveredIte.TagId() == 0x9286 { // expectedValueBytes := make([]byte, 0) // expectedValueBytes = append(expectedValueBytes, []byte{'A', 'S', 'C', 'I', 'I', 0, 0, 0}...) // expectedValueBytes = append(expectedValueBytes, []byte("TEST COMMENT")...) // if bytes.Compare(recoveredValueBytes, expectedValueBytes) != 0 { // t.Fatalf("Recovered UserComment does not have the right value: %v != %v", recoveredValueBytes, expectedValueBytes) // } // } else if bytes.Compare(recoveredValueBytes, originalValueBytes) != 0 { // t.Fatalf("bytes of tag content not correct: %v != %v ITE=%s", recoveredValueBytes, originalValueBytes, recoveredIte) // } // } // } func ExampleIfd_Thumbnail() { testImageFilepath := getTestImageFilepath() rawExif, err := SearchFileAndExtractExif(testImageFilepath) log.PanicIf(err) im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() _, index, err := Collect(im, ti, rawExif) log.PanicIf(err) // This returns the raw bytes that you will be looking for, but there's no // use for them at this point in the example. _, err = index.RootIfd.nextIfd.Thumbnail() log.PanicIf(err) // Output: } func ExampleBuilderTag_SetValue() { testImageFilepath := getTestImageFilepath() rawExif, err := SearchFileAndExtractExif(testImageFilepath) log.PanicIf(err) im, err := exifcommon.NewIfdMappingWithStandard() 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() // This returns the raw bytes that you will be looking for, but there's no // use for them at this point in the example. _, err = ibe.EncodeToExif(rootIb) log.PanicIf(err) // Output: } // 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. func ExampleIfdBuilder_SetStandardWithName() { testImageFilepath := getTestImageFilepath() rawExif, err := SearchFileAndExtractExif(testImageFilepath) log.PanicIf(err) // Boilerplate. im, err := exifcommon.NewIfdMappingWithStandard() 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 TestIfdBuilder_CreateIfdBuilderWithExistingIfd(t *testing.T) { ti := NewTagIndex() im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) mi, err := im.GetWithPath(exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString()) log.PanicIf(err) tagId := mi.TagId parentIfd := &Ifd{ ifdIdentity: exifcommon.IfdStandardIfdIdentity, tagIndex: ti, } ifd := &Ifd{ ifdIdentity: exifcommon.IfdGpsInfoStandardIfdIdentity, byteOrder: exifcommon.TestDefaultByteOrder, offset: 0x123, parentIfd: parentIfd, ifdMapping: im, tagIndex: ti, } ib := NewIfdBuilderWithExistingIfd(ifd) if ib.IfdIdentity().UnindexedString() != ifd.ifdIdentity.UnindexedString() { t.Fatalf("IFD-name not correct.") } else if ib.IfdIdentity().TagId() != tagId { t.Fatalf("IFD tag-ID not correct.") } else if ib.byteOrder != ifd.ByteOrder() { t.Fatalf("IFD byte-order not correct.") } else if ib.existingOffset != ifd.Offset() { t.Fatalf("IFD offset not correct.") } } func TestNewStandardBuilderTag__OneUnit(t *testing.T) { ti := NewTagIndex() it, err := ti.Get(exifcommon.IfdExifStandardIfdIdentity, uint16(0x8833)) log.PanicIf(err) bt := NewStandardBuilderTag(exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), it, exifcommon.TestDefaultByteOrder, []uint32{uint32(0x1234)}) if bt.ifdPath != exifcommon.IfdExifStandardIfdIdentity.UnindexedString() { 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}) != 0 { t.Fatalf("value not correct") } } func TestNewStandardBuilderTag__TwoUnits(t *testing.T) { ti := NewTagIndex() it, err := ti.Get(exifcommon.IfdExifStandardIfdIdentity, uint16(0x8833)) log.PanicIf(err) bt := NewStandardBuilderTag(exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), it, exifcommon.TestDefaultByteOrder, []uint32{uint32(0x1234), uint32(0x5678)}) if bt.ifdPath != exifcommon.IfdExifStandardIfdIdentity.UnindexedString() { 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 TestIfdBuilder_AddStandardWithName(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() ib := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) err = ib.AddStandardWithName("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.ifdPath != exifcommon.IfdStandardIfdIdentity.UnindexedString() { t.Fatalf("II not correct: %s", bt.ifdPath) } else if bt.tagId != 0x000b { t.Fatalf("Tag-ID not correct: (0x%04x)", bt.tagId) } s := string(bt.value.Bytes()) if s != "some software\000" { t.Fatalf("Value not correct: (%d) [%s]", len(s), s) } } func TestGetOrCreateIbFromRootIb__Noop(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() rootIb := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) ib, err := GetOrCreateIbFromRootIb(rootIb, "IFD") log.PanicIf(err) if ib != rootIb { t.Fatalf("Expected same IB back from no-op get-or-create.") } else if ib.nextIb != nil { t.Fatalf("Expected no siblings on IB from no-op get-or-create.") } else if len(ib.tags) != 0 { t.Fatalf("Expected no new tags on IB from no-op get-or-create.") } } func TestGetOrCreateIbFromRootIb__FqNoop(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() rootIb := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) ib, err := GetOrCreateIbFromRootIb(rootIb, "IFD0") log.PanicIf(err) if ib != rootIb { t.Fatalf("Expected same IB back from no-op get-or-create.") } else if ib.nextIb != nil { t.Fatalf("Expected no siblings on IB from no-op get-or-create.") } else if len(ib.tags) != 0 { t.Fatalf("Expected no new tags on IB from no-op get-or-create.") } } func TestGetOrCreateIbFromRootIb_InvalidChild(t *testing.T) { im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() rootIb := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) _, err = GetOrCreateIbFromRootIb(rootIb, "IFD/Invalid") if err == nil { t.Fatalf("Expected failure for invalid IFD child in IB get-or-create.") } else if err.Error() != "ifd child with name [Invalid] not registered: [IFD/Invalid]" { log.Panic(err) } } func TestGetOrCreateIbFromRootIb__Child(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) log.PrintErrorf(err, "Test failure.") } }() im, err := exifcommon.NewIfdMappingWithStandard() log.PanicIf(err) ti := NewTagIndex() rootIb := NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity, exifcommon.TestDefaultByteOrder) lines := rootIb.DumpToStrings() expected := []string{ "IFD", } if reflect.DeepEqual(lines, expected) != true { fmt.Printf("ACTUAL:\n") fmt.Printf("\n") for i, line := range lines { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") fmt.Printf("EXPECTED:\n") fmt.Printf("\n") for i, line := range expected { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") t.Fatalf("Constructed IFDs not correct.") } ib, err := GetOrCreateIbFromRootIb(rootIb, "IFD/Exif") log.PanicIf(err) if ib.IfdIdentity().String() != "IFD/Exif" { t.Fatalf("Returned IB does not have the expected path (IFD/Exif).") } lines = rootIb.DumpToStrings() expected = []string{ "IFD", "TAG", "IFD", } if reflect.DeepEqual(lines, expected) != true { fmt.Printf("ACTUAL:\n") fmt.Printf("\n") for i, line := range lines { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") fmt.Printf("EXPECTED:\n") fmt.Printf("\n") for i, line := range expected { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") t.Fatalf("Constructed IFDs not correct.") } ib, err = GetOrCreateIbFromRootIb(rootIb, "IFD0/Exif/Iop") log.PanicIf(err) if ib.IfdIdentity().String() != "IFD/Exif/Iop" { t.Fatalf("Returned IB does not have the expected path (IFD/Exif/Iop).") } lines = rootIb.DumpToStrings() expected = []string{ "IFD", "TAG", "IFD", "TAG", "IFDIFD/Exif] FQ-IFD-PATH=[IFD/Exif/Iop] IFD-INDEX=(0) IFD-TAG-ID=(0xa005) TAG=[0xa005]>", } if reflect.DeepEqual(lines, expected) != true { fmt.Printf("ACTUAL:\n") fmt.Printf("\n") for i, line := range lines { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") fmt.Printf("EXPECTED:\n") fmt.Printf("\n") for i, line := range expected { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") t.Fatalf("Constructed IFDs not correct.") } ib, err = GetOrCreateIbFromRootIb(rootIb, "IFD1") log.PanicIf(err) if ib.IfdIdentity().String() != "IFD1" { t.Fatalf("Returned IB does not have the expected path (IFD1).") } lines = rootIb.DumpToStrings() expected = []string{ "IFD", "TAG", "IFD", "TAG", "IFDIFD/Exif] FQ-IFD-PATH=[IFD/Exif/Iop] IFD-INDEX=(0) IFD-TAG-ID=(0xa005) TAG=[0xa005]>", "IFD", } if reflect.DeepEqual(lines, expected) != true { fmt.Printf("ACTUAL:\n") fmt.Printf("\n") for i, line := range lines { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") fmt.Printf("EXPECTED:\n") fmt.Printf("\n") for i, line := range expected { fmt.Printf("%d: %s\n", i, line) } fmt.Printf("\n") t.Fatalf("Constructed IFDs not correct.") } }