Skip to content

Commit

Permalink
Fix bug when several tags need to be removed at once
Browse files Browse the repository at this point in the history
  • Loading branch information
rtio committed Mar 3, 2023
1 parent e5d2eb9 commit 53f7e73
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 73 deletions.
32 changes: 22 additions & 10 deletions exif.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,34 @@ func makeExifDatumIterator(data *ExifData, cIter *C.Exiv2ExifDatumIterator) *Exi
return datum
}

// ExifStripKey removes the given key from the EXIF data.
func (i *Image) ExifStripKey(key string) error {
return i.StripKey(EXIF, key)
}

// ExifStripMetadata removes all EXIF metadata except the keys in the unless array.
func (i *Image) ExifStripMetadata(unless []string) error {
exifData := i.GetExifData()
for iter := exifData.Iterator(); iter.HasNext(); {
key := iter.Next().Key()
// Skip unless
if contains(key, unless) {
continue
}
err := i.StripKey(EXIF, key)
if err != nil {
return err
var cErr *C.Exiv2Error

tagsToRemove := getKeysToRemove(i.GetExifData(), unless)
if len(tagsToRemove) == 0 {
return nil
}

cTags := getCTags(tagsToRemove)
defer func() {
for _, cstr := range cTags {
C.free(unsafe.Pointer(cstr))
}
}()

C.exiv2_exif_strip_data(i.img, &cTags[0], C.int(len(cTags)), &cErr)

if cErr != nil {
err := makeError(cErr)
C.exiv2_error_free(cErr)
return err
}

return nil
}
28 changes: 28 additions & 0 deletions exiv.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,31 @@ func contains(needle string, haystack []string) bool {
}
return false
}

// getCTags converts a map of tags to a C array of C strings
func getCTags(goTags []string) (input []*C.char) {
for _, value := range goTags {
input = append(input, C.CString(value))
}

return
}

// This interface is used to get all the tags from a metadata format.
// Won't be available in the public API.
type dataFormat interface {
AllTags() map[string]string
}

// getTagsToRemove returns a list of tags to remove from the metadata
// For now this method won't be added to the public API. We must see if it's
// useful or not.
func getKeysToRemove(m dataFormat, unless []string) (tagsToRemove []string) {
for key, _ := range m.AllTags() {
if !contains(key, unless) {
tagsToRemove = append(tagsToRemove, key)
}
}

return
}
67 changes: 24 additions & 43 deletions exiv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ func TestMetadata(t *testing.T) {
}

assert.Equal(t, map[string]string{
"Exif.Image.ExifTag": "130",
"Exif.Image.Artist": "John Doe",
"Exif.Image.Copyright": "©2023 John Doe, all rights reserved",
"Exif.Image.ExifTag": "202",
"Exif.Image.Make": "FakeMake",
"Exif.Image.Model": "FakeModel",
"Exif.Image.ResolutionUnit": "2",
Expand Down Expand Up @@ -611,61 +613,38 @@ func TestXmpStrip(t *testing.T) {
}

func TestStripMetadata(t *testing.T) {
// The test for this function must need plenty of tags to ensure it won't generate an unexpected behavior
initializeImage("testdata/pixel.jpg", t)
img, err := goexiv.Open("testdata/pixel.jpg")
require.NoError(t, err)

// add two strings to the EXIF data
err = img.SetExifString("Exif.Photo.UserComment", "123")
require.NoError(t, err)

err = img.SetExifString("Exif.Photo.DateTimeOriginal", "123")
require.NoError(t, err)

// add two strings to the IPTC data
err = img.SetIptcString("Iptc.Application2.Caption", "123")
require.NoError(t, err)

err = img.SetIptcString("Iptc.Application2.Keywords", "123")
require.NoError(t, err)

// add two strings to the XMP data
err = img.SetXmpString("Xmp.dc.description", "123")
require.NoError(t, err)

err = img.SetXmpString("Xmp.dc.subject", "123")
require.NoError(t, err)

err = img.StripMetadata([]string{"Exif.Photo.UserComment", "Iptc.Application2.Caption", "Xmp.dc.description"})
require.NoError(t, err)

err = img.ReadMetadata()
require.NoError(t, err)

exifData := img.GetExifData()
iptcData := img.GetIptcData()
xmpData := img.GetXmpData()

_, err = exifData.GetString("Exif.Photo.UserComment")
require.NoError(t, err)

_, err = exifData.GetString("Exif.Photo.DateTimeOriginal")
require.Error(t, err)

_, err = iptcData.GetString("Iptc.Application2.Caption")
err = img.StripMetadata([]string{"Exif.Image.Copyright", "Iptc.Application2.Copyright", "Xmp.iptc.CreditLine"})
require.NoError(t, err)

_, err = iptcData.GetString("Iptc.Application2.Keywords")
require.Error(t, err)
// Exif
exifData := img.GetExifData()
assert.Equal(t, map[string]string{
"Exif.Image.Copyright": "©2023 John Doe, all rights reserved",
}, exifData.AllTags())

_, err = xmpData.GetString("Xmp.dc.description")
require.NoError(t, err)
// IPTC
iptcData := img.GetIptcData()
assert.Equal(t, map[string]string{
"Iptc.Application2.Copyright": "this is the copy, right?",
}, iptcData.AllTags())

_, err = xmpData.GetString("Xmp.dc.subject")
require.Error(t, err)
// XMP
xmpData := img.GetXmpData()
assert.Equal(t, map[string]string{
"Xmp.iptc.CreditLine": "John Doe",
}, xmpData.AllTags())
}

func BenchmarkImage_GetBytes_KeepAlive(b *testing.B) {
bytes, err := ioutil.ReadFile("testdata/stripped_pixel.jpg")
bytes, err := os.ReadFile("testdata/stripped_pixel.jpg")
require.NoError(b, err)
var wg sync.WaitGroup

Expand Down Expand Up @@ -721,6 +700,8 @@ func initializeImage(path string, t *testing.T) {
img.SetIptcString("Iptc.Application2.TimeCreated", "124932:0100")

exifTags := map[string]string{
"Exif.Image.Artist": "John Doe",
"Exif.Image.Copyright": "©2023 John Doe, all rights reserved",
"Exif.Image.Make": "FakeMake",
"Exif.Image.Model": "FakeModel",
"Exif.Image.ResolutionUnit": "2",
Expand Down
66 changes: 66 additions & 0 deletions helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,72 @@ exiv2_xmp_strip_key(Exiv2Image *img, char *key, Exiv2Error **error)
}
}

void
exiv2_exif_strip_data(Exiv2Image *img, char **keysToRemove, int len, Exiv2Error **error) {
Exiv2::ExifData exifData = img->image->exifData();

for (int i = 0; i < len; i++) {
try {
Exiv2::ExifData::iterator pos = exifData.findKey(Exiv2::ExifKey(keysToRemove[i]));
if (pos == exifData.end()) {
continue;
}
exifData.erase(pos);
} catch (Exiv2::Error &e) {
if (error) {
*error = new Exiv2Error(e);
}
}
}
// Finally, write the remaining Exif data to the image file
img->image->setExifData(exifData);
img->image->writeMetadata();
}

void
exiv2_iptc_strip_data(Exiv2Image *img, char **keysToRemove, int len, Exiv2Error **error) {
Exiv2::IptcData iptcData = img->image->iptcData();

for (int i = 0; i < len; i++) {
try {
Exiv2::IptcData::iterator pos = iptcData.findKey(Exiv2::IptcKey(keysToRemove[i]));
if (pos == iptcData.end()) {
continue;
}
iptcData.erase(pos);
} catch (Exiv2::Error &e) {
if (error) {
*error = new Exiv2Error(e);
}
}
}
// Finally, write the remaining Iptc data to the image file
img->image->setIptcData(iptcData);
img->image->writeMetadata();
}

void
exiv2_xmp_strip_data(Exiv2Image *img, char **keysToRemove, int len, Exiv2Error **error) {
Exiv2::XmpData xmpData = img->image->xmpData();

for (int i = 0; i < len; i++) {
try {
Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey(keysToRemove[i]));
if (pos == xmpData.end()) {
continue;
}
xmpData.erase(pos);
} catch (Exiv2::Error &e) {
if (error) {
*error = new Exiv2Error(e);
}
}
}
// Finally, write the remaining Xmp data to the image file
img->image->setXmpData(xmpData);
img->image->writeMetadata();
}

DEFINE_FREE_FUNCTION(exiv2_exif_data, Exiv2ExifData*);

const char* exiv2_exif_datum_key(const Exiv2ExifDatum *datum)
Expand Down
4 changes: 4 additions & 0 deletions helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ void exiv2_exif_strip_key(Exiv2Image *img, char *key, Exiv2Error **error);
void exiv2_iptc_strip_key(Exiv2Image *img, char *key, Exiv2Error **error);
void exiv2_xmp_strip_key(Exiv2Image *img, char *key, Exiv2Error **error);

void exiv2_exif_strip_data(Exiv2Image *img, char **keysToRemove, int len, Exiv2Error **error);
void exiv2_iptc_strip_data(Exiv2Image *img, char **keysToRemove, int len, Exiv2Error **error);
void exiv2_xmp_strip_data(Exiv2Image *img, char **keysToRemove, int len, Exiv2Error **error);

const unsigned char* exiv2_image_icc_profile(Exiv2Image *img);
long exiv2_image_icc_profile_size(Exiv2Image *img);

Expand Down
32 changes: 22 additions & 10 deletions iptc.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,22 +147,34 @@ func makeIptcDatumIterator(data *IptcData, cIter *C.Exiv2IptcDatumIterator) *Ipt
return datum
}

// IptcStripKey removes the given key from the IPTC metadata.
func (i *Image) IptcStripKey(key string) error {
return i.StripKey(IPTC, key)
}

// IptcStripMetadata removes all EXIF metadata except the keys in the unless array.
func (i *Image) IptcStripMetadata(unless []string) error {
iptcData := i.GetIptcData()
for iter := iptcData.Iterator(); iter.HasNext(); {
key := iter.Next().Key()
// Skip unless
if contains(key, unless) {
continue
}
err := i.StripKey(IPTC, key)
if err != nil {
return err
var cErr *C.Exiv2Error

tagsToRemove := getKeysToRemove(i.GetIptcData(), unless)
if len(tagsToRemove) == 0 {
return nil
}

cTags := getCTags(tagsToRemove)
defer func() {
for _, cstr := range cTags {
C.free(unsafe.Pointer(cstr))
}
}()

C.exiv2_iptc_strip_data(i.img, &cTags[0], C.int(len(cTags)), &cErr)

if cErr != nil {
err := makeError(cErr)
C.exiv2_error_free(cErr)
return err
}

return nil
}
42 changes: 32 additions & 10 deletions xmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,41 @@ func (d *XmpData) GetString(key string) (string, error) {
return datum.String(), nil
}

// AllTags returns all ZMP tags
func (d *XmpData) AllTags() map[string]string {
keyValues := map[string]string{}
for i := d.Iterator(); i.HasNext(); {
d := i.Next()
keyValues[d.Key()] = d.String()
}

return keyValues
}

// XmpStripMetadata removes all EXIF metadata except the keys in the unless array.
func (i *Image) XmpStripMetadata(unless []string) error {
xmpData := i.GetXmpData()
for iter := xmpData.Iterator(); iter.HasNext(); {
key := iter.Next().Key()
// Skip unless
if contains(key, unless) {
continue
}
err := i.StripKey(XMP, key)
if err != nil {
return err
var cErr *C.Exiv2Error

tagsToRemove := getKeysToRemove(i.GetXmpData(), unless)
if len(tagsToRemove) == 0 {
return nil
}

cTags := getCTags(tagsToRemove)
defer func() {
for _, cstr := range cTags {
C.free(unsafe.Pointer(cstr))
}
}()

C.exiv2_xmp_strip_data(i.img, &cTags[0], C.int(len(cTags)), &cErr)

if cErr != nil {
err := makeError(cErr)
C.exiv2_error_free(cErr)
return err
}

return nil
}

Expand Down

0 comments on commit 53f7e73

Please sign in to comment.