From 3725cfdd02339299b7326f7f6d9a6d86aecc3b9c Mon Sep 17 00:00:00 2001 From: rtio Date: Tue, 28 Feb 2023 16:30:31 -0300 Subject: [PATCH] Add strip metadata --- exif.go | 16 +++++++ exiv.go | 27 +++++++++++ exiv_test.go | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ helper.cpp | 42 ++++++++++++++++ helper.h | 6 +++ iptc.go | 16 +++++++ xmp.go | 52 ++++++++++++++++++++ 7 files changed, 291 insertions(+) diff --git a/exif.go b/exif.go index 0c16267..0b1297c 100644 --- a/exif.go +++ b/exif.go @@ -145,3 +145,19 @@ func makeExifDatumIterator(data *ExifData, cIter *C.Exiv2ExifDatumIterator) *Exi func (i *Image) ExifStripKey(key string) error { return i.StripKey(EXIF, key) } + +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 + } + } + return nil +} diff --git a/exiv.go b/exiv.go index c73ce79..7877d1f 100644 --- a/exiv.go +++ b/exiv.go @@ -258,3 +258,30 @@ func (i *Image) StripKey(f MetadataFormat, key string) error { return nil } + +func (i *Image) StripMetadata(unless []string) error { + var err error + err = i.ExifStripMetadata(unless) + if err != nil { + return err + } + err = i.IptcStripMetadata(unless) + if err != nil { + return err + } + err = i.XmpStripMetadata(unless) + if err != nil { + return err + } + return nil +} + +// contains checks if a string is present in a string slice +func contains(needle string, haystack []string) bool { + for _, s := range haystack { + if s == needle { + return true + } + } + return false +} diff --git a/exiv_test.go b/exiv_test.go index f9597fa..78d2e7b 100644 --- a/exiv_test.go +++ b/exiv_test.go @@ -522,6 +522,138 @@ func TestXmpStripKey(t *testing.T) { require.Error(t, err) } +func TestExifStrip(t *testing.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) + + err = img.ExifStripMetadata([]string{"Exif.Photo.UserComment"}) + require.NoError(t, err) + + err = img.ReadMetadata() + require.NoError(t, err) + + data := img.GetExifData() + + _, err = data.GetString("Exif.Photo.UserComment") + require.NoError(t, err) + + _, err = data.GetString("Exif.Photo.DateTimeOriginal") + require.Error(t, err) +} + +func TestIptcStrip(t *testing.T) { + img, err := goexiv.Open("testdata/pixel.jpg") + 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) + + err = img.IptcStripMetadata([]string{"Iptc.Application2.Caption"}) + require.NoError(t, err) + + err = img.ReadMetadata() + require.NoError(t, err) + + data := img.GetIptcData() + + _, err = data.GetString("Iptc.Application2.Caption") + require.NoError(t, err) + + _, err = data.GetString("Iptc.Application2.Keywords") + require.Error(t, err) +} + +func TestXmpStrip(t *testing.T) { + img, err := goexiv.Open("testdata/pixel.jpg") + 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.XmpStripMetadata([]string{"Xmp.dc.description"}) + require.NoError(t, err) + + err = img.ReadMetadata() + require.NoError(t, err) + + data := img.GetXmpData() + + _, err = data.GetString("Xmp.dc.description") + require.NoError(t, err) + + _, err = data.GetString("Xmp.dc.subject") + require.Error(t, err) +} + +func TestStripMetadata(t *testing.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") + require.NoError(t, err) + + _, err = iptcData.GetString("Iptc.Application2.Keywords") + require.Error(t, err) + + _, err = xmpData.GetString("Xmp.dc.description") + require.NoError(t, err) + + _, err = xmpData.GetString("Xmp.dc.subject") + require.Error(t, err) +} + func BenchmarkImage_GetBytes_KeepAlive(b *testing.B) { bytes, err := ioutil.ReadFile("testdata/stripped_pixel.jpg") require.NoError(b, err) diff --git a/helper.cpp b/helper.cpp index 6e1d8b5..11dcb9f 100644 --- a/helper.cpp +++ b/helper.cpp @@ -23,6 +23,14 @@ DEFINE_STRUCT(Exiv2Image, Exiv2::Image::AutoPtr, image); DEFINE_STRUCT(Exiv2XmpData, const Exiv2::XmpData&, data); DEFINE_STRUCT(Exiv2XmpDatum, const Exiv2::Xmpdatum&, datum); +struct _Exiv2XmpDatumIterator { + _Exiv2XmpDatumIterator(Exiv2::XmpMetadata::const_iterator i, Exiv2::XmpMetadata::const_iterator e) : it(i), end(e) {} + Exiv2::XmpMetadata::const_iterator it; + Exiv2::XmpMetadata::const_iterator end; + + bool has_next() const; + Exiv2XmpDatum* next(); +}; DEFINE_STRUCT(Exiv2ExifData, const Exiv2::ExifData&, data); DEFINE_STRUCT(Exiv2ExifDatum, const Exiv2::Exifdatum&, datum); @@ -46,6 +54,7 @@ struct _Exiv2IptcDatumIterator { Exiv2IptcDatum* next(); }; +DEFINE_FREE_FUNCTION(exiv2_xmp_datum_iterator, Exiv2XmpDatumIterator*); DEFINE_FREE_FUNCTION(exiv2_iptc_datum_iterator, Exiv2IptcDatumIterator*); DEFINE_FREE_FUNCTION(exiv2_exif_datum_iterator, Exiv2ExifDatumIterator*); @@ -275,8 +284,41 @@ exiv2_xmp_data_find_key(const Exiv2XmpData *data, const char *key, Exiv2Error ** } } +Exiv2XmpDatumIterator* exiv2_xmp_data_iterator(const Exiv2XmpData *data) +{ + return new Exiv2XmpDatumIterator(data->data.begin(), data->data.end()); +} + +bool Exiv2XmpDatumIterator::has_next() const +{ + return it != end; +} + +int exiv2_xmp_data_iterator_has_next(const Exiv2XmpDatumIterator *iter) +{ + return iter->has_next() ? 1 : 0; +} + +Exiv2XmpDatum* Exiv2XmpDatumIterator::next() +{ + if (it == end) { + return 0; + } + return new Exiv2XmpDatum(*it++); +} + +Exiv2XmpDatum* exiv2_xmp_datum_iterator_next(Exiv2XmpDatumIterator *iter) +{ + return iter->next(); +} + DEFINE_FREE_FUNCTION(exiv2_xmp_data, Exiv2XmpData*); +const char* exiv2_xmp_datum_key(const Exiv2XmpDatum *datum) +{ + return strdup(datum->datum.key().c_str()); +} + char* exiv2_xmp_datum_to_string(const Exiv2XmpDatum *datum) { diff --git a/helper.h b/helper.h index 3530a0c..b6b34b8 100644 --- a/helper.h +++ b/helper.h @@ -8,6 +8,7 @@ DECLARE_STRUCT(Exiv2ImageFactory); DECLARE_STRUCT(Exiv2Image); DECLARE_STRUCT(Exiv2XmpData); DECLARE_STRUCT(Exiv2XmpDatum); +DECLARE_STRUCT(Exiv2XmpDatumIterator); DECLARE_STRUCT(Exiv2IptcData); DECLARE_STRUCT(Exiv2IptcDatum); DECLARE_STRUCT(Exiv2IptcDatumIterator); @@ -16,6 +17,7 @@ DECLARE_STRUCT(Exiv2ExifDatum); DECLARE_STRUCT(Exiv2ExifDatumIterator); DECLARE_STRUCT(Exiv2Error); +void exiv2_xmp_datum_iterator_free(Exiv2XmpDatumIterator *datum); void exiv2_iptc_datum_iterator_free(Exiv2IptcDatumIterator *datum); void exiv2_exif_datum_iterator_free(Exiv2ExifDatumIterator *datum); @@ -38,9 +40,13 @@ int exiv2_image_get_pixel_height(Exiv2Image *img); Exiv2XmpData* exiv2_image_get_xmp_data(const Exiv2Image *img); void exiv2_xmp_data_free(Exiv2XmpData *data); +const char* exiv2_xmp_datum_key(const Exiv2XmpDatum *datum); char* exiv2_xmp_datum_to_string(const Exiv2XmpDatum *datum); void exiv2_xmp_datum_free(Exiv2XmpDatum *datum); Exiv2XmpDatum* exiv2_xmp_data_find_key(const Exiv2XmpData *data, const char *key, Exiv2Error **error); +Exiv2XmpDatumIterator* exiv2_xmp_data_iterator(const Exiv2XmpData *data); +int exiv2_xmp_data_iterator_has_next(const Exiv2XmpDatumIterator *iter); +Exiv2XmpDatum* exiv2_xmp_datum_iterator_next(Exiv2XmpDatumIterator *iter); Exiv2IptcData* exiv2_image_get_iptc_data(const Exiv2Image *img); void exiv2_iptc_data_free(Exiv2IptcData *data); diff --git a/iptc.go b/iptc.go index bda7852..f027dde 100644 --- a/iptc.go +++ b/iptc.go @@ -149,3 +149,19 @@ func makeIptcDatumIterator(data *IptcData, cIter *C.Exiv2IptcDatumIterator) *Ipt func (i *Image) IptcStripKey(key string) error { return i.StripKey(IPTC, key) } + +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 + } + } + return nil +} diff --git a/xmp.go b/xmp.go index 8a40380..623b659 100644 --- a/xmp.go +++ b/xmp.go @@ -22,6 +22,12 @@ type XmpDatum struct { datum *C.Exiv2XmpDatum } +// XmpDatumIterator wraps the respective C++ structure. +type XmpDatumIterator struct { + data *XmpData + iter *C.Exiv2XmpDatumIterator +} + func makeXmpData(img *Image, cdata *C.Exiv2XmpData) *XmpData { data := &XmpData{ img, @@ -105,3 +111,49 @@ func (d *XmpData) GetString(key string) (string, error) { return datum.String(), nil } + +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 + } + } + return nil +} + +// Iterator returns a new XmpDatumIterator to iterate over all IPTC data. +func (d *XmpData) Iterator() *XmpDatumIterator { + return makeXmpDatumIterator(d, C.exiv2_xmp_data_iterator(d.data)) +} + +// HasNext returns true as long as the iterator has another datum to deliver. +func (i *XmpDatumIterator) HasNext() bool { + return C.exiv2_xmp_data_iterator_has_next(i.iter) != 0 +} + +// Next returns the next XmpDatum of the iterator or nil if iterator has reached the end. +func (i *XmpDatumIterator) Next() *XmpDatum { + return makeXmpDatum(i.data, C.exiv2_xmp_datum_iterator_next(i.iter)) +} + +func makeXmpDatumIterator(data *XmpData, cIter *C.Exiv2XmpDatumIterator) *XmpDatumIterator { + datum := &XmpDatumIterator{data, cIter} + + runtime.SetFinalizer(datum, func(i *XmpDatumIterator) { + C.exiv2_xmp_datum_iterator_free(i.iter) + }) + + return datum +} + +// Key returns the XMP key of the datum. +func (d *XmpDatum) Key() string { + return C.GoString(C.exiv2_xmp_datum_key(d.datum)) +}