...

Source file src/github.com/dsoprea/go-exif/v3/exif_test.go

Documentation: github.com/dsoprea/go-exif/v3

     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  	// Open the file.
    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  	// Search for the beginning of the EXIF information. The EXIF is near the
    44  	// very beginning of our/most JPEGs, so this has a very low cost.
    45  
    46  	// TODO(dustin): Just use SearchAndExtractExifWithReader() here.
    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  	// Run the parse.
    63  
    64  	im, err := exifcommon.NewIfdMappingWithStandard()
    65  	log.PanicIf(err)
    66  
    67  	tags := make([]string, 0)
    68  
    69  	// DEPRECATED(dustin): fqIfdPath and ifdIndex are now redundant. Remove in next module version.
    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  	// Returns a slice starting with the EXIF data and going to the end of the
   197  	// image.
   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  	// Output: ExifHeader<BYTE-ORDER=[BigEndian] FIRST-IFD-OFFSET=(0x11223344)>
   422  }
   423  

View as plain text