1 package exif
2
3 import (
4 "bytes"
5 "fmt"
6 "os"
7 "reflect"
8 "sort"
9 "testing"
10
11 "encoding/binary"
12 "io/ioutil"
13
14 "github.com/dsoprea/go-logging"
15
16 "github.com/dsoprea/go-exif/v3/common"
17 )
18
19 func TestVisit(t *testing.T) {
20 defer func() {
21 if state := recover(); state != nil {
22 err := log.Wrap(state.(error))
23 log.PrintError(err)
24
25 t.Fatalf("Test failure.")
26 }
27 }()
28
29 ti := NewTagIndex()
30
31
32
33 testImageFilepath := getTestImageFilepath()
34
35 f, err := os.Open(testImageFilepath)
36 log.PanicIf(err)
37
38 defer f.Close()
39
40 data, err := ioutil.ReadAll(f)
41 log.PanicIf(err)
42
43
44
45
46
47
48 foundAt := -1
49 for i := 0; i < len(data); i++ {
50 if _, err := ParseExifHeader(data[i:]); err == nil {
51 foundAt = i
52 break
53 } else if log.Is(err, ErrNoExif) == false {
54 log.Panic(err)
55 }
56 }
57
58 if foundAt == -1 {
59 log.Panicf("EXIF start not found")
60 }
61
62
63
64 im, err := exifcommon.NewIfdMappingWithStandard()
65 log.PanicIf(err)
66
67 tags := make([]string, 0)
68
69
70 visitor := func(ite *IfdTagEntry) (err error) {
71 defer func() {
72 if state := recover(); state != nil {
73 err = log.Wrap(state.(error))
74 log.Panic(err)
75 }
76 }()
77
78 tagId := ite.TagId()
79 tagType := ite.TagType()
80 ii := ite.ifdIdentity
81
82 it, err := ti.Get(ii, tagId)
83 if err != nil {
84 if log.Is(err, ErrTagNotFound) {
85 fmt.Printf("Unknown tag: [%s] (%04x)\n", ii.String(), tagId)
86 return nil
87 }
88
89 log.Panic(err)
90 }
91
92 valueString, err := ite.FormatFirst()
93 log.PanicIf(err)
94
95 description := fmt.Sprintf("IFD-PATH=[%s] ID=(0x%04x) NAME=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]", ii.String(), tagId, it.Name, ite.UnitCount(), tagType.String(), valueString)
96 tags = append(tags, description)
97
98 return nil
99 }
100
101 _, furthestOffset, err := Visit(exifcommon.IfdStandardIfdIdentity, im, ti, data[foundAt:], visitor, nil)
102 log.PanicIf(err)
103
104 if furthestOffset != 32935 {
105 t.Fatalf("Furthest-offset is not valid: (%d)", furthestOffset)
106 }
107
108 expected := []string{
109 "IFD-PATH=[IFD] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]",
110 "IFD-PATH=[IFD] ID=(0x0110) NAME=[Model] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]",
111 "IFD-PATH=[IFD] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[1]",
112 "IFD-PATH=[IFD] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
113 "IFD-PATH=[IFD] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
114 "IFD-PATH=[IFD] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
115 "IFD-PATH=[IFD] ID=(0x0132) NAME=[DateTime] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
116 "IFD-PATH=[IFD] ID=(0x013b) NAME=[Artist] COUNT=(1) TYPE=[ASCII] VALUE=[]",
117 "IFD-PATH=[IFD] ID=(0x0213) NAME=[YCbCrPositioning] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
118 "IFD-PATH=[IFD] ID=(0x8298) NAME=[Copyright] COUNT=(1) TYPE=[ASCII] VALUE=[]",
119 "IFD-PATH=[IFD] ID=(0x8769) NAME=[ExifTag] COUNT=(1) TYPE=[LONG] VALUE=[360]",
120 "IFD-PATH=[IFD/Exif] ID=(0x829a) NAME=[ExposureTime] COUNT=(1) TYPE=[RATIONAL] VALUE=[1/640]",
121 "IFD-PATH=[IFD/Exif] ID=(0x829d) NAME=[FNumber] COUNT=(1) TYPE=[RATIONAL] VALUE=[4/1]",
122 "IFD-PATH=[IFD/Exif] ID=(0x8822) NAME=[ExposureProgram] COUNT=(1) TYPE=[SHORT] VALUE=[4]",
123 "IFD-PATH=[IFD/Exif] ID=(0x8827) NAME=[ISOSpeedRatings] COUNT=(1) TYPE=[SHORT] VALUE=[1600]",
124 "IFD-PATH=[IFD/Exif] ID=(0x8830) NAME=[SensitivityType] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
125 "IFD-PATH=[IFD/Exif] ID=(0x8832) NAME=[RecommendedExposureIndex] COUNT=(1) TYPE=[LONG] VALUE=[1600]",
126 "IFD-PATH=[IFD/Exif] ID=(0x9000) NAME=[ExifVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[0230]",
127 "IFD-PATH=[IFD/Exif] ID=(0x9003) NAME=[DateTimeOriginal] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
128 "IFD-PATH=[IFD/Exif] ID=(0x9004) NAME=[DateTimeDigitized] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
129 "IFD-PATH=[IFD/Exif] ID=(0x9101) NAME=[ComponentsConfiguration] COUNT=(4) TYPE=[UNDEFINED] VALUE=[Exif9101ComponentsConfiguration<ID=[YCBCR] BYTES=[1 2 3 0]>]",
130 "IFD-PATH=[IFD/Exif] ID=(0x9201) NAME=[ShutterSpeedValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[614400/65536]",
131 "IFD-PATH=[IFD/Exif] ID=(0x9202) NAME=[ApertureValue] COUNT=(1) TYPE=[RATIONAL] VALUE=[262144/65536]",
132 "IFD-PATH=[IFD/Exif] ID=(0x9204) NAME=[ExposureBiasValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[0/1]",
133 "IFD-PATH=[IFD/Exif] ID=(0x9207) NAME=[MeteringMode] COUNT=(1) TYPE=[SHORT] VALUE=[5]",
134 "IFD-PATH=[IFD/Exif] ID=(0x9209) NAME=[Flash] COUNT=(1) TYPE=[SHORT] VALUE=[16]",
135 "IFD-PATH=[IFD/Exif] ID=(0x920a) NAME=[FocalLength] COUNT=(1) TYPE=[RATIONAL] VALUE=[16/1]",
136 "IFD-PATH=[IFD/Exif] ID=(0x927c) NAME=[MakerNote] COUNT=(8152) TYPE=[UNDEFINED] VALUE=[MakerNote<TYPE-ID=[28 00 01 00 03 00 31 00 00 00 74 05 00 00 02 00 03 00 04 00] LEN=(8152) SHA1=[d4154aa7df5474efe7ab38de2595919b9b4cc29f]>]",
137 "IFD-PATH=[IFD/Exif] ID=(0x9286) NAME=[UserComment] COUNT=(264) TYPE=[UNDEFINED] VALUE=[UserComment<SIZE=(256) ENCODING=[UNDEFINED] V=[0 0 0 0 0 0 0 0]... LEN=(256)>]",
138 "IFD-PATH=[IFD/Exif] ID=(0x9290) NAME=[SubSecTime] COUNT=(3) TYPE=[ASCII] VALUE=[00]",
139 "IFD-PATH=[IFD/Exif] ID=(0x9291) NAME=[SubSecTimeOriginal] COUNT=(3) TYPE=[ASCII] VALUE=[00]",
140 "IFD-PATH=[IFD/Exif] ID=(0x9292) NAME=[SubSecTimeDigitized] COUNT=(3) TYPE=[ASCII] VALUE=[00]",
141 "IFD-PATH=[IFD/Exif] ID=(0xa000) NAME=[FlashpixVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[0100]",
142 "IFD-PATH=[IFD/Exif] ID=(0xa001) NAME=[ColorSpace] COUNT=(1) TYPE=[SHORT] VALUE=[1]",
143 "IFD-PATH=[IFD/Exif] ID=(0xa002) NAME=[PixelXDimension] COUNT=(1) TYPE=[SHORT] VALUE=[3840]",
144 "IFD-PATH=[IFD/Exif] ID=(0xa003) NAME=[PixelYDimension] COUNT=(1) TYPE=[SHORT] VALUE=[2560]",
145 "IFD-PATH=[IFD/Exif] ID=(0xa005) NAME=[InteroperabilityTag] COUNT=(1) TYPE=[LONG] VALUE=[9326]",
146 "IFD-PATH=[IFD/Exif/Iop] ID=(0x0001) NAME=[InteroperabilityIndex] COUNT=(4) TYPE=[ASCII] VALUE=[R98]",
147 "IFD-PATH=[IFD/Exif/Iop] ID=(0x0002) NAME=[InteroperabilityVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[0100]",
148 "IFD-PATH=[IFD/Exif] ID=(0xa20e) NAME=[FocalPlaneXResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[3840000/1461]",
149 "IFD-PATH=[IFD/Exif] ID=(0xa20f) NAME=[FocalPlaneYResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[2560000/972]",
150 "IFD-PATH=[IFD/Exif] ID=(0xa210) NAME=[FocalPlaneResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
151 "IFD-PATH=[IFD/Exif] ID=(0xa401) NAME=[CustomRendered] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
152 "IFD-PATH=[IFD/Exif] ID=(0xa402) NAME=[ExposureMode] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
153 "IFD-PATH=[IFD/Exif] ID=(0xa403) NAME=[WhiteBalance] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
154 "IFD-PATH=[IFD/Exif] ID=(0xa406) NAME=[SceneCaptureType] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
155 "IFD-PATH=[IFD/Exif] ID=(0xa430) NAME=[CameraOwnerName] COUNT=(1) TYPE=[ASCII] VALUE=[]",
156 "IFD-PATH=[IFD/Exif] ID=(0xa431) NAME=[BodySerialNumber] COUNT=(13) TYPE=[ASCII] VALUE=[063024020097]",
157 "IFD-PATH=[IFD/Exif] ID=(0xa432) NAME=[LensSpecification] COUNT=(4) TYPE=[RATIONAL] VALUE=[16/1...]",
158 "IFD-PATH=[IFD/Exif] ID=(0xa434) NAME=[LensModel] COUNT=(22) TYPE=[ASCII] VALUE=[EF16-35mm f/4L IS USM]",
159 "IFD-PATH=[IFD/Exif] ID=(0xa435) NAME=[LensSerialNumber] COUNT=(11) TYPE=[ASCII] VALUE=[2400001068]",
160 "IFD-PATH=[IFD] ID=(0x8825) NAME=[GPSTag] COUNT=(1) TYPE=[LONG] VALUE=[9554]",
161 "IFD-PATH=[IFD/GPSInfo] ID=(0x0000) NAME=[GPSVersionID] COUNT=(4) TYPE=[BYTE] VALUE=[02 03 00 00]",
162 "IFD-PATH=[IFD1] ID=(0x0103) NAME=[Compression] COUNT=(1) TYPE=[SHORT] VALUE=[6]",
163 "IFD-PATH=[IFD1] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
164 "IFD-PATH=[IFD1] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
165 "IFD-PATH=[IFD1] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
166 "IFD-PATH=[IFD1] ID=(0x0201) NAME=[JPEGInterchangeFormat] COUNT=(1) TYPE=[LONG] VALUE=[11444]",
167 "IFD-PATH=[IFD1] ID=(0x0202) NAME=[JPEGInterchangeFormatLength] COUNT=(1) TYPE=[LONG] VALUE=[21491]",
168 }
169
170 if reflect.DeepEqual(tags, expected) == false {
171 fmt.Printf("\n")
172 fmt.Printf("ACTUAL:\n")
173 fmt.Printf("\n")
174
175 for _, line := range tags {
176 fmt.Println(line)
177 }
178
179 fmt.Printf("\n")
180 fmt.Printf("EXPECTED:\n")
181 fmt.Printf("\n")
182
183 for _, line := range expected {
184 fmt.Println(line)
185 }
186
187 fmt.Printf("\n")
188
189 t.Fatalf("tags not correct.")
190 }
191 }
192
193 func TestSearchFileAndExtractExif(t *testing.T) {
194 testImageFilepath := getTestImageFilepath()
195
196
197
198 rawExif, err := SearchFileAndExtractExif(testImageFilepath)
199 log.PanicIf(err)
200
201 testExifData := getTestExifData()
202
203 if bytes.Compare(rawExif[:len(testExifData)], testExifData) != 0 {
204 t.Fatalf("found EXIF data not correct")
205 }
206 }
207
208 func TestSearchAndExtractExif(t *testing.T) {
209 testImageFilepath := getTestImageFilepath()
210
211 imageData, err := ioutil.ReadFile(testImageFilepath)
212 log.PanicIf(err)
213
214 rawExif, err := SearchAndExtractExif(imageData)
215 log.PanicIf(err)
216
217 testExifData := getTestExifData()
218
219 if bytes.Compare(rawExif[:len(testExifData)], testExifData) != 0 {
220 t.Fatalf("found EXIF data not correct")
221 }
222 }
223
224 func TestSearchAndExtractExifWithReader(t *testing.T) {
225 testImageFilepath := getTestImageFilepath()
226
227 f, err := os.Open(testImageFilepath)
228 log.PanicIf(err)
229
230 defer f.Close()
231
232 rawExif, err := SearchAndExtractExifWithReader(f)
233 log.PanicIf(err)
234
235 testExifData := getTestExifData()
236
237 if bytes.Compare(rawExif[:len(testExifData)], testExifData) != 0 {
238 t.Fatalf("found EXIF data not correct")
239 }
240 }
241
242 func TestCollect(t *testing.T) {
243 defer func() {
244 if state := recover(); state != nil {
245 err := log.Wrap(state.(error))
246 log.PrintError(err)
247
248 t.Fatalf("Test failure.")
249 }
250 }()
251
252 testImageFilepath := getTestImageFilepath()
253
254 rawExif, err := SearchFileAndExtractExif(testImageFilepath)
255 log.PanicIf(err)
256
257 im, err := exifcommon.NewIfdMappingWithStandard()
258 log.PanicIf(err)
259
260 ti := NewTagIndex()
261
262 _, index, err := Collect(im, ti, rawExif)
263 log.PanicIf(err)
264
265 rootIfd := index.RootIfd
266 ifds := index.Ifds
267 tree := index.Tree
268 lookup := index.Lookup
269
270 if rootIfd.Offset() != uint32(0x0008) {
271 t.Fatalf("Root-IFD not correct: (0x%04d).", rootIfd.Offset())
272 } else if rootIfd.id != 0 {
273 t.Fatalf("Root-IFD does not have the right ID: (%d)", rootIfd.id)
274 } else if tree[0] != rootIfd {
275 t.Fatalf("Root-IFD is not indexed properly.")
276 } else if len(ifds) != 5 {
277 t.Fatalf("The IFD list is not the right size: (%d)", len(ifds))
278 } else if len(tree) != 5 {
279 t.Fatalf("The IFD tree is not the right size: (%d)", len(tree))
280 }
281
282 actualIfdPaths := make([]string, len(lookup))
283 i := 0
284 for ifdPath := range lookup {
285 actualIfdPaths[i] = ifdPath
286 i++
287 }
288
289 sort.Strings(actualIfdPaths)
290
291 expectedIfdPaths := []string{
292 "IFD",
293 "IFD/Exif",
294 "IFD/Exif/Iop",
295 "IFD/GPSInfo",
296 "IFD1",
297 }
298
299 if reflect.DeepEqual(actualIfdPaths, expectedIfdPaths) != true {
300 t.Fatalf("The IFD lookup is not the right size: %v", actualIfdPaths)
301 }
302
303 if rootIfd.nextIfdOffset != 0x2c54 {
304 t.Fatalf("Root IFD does not continue correctly: (0x%04x)", rootIfd.nextIfdOffset)
305 } else if rootIfd.nextIfd.Offset() != rootIfd.nextIfdOffset {
306 t.Fatalf("Root IFD neighbor object does not have the right offset: (0x%04x != 0x%04x)", rootIfd.nextIfd.Offset(), rootIfd.nextIfdOffset)
307 } else if rootIfd.nextIfd.nextIfdOffset != 0 {
308 t.Fatalf("Root IFD chain not terminated correctly (1).")
309 } else if rootIfd.nextIfd.nextIfd != nil {
310 t.Fatalf("Root IFD chain not terminated correctly (2).")
311 }
312
313 if rootIfd.ifdIdentity.UnindexedString() != exifcommon.IfdStandardIfdIdentity.UnindexedString() {
314 t.Fatalf("Root IFD is not labeled correctly: [%s]", rootIfd.ifdIdentity.UnindexedString())
315 } else if rootIfd.nextIfd.ifdIdentity.UnindexedString() != exifcommon.IfdStandardIfdIdentity.UnindexedString() {
316 t.Fatalf("Root IFD sibling is not labeled correctly: [%s]", rootIfd.ifdIdentity.UnindexedString())
317 } else if rootIfd.Children()[0].ifdIdentity.UnindexedString() != exifcommon.IfdExifStandardIfdIdentity.UnindexedString() {
318 t.Fatalf("Root IFD child (0) is not labeled correctly: [%s]", rootIfd.Children()[0].ifdIdentity.UnindexedString())
319 } else if rootIfd.Children()[1].ifdIdentity.UnindexedString() != exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString() {
320 t.Fatalf("Root IFD child (1) is not labeled correctly: [%s]", rootIfd.Children()[1].ifdIdentity.UnindexedString())
321 } else if rootIfd.Children()[0].children[0].ifdIdentity.UnindexedString() != exifcommon.IfdExifIopStandardIfdIdentity.UnindexedString() {
322 t.Fatalf("Exif IFD child is not an IOP IFD: [%s]", rootIfd.Children()[0].children[0].ifdIdentity.UnindexedString())
323 }
324
325 if lookup[exifcommon.IfdStandardIfdIdentity.UnindexedString()].ifdIdentity.UnindexedString() != exifcommon.IfdStandardIfdIdentity.UnindexedString() {
326 t.Fatalf("Lookup for standard IFD not correct.")
327 } else if lookup[exifcommon.IfdStandardIfdIdentity.UnindexedString()+"1"].ifdIdentity.UnindexedString() != exifcommon.IfdStandardIfdIdentity.UnindexedString() {
328 t.Fatalf("Lookup for standard IFD not correct.")
329 }
330
331 if lookup[exifcommon.IfdExifStandardIfdIdentity.UnindexedString()].ifdIdentity.UnindexedString() != exifcommon.IfdExifStandardIfdIdentity.UnindexedString() {
332 t.Fatalf("Lookup for EXIF IFD not correct.")
333 }
334
335 if lookup[exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString()].ifdIdentity.UnindexedString() != exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString() {
336 t.Fatalf("Lookup for GPS IFD not correct.")
337 }
338
339 if lookup[exifcommon.IfdExifIopStandardIfdIdentity.UnindexedString()].ifdIdentity.UnindexedString() != exifcommon.IfdExifIopStandardIfdIdentity.UnindexedString() {
340 t.Fatalf("Lookup for IOP IFD not correct.")
341 }
342
343 foundExif := 0
344 foundGps := 0
345 for _, ite := range lookup[exifcommon.IfdStandardIfdIdentity.UnindexedString()].entries {
346 if ite.ChildIfdPath() == exifcommon.IfdExifStandardIfdIdentity.UnindexedString() {
347 foundExif++
348
349 if ite.TagId() != exifcommon.IfdExifStandardIfdIdentity.TagId() {
350 t.Fatalf("EXIF IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId(), exifcommon.IfdExifStandardIfdIdentity.TagId())
351 }
352 }
353
354 if ite.ChildIfdPath() == exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString() {
355 foundGps++
356
357 if ite.TagId() != exifcommon.IfdGpsInfoStandardIfdIdentity.TagId() {
358 t.Fatalf("GPS IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId(), exifcommon.IfdGpsInfoStandardIfdIdentity.TagId())
359 }
360 }
361 }
362
363 if foundExif != 1 {
364 t.Fatalf("Exactly one EXIF IFD tag wasn't found: (%d)", foundExif)
365 } else if foundGps != 1 {
366 t.Fatalf("Exactly one GPS IFD tag wasn't found: (%d)", foundGps)
367 }
368
369 foundIop := 0
370 for _, ite := range lookup[exifcommon.IfdExifStandardIfdIdentity.UnindexedString()].entries {
371 if ite.ChildIfdPath() == exifcommon.IfdExifIopStandardIfdIdentity.UnindexedString() {
372 foundIop++
373
374 if ite.TagId() != exifcommon.IfdExifIopStandardIfdIdentity.TagId() {
375 t.Fatalf("IOP IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId(), exifcommon.IfdExifIopStandardIfdIdentity.TagId())
376 }
377 }
378 }
379
380 if foundIop != 1 {
381 t.Fatalf("Exactly one IOP IFD tag wasn't found: (%d)", foundIop)
382 }
383 }
384
385 func TestParseExifHeader(t *testing.T) {
386 testExifData := getTestExifData()
387
388 eh, err := ParseExifHeader(testExifData)
389 log.PanicIf(err)
390
391 if eh.ByteOrder != binary.LittleEndian {
392 t.Fatalf("Byte-order of EXIF header not correct.")
393 } else if eh.FirstIfdOffset != 0x8 {
394 t.Fatalf("First IFD offset not correct.")
395 }
396 }
397
398 func TestExif_BuildAndParseExifHeader(t *testing.T) {
399 headerBytes, err := BuildExifHeader(exifcommon.TestDefaultByteOrder, 0x11223344)
400 log.PanicIf(err)
401
402 eh, err := ParseExifHeader(headerBytes)
403 log.PanicIf(err)
404
405 if eh.ByteOrder != exifcommon.TestDefaultByteOrder {
406 t.Fatalf("Byte-order of EXIF header not correct.")
407 } else if eh.FirstIfdOffset != 0x11223344 {
408 t.Fatalf("First IFD offset not correct.")
409 }
410 }
411
412 func ExampleBuildExifHeader() {
413 headerBytes, err := BuildExifHeader(exifcommon.TestDefaultByteOrder, 0x11223344)
414 log.PanicIf(err)
415
416 eh, err := ParseExifHeader(headerBytes)
417 log.PanicIf(err)
418
419 fmt.Printf("%v\n", eh)
420
421
422 }
423
View as plain text