dsoprea / go-exif Goto Github PK
View Code? Open in Web Editor NEWA very complete, highly tested, standards-driven (but customizable) EXIF reader/writer lovingly written in Go.
License: MIT License
A very complete, highly tested, standards-driven (but customizable) EXIF reader/writer lovingly written in Go.
License: MIT License
The DQT is lost.
The eog
indicates it directly.
It's also visible in the trace below.
Hence, it produces a broken JPEG:
Some trace:
FrameCallback(w=2448, h=2048, pt=01080001=<EPixelType:Mono8>, size=5013504, buffer=0xff...)
Loaded:
0: OFFSET=(0x00000000 0) ID=(0x000000d8) NAME=[SOI ] SIZE=( 0) SHA1=[da39a3ee5e6b4b0d3255bfef95601890afd80709]
1: OFFSET=(0x00000002 2) ID=(0x000000db) NAME=[DQT ] SIZE=( 130) SHA1=[f516966307b0ce1fd5996ded20032c5c0219fa47]
2: OFFSET=(0x00000088 136) ID=(0x000000c0) NAME=[SOF0] SIZE=( 9) SHA1=[08358e300dbec36b4a3e4fb01a328d83ab36f967]
3: OFFSET=(0x00000095 149) ID=(0x000000c4) NAME=[DHT ] SIZE=( 208) SHA1=[6d90c1133ce09c6a8908b62ace6a9ffbb66e2774]
4: OFFSET=(0x00000169 361) ID=(0x000000da) NAME=[SOS ] SIZE=( 0) SHA1=[da39a3ee5e6b4b0d3255bfef95601890afd80709]
5: OFFSET=(0x0000016b 363) ID=(0x00000000) NAME=[ ] SIZE=( 234452) SHA1=[b55d777adce148b5d1d719a83852194ce24aeb63]
6: OFFSET=(0x0003953f 234815) ID=(0x000000d9) NAME=[EOI ] SIZE=( 0) SHA1=[da39a3ee5e6b4b0d3255bfef95601890afd80709]
EXIF added:
0: OFFSET=(0x00000000 0) ID=(0x000000d8) NAME=[SOI ] SIZE=( 0) SHA1=[da39a3ee5e6b4b0d3255bfef95601890afd80709]
1: OFFSET=(0x00000000 0) ID=(0x000000e1) NAME=[APP1] SIZE=( 516) SHA1=[97714d669e289f09fe05690e93442570e39c11f4]
2: OFFSET=(0x00000000 0) ID=(0x000000e1) NAME=[APP1] SIZE=( 516) SHA1=[97714d669e289f09fe05690e93442570e39c11f4]
3: OFFSET=(0x00000088 136) ID=(0x000000c0) NAME=[SOF0] SIZE=( 9) SHA1=[08358e300dbec36b4a3e4fb01a328d83ab36f967]
4: OFFSET=(0x00000095 149) ID=(0x000000c4) NAME=[DHT ] SIZE=( 208) SHA1=[6d90c1133ce09c6a8908b62ace6a9ffbb66e2774]
5: OFFSET=(0x00000169 361) ID=(0x000000da) NAME=[SOS ] SIZE=( 0) SHA1=[da39a3ee5e6b4b0d3255bfef95601890afd80709]
6: OFFSET=(0x0000016b 363) ID=(0x00000000) NAME=[ ] SIZE=( 234452) SHA1=[b55d777adce148b5d1d719a83852194ce24aeb63]
7: OFFSET=(0x0003953f 234815) ID=(0x000000d9) NAME=[EOI ] SIZE=( 0) SHA1=[da39a3ee5e6b4b0d3255bfef95601890afd80709]
194 0]
Written to "/tmp/go-basler-pylon-test/595f6491a6e9c.jpg"
FrameCallback taken 83.717249ms
I've created (using Go image/jpeg
) a new JPEG image (really captured from a cam).
Then I've parsed it with go-exif
, added some tags, and written next to the first one.
The "original" is viewable, the "exifed" is not.
Hi!
While testing this lib I found few IFDs that are not recognized - 9010
, 9011
, 9012
, a002
, and a003
.
Here are couple of files for testing purpose https://www.amazon.fr/clouddrive/share/hOMmnLr6fNF89zwQQhqaWHFocKzDdhSBGivOYOXi46V
I'm quite interested in making heavy use of this package because it's the only one I can find that's pure Go and seems reasonably-recently maintained.
Unfortunately, like the other exif packages I've tried, this package does error handling very non-conventionally and makes it difficult to write stable programs that rely on this package. Instead of just return err
it does this odd panic-recover
flow that obscures error values and in many cases, panics aren't recovered.
Stack traces are difficult to follow back because of mulltiple, nested recovered panics (this is from the readme snippet, as fixed in PR #3 - except I use SearchFileAndExtractExif
instead of SearchAndExtractExif
:
panic: runtime error: index out of range [recovered]
panic: runtime error: index out of range [recovered]
panic: runtime error: index out of range
goroutine 21 [running]:
github.com/dsoprea/go-logging.Panic(0x45a7c00, 0xc0006c8a50)
/Users/matt/Dev/src/github.com/dsoprea/go-logging/log.go:458 +0x79
github.com/dsoprea/go-exif.SearchFileAndExtractExif.func1()
/Users/matt/Dev/src/github.com/dsoprea/go-exif/exif.go:100 +0x88
panic(0x45a7c00, 0xc0006c8a50)
/usr/local/go/src/runtime/panic.go:513 +0x1b9
github.com/dsoprea/go-logging.Panic(0x45a7c00, 0xc0006c8a50)
/Users/matt/Dev/src/github.com/dsoprea/go-logging/log.go:458 +0x79
github.com/dsoprea/go-exif.SearchAndExtractExif.func1()
/Users/matt/Dev/src/github.com/dsoprea/go-exif/exif.go:69 +0x88
panic(0x45a7c00, 0xc0006c8a50)
/usr/local/go/src/runtime/panic.go:513 +0x1b9
github.com/dsoprea/go-logging.Panic(0x45a7c00, 0xc0006c8a50)
/Users/matt/Dev/src/github.com/dsoprea/go-logging/log.go:458 +0x79
github.com/dsoprea/go-exif.SearchAndExtractExif(0xc0005e4000, 0x770e8, 0x7fe00, 0x0, 0x0, 0x0, 0x0, 0x0)
/Users/matt/Dev/src/github.com/dsoprea/go-exif/exif.go:82 +0x126
github.com/dsoprea/go-exif.SearchFileAndExtractExif(0xc00052e0c0, 0x59, 0x0, 0x0, 0x0, 0x0, 0x0)
...
I'm willing to submit pull requests, but I'll have to get it working first -- I still haven't been able to get the readme example to work quite yet.
I'm very hopeful for this package though since the only other EXIF library that (mostly) meets my needs hasn't been maintained for months. And even its forks are falling out of maintenance. But it it looks like this package has good tests for reading EXIF data and supports multiple file formats which is π
Assuming I can get this working, is this a change you'd be willing to have merged in, if I revamp the error handling?
Finished implementing go-exif into my project, and tested the first files from 200.000.
Issues found:
Will post examples in the next days. Stay tuned...
I have tried to add EXIF information to a PNG image, but it does not seem parseable by go-exif. I'm not sure how to confirm if this is an issue with how the data is being written to the file or with how go-exif is reading from the file.
It's parseable by some online viewers, which say they are parsing the EXIF, such as http://metapicz.com/#landing?imgsrc=https%3A%2F%2Fuser-images.githubusercontent.com%2F469790%2F59881044-770e9300-9363-11e9-9fee-2956e4be1524.png
One of our users came across JPEG files that cause the Exif()
function to use 100% CPU... might be stuck in an infinite loop somewhere... couldn't find out where yet... hope you can help us with this!
GitHub issue: photoprism/photoprism#1326
Files for debugging: https://github.com/photoprism/photoprism/files/6529695/Pictures.zip
Exif()
function call in our code:
I read your comment on MakerNote Parsing, any suggestion as to how I could implement it? I looked at the code and couldn't figure it out.
There seems good description of MakerNotes from Phil who built the exiftool in Perl.
https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
Thanks
Based on apple's reply, https://forums.developer.apple.com/thread/89759, seems like HEIF stores EXIF into mp4 box/atom container. Just wonder if you have any plan to support it?
it's not like a good habit...
Trying to help by providing old pics that are causing hiccups for library.
Hope this is welcome. If not no big deal.
Other programs on linux and mac can parse the exif data.
$ git log -1
commit 8da3881 (HEAD -> master, origin/master, origin/HEAD)
Author: Dustin Oprea [email protected]
Date: Wed May 6 01:47:03 2020 -0400
Fix GPS 2.0.0.0 test image name
Orientation of this image is reported as 6 while it should be 1, see internal/meta/exif_test.go#L247. Exiftool and other libraries also say it is 1.
Didn't manage to test with your latest code as the API has changed and I didn't know how to upgrade safely (without accidentally breaking existing functionality), see internal/meta/exif.go#L148.
Hello!
I am writing a plugin for Mattermost that automatically strips EXIF tags from uploaded images.
https://github.com/scottleedavis/mattermost-plugin-noexif
So far, using this library has worked well for reading the tags, but writing back empty or striping out the tags of the files is not straightforward to me yet.
Can this be used to strip out exif tags and if so how?
Thank you!
I'am trying to read and modify exif data, and store it back to jpg files. And i found that after getting the ifdBuilder, even if i did not change anything, when i store it back, the tags'value has been changed. The value lost 6 byte data which is at the very beginning of the original value.
here is my codes:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
exif "github.com/dsoprea/go-exif"
jpegStruct "github.com/dsoprea/go-jpeg-image-structure"
)
// CheckExifReader sets the EXIF DateTime to the given Time unless it has
// already been defined.
func CheckExifReader(filepath string) error {
parser := jpegStruct.NewJpegMediaParser()
sl, err := parser.ParseFile(filepath)
if err != nil {
return fmt.Errorf("failed to parse JPEG file: %v", err)
}
rootIb, err := sl.ConstructExifBuilder()
if err != nil {
log.Println("No EXIF; creating it from scratch")
//TODO what is wrong with this - no EXIF data end up in the JPEG
im := exif.NewIfdMappingWithStandard()
ti := exif.NewTagIndex()
if err := exif.LoadStandardTags(ti); err != nil {
return fmt.Errorf("failed to load standard tags: %v", err)
}
rootIb = exif.NewIfdBuilder(im, ti, exif.IfdPathStandard,
exif.EncodeDefaultByteOrder)
rootIb.AddStandardWithName("ProcessingSoftware", "photos-uploader")
}
// Update the exif segment.
if err := sl.SetExif(rootIb); err != nil {
return fmt.Errorf("failed to set EXIF to jpeg: %v", err)
}
// Write the modified file
b := new(bytes.Buffer)
if err := sl.Write(b); err != nil {
return fmt.Errorf("failed to create JPEG data: %v", err)
}
filepath = "../data/output_image01.jpg"
// Save the file
if err := ioutil.WriteFile(filepath, b.Bytes(), 0644); err != nil {
return fmt.Errorf("failed to write JPEG file: %v", err)
}
fmt.Printf("Wrote %v\n", filepath)
return nil
}
func main() {
err := CheckExifReader("../data/image_01.jpg")
if err != nil {
fmt.Printf("error: %v", err)
}
}
For example, the original exif Model tag's value is "Redmi Note 5A", after i run the program, it become "Note 5A".
However, If change file ifd_tag_entry.go line 139
value, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount)
to
value, err = tt.ParseBytes(addressableData[ite.ValueOffset-6:], byteCount)
everything will be fine, except some unit test will fail. But that not the right way to do it, and i checked some codes, still did't get a clue. Any solutions?
Thanks for creating such a great package. I created a fork of go-exif/v2 at github.com/imclaren/go-exif that extracts the EXIF tags from files without storing all of the bytes from the start of the EXIF block in memory. I left all of the existing tests in place and they all still pass.
By default the go-exif scanner searches up to 5MB into the file for the EXIF header, and then scans up to 1MB of EXIF data. These defaults can be changed when creating a new scanner. I haven't made a pull request because of the substantial changes that I needed to make to go-exif to make this work, but I can if you like.
Hopefully you find this useful or at least interesting.
I am trying to update the DateTime Exif property of an image , i used the code sample what was provided by you
It seems to not update the DateTime correctly, below is the code spinet from my code base
// Parse the image.
jmp := jpeg.NewJpegMediaParser()
sl, err := jmp.ParseFile(filepath)
es.ThrowIf(err)
// Update the UserComment tag.
rootIb, err := sl.ConstructExifBuilder()
es.ThrowIf(err)
ifdPath := "IFD0"
ifdIb, err := exif.GetOrCreateIbFromRootIb(rootIb, ifdPath)
es.ThrowIf(err)
now := time.Now().UTC()
updatedTimestampPhrase := exif.ExifFullTimestampString(now)
log.Printf("time stamp %s", updatedTimestampPhrase)
err = ifdIb.SetStandardWithName("DateTime", updatedTimestampPhrase)
es.ThrowIf(err)
// Update the exif segment.
err = sl.SetExif(rootIb)
es.ThrowIf(err)
b := new(bytes.Buffer)
err = sl.Write(b)
es.ThrowIf(err)
updatedImageBytes := b.Bytes()
//updatedImageBytes = updatedImageBytes
ioutil.WriteFile(path.Join(assetsPath, "good", "updated.jpg"), updatedImageBytes, 0644)
The original image information
The updated image with new datetime
If you see the properties is seems to have corrupted the exif
Do let me know what i am doing wrong
Thanks in advance
Koushik
Ran exif-read-tool on my photo corpus. There are about 100K files. Several of them from a Canon EOS 5D Mark II that were taken in 2011 cannot be processed. They show the following error.
I can provide photo but would prefer to not attach in bug as it is a picture of some one I cannot get permission from anymore. Thanks in advance
Program error.
Stack:
runtime.boundsError runtime error: slice bounds out of range [:8] with capacity 4
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/undefined/exif_9286_user_comment.go:99 (0x58e632)
/usr/local/go/src/runtime/panic.go:969 (0x431a96)
/usr/local/go/src/runtime/panic.go:106 (0x4300c3)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/undefined/exif_9286_user_comment.go:113 (0x58a841)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/undefined/accessor.go:51 (0x587bbf)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/ifd_tag_entry.go:149 (0x5e2f84)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/exif-read-tool/main.go:120 (0x5e897a)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/ifd_enumerate.go:252 (0x5dc40d)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/ifd_enumerate.go:323 (0x5dcd20)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/ifd_enumerate.go:263 (0x5dc395)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/ifd_enumerate.go:323 (0x5dcd20)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/ifd_enumerate.go:345 (0x5dce5e)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/exif.go:192 (0x5d86f5)
/home/mhr/go/src/github.com/dsoprea/go-exif/v2/exif-read-tool/main.go:151 (0x5e7e21)
/usr/local/go/src/runtime/proc.go:203 (0x434802)
/usr/local/go/src/runtime/asm_amd64.s:1373 (0x45ee41)
What is the best way to provide version as I don't see a subcommand.
I did the "go get" last night.
Hi there! (Merry Christmas, by the way)
I might be missing it (I'm new to this package), but is there a way to get the EXIF data without reading the whole file into memory? (Imagine several-hundred-MB or even multi-GB video files...)
I'm trying to set the latitude and longitude (and their refs) correctly. However I'm not really sure about what kind of data I need to convert it to.
For example I use the the following code to convert lat float64
to something like 52/1 22/1 1332/100
:
latitudeRef := "N"
if lat < 0 {
latitudeRef = "S"
lat = -lat
}
degrees := int(math.Floor(lat))
minutes := int(math.Floor(math.Mod(lat*60, 60)))
seconds := int(math.Floor(math.Mod(lat*3600, 3600)))
val := fmt.Sprintf("%d/1 %d/1 %d/100", degrees, minutes, seconds)
Then I'm trying to update the exif data with this value like so:
ifdGPSInfo, err := exif.GetOrCreateIbFromRootIb(rootIb, "IFD/GPSInfo")
if err != nil {
return err
}
err = ifdGPSInfo.SetStandardWithName("GPSLatitudeRef", latitudeRef)
if err != nil {
return err
}
err = ifdGPSInfo.SetStandardWithName("GPSLatitude", val)
if err != nil {
return err
}
But I'm getting this error:
panic: tag (0x0002) value of (19) bytes not evenly divisible by type-size (8)
Which makes sense since I guess I shouldn't be using a string value. So my question is, what should I use instead?
I notices the GpsDegrees struct, which I guess has something to do with this... However I didn't find any information on how to use it.
I'm not sure if the library is not parsing well the value or if I need to do some additional parsing of the value. For some reason I'm getting the following value for SceneType
:
IFD-PATH=[IFD/Exif] ID=(0xa301) NAME=[SceneType] COUNT=(1) TYPE=[UNDEFINED] VALUE=[0x01000000]
For the following image:
I'm getting the value 1
using other tools to get Exif data. Not sure if the parsing of the value is made wrong by go-exif or if I should get only the first byte for this specific tag.
Using v3.0.0
of go-exif.
Thank you.
As a follow up to #11 - it looks like the https://godoc.org/github.com/dsoprea/go-jpeg-image-structure#example-SegmentList-SetExif example is incomplete in so far as the actual metadata is not written to the image file.
I followed the code example in #11 and updated it to your v3 of exif and v2 of jpeg image structure:
import (
exif "github.com/dsoprea/go-exif/v3"
exifcommon "github.com/dsoprea/go-exif/v3/common"
jpeg "github.com/dsoprea/go-jpeg-image-structure/v2"
)
The following code will write the tag data to the image file:
func setExifTag(rootIB *exif.IfdBuilder, ifdPath, tagName, tagValue string) error {
fmt.Printf("setTag(): ifdPath: %v, tagName: %v, tagValue: %v",
ifdPath, tagName, tagValue)
ifdIb, err := exif.GetOrCreateIbFromRootIb(rootIB, ifdPath)
if err != nil {
return fmt.Errorf("Failed to get or create IB: %v", err)
}
if err := ifdIb.SetStandardWithName(tagName, tagValue); err != nil {
return fmt.Errorf("failed to set DateTime tag: %v", err)
}
return nil
}
func setDateIfNone(filepath string, t time.Time) error {
parser := jpeg.NewJpegMediaParser()
intfc, err := parser.ParseFile(filepath)
if err != nil {
return fmt.Errorf("Failed to parse JPEG file: %v", err)
}
sl := intfc.(*jpeg.SegmentList)
rootIb, err := sl.ConstructExifBuilder()
if err != nil {
fmt.Println("No EXIF; creating it from scratch")
im, err := exifcommon.NewIfdMappingWithStandard()
if err != nil {
return fmt.Errorf("Failed to create new IFD mapping with standard tags: %v", err)
}
ti := exif.NewTagIndex()
if err := exif.LoadStandardTags(ti); err != nil {
return fmt.Errorf("Failed to load standard tags: %v", err)
}
rootIb = exif.NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity,
exifcommon.EncodeDefaultByteOrder)
rootIb.AddStandardWithName("ProcessingSoftware", "photos-uploader")
}
//TODO check if DateTime is already set.
// Form our timestamp string
ts := exifcommon.ExifFullTimestampString(t)
// Set DateTime
ifdPath := "IFD0"
if err := setExifTag(rootIb, ifdPath, "DateTime", ts); err != nil {
return fmt.Errorf("Failed to set tag %v: %v", "DateTime", err)
}
// Set DateTimeOriginal
ifdPath = "IFD/Exif"
if err := setExifTag(rootIb, ifdPath, "DateTimeOriginal", ts); err != nil {
return fmt.Errorf("Failed to set tag %v: %v", "DateTimeOriginal", err)
}
// Update the exif segment.
if err := sl.SetExif(rootIb); err != nil {
return fmt.Errorf("Failed to set EXIF to jpeg: %v", err)
}
// Write the modified file
b := new(bytes.Buffer)
if err := sl.Write(b); err != nil {
return fmt.Errorf("Failed to create JPEG data: %v", err)
}
fmt.Printf("Number of image bytes: %v\n", len(b.Bytes()))
// Save the file
if err := ioutil.WriteFile(filepath, b.Bytes(), 0644); err != nil {
return fmt.Errorf("Failed to write JPEG file: %v", err)
}
fmt.Printf("Wrote %v\n", filepath)
return nil
}
Hi, I'm trying to use our tool to parse exif of CR3 photos and it seems to be stopped in the middle. Just a few fields is detected while we have more fields in that photo. Are CR3 photos not fully supported ? Many thanks.
PS: It's quite big and cannot attach here, you can find it in https://www.imaging-resource.com/PRODS/canon-eos-r5/canon-eos-r5A7.HTM
$ exif-read-tool -f EOSR5hSLI000050NR0.cr3 -v
2021/01/19 10:07:23 exif.exif: [DEBUG] Found EXIF blob (320) bytes from initial position.
2021/01/19 10:07:23 main.main: [DEBUG] EXIF blob is (60696338) bytes.
2021/01/19 10:07:23 exif.ifd_enumerate: [DEBUG] Parsing IFD [IFD] at offset (0x0008) (scan).
2021/01/19 10:07:23 exif.ifd_enumerate: [DEBUG] IFD [IFD] tag-count: (13)
2021/01/19 10:07:23 exif.tags: [DEBUG] (305) tags loaded.
2021/01/19 10:07:23 exif.ifd_enumerate: [DEBUG] Next IFD at offset: (00000000)
2021/01/19 10:07:23 exif.ifd_enumerate: [DEBUG] Scan: It looks like the furthest offset that contained EXIF data in the EXIF blob was (250) (Scan).
IFD-PATH=[IFD] ID=(0x0100) NAME=[ImageWidth] COUNT=(1) TYPE=[SHORT] VALUE=[[8192]]
IFD-PATH=[IFD] ID=(0x0101) NAME=[ImageLength] COUNT=(1) TYPE=[SHORT] VALUE=[[5464]]
IFD-PATH=[IFD] ID=(0x0102) NAME=[BitsPerSample] COUNT=(3) TYPE=[SHORT] VALUE=[[8 8 8]]
IFD-PATH=[IFD] ID=(0x0103) NAME=[Compression] COUNT=(1) TYPE=[SHORT] VALUE=[[6]]
IFD-PATH=[IFD] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]
IFD-PATH=[IFD] ID=(0x0110) NAME=[Model] COUNT=(13) TYPE=[ASCII] VALUE=[Canon EOS R5]
IFD-PATH=[IFD] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[[1]]
IFD-PATH=[IFD] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[[72/1]]
IFD-PATH=[IFD] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[[72/1]]
IFD-PATH=[IFD] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[[2]]
IFD-PATH=[IFD] ID=(0x0132) NAME=[DateTime] COUNT=(20) TYPE=[ASCII] VALUE=[2020:07:22 17:52:08]
IFD-PATH=[IFD] ID=(0x013b) NAME=[Artist] COUNT=(1) TYPE=[ASCII] VALUE=[]
IFD-PATH=[IFD] ID=(0x8298) NAME=[Copyright] COUNT=(1) TYPE=[ASCII] VALUE=[]
See bottom of tags_unknown.go:
Lines 387 to 392 in 9aa2497
Tags with undefined types are size-unknown without additional handling, which means they are skipped when we construct a updated EXIF block. So, at the current moment, these tags will be dropped when an EXIF block is updated.
In that example snippet, there's this
for i := 0; i < len(data); i++ {
if e.IsExif(data[i:i + 6]) == true {
foundAt = i
break
}
}
But what is e
?
I've searched the source for IsExif
and I can't find that function anywhere
Can you please provide one sample code for writer? I'm trying to use your tool to put EXIF into thumbnail pictures.
Thanks
I reported this earlier in #56 but the chosen solution was not accepted.
I run a service that extracts certain exif data from user supplied image files. The problem is that for some images this service goes into an infinite loop while extracting and I have traced the source of the problem to this library.
An example image file is this one (zipped to preserve exif data): broken_exif.zip.
When I run the following test code:
file, err := os.Open("broken_exif.jpg")
if err != nil {
panic(err)
}
rawExif, err := SearchAndExtractExifWithReader(file)
if err != nil {
panic(err)
}
ifd, err := exifcommon.NewIfdMappingWithStandard()
if err != nil {
panic(err)
}
tag := NewTagIndex()
_, _, err = Collect(ifd, tag, rawExif)
if err != nil {
panic(err)
}
The code never returns and remains looping, repeating the same byte offsets over and over again.
Add a function to search for a tag-ID across our tag-index for the first match agnostic to the type of IFD it could be found in. Allow for an optional parameter that takes an ordered slice of IFD paths to search. If not provided, use a reasonable default (e.g. "EXIF IFD", "IFD0", "GPS").
Occasionally someone shows up with a picture having one or more tags in a different IFD than what is prescribed by the standard. This won't be default functionality, but at least we can provide the option. We might even suggest using it if we find some unknown tags and a preliminary check shows that they might be valid tags if found in another IFD.
One of the reasons that we don't want to make this default behavior is that if the tag-ID matches but the type and the data are not consistent, we might end-up trying to display non-printable binary.
This effectively converts a proper description like "Hello World\n\nWhere are you?" to "string with binary data (%d bytes)" which then shows up as image description throughout the application:
if isPrintableText(t) == false {
phrase = fmt.Sprintf("string with binary data (%d bytes)", len(t))
return phrase, nil
}
It seems better to allow \n, \r, and \t or return an empty string so that the value returned by Exiftool can be used as fallback. Same for Emojis if they don't qualify as printable text.
I have use-case where I need to remove the EXIF IFD's from an image can this be done using go-exif
?
Thanks !
It seems like go-logging is susceptible to race conditions which cause my tests to fail when using the Go Data Race Detector. It looks like a single logging instance is being used at a global level, and then modified with each log?
==================
WARNING: DATA RACE
Read at 0x00c0000c2230 by goroutine 21:
github.com/dsoprea/go-logging.(*Logger).doConfigure()
/go/pkg/mod/github.com/dsoprea/[email protected]/log.go:183 +0x55
github.com/dsoprea/go-logging.(*Logger).Warningf()
/go/pkg/mod/github.com/dsoprea/[email protected]/log.go:339 +0x55
github.com/dsoprea/go-exif.ParseExifHeader()
/go/pkg/mod/github.com/dsoprea/[email protected]/exif.go:146 +0x501
github.com/dsoprea/go-exif.SearchAndExtractExif()
/go/pkg/mod/github.com/dsoprea/[email protected]/exif.go:78 +0xc0
gitlab.com/keithwoelke/parking-police/pkg/photo.parseIFDs()
/mnt/pkg/photo/metadata.go:44 +0xa0
gitlab.com/keithwoelke/parking-police/pkg/photo.ImageMetadata()
/mnt/pkg/photo/metadata.go:21 +0x85
gitlab.com/keithwoelke/parking-police/pkg/photo_test.TestGetMetadataWhenNoExifMetadata.func1()
/mnt/pkg/photo/metadata_test.go:77 +0xe1
testing.tRunner()
/usr/local/go/src/testing/testing.go:865 +0x163
Previous write at 0x00c0000c2230 by goroutine 19:
github.com/dsoprea/go-logging.(*Logger).doConfigure()
/go/pkg/mod/github.com/dsoprea/[email protected]/log.go:231 +0x38b
github.com/dsoprea/go-logging.(*Logger).Warningf()
/go/pkg/mod/github.com/dsoprea/[email protected]/log.go:339 +0x55
github.com/dsoprea/go-exif.ParseExifHeader()
/go/pkg/mod/github.com/dsoprea/[email protected]/exif.go:146 +0x501
github.com/dsoprea/go-exif.SearchAndExtractExif()
/go/pkg/mod/github.com/dsoprea/[email protected]/exif.go:78 +0xc0
gitlab.com/keithwoelke/parking-police/pkg/photo.parseIFDs()
/mnt/pkg/photo/metadata.go:44 +0xa0
gitlab.com/keithwoelke/parking-police/pkg/photo.ImageMetadata()
/mnt/pkg/photo/metadata.go:21 +0x85
gitlab.com/keithwoelke/parking-police/pkg/photo_test.TestGetMetadataWhenNoExifMetadata.func1()
/mnt/pkg/photo/metadata_test.go:77 +0xe1
testing.tRunner()
/usr/local/go/src/testing/testing.go:865 +0x163
Goroutine 21 (running) created at:
testing.(*T).Run()
/usr/local/go/src/testing/testing.go:916 +0x65a
gitlab.com/keithwoelke/parking-police/pkg/photo_test.TestGetMetadataWhenNoExifMetadata()
/mnt/pkg/photo/metadata_test.go:71 +0x156
testing.tRunner()
/usr/local/go/src/testing/testing.go:865 +0x163
Goroutine 19 (finished) created at:
testing.(*T).Run()
/usr/local/go/src/testing/testing.go:916 +0x65a
gitlab.com/keithwoelke/parking-police/pkg/photo_test.TestGetMetadataWhenNoExifMetadata()
/mnt/pkg/photo/metadata_test.go:71 +0x156
testing.tRunner()
/usr/local/go/src/testing/testing.go:865 +0x163
==================
Lines 206 to 218 in f63d290
This is required to write custom undefined-type tags.
is there any plan to support webp?
Hi Dustin, I am really enjoying your library! I can see that you have dedicated much effort to details. For a project that I am working on I am using parts of your library and did some benchmarking. .
Benchmarks are the difference between moving the whole file into a []byte (ParseExif2Header) vs using a reader (ParseExifHeader):
// JPG file with ExifData
BenchmarkParseExifHeader200-8 408568 2915 ns/op 1088 B/op 3 allocs/op
BenchmarkParseExif2Header200-8 1047 956161 ns/op 8386637 B/op 15 allocs/op
// JPG file with ExifData #2
BenchmarkParseExifHeader200-8 412591 2883 ns/op 1088 B/op 3 allocs/op
BenchmarkParseExif2Header200-8 996 1051139 ns/op 8386637 B/op 15 allocs/op
// CR2 file
BenchmarkParseExifHeader200-8 409156 2654 ns/op 1088 B/op 3 allocs/op
BenchmarkParseExif2Header200-8 68 16370650 ns/op 67106902 B/op 18 allocs/op
// JPG file with No ExifData
BenchmarkParseExifHeader200-8 261 4603787 ns/op 1056 B/op 2 allocs/op
BenchmarkParseExif2Header200-8 254 4749712 ns/op 1046565 B/op 11 allocs/op
Changes are io.Reader instead of []byte, bigEndianHeaderBytes, littleEndianHeaderBytes and ExifHeader has a foundAt field for compactness.
// ParseExifHeader parses the bytes at the very top of the header.
// Benchmarked
func parseExifHeader(r io.Reader) (*ExifHeader, error) {
bufSize := 1024 // 1kB Buffer
data := make([]byte, bufSize)
var err error
var a, j int
for {
copy(data[:8], data[len(data)-8:])
a, err = r.Read(data[8:])
if err == io.EOF {
return &ExifHeader{}, ErrNoExif
} else if err != nil {
panic(err)
}
j += a
for i := 0; i < len(data)-8; i++ {
if bytes.Equal([]byte{data[i], data[i+1], data[i+2], data[i+3]}, bigEndianHeaderBytes[:]) {
return &ExifHeader{
ByteOrder: binary.BigEndian,
FirstIfdOffset: binary.BigEndian.Uint32(data[i+4 : i+8]),
foundAt: (j + i) - bufSize,
}, nil
}
if bytes.Equal([]byte{data[i], data[i+1], data[i+2], data[i+3]}, littleEndianHeaderBytes[:]) {
return &ExifHeader{
ByteOrder: binary.LittleEndian,
FirstIfdOffset: binary.LittleEndian.Uint32(data[i+4 : i+8]),
foundAt: (j + i) - bufSize,
}, nil
}
}
}
}
// Search for the beginning of the EXIF information. The EXIF is near the
// beginning of our/most JPEGs, so this has a very low cost.
// ParseExifHeader parses the bytes at the very top of the header.
func parseExifHeader2(r io.Reader) (*ExifHeader, error) {
eh := &ExifHeader{
foundAt: -1,
}
data, err := ioutil.ReadAll(r)
if err != nil {
return eh, err
}
for i := 0; i < len(data)-8; i++ {
if bytes.Equal([]byte{data[i], data[i+1], data[i+2], data[i+3]}, bigEndianHeaderBytes[:]) {
firstIfdOffset := binary.BigEndian.Uint32(data[i+4 : i+8])
return &ExifHeader{
ByteOrder: binary.BigEndian,
FirstIfdOffset: firstIfdOffset,
foundAt: i,
}, nil
}
if bytes.Equal([]byte{data[i], data[i+1], data[i+2], data[i+3]}, littleEndianHeaderBytes[:]) {
firstIfdOffset := binary.LittleEndian.Uint32(data[i+4 : i+8])
return &ExifHeader{
ByteOrder: binary.LittleEndian,
FirstIfdOffset: firstIfdOffset,
foundAt: i,
}, nil
}
}
return eh, ErrNoExif
}
var (
bigEndianHeaderBytes = [4]byte{'M', 'M', 0x00, 0x2a}
littleEndianHeaderBytes = [4]byte{'I', 'I', 0x2a, 0x00}
)
// ExifHeader - The Exif header
type ExifHeader struct {
ByteOrder binary.ByteOrder
FirstIfdOffset uint32
foundAt int
}
Bechmark Code:
var path = "../../test/img/1.jpg"
func BenchmarkParseExifHeader200(b *testing.B) {
var err error
f, err := os.Open(path)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Seek(0, 0)
_, _ = parseExifHeader(f)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParseExif2Header200(b *testing.B) {
var err error
f, err := os.Open(path)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Seek(0, 0)
_, _ = parseExifHeader2(f)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCurrent200(b *testing.B) {
var err error
f, err := os.Open(path)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Seek(0, 0)
data, _ := ioutil.ReadAll(f)
_, _ = exif.SearchAndExtractExif(data)
if err != nil {
b.Fatal(err)
}
}
}
Hey, thanks for the cool project!
I have a test.gif
file in the current directory. Running the following command, throws panic error:
$ exif-read-tool -filepath test.gif
*fs.PathError open ilepath: no such file or directory
/Users/furkan.turkal/go/pkg/mod/github.com/dsoprea/go-exif/[email protected]/command/exif-read-tool/main.go:92 (0x11cb177)
main: log.PanicIf(err)
/usr/local/go/src/runtime/proc.go:225 (0x1037516)
main: fn()
/usr/local/go/src/runtime/asm_amd64.s:1371 (0x1067c21)
goexit: BYTE $0x90 // NOP
$ open test.gif # this OK
Any ideas? π€
Any reason GPSVersionID 2.0.0.0 is not supported, only 2.2.0.0 and 2.3.0.0?
Didn't find any useful information on how version 2.0 is different from 2.2 or 2.3, coordinates look the same.
This was a lapse in the original implementation. We'll do this while we're releasing the next major version so we don't break anyone.
One of our users found a photo that causes GpsInfo()
to error with interface conversion: interface {} is []exifcommon.Rational, not []uint8
JPEG file link in this comment:
photoprism/photoprism#295 (comment)
When I open the file in Photoshop 2020 and save it again (resized), it works just fine. So Photoshop seems to change / fix something.
Something is happening with the memory in Collect
. It might be a memory leak that I couldn't figure out my self.
Here is the error I'm getting with stacktrace:
fatal error: runtime: out of memory
runtime stack:
runtime.throw(0xe421fd, 0x16)
/usr/local/go/src/runtime/panic.go:1116 +0x72
runtime.sysMap(0xc134000000, 0x4000000, 0x1649638)
/usr/local/go/src/runtime/mem_bsd.go:73 +0xc5
runtime.(*mheap).sysAlloc(0x1634dc0, 0x400000, 0x7501a9040, 0x7501aa470)
/usr/local/go/src/runtime/malloc.go:715 +0x1cd
runtime.(*mheap).grow(0x1634dc0, 0x1, 0x0)
/usr/local/go/src/runtime/mheap.go:1286 +0x11c
runtime.(*mheap).allocSpan(0x1634dc0, 0x1, 0x74fd52000, 0x1649648, 0x7501aa360)
/usr/local/go/src/runtime/mheap.go:1124 +0x65d
runtime.(*mheap).alloc.func1()
/usr/local/go/src/runtime/mheap.go:871 +0x64
runtime.systemstack(0x7fffdd9eaf80)
/usr/local/go/src/runtime/asm_amd64.s:370 +0x66
runtime.mstart()
/usr/local/go/src/runtime/proc.go:1041
goroutine 315006 [running]:
runtime.systemstack_switch()
/usr/local/go/src/runtime/asm_amd64.s:330 fp=0xc0001f2030 sp=0xc0001f2028 pc=0x4699c0
runtime.(*mheap).alloc(0x1634dc0, 0x1, 0x410120, 0x7501a6140)
/usr/local/go/src/runtime/mheap.go:865 +0x81 fp=0xc0001f2080 sp=0xc0001f2030 pc=0x42b091
runtime.(*mcentral).grow(0x1645a18, 0x0)
/usr/local/go/src/runtime/mcentral.go:255 +0x79 fp=0xc0001f20c0 sp=0xc0001f2080 pc=0x41d819
runtime.(*mcentral).cacheSpan(0x1645a18, 0x60)
/usr/local/go/src/runtime/mcentral.go:106 +0x2bc fp=0xc0001f2108 sp=0xc0001f20c0 pc=0x41d34c
runtime.(*mcache).refill(0x701659560, 0x20)
/usr/local/go/src/runtime/mcache.go:138 +0x85 fp=0xc0001f2128 sp=0xc0001f2108 pc=0x41ce35
runtime.(*mcache).nextFree(0x701659560, 0x20, 0x30, 0x30, 0xc133ffcc30)
/usr/local/go/src/runtime/malloc.go:868 +0x87 fp=0xc0001f2160 sp=0xc0001f2128 pc=0x411a67
runtime.mallocgc(0xf0, 0xe140c0, 0xdf1c01, 0x16477e0)
/usr/local/go/src/runtime/malloc.go:1036 +0x793 fp=0xc0001f2200 sp=0xc0001f2160 pc=0x4123a3
runtime.newobject(0xe140c0, 0x0)
/usr/local/go/src/runtime/malloc.go:1165 +0x38 fp=0xc0001f2230 sp=0xc0001f2200 pc=0x412798
github.com/dsoprea/go-exif/v2.(*IfdEnumerate).Collect(0xc0001f2780, 0xc000000008, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
/go/pkg/mod/github.com/dsoprea/go-exif/[email protected]/ifd_enumerate.go:958 +0x3ef fp=0xc0001f2718 sp=0xc0001f2230 pc=0xc4a6cf
github.com/dsoprea/go-exif/v2.Collect(0xc00064c030, 0xc0006f61a0, 0xc00053403c, 0x1ffc4, 0x3fdc4, 0x1016da0, 0x16477e0, 0x8, 0x0, 0x0, ...)
/go/pkg/mod/github.com/dsoprea/go-exif/[email protected]/exif.go:211 +0x232 fp=0xc0001f27e8 sp=0xc0001f2718 pc=0xc41da2
Would be great if someone can have look and find out what is going on here.
Hi @dsoprea ,
I'm having some troubles when trying to add some metadata to cropped images, there's a functionality on go-jpeg-image-structure
or go-exif
to deal with this before it raises the error no exif data
?
I'm sending the cropped image as example (you can use any image from image.SubImage
or crop an image with gimp without saving EXIF structure or any other metadata) and here's a piece of code (basically the example provided on jpegstructure
)
jmp := jpg.NewJpegMediaParser()
intfc, err := jmp.ParseBytes(b.Bytes())
if err != nil {
return err
}
sl := intfc.(*jpg.SegmentList)
// Throws: no exif data
rootIb, err := sl.ConstructExifBuilder()
if err != nil {
return err
}
ifdPath := "IFD0"
ifdIb, err := exif.GetOrCreateIbFromRootIb(rootIb, ifdPath)
if err != nil {
return err
}
err = ifdIb.SetStandardWithName("ImageDescription", description)
if err != nil {
return err
}
// Update the exif segment.
err = sl.SetExif(rootIb)
if err != nil {
return err
}
There are two example images in our test suite that still cause trouble when parsing GPS coordinates:
runtime error: index out of range [1] with length 1
This seems to affect a lot of images, also some older ones taken with a Canon EOS 6D. This example was taken with a Huawei P30 phone, so probably not a vendor specific issue.
runtime error: integer divide by zero
This file is a resized version (using ImageMagick) of a large panorama image attached to this feature request: photoprism/photoprism#352
Otherwise very happy with your libraries. Keep up the great work! Also added the new heic parser and noticed that an old issue with parsing width and height was fixed.
Hi, as the library is not all straightforward, (nor do I understand EXIF that well), I'd love to see some documentation / examples on how to write tag values -- more specifically, in my case, how to write the "Date and Time" tag of an photo. My photos might lack the whole EXIF data entirely as they are somewhat old.
Kudos for making this library. Was surprised to find so little in the way of EXIF in the Golang library-verse.
Attempting to parse the exif data from the attached image causes a panic. Running the same image through exiftool does seem to work. I spent a small amount of time trying to figure this out but I didn't get too far.
FQ-IFD-PATH=[IFD] ID=(0x0100) NAME=[ImageWidth] COUNT=(1) TYPE=[LONG] VALUE=[5312]
FQ-IFD-PATH=[IFD] ID=(0x0101) NAME=[ImageLength] COUNT=(1) TYPE=[LONG] VALUE=[2988]
FQ-IFD-PATH=[IFD] ID=(0x010f) NAME=[Make] COUNT=(8) TYPE=[ASCII] VALUE=[samsung]
FQ-IFD-PATH=[IFD] ID=(0x0110) NAME=[Model] COUNT=(10) TYPE=[ASCII] VALUE=[SM-G920W8]
FQ-IFD-PATH=[IFD] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[6]
FQ-IFD-PATH=[IFD] ID=(0x0131) NAME=[Software] COUNT=(15) TYPE=[ASCII] VALUE=[G920W8VLU6DRF1]
FQ-IFD-PATH=[IFD] ID=(0x0132) NAME=[DateTime] COUNT=(20) TYPE=[ASCII] VALUE=[2018:08:15 15:38:33]
FQ-IFD-PATH=[IFD] ID=(0x0213) NAME=[YCbCrPositioning] COUNT=(1) TYPE=[SHORT] VALUE=[1]
FQ-IFD-PATH=[IFD] ID=(0x8769) NAME=[ExifTag] COUNT=(1) TYPE=[LONG] VALUE=[175]
FQ-IFD-PATH=[IFD/Exif] ID=(0x829a) NAME=[ExposureTime] COUNT=(1) TYPE=[RATIONAL] VALUE=[1/50]
FQ-IFD-PATH=[IFD/Exif] ID=(0x829d) NAME=[FNumber] COUNT=(1) TYPE=[RATIONAL] VALUE=[19/10]
FQ-IFD-PATH=[IFD/Exif] ID=(0x8822) NAME=[ExposureProgram] COUNT=(1) TYPE=[SHORT] VALUE=[2]
FQ-IFD-PATH=[IFD/Exif] ID=(0x8827) NAME=[ISOSpeedRatings] COUNT=(1) TYPE=[SHORT] VALUE=[100]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9000) NAME=[ExifVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[0220]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9003) NAME=[DateTimeOriginal] COUNT=(20) TYPE=[ASCII] VALUE=[2018:08:15 15:38:33]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9004) NAME=[DateTimeDigitized] COUNT=(20) TYPE=[ASCII] VALUE=[2018:08:15 15:38:33]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9201) NAME=[ShutterSpeedValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[564/100]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9202) NAME=[ApertureValue] COUNT=(1) TYPE=[RATIONAL] VALUE=[185/100]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9203) NAME=[BrightnessValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[261/100]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9204) NAME=[ExposureBiasValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[0/10]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9205) NAME=[MaxApertureValue] COUNT=(1) TYPE=[RATIONAL] VALUE=[185/100]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9207) NAME=[MeteringMode] COUNT=(1) TYPE=[SHORT] VALUE=[2]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9209) NAME=[Flash] COUNT=(1) TYPE=[SHORT] VALUE=[0]
FQ-IFD-PATH=[IFD/Exif] ID=(0x920a) NAME=[FocalLength] COUNT=(1) TYPE=[RATIONAL] VALUE=[430/100]
FQ-IFD-PATH=[IFD/Exif] ID=(0x927c) NAME=[MakerNote] COUNT=(98) TYPE=[UNDEFINED] VALUE=[MakerNote<TYPE-ID=[07 00 01 00 07 00 04 00 00 00 30 31 30 30 02 00 04 00 01 00]>]
FQ-IFD-PATH=[IFD/Exif] ID=(0x9286) NAME=[UserComment] COUNT=(21) TYPE=[UNDEFINED] VALUE=[UserComment<SIZE=(0) ENCODING=[UNDEFINED] V=[] LEN=(0)>]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa001) NAME=[ColorSpace] COUNT=(1) TYPE=[SHORT] VALUE=[1]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa002) NAME=[PixelXDimension] COUNT=(1) TYPE=[LONG] VALUE=[5312]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa003) NAME=[PixelYDimension] COUNT=(1) TYPE=[LONG] VALUE=[2988]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa402) NAME=[ExposureMode] COUNT=(1) TYPE=[LONG] VALUE=[0]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa403) NAME=[WhiteBalance] COUNT=(1) TYPE=[LONG] VALUE=[0]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa405) NAME=[FocalLengthIn35mmFilm] COUNT=(1) TYPE=[LONG] VALUE=[28]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa406) NAME=[SceneCaptureType] COUNT=(1) TYPE=[LONG] VALUE=[0]
FQ-IFD-PATH=[IFD/Exif] ID=(0xa420) NAME=[ImageUniqueID] COUNT=(30) TYPE=[ASCII] VALUE=[A16LSIA00VM A16LSJL02SM
]
panic: tag-type not valid: 0x0000
@linosgian originally reported this issue: photoprism/photoprism#219
Indexing did not get exif's "Date/Time Original" or "Create Date" right for some of my photos. exiftool seems to be able to parse everything correctly on all photos. As a result, the photo appears to be taken in 2020, as (I guess) indexing falls back to the file's mtime
After checking import logs, I learned many of my photos are also affected. So it must be something with how fields are parsed. Error originates from here:
Line 53 in 0bbb7a3
Maybe a condition that's not clearly defined in the Exif standard? Would using ParseAsciiNoNul()
instead help?
Was working off of https://github.com/dsoprea/go-exif/blob/master/v2/exif-read-tool/main.go for my own thing and I can't get the v2 package to compile. I'm using go 1.14 and go modules.
# github.com/dsoprea/go-exif/v2
../../go/pkg/mod/github.com/dsoprea/go-exif/[email protected]/ifd_enumerate.go:1502:7: undefined: i
Is it planned to add support for IPTC and XMP metadata to the library (despite its name)? Asking because these three usually have to be dealt with in conjunction, are similar in structure and intention (at least from the end user's point of view), and tools such as exiftool support them.
Hello! In the process of trying out this library (thank you, by the way!), I discovered the GPS coordinates are calculated slightly incorrectly.
In ifd_enumerate.go, the values are calculated by dividing the Rationals as expected. However, for some reason, they are truncated instead of stored as float64 types. This causes the value to be off slightly when calculating the final GPS coordinates in degrees.
For example, a Longitude of 77.0365 is returned as [{Numerator:77 Denominator:1} {Numerator:2 Denominator:1} {Numerator:57 Denominator:5}] which calculates as expected by hand, but after the truncating, Decimal() returns it as 77.0363888888889 instead.
Hi, I was trying to add some geotiff tags on a image but tags like ModelPixelScaleTag
, ModelTiepointTag
, ModelTransformationTag
aren't available (no tag found).
Looking at the lib in versions v2 and v3, there's tags_data.go
with a variable tagsYaml
with copy pasted value from assets. Can I add those tags to tags asset
or there's another approach?
I'm trying to write metadata on this image (I hope github don't make changes on metadata, if it does, I'll provide another link):
That's the original picture that I take from Ada with my phone. I'm using this code based on example
jmp := jpg.NewJpegMediaParser()
intfc, err := jmp.ParseBytes(b.Bytes())
if err != nil {
return err
}
sl := intfc.(*jpg.SegmentList)
// Update the UserComment tag.
rootIb, err := sl.ConstructExifBuilder()
if err != nil {
return err
}
But when trying to call exif.ConstructExifBuilder
, the function returns the error: unparseable undefined tag
.
Somebody help me!
Here's some metadata using exiftool with the command:
exiftool -a -u -g1 IMG_20201102_110021.jpg
Output:
---- ExifTool ----
ExifTool Version Number : 12.09
Warning : [minor] Unrecognized MakerNotes
Warning : [minor] Unrecognized MakerNotes
---- System ----
File Name : IMG_20201102_110021.jpg
Directory : ..
File Size : 3.1 MB
File Modification Date/Time : 2020:11:04 11:11:19-03:00
File Access Date/Time : 2020:11:04 11:11:31-03:00
File Inode Change Date/Time : 2020:11:04 11:11:19-03:00
File Permissions : rw-rw-r--
---- File ----
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
Exif Byte Order : Big-endian (Motorola, MM)
Image Width : 3456
Image Height : 4608
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
---- IFD0 ----
Image Width : 3456
Image Height : 4608
Bits Per Sample : 8 8 8
Image Description : sdr
Make : HUAWEI
Camera Model Name : BND-L34
Orientation : Unknown (0)
X Resolution : 72
Y Resolution : 72
Resolution Unit : inches
Software : BND-L34 8.0.0.362(C567)
Modify Date : 2020:11:02 11:00:23
Y Cb Cr Positioning : Centered
Device Setting Description : (Binary data 4 bytes, use -b option to extract)
---- ExifIFD ----
Document Name :
Exposure Time : 1/256
F Number : 2.2
Exposure Program : Program AE
ISO : 50
Exif Version : 0210
Date/Time Original : 2020:11:02 11:00:23
Create Date : 2020:11:02 11:00:23
Components Configuration : Y, Cb, Cr, -
Shutter Speed Value : 1/999963365
Aperture Value : 2.2
Brightness Value : 0
Exposure Compensation : 0
Metering Mode : Multi-segment
Light Source : Daylight
Flash : No Flash
Focal Length : 3.8 mm
Maker Note Unknown Text : Auto
Sub Sec Time : 543266
Sub Sec Time Original : 543266
Sub Sec Time Digitized : 543266
Flashpix Version : 0100
Color Space : sRGB
Exif Image Width : 3456
Exif Image Height : 4608
Sensing Method : One-chip color area
File Source : Digital Camera
Scene Type : Directly photographed
Custom Rendered : Custom
Exposure Mode : Auto
White Balance : Auto
Digital Zoom Ratio : 1
Focal Length In 35mm Format : 26 mm
Scene Capture Type : Standard
Gain Control : None
Contrast : Normal
Saturation : Normal
Sharpness : Normal
Subject Distance Range : Unknown
---- InteropIFD ----
Interoperability Index : R98 - DCF basic file (sRGB)
Interoperability Version : 0100
---- IFD1 ----
Image Width : 384
Image Height : 512
Compression : JPEG (old-style)
Orientation : Unknown (0)
X Resolution : 72
Y Resolution : 72
Resolution Unit : inches
Thumbnail Offset : 8592
Thumbnail Length : 19480
Thumbnail Image : (Binary data 19480 bytes, use -b option to extract)
---- JFIF ----
JFIF Version : 1.01
Resolution Unit : inches
X Resolution : 96
Y Resolution : 96
---- Composite ----
Aperture : 2.2
Image Size : 3456x4608
Megapixels : 15.9
Scale Factor To 35 mm Equivalent: 6.8
Shutter Speed : 1/256
Create Date : 2020:11:02 11:00:23.543266
Date/Time Original : 2020:11:02 11:00:23.543266
Modify Date : 2020:11:02 11:00:23.543266
Circle Of Confusion : 0.004 mm
Field Of View : 69.4 deg
Focal Length : 3.8 mm (35 mm equivalent: 26.0 mm)
Hyperfocal Distance : 1.51 m
Light Value : 11.3
Hi,
Where is the release .exe ?
I can't find them on the releases page nor on Travis CI.
Thank you.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.