├── .travis.yml ├── LICENSE ├── README.md ├── assets ├── DC-008-2012_E.pdf ├── DC-008-Translation-2016-E.pdf ├── NDM_8901.jpg ├── NDM_8901.jpg.exif ├── NDM_8901.jpg.thumbnail ├── PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf ├── exif_read.json ├── gps.jpg ├── raw_tags │ ├── requirements.txt │ ├── tags.html │ └── translate_tags.py └── tags.yaml ├── common_test.go ├── error.go ├── exif-read-tool ├── main.go └── main_test.go ├── exif.go ├── exif_test.go ├── go.mod ├── go.sum ├── gps.go ├── ifd.go ├── ifd_builder.go ├── ifd_builder_encode.go ├── ifd_builder_encode_test.go ├── ifd_builder_test.go ├── ifd_enumerate.go ├── ifd_enumerate_test.go ├── ifd_tag_entry.go ├── ifd_tag_entry_test.go ├── ifd_test.go ├── package.go ├── parser.go ├── tag_type.go ├── tags.go ├── tags_data.go ├── tags_test.go ├── tags_undefined.go ├── tags_undefined_test.go ├── type.go ├── type_encode.go ├── type_encode_test.go ├── type_test.go ├── utility.go ├── utility_test.go ├── v2 ├── .MODULE_ROOT ├── LICENSE ├── assets │ ├── DC-008-2012_E.pdf │ ├── DC-008-Translation-2016-E.pdf │ ├── NDM_8901.jpg │ ├── NDM_8901.jpg.exif │ ├── NDM_8901.jpg.thumbnail │ ├── PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf │ ├── gps-2000-scaled.jpg │ ├── gps.jpg │ ├── main_test_exif.json │ ├── raw_tags │ │ ├── requirements.txt │ │ ├── tags.html │ │ └── translate_tags.py │ └── tags.yaml ├── common │ ├── assets │ │ └── NDM_8901.jpg.exif │ ├── ifd.go │ ├── ifd_test.go │ ├── parser.go │ ├── parser_test.go │ ├── testing_common.go │ ├── type.go │ ├── type_test.go │ ├── utility.go │ ├── utility_test.go │ ├── value_context.go │ ├── value_context_test.go │ ├── value_encoder.go │ └── value_encoder_test.go ├── error.go ├── exif-read-tool │ ├── main.go │ └── main_test.go ├── exif.go ├── exif_test.go ├── go.mod ├── go.sum ├── gps.go ├── gps_test.go ├── ifd.go ├── ifd_builder.go ├── ifd_builder_encode.go ├── ifd_builder_encode_test.go ├── ifd_builder_test.go ├── ifd_enumerate.go ├── ifd_enumerate_test.go ├── ifd_tag_entry.go ├── ifd_tag_entry_test.go ├── package.go ├── tags.go ├── tags_data.go ├── tags_test.go ├── testing_common.go ├── undefined │ ├── README.md │ ├── accessor.go │ ├── exif_8828_oecf.go │ ├── exif_8828_oecf_test.go │ ├── exif_9000_exif_version.go │ ├── exif_9000_exif_version_test.go │ ├── exif_9101_components_configuration.go │ ├── exif_9101_components_configuration_test.go │ ├── exif_927C_maker_note.go │ ├── exif_927C_maker_note_test.go │ ├── exif_9286_user_comment.go │ ├── exif_9286_user_comment_test.go │ ├── exif_A000_flashpix_version.go │ ├── exif_A000_flashpix_version_test.go │ ├── exif_A20C_spatial_frequency_response.go │ ├── exif_A20C_spatial_frequency_response_test.go │ ├── exif_A300_file_source.go │ ├── exif_A300_file_source_test.go │ ├── exif_A301_scene_type.go │ ├── exif_A301_scene_type_test.go │ ├── exif_A302_cfa_pattern.go │ ├── exif_A302_cfa_pattern_test.go │ ├── exif_iop_0002_interop_version.go │ ├── exif_iop_0002_interop_version_test.go │ ├── gps_001B_gps_processing_method.go │ ├── gps_001B_gps_processing_method_test.go │ ├── gps_001C_gps_area_information.go │ ├── gps_001C_gps_area_information_test.go │ ├── registration.go │ └── type.go ├── utility.go └── utility_test.go ├── v3 ├── .MODULE_ROOT ├── LICENSE ├── assets │ ├── DC-008-2012_E.pdf │ ├── DC-008-Translation-2016-E.pdf │ ├── GeoTIFF_Profile_for_Georeferenced_Imagery.doc │ ├── NDM_8901.jpg │ ├── NDM_8901.jpg.exif │ ├── NDM_8901.jpg.thumbnail │ ├── PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf │ ├── geotiff_example.tif │ ├── gps-2000-scaled.jpg │ ├── gps.jpg │ ├── main_test_exif.json │ ├── raw_tags │ │ ├── requirements.txt │ │ ├── tags.html │ │ └── translate_tags.py │ └── tags.yaml ├── command │ └── exif-read-tool │ │ ├── main.go │ │ └── main_test.go ├── common │ ├── assets │ │ └── NDM_8901.jpg.exif │ ├── ifd.go │ ├── ifd_test.go │ ├── parser.go │ ├── parser_test.go │ ├── testing_common.go │ ├── type.go │ ├── type_test.go │ ├── utility.go │ ├── utility_test.go │ ├── value_context.go │ ├── value_context_test.go │ ├── value_encoder.go │ └── value_encoder_test.go ├── data_layer.go ├── error.go ├── exif.go ├── exif_test.go ├── go.mod ├── go.sum ├── gps.go ├── gps_test.go ├── ifd_builder.go ├── ifd_builder_encode.go ├── ifd_builder_encode_test.go ├── ifd_builder_test.go ├── ifd_enumerate.go ├── ifd_enumerate_test.go ├── ifd_tag_entry.go ├── ifd_tag_entry_test.go ├── package.go ├── tags.go ├── tags_data.go ├── tags_data_test.go ├── tags_test.go ├── testing_common.go ├── undefined │ ├── README.md │ ├── accessor.go │ ├── exif_8828_oecf.go │ ├── exif_8828_oecf_test.go │ ├── exif_9000_exif_version.go │ ├── exif_9000_exif_version_test.go │ ├── exif_9101_components_configuration.go │ ├── exif_9101_components_configuration_test.go │ ├── exif_927C_maker_note.go │ ├── exif_927C_maker_note_test.go │ ├── exif_9286_user_comment.go │ ├── exif_9286_user_comment_test.go │ ├── exif_A000_flashpix_version.go │ ├── exif_A000_flashpix_version_test.go │ ├── exif_A20C_spatial_frequency_response.go │ ├── exif_A20C_spatial_frequency_response_test.go │ ├── exif_A300_file_source.go │ ├── exif_A300_file_source_test.go │ ├── exif_A301_scene_type.go │ ├── exif_A301_scene_type_test.go │ ├── exif_A302_cfa_pattern.go │ ├── exif_A302_cfa_pattern_test.go │ ├── exif_iop_0002_interop_version.go │ ├── exif_iop_0002_interop_version_test.go │ ├── gps_001B_gps_processing_method.go │ ├── gps_001B_gps_processing_method_test.go │ ├── gps_001C_gps_area_information.go │ ├── gps_001C_gps_area_information_test.go │ ├── registration.go │ └── type.go ├── utility.go └── utility_test.go ├── value_context.go └── value_context_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - stable 4 | - "1.19" 5 | - "1.18" 6 | - "1.17" 7 | install: 8 | - go get -t ./... 9 | script: 10 | # v1 11 | - go test -v . 12 | - go test -v ./exif-read-tool 13 | # v2 14 | - cd v2 15 | - go test -v ./... 16 | - cd .. 17 | # v3. Coverage reports comes from this. 18 | - cd v3 19 | - go test -v ./... -coverprofile=coverage.txt -covermode=atomic 20 | after_success: 21 | - curl -s https://codecov.io/bash | bash 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright 2019 Dustin Oprea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /assets/DC-008-2012_E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/DC-008-2012_E.pdf -------------------------------------------------------------------------------- /assets/DC-008-Translation-2016-E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/DC-008-Translation-2016-E.pdf -------------------------------------------------------------------------------- /assets/NDM_8901.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/NDM_8901.jpg -------------------------------------------------------------------------------- /assets/NDM_8901.jpg.exif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/NDM_8901.jpg.exif -------------------------------------------------------------------------------- /assets/NDM_8901.jpg.thumbnail: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/NDM_8901.jpg.thumbnail -------------------------------------------------------------------------------- /assets/PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf -------------------------------------------------------------------------------- /assets/gps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/assets/gps.jpg -------------------------------------------------------------------------------- /assets/raw_tags/requirements.txt: -------------------------------------------------------------------------------- 1 | ruamel.yaml 2 | -------------------------------------------------------------------------------- /assets/raw_tags/translate_tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | """ 4 | Parses the table-data from view-source:http://www.exiv2.org/tags.html 5 | """ 6 | 7 | import sys 8 | import collections 9 | 10 | import xml.etree.ElementTree as ET 11 | 12 | import ruamel.yaml 13 | 14 | 15 | # Prepare YAML to write hex expressions (otherwise the hex will be a string and 16 | # quotes or a decimal and a base-10 number). 17 | 18 | class HexInt(int): 19 | pass 20 | 21 | def representer(dumper, data): 22 | return \ 23 | ruamel.yaml.ScalarNode( 24 | 'tag:yaml.org,2002:int', 25 | '0x{:04x}'.format(data)) 26 | 27 | ruamel.yaml.add_representer(HexInt, representer) 28 | 29 | def _write(tags): 30 | writeable = {} 31 | 32 | for tag in tags: 33 | pivot = tag['fq_key'].rindex('.') 34 | 35 | item = { 36 | 'id': HexInt(tag['id_dec']), 37 | 'name': tag['fq_key'][pivot + 1:], 38 | 'type_name': tag['type'].upper(), 39 | } 40 | 41 | ifdName = tag['ifd'] 42 | if ifdName == 'Image': 43 | ifdName = 'IFD' 44 | if ifdName == 'Photo': 45 | ifdName = 'Exif' 46 | 47 | # UserComment. Has invalid type "COMMENT". 48 | if item['id'] == 0x9286 and ifdName == 'Exif': 49 | item['type_name'] = 'UNDEFINED' 50 | 51 | try: 52 | writeable[ifdName].append(item) 53 | except KeyError: 54 | writeable[ifdName] = [item] 55 | 56 | with open('tags.yaml', 'w') as f: 57 | # Otherwise, the next dictionaries will look like Python dictionaries, 58 | # whatever sense that makes. 59 | ruamel.yaml.dump(writeable, f, default_flow_style=False) 60 | 61 | def _main(): 62 | tree = ET.parse('tags.html') 63 | root = tree.getroot() 64 | 65 | labels = [ 66 | 'id_hex', 67 | 'id_dec', 68 | 'ifd', 69 | 'fq_key', 70 | 'type', 71 | 'description', 72 | ] 73 | 74 | tags = [] 75 | for node in root.iter('tr'): 76 | values = [child.text.strip() for child in node.iter('td')] 77 | 78 | # Skips the header row. 79 | if not values: 80 | continue 81 | 82 | assert \ 83 | len(values) == len(labels), \ 84 | "Row fields count not the same as labels: {}".format(values) 85 | 86 | tags.append(dict(zip(labels, values))) 87 | 88 | _write(tags) 89 | 90 | if __name__ == '__main__': 91 | _main() 92 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrTagNotFound = errors.New("tag not found") 9 | ErrTagNotStandard = errors.New("tag not a standard tag") 10 | ) 11 | -------------------------------------------------------------------------------- /exif-read-tool/main.go: -------------------------------------------------------------------------------- 1 | // This tool dumps EXIF information from images. 2 | // 3 | // Example command-line: 4 | // 5 | // exif-read-tool -filepath 6 | // 7 | // Example Output: 8 | // 9 | // IFD=[IfdIdentity] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon] 10 | // IFD=[IfdIdentity] ID=(0x0110) NAME=[Model] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III] 11 | // IFD=[IfdIdentity] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[1] 12 | // IFD=[IfdIdentity] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1] 13 | // ... 14 | package main 15 | 16 | import ( 17 | "flag" 18 | "fmt" 19 | "os" 20 | 21 | "encoding/json" 22 | "io/ioutil" 23 | 24 | "github.com/dsoprea/go-exif" 25 | "github.com/dsoprea/go-logging" 26 | ) 27 | 28 | var ( 29 | filepathArg = "" 30 | printAsJsonArg = false 31 | printLoggingArg = false 32 | ) 33 | 34 | type IfdEntry struct { 35 | IfdPath string `json:"ifd_path"` 36 | FqIfdPath string `json:"fq_ifd_path"` 37 | IfdIndex int `json:"ifd_index"` 38 | TagId uint16 `json:"tag_id"` 39 | TagName string `json:"tag_name"` 40 | TagTypeId exif.TagTypePrimitive `json:"tag_type_id"` 41 | TagTypeName string `json:"tag_type_name"` 42 | UnitCount uint32 `json:"unit_count"` 43 | Value interface{} `json:"value"` 44 | ValueString string `json:"value_string"` 45 | } 46 | 47 | func main() { 48 | defer func() { 49 | if state := recover(); state != nil { 50 | err := log.Wrap(state.(error)) 51 | log.PrintErrorf(err, "Program error.") 52 | os.Exit(1) 53 | } 54 | }() 55 | 56 | flag.StringVar(&filepathArg, "filepath", "", "File-path of image") 57 | flag.BoolVar(&printAsJsonArg, "json", false, "Print JSON") 58 | flag.BoolVar(&printLoggingArg, "verbose", false, "Print logging") 59 | 60 | flag.Parse() 61 | 62 | if filepathArg == "" { 63 | fmt.Printf("Please provide a file-path for an image.\n") 64 | os.Exit(1) 65 | } 66 | 67 | if printLoggingArg == true { 68 | cla := log.NewConsoleLogAdapter() 69 | log.AddAdapter("console", cla) 70 | } 71 | 72 | f, err := os.Open(filepathArg) 73 | log.PanicIf(err) 74 | 75 | data, err := ioutil.ReadAll(f) 76 | log.PanicIf(err) 77 | 78 | rawExif, err := exif.SearchAndExtractExif(data) 79 | log.PanicIf(err) 80 | 81 | // Run the parse. 82 | 83 | im := exif.NewIfdMappingWithStandard() 84 | ti := exif.NewTagIndex() 85 | 86 | entries := make([]IfdEntry, 0) 87 | visitor := func(fqIfdPath string, ifdIndex int, tagId uint16, tagType exif.TagType, valueContext exif.ValueContext) (err error) { 88 | defer func() { 89 | if state := recover(); state != nil { 90 | err = log.Wrap(state.(error)) 91 | log.Panic(err) 92 | } 93 | }() 94 | 95 | ifdPath, err := im.StripPathPhraseIndices(fqIfdPath) 96 | log.PanicIf(err) 97 | 98 | it, err := ti.Get(ifdPath, tagId) 99 | if err != nil { 100 | if log.Is(err, exif.ErrTagNotFound) { 101 | fmt.Printf("WARNING: Unknown tag: [%s] (%04x)\n", ifdPath, tagId) 102 | return nil 103 | } else { 104 | log.Panic(err) 105 | } 106 | } 107 | 108 | valueString := "" 109 | var value interface{} 110 | if tagType.Type() == exif.TypeUndefined { 111 | var err error 112 | value, err = valueContext.Undefined() 113 | if err != nil { 114 | if err == exif.ErrUnhandledUnknownTypedTag { 115 | value = nil 116 | } else { 117 | log.Panic(err) 118 | } 119 | } 120 | 121 | valueString = fmt.Sprintf("%v", value) 122 | } else { 123 | valueString, err = valueContext.FormatFirst() 124 | log.PanicIf(err) 125 | 126 | value = valueString 127 | } 128 | 129 | entry := IfdEntry{ 130 | IfdPath: ifdPath, 131 | FqIfdPath: fqIfdPath, 132 | IfdIndex: ifdIndex, 133 | TagId: tagId, 134 | TagName: it.Name, 135 | TagTypeId: tagType.Type(), 136 | TagTypeName: tagType.Name(), 137 | UnitCount: valueContext.UnitCount(), 138 | Value: value, 139 | ValueString: valueString, 140 | } 141 | 142 | entries = append(entries, entry) 143 | 144 | return nil 145 | } 146 | 147 | _, err = exif.Visit(exif.IfdStandard, im, ti, rawExif, visitor) 148 | log.PanicIf(err) 149 | 150 | if printAsJsonArg == true { 151 | data, err := json.MarshalIndent(entries, "", " ") 152 | log.PanicIf(err) 153 | 154 | fmt.Println(string(data)) 155 | } else { 156 | for _, entry := range entries { 157 | fmt.Printf("IFD-PATH=[%s] ID=(0x%04x) NAME=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]\n", entry.IfdPath, entry.TagId, entry.TagName, entry.UnitCount, entry.TagTypeName, entry.ValueString) 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dsoprea/go-exif 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 7 | github.com/go-errors/errors v1.0.1 // indirect 8 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec 9 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect 10 | gopkg.in/yaml.v2 v2.2.7 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y= 2 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= 3 | github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= 4 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 5 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec h1:lJwO/92dFXWeXOZdoGXgptLmNLwynMSHUmU6besqtiw= 6 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 7 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 8 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= 9 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 10 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 11 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= 14 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 15 | -------------------------------------------------------------------------------- /gps.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/golang/geo/s2" 9 | ) 10 | 11 | var ( 12 | ErrGpsCoordinatesNotValid = errors.New("GPS coordinates not valid") 13 | ) 14 | 15 | type GpsDegrees struct { 16 | Orientation byte 17 | Degrees, Minutes, Seconds float64 18 | } 19 | 20 | func (d GpsDegrees) String() string { 21 | return fmt.Sprintf("Degrees", string([]byte{d.Orientation}), d.Degrees, d.Minutes, d.Seconds) 22 | } 23 | 24 | func (d GpsDegrees) Decimal() float64 { 25 | decimal := float64(d.Degrees) + float64(d.Minutes)/60.0 + float64(d.Seconds)/3600.0 26 | 27 | if d.Orientation == 'S' || d.Orientation == 'W' { 28 | return -decimal 29 | } else { 30 | return decimal 31 | } 32 | } 33 | 34 | type GpsInfo struct { 35 | Latitude, Longitude GpsDegrees 36 | Altitude int 37 | Timestamp time.Time 38 | } 39 | 40 | func (gi *GpsInfo) String() string { 41 | return fmt.Sprintf("GpsInfo", gi.Latitude.Decimal(), gi.Longitude.Decimal(), gi.Altitude, gi.Timestamp) 42 | } 43 | 44 | func (gi *GpsInfo) S2CellId() s2.CellID { 45 | latitude := gi.Latitude.Decimal() 46 | longitude := gi.Longitude.Decimal() 47 | 48 | ll := s2.LatLngFromDegrees(latitude, longitude) 49 | cellId := s2.CellIDFromLatLng(ll) 50 | 51 | if cellId.IsValid() == false { 52 | panic(ErrGpsCoordinatesNotValid) 53 | } 54 | 55 | return cellId 56 | } 57 | -------------------------------------------------------------------------------- /package.go: -------------------------------------------------------------------------------- 1 | // exif parses raw EXIF information given a block of raw EXIF data. 2 | // 3 | // v1 of go-exif is now deprecated. Please use v2. 4 | package exif 5 | -------------------------------------------------------------------------------- /tags_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dsoprea/go-logging" 7 | ) 8 | 9 | func TestGet(t *testing.T) { 10 | ti := NewTagIndex() 11 | 12 | it, err := ti.Get(IfdPathStandard, 0x10f) 13 | log.PanicIf(err) 14 | 15 | if it.Is(IfdPathStandard, 0x10f) == false || it.IsName(IfdPathStandard, "Make") == false { 16 | t.Fatalf("tag info not correct") 17 | } 18 | } 19 | 20 | func TestGetWithName(t *testing.T) { 21 | ti := NewTagIndex() 22 | 23 | it, err := ti.GetWithName(IfdPathStandard, "Make") 24 | log.PanicIf(err) 25 | 26 | if it.Is(IfdPathStandard, 0x10f) == false || it.Is(IfdPathStandard, 0x10f) == false { 27 | t.Fatalf("tag info not correct") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tags_undefined_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/dsoprea/go-logging" 8 | ) 9 | 10 | func TestUndefinedValue_ExifVersion(t *testing.T) { 11 | byteOrder := TestDefaultByteOrder 12 | fqIfdPath := "IFD0/Exif0" 13 | ifdPath := "IFD/Exif" 14 | 15 | // Create our unknown-type tag's value using the fact that we know it's a 16 | // non-null-terminated string. 17 | 18 | ve := NewValueEncoder(byteOrder) 19 | 20 | tt := NewTagType(TypeAsciiNoNul, byteOrder) 21 | valueString := "0230" 22 | 23 | ed, err := ve.EncodeWithType(tt, valueString) 24 | log.PanicIf(err) 25 | 26 | // Create the tag using the official "unknown" type now that we already 27 | // have the bytes. 28 | 29 | encodedValue := NewIfdBuilderTagValueFromBytes(ed.Encoded) 30 | 31 | bt := &BuilderTag{ 32 | ifdPath: ifdPath, 33 | tagId: 0x9000, 34 | typeId: TypeUndefined, 35 | value: encodedValue, 36 | } 37 | 38 | // Stage the build. 39 | 40 | im := NewIfdMapping() 41 | 42 | err = LoadStandardIfds(im) 43 | log.PanicIf(err) 44 | 45 | ti := NewTagIndex() 46 | 47 | ibe := NewIfdByteEncoder() 48 | ib := NewIfdBuilder(im, ti, ifdPath, byteOrder) 49 | 50 | b := new(bytes.Buffer) 51 | bw := NewByteWriter(b, byteOrder) 52 | 53 | addressableOffset := uint32(0x1234) 54 | ida := newIfdDataAllocator(addressableOffset) 55 | 56 | // Encode. 57 | 58 | _, err = ibe.encodeTagToBytes(ib, bt, bw, ida, uint32(0)) 59 | log.PanicIf(err) 60 | 61 | tagBytes := b.Bytes() 62 | 63 | if len(tagBytes) != 12 { 64 | t.Fatalf("Tag not encoded to the right number of bytes: (%d)", len(tagBytes)) 65 | } 66 | 67 | ite, err := ParseOneTag(im, ti, fqIfdPath, ifdPath, byteOrder, tagBytes, false) 68 | log.PanicIf(err) 69 | 70 | if ite.TagId != 0x9000 { 71 | t.Fatalf("Tag-ID not correct: (0x%02x)", ite.TagId) 72 | } else if ite.TagIndex != 0 { 73 | t.Fatalf("Tag index not correct: (%d)", ite.TagIndex) 74 | } else if ite.TagType != TypeUndefined { 75 | t.Fatalf("Tag type not correct: (%d)", ite.TagType) 76 | } else if ite.UnitCount != (uint32(len(valueString))) { 77 | t.Fatalf("Tag unit-count not correct: (%d)", ite.UnitCount) 78 | } else if bytes.Compare(ite.RawValueOffset, []byte{'0', '2', '3', '0'}) != 0 { 79 | t.Fatalf("Tag's value (as raw bytes) is not correct: [%x]", ite.RawValueOffset) 80 | } else if ite.ChildIfdPath != "" { 81 | t.Fatalf("Tag's child IFD-path should be empty: [%s]", ite.ChildIfdPath) 82 | } else if ite.IfdPath != ifdPath { 83 | t.Fatalf("Tag's parent IFD is not correct: %v", ite.IfdPath) 84 | } 85 | } 86 | 87 | // TODO(dustin): !! Add tests for remaining, well-defined unknown 88 | // TODO(dustin): !! Test what happens with unhandled unknown-type tags (though it should never get to this point in the normal workflow). 89 | -------------------------------------------------------------------------------- /utility_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "fmt" 7 | "os" 8 | "testing" 9 | "time" 10 | 11 | "github.com/dsoprea/go-logging" 12 | ) 13 | 14 | func TestDumpBytes(t *testing.T) { 15 | f, err := ioutil.TempFile(os.TempDir(), "utilitytest") 16 | log.PanicIf(err) 17 | 18 | defer os.Remove(f.Name()) 19 | 20 | originalStdout := os.Stdout 21 | os.Stdout = f 22 | 23 | DumpBytes([]byte{0x11, 0x22}) 24 | 25 | os.Stdout = originalStdout 26 | 27 | _, err = f.Seek(0, 0) 28 | log.PanicIf(err) 29 | 30 | content, err := ioutil.ReadAll(f) 31 | log.PanicIf(err) 32 | 33 | if string(content) != "DUMP: 11 22 \n" { 34 | t.Fatalf("content not correct: [%s]", string(content)) 35 | } 36 | } 37 | 38 | func TestDumpBytesClause(t *testing.T) { 39 | f, err := ioutil.TempFile(os.TempDir(), "utilitytest") 40 | log.PanicIf(err) 41 | 42 | defer os.Remove(f.Name()) 43 | 44 | originalStdout := os.Stdout 45 | os.Stdout = f 46 | 47 | DumpBytesClause([]byte{0x11, 0x22}) 48 | 49 | os.Stdout = originalStdout 50 | 51 | _, err = f.Seek(0, 0) 52 | log.PanicIf(err) 53 | 54 | content, err := ioutil.ReadAll(f) 55 | log.PanicIf(err) 56 | 57 | if string(content) != "DUMP: []byte { 0x11, 0x22 }\n" { 58 | t.Fatalf("content not correct: [%s]", string(content)) 59 | } 60 | } 61 | 62 | func TestDumpBytesToString(t *testing.T) { 63 | s := DumpBytesToString([]byte{0x12, 0x34, 0x56}) 64 | 65 | if s != "12 34 56" { 66 | t.Fatalf("result not expected") 67 | } 68 | } 69 | 70 | func TestDumpBytesClauseToString(t *testing.T) { 71 | s := DumpBytesClauseToString([]byte{0x12, 0x34, 0x56}) 72 | 73 | if s != "0x12, 0x34, 0x56" { 74 | t.Fatalf("result not expected") 75 | } 76 | } 77 | 78 | func TestParseExifFullTimestamp(t *testing.T) { 79 | timestamp, err := ParseExifFullTimestamp("2018:11:30 13:01:49") 80 | log.PanicIf(err) 81 | 82 | actual := timestamp.Format(time.RFC3339) 83 | expected := "2018-11-30T13:01:49Z" 84 | 85 | if actual != expected { 86 | t.Fatalf("time not formatted correctly: [%s] != [%s]", actual, expected) 87 | } 88 | } 89 | 90 | func TestExifFullTimestampString(t *testing.T) { 91 | originalPhrase := "2018:11:30 13:01:49" 92 | 93 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 94 | log.PanicIf(err) 95 | 96 | restoredPhrase := ExifFullTimestampString(timestamp) 97 | if restoredPhrase != originalPhrase { 98 | t.Fatalf("Final phrase [%s] does not equal original phrase [%s]", restoredPhrase, originalPhrase) 99 | } 100 | } 101 | 102 | func ExampleParseExifFullTimestamp() { 103 | originalPhrase := "2018:11:30 13:01:49" 104 | 105 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 106 | log.PanicIf(err) 107 | 108 | fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339)) 109 | 110 | // Output: 111 | // To Go timestamp: [2018-11-30T13:01:49Z] 112 | } 113 | 114 | func ExampleExifFullTimestampString() { 115 | originalPhrase := "2018:11:30 13:01:49" 116 | 117 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 118 | log.PanicIf(err) 119 | 120 | restoredPhrase := ExifFullTimestampString(timestamp) 121 | fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase) 122 | 123 | // Output: 124 | // To EXIF timestamp: [2018:11:30 13:01:49] 125 | } 126 | -------------------------------------------------------------------------------- /v2/.MODULE_ROOT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/.MODULE_ROOT -------------------------------------------------------------------------------- /v2/LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright 2019 Dustin Oprea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /v2/assets/DC-008-2012_E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/DC-008-2012_E.pdf -------------------------------------------------------------------------------- /v2/assets/DC-008-Translation-2016-E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/DC-008-Translation-2016-E.pdf -------------------------------------------------------------------------------- /v2/assets/NDM_8901.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/NDM_8901.jpg -------------------------------------------------------------------------------- /v2/assets/NDM_8901.jpg.exif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/NDM_8901.jpg.exif -------------------------------------------------------------------------------- /v2/assets/NDM_8901.jpg.thumbnail: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/NDM_8901.jpg.thumbnail -------------------------------------------------------------------------------- /v2/assets/PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf -------------------------------------------------------------------------------- /v2/assets/gps-2000-scaled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/gps-2000-scaled.jpg -------------------------------------------------------------------------------- /v2/assets/gps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/assets/gps.jpg -------------------------------------------------------------------------------- /v2/assets/raw_tags/requirements.txt: -------------------------------------------------------------------------------- 1 | ruamel.yaml 2 | -------------------------------------------------------------------------------- /v2/assets/raw_tags/translate_tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | """ 4 | Parses the table-data from view-source:http://www.exiv2.org/tags.html 5 | """ 6 | 7 | import sys 8 | import collections 9 | 10 | import xml.etree.ElementTree as ET 11 | 12 | import ruamel.yaml 13 | 14 | 15 | # Prepare YAML to write hex expressions (otherwise the hex will be a string and 16 | # quotes or a decimal and a base-10 number). 17 | 18 | class HexInt(int): 19 | pass 20 | 21 | def representer(dumper, data): 22 | return \ 23 | ruamel.yaml.ScalarNode( 24 | 'tag:yaml.org,2002:int', 25 | '0x{:04x}'.format(data)) 26 | 27 | ruamel.yaml.add_representer(HexInt, representer) 28 | 29 | def _write(tags): 30 | writeable = {} 31 | 32 | for tag in tags: 33 | pivot = tag['fq_key'].rindex('.') 34 | 35 | item = { 36 | 'id': HexInt(tag['id_dec']), 37 | 'name': tag['fq_key'][pivot + 1:], 38 | 'type_name': tag['type'].upper(), 39 | } 40 | 41 | ifdName = tag['ifd'] 42 | if ifdName == 'Image': 43 | ifdName = 'IFD' 44 | if ifdName == 'Photo': 45 | ifdName = 'Exif' 46 | 47 | # UserComment. Has invalid type "COMMENT". 48 | if item['id'] == 0x9286 and ifdName == 'Exif': 49 | item['type_name'] = 'UNDEFINED' 50 | 51 | try: 52 | writeable[ifdName].append(item) 53 | except KeyError: 54 | writeable[ifdName] = [item] 55 | 56 | with open('tags.yaml', 'w') as f: 57 | # Otherwise, the next dictionaries will look like Python dictionaries, 58 | # whatever sense that makes. 59 | ruamel.yaml.dump(writeable, f, default_flow_style=False) 60 | 61 | def _main(): 62 | tree = ET.parse('tags.html') 63 | root = tree.getroot() 64 | 65 | labels = [ 66 | 'id_hex', 67 | 'id_dec', 68 | 'ifd', 69 | 'fq_key', 70 | 'type', 71 | 'description', 72 | ] 73 | 74 | tags = [] 75 | for node in root.iter('tr'): 76 | values = [child.text.strip() for child in node.iter('td')] 77 | 78 | # Skips the header row. 79 | if not values: 80 | continue 81 | 82 | assert \ 83 | len(values) == len(labels), \ 84 | "Row fields count not the same as labels: {}".format(values) 85 | 86 | tags.append(dict(zip(labels, values))) 87 | 88 | _write(tags) 89 | 90 | if __name__ == '__main__': 91 | _main() 92 | -------------------------------------------------------------------------------- /v2/common/assets/NDM_8901.jpg.exif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v2/common/assets/NDM_8901.jpg.exif -------------------------------------------------------------------------------- /v2/common/testing_common.go: -------------------------------------------------------------------------------- 1 | package exifcommon 2 | 3 | import ( 4 | "os" 5 | "path" 6 | 7 | "encoding/binary" 8 | "io/ioutil" 9 | 10 | "github.com/dsoprea/go-logging" 11 | ) 12 | 13 | var ( 14 | moduleRootPath = "" 15 | 16 | testExifData []byte = nil 17 | 18 | // EncodeDefaultByteOrder is the default byte-order for encoding operations. 19 | EncodeDefaultByteOrder = binary.BigEndian 20 | 21 | // Default byte order for tests. 22 | TestDefaultByteOrder = binary.BigEndian 23 | ) 24 | 25 | func GetModuleRootPath() string { 26 | if moduleRootPath == "" { 27 | moduleRootPath = os.Getenv("EXIF_MODULE_ROOT_PATH") 28 | if moduleRootPath != "" { 29 | return moduleRootPath 30 | } 31 | 32 | currentWd, err := os.Getwd() 33 | log.PanicIf(err) 34 | 35 | currentPath := currentWd 36 | 37 | visited := make([]string, 0) 38 | 39 | for { 40 | tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT") 41 | 42 | _, err := os.Stat(tryStampFilepath) 43 | if err != nil && os.IsNotExist(err) != true { 44 | log.Panic(err) 45 | } else if err == nil { 46 | break 47 | } 48 | 49 | visited = append(visited, tryStampFilepath) 50 | 51 | currentPath = path.Dir(currentPath) 52 | if currentPath == "/" { 53 | log.Panicf("could not find module-root: %v", visited) 54 | } 55 | } 56 | 57 | moduleRootPath = currentPath 58 | } 59 | 60 | return moduleRootPath 61 | } 62 | 63 | func GetTestAssetsPath() string { 64 | moduleRootPath := GetModuleRootPath() 65 | assetsPath := path.Join(moduleRootPath, "assets") 66 | 67 | return assetsPath 68 | } 69 | 70 | func getTestImageFilepath() string { 71 | assetsPath := GetTestAssetsPath() 72 | testImageFilepath := path.Join(assetsPath, "NDM_8901.jpg") 73 | return testImageFilepath 74 | } 75 | 76 | func getTestExifData() []byte { 77 | if testExifData == nil { 78 | assetsPath := GetTestAssetsPath() 79 | filepath := path.Join(assetsPath, "NDM_8901.jpg.exif") 80 | 81 | var err error 82 | 83 | testExifData, err = ioutil.ReadFile(filepath) 84 | log.PanicIf(err) 85 | } 86 | 87 | return testExifData 88 | } 89 | -------------------------------------------------------------------------------- /v2/common/utility.go: -------------------------------------------------------------------------------- 1 | package exifcommon 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/dsoprea/go-logging" 9 | ) 10 | 11 | // DumpBytes prints a list of hex-encoded bytes. 12 | func DumpBytes(data []byte) { 13 | fmt.Printf("DUMP: ") 14 | for _, x := range data { 15 | fmt.Printf("%02x ", x) 16 | } 17 | 18 | fmt.Printf("\n") 19 | } 20 | 21 | // DumpBytesClause prints a list like DumpBytes(), but encapsulated in 22 | // "[]byte { ... }". 23 | func DumpBytesClause(data []byte) { 24 | fmt.Printf("DUMP: ") 25 | 26 | fmt.Printf("[]byte { ") 27 | 28 | for i, x := range data { 29 | fmt.Printf("0x%02x", x) 30 | 31 | if i < len(data)-1 { 32 | fmt.Printf(", ") 33 | } 34 | } 35 | 36 | fmt.Printf(" }\n") 37 | } 38 | 39 | // DumpBytesToString returns a stringified list of hex-encoded bytes. 40 | func DumpBytesToString(data []byte) string { 41 | b := new(bytes.Buffer) 42 | 43 | for i, x := range data { 44 | _, err := b.WriteString(fmt.Sprintf("%02x", x)) 45 | log.PanicIf(err) 46 | 47 | if i < len(data)-1 { 48 | _, err := b.WriteRune(' ') 49 | log.PanicIf(err) 50 | } 51 | } 52 | 53 | return b.String() 54 | } 55 | 56 | // DumpBytesClauseToString returns a comma-separated list of hex-encoded bytes. 57 | func DumpBytesClauseToString(data []byte) string { 58 | b := new(bytes.Buffer) 59 | 60 | for i, x := range data { 61 | _, err := b.WriteString(fmt.Sprintf("0x%02x", x)) 62 | log.PanicIf(err) 63 | 64 | if i < len(data)-1 { 65 | _, err := b.WriteString(", ") 66 | log.PanicIf(err) 67 | } 68 | } 69 | 70 | return b.String() 71 | } 72 | 73 | // ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a 74 | // `time.Time` struct. It will attempt to convert to UTC first. 75 | func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) { 76 | t = t.UTC() 77 | 78 | return fmt.Sprintf("%04d:%02d:%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) 79 | } 80 | -------------------------------------------------------------------------------- /v2/common/utility_test.go: -------------------------------------------------------------------------------- 1 | package exifcommon 2 | 3 | import ( 4 | "testing" 5 | // "github.com/dsoprea/go-logging" 6 | ) 7 | 8 | func TestDumpBytes(t *testing.T) { 9 | DumpBytes([]byte{1, 2, 3, 4}) 10 | } 11 | 12 | func TestDumpBytesClause(t *testing.T) { 13 | DumpBytesClause([]byte{1, 2, 3, 4}) 14 | } 15 | 16 | func TestDumpBytesToString(t *testing.T) { 17 | s := DumpBytesToString([]byte{1, 2, 3, 4}) 18 | if s != "01 02 03 04" { 19 | t.Fatalf("String not correct: [%s]", s) 20 | } 21 | } 22 | 23 | func TestDumpBytesClauseToString(t *testing.T) { 24 | s := DumpBytesClauseToString([]byte{1, 2, 3, 4}) 25 | if s != "0x01, 0x02, 0x03, 0x04" { 26 | t.Fatalf("Stringified clause is not correct: [%s]", s) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /v2/error.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrTagNotFound indicates that the tag was not found. 9 | ErrTagNotFound = errors.New("tag not found") 10 | 11 | // ErrTagNotKnown indicates that the tag is not registered with us as a 12 | // known tag. 13 | ErrTagNotKnown = errors.New("tag is not known") 14 | ) 15 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dsoprea/go-exif/v2 2 | 3 | go 1.13 4 | 5 | // Development only 6 | // replace github.com/dsoprea/go-logging => ../../go-logging 7 | 8 | require ( 9 | github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d 10 | github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf // indirect 11 | github.com/golang/geo v0.0.0-20200319012246-673a6f80352d 12 | github.com/jessevdk/go-flags v1.4.0 13 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect 14 | gopkg.in/yaml.v2 v2.3.0 15 | ) 16 | -------------------------------------------------------------------------------- /v2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= 2 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y= 3 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= 4 | github.com/dsoprea/go-logging v0.0.0-20200502191043-ec333ec7635f h1:XM9MVftaUNA4CcjV97+4bSy7u9Ns04DEYbZkswUrRtc= 5 | github.com/dsoprea/go-logging v0.0.0-20200502191043-ec333ec7635f/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= 6 | github.com/dsoprea/go-logging v0.0.0-20200502201358-170ff607885f h1:FonKAuW3PmNtqk9tOR+Z7bnyQHytmnZBCmm5z1PQMss= 7 | github.com/dsoprea/go-logging v0.0.0-20200502201358-170ff607885f/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= 8 | github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk= 9 | github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= 10 | github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU= 11 | github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= 12 | github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= 13 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 14 | github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4= 15 | github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= 16 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec h1:lJwO/92dFXWeXOZdoGXgptLmNLwynMSHUmU6besqtiw= 17 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 18 | github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc= 19 | github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 20 | github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= 21 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 23 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= 24 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 25 | golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 26 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= 27 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 28 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= 29 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= 35 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 36 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 37 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 38 | -------------------------------------------------------------------------------- /v2/gps.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/golang/geo/s2" 10 | 11 | "github.com/dsoprea/go-exif/v2/common" 12 | ) 13 | 14 | var ( 15 | // ErrGpsCoordinatesNotValid means that some part of the geographic data was 16 | // unparseable. 17 | ErrGpsCoordinatesNotValid = errors.New("GPS coordinates not valid") 18 | ) 19 | 20 | // GpsDegrees is a high-level struct representing geographic data. 21 | type GpsDegrees struct { 22 | // Orientation describes the N/E/S/W direction that this position is 23 | // relative to. 24 | Orientation byte 25 | 26 | // Degrees is a simple float representing the underlying rational degrees 27 | // amount. 28 | Degrees float64 29 | 30 | // Minutes is a simple float representing the underlying rational minutes 31 | // amount. 32 | Minutes float64 33 | 34 | // Seconds is a simple float representing the underlying ration seconds 35 | // amount. 36 | Seconds float64 37 | } 38 | 39 | // NewGpsDegreesFromRationals returns a GpsDegrees struct given the EXIF-encoded 40 | // information. The refValue is the N/E/S/W direction that this position is 41 | // relative to. 42 | func NewGpsDegreesFromRationals(refValue string, rawCoordinate []exifcommon.Rational) (gd GpsDegrees, err error) { 43 | defer func() { 44 | if state := recover(); state != nil { 45 | err = log.Wrap(state.(error)) 46 | } 47 | }() 48 | 49 | if len(rawCoordinate) != 3 { 50 | log.Panicf("new GpsDegrees struct requires a raw-coordinate with exactly three rationals") 51 | } 52 | 53 | gd = GpsDegrees{ 54 | Orientation: refValue[0], 55 | Degrees: float64(rawCoordinate[0].Numerator) / float64(rawCoordinate[0].Denominator), 56 | Minutes: float64(rawCoordinate[1].Numerator) / float64(rawCoordinate[1].Denominator), 57 | Seconds: float64(rawCoordinate[2].Numerator) / float64(rawCoordinate[2].Denominator), 58 | } 59 | 60 | return gd, nil 61 | } 62 | 63 | // String provides returns a descriptive string. 64 | func (d GpsDegrees) String() string { 65 | return fmt.Sprintf("Degrees", string([]byte{d.Orientation}), d.Degrees, d.Minutes, d.Seconds) 66 | } 67 | 68 | // Decimal calculates and returns the simplified float representation of the 69 | // component degrees. 70 | func (d GpsDegrees) Decimal() float64 { 71 | decimal := float64(d.Degrees) + float64(d.Minutes)/60.0 + float64(d.Seconds)/3600.0 72 | 73 | if d.Orientation == 'S' || d.Orientation == 'W' { 74 | return -decimal 75 | } 76 | 77 | return decimal 78 | } 79 | 80 | // Raw returns a Rational struct that can be used to *write* coordinates. In 81 | // practice, the denominator are typically (1) in the original EXIF data, and, 82 | // that being the case, this will best preserve precision. 83 | func (d GpsDegrees) Raw() []exifcommon.Rational { 84 | return []exifcommon.Rational{ 85 | {Numerator: uint32(d.Degrees), Denominator: 1}, 86 | {Numerator: uint32(d.Minutes), Denominator: 1}, 87 | {Numerator: uint32(d.Seconds), Denominator: 1}, 88 | } 89 | } 90 | 91 | // GpsInfo encapsulates all of the geographic information in one place. 92 | type GpsInfo struct { 93 | Latitude, Longitude GpsDegrees 94 | Altitude int 95 | Timestamp time.Time 96 | } 97 | 98 | // String returns a descriptive string. 99 | func (gi *GpsInfo) String() string { 100 | return fmt.Sprintf("GpsInfo", 101 | gi.Latitude.Decimal(), gi.Longitude.Decimal(), gi.Altitude, gi.Timestamp) 102 | } 103 | 104 | // S2CellId returns the cell-ID of the geographic location on the earth. 105 | func (gi *GpsInfo) S2CellId() s2.CellID { 106 | latitude := gi.Latitude.Decimal() 107 | longitude := gi.Longitude.Decimal() 108 | 109 | ll := s2.LatLngFromDegrees(latitude, longitude) 110 | cellId := s2.CellIDFromLatLng(ll) 111 | 112 | if cellId.IsValid() == false { 113 | panic(ErrGpsCoordinatesNotValid) 114 | } 115 | 116 | return cellId 117 | } 118 | -------------------------------------------------------------------------------- /v2/gps_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestNewGpsDegreesFromRationals(t *testing.T) { 14 | latitudeRaw := []exifcommon.Rational{ 15 | {Numerator: 22, Denominator: 2}, 16 | {Numerator: 66, Denominator: 3}, 17 | {Numerator: 132, Denominator: 4}, 18 | } 19 | 20 | gd, err := NewGpsDegreesFromRationals("W", latitudeRaw) 21 | log.PanicIf(err) 22 | 23 | if gd.Orientation != 'W' { 24 | t.Fatalf("Orientation was not set correctly: [%s]", string([]byte{gd.Orientation})) 25 | } 26 | 27 | degreesRightBound := math.Nextafter(11.0, 12.0) 28 | minutesRightBound := math.Nextafter(22.0, 23.0) 29 | secondsRightBound := math.Nextafter(33.0, 34.0) 30 | 31 | if gd.Degrees < 11.0 || gd.Degrees >= degreesRightBound { 32 | t.Fatalf("Degrees is not correct: (%.2f)", gd.Degrees) 33 | } else if gd.Minutes < 22.0 || gd.Minutes >= minutesRightBound { 34 | t.Fatalf("Minutes is not correct: (%.2f)", gd.Minutes) 35 | } else if gd.Seconds < 33.0 || gd.Seconds >= secondsRightBound { 36 | t.Fatalf("Seconds is not correct: (%.2f)", gd.Seconds) 37 | } 38 | } 39 | 40 | func TestGpsDegrees_Raw(t *testing.T) { 41 | latitudeRaw := []exifcommon.Rational{ 42 | {Numerator: 22, Denominator: 2}, 43 | {Numerator: 66, Denominator: 3}, 44 | {Numerator: 132, Denominator: 4}, 45 | } 46 | 47 | gd, err := NewGpsDegreesFromRationals("W", latitudeRaw) 48 | log.PanicIf(err) 49 | 50 | actual := gd.Raw() 51 | 52 | expected := []exifcommon.Rational{ 53 | {Numerator: 11, Denominator: 1}, 54 | {Numerator: 22, Denominator: 1}, 55 | {Numerator: 33, Denominator: 1}, 56 | } 57 | 58 | if reflect.DeepEqual(actual, expected) != true { 59 | t.Fatalf("GpsInfo not correctly encoded down to raw: %v\n", actual) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /v2/ifd.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "github.com/dsoprea/go-logging" 5 | 6 | "github.com/dsoprea/go-exif/v2/common" 7 | ) 8 | 9 | // TODO(dustin): This file now exists for backwards-compatibility only. 10 | 11 | // NewIfdMapping returns a new IfdMapping struct. 12 | func NewIfdMapping() (ifdMapping *exifcommon.IfdMapping) { 13 | return exifcommon.NewIfdMapping() 14 | } 15 | 16 | // NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the 17 | // standard IFDs. 18 | func NewIfdMappingWithStandard() (ifdMapping *exifcommon.IfdMapping) { 19 | return exifcommon.NewIfdMappingWithStandard() 20 | } 21 | 22 | // LoadStandardIfds loads the standard IFDs into the mapping. 23 | func LoadStandardIfds(im *exifcommon.IfdMapping) (err error) { 24 | defer func() { 25 | if state := recover(); state != nil { 26 | err = log.Wrap(state.(error)) 27 | } 28 | }() 29 | 30 | err = exifcommon.LoadStandardIfds(im) 31 | log.PanicIf(err) 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /v2/ifd_tag_entry_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/dsoprea/go-logging" 8 | 9 | "github.com/dsoprea/go-exif/v2/common" 10 | ) 11 | 12 | func TestIfdTagEntry_RawBytes_Allocated(t *testing.T) { 13 | data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} 14 | 15 | addressableBytes := data 16 | 17 | ite := newIfdTagEntry( 18 | exifcommon.IfdStandardIfdIdentity, 19 | 0x1, 20 | 0, 21 | exifcommon.TypeByte, 22 | 6, 23 | 0, 24 | nil, 25 | addressableBytes, 26 | exifcommon.TestDefaultByteOrder) 27 | 28 | value, err := ite.GetRawBytes() 29 | log.PanicIf(err) 30 | 31 | if bytes.Compare(value, data) != 0 { 32 | t.Fatalf("Value not expected: [%s] != [%s]", value, data) 33 | } 34 | } 35 | 36 | func TestIfdTagEntry_RawBytes_Embedded(t *testing.T) { 37 | defer func() { 38 | if state := recover(); state != nil { 39 | err := log.Wrap(state.(error)) 40 | log.PrintError(err) 41 | 42 | t.Fatalf("Test failure.") 43 | } 44 | }() 45 | 46 | data := []byte{0x11, 0x22, 0x33, 0x44} 47 | 48 | ite := newIfdTagEntry( 49 | exifcommon.IfdStandardIfdIdentity, 50 | 0x1, 51 | 0, 52 | exifcommon.TypeByte, 53 | 4, 54 | 0, 55 | data, 56 | nil, 57 | exifcommon.TestDefaultByteOrder) 58 | 59 | value, err := ite.GetRawBytes() 60 | log.PanicIf(err) 61 | 62 | if bytes.Compare(value, data) != 0 { 63 | t.Fatalf("Value not expected: %v != %v", value, data) 64 | } 65 | } 66 | 67 | func TestIfdTagEntry_String(t *testing.T) { 68 | ite := newIfdTagEntry( 69 | exifcommon.IfdStandardIfdIdentity, 70 | 0x1, 71 | 0, 72 | exifcommon.TypeByte, 73 | 6, 74 | 0, 75 | nil, 76 | nil, 77 | exifcommon.TestDefaultByteOrder) 78 | 79 | expected := "IfdTagEntry" 80 | if ite.String() != expected { 81 | t.Fatalf("string representation not expected: [%s] != [%s]", ite.String(), expected) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /v2/package.go: -------------------------------------------------------------------------------- 1 | // Package exif parses raw EXIF information given a block of raw EXIF data. It 2 | // can also construct new EXIF information, and provides tools for doing so. 3 | // This package is not involved with the parsing of particular file-formats. 4 | // 5 | // The EXIF data must first be extracted and then provided to us. Conversely, 6 | // when constructing new EXIF data, the caller is responsible for packaging 7 | // this in whichever format they require. 8 | package exif 9 | -------------------------------------------------------------------------------- /v2/undefined/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0xa40b 3 | 4 | The specification is not specific/clear enough to be handled. Without a working example ,we're deferring until some point in the future when either we or someone else has a better understanding. 5 | -------------------------------------------------------------------------------- /v2/undefined/accessor.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | // Encode encodes the given encodeable undefined value to bytes. 12 | func Encode(value EncodeableValue, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 13 | defer func() { 14 | if state := recover(); state != nil { 15 | err = log.Wrap(state.(error)) 16 | } 17 | }() 18 | 19 | encoderName := value.EncoderName() 20 | 21 | encoder, found := encoders[encoderName] 22 | if found == false { 23 | log.Panicf("no encoder registered for type [%s]", encoderName) 24 | } 25 | 26 | encoded, unitCount, err = encoder.Encode(value, byteOrder) 27 | log.PanicIf(err) 28 | 29 | return encoded, unitCount, nil 30 | } 31 | 32 | // Decode constructs a value from raw encoded bytes 33 | func Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 34 | defer func() { 35 | if state := recover(); state != nil { 36 | err = log.Wrap(state.(error)) 37 | } 38 | }() 39 | 40 | uth := UndefinedTagHandle{ 41 | IfdPath: valueContext.IfdPath(), 42 | TagId: valueContext.TagId(), 43 | } 44 | 45 | decoder, found := decoders[uth] 46 | if found == false { 47 | // We have no choice but to return the error. We have no way of knowing how 48 | // much data there is without already knowing what data-type this tag is. 49 | return nil, exifcommon.ErrUnhandledUndefinedTypedTag 50 | } 51 | 52 | value, err = decoder.Decode(valueContext) 53 | if err != nil { 54 | if err == ErrUnparseableValue { 55 | return nil, err 56 | } 57 | 58 | log.Panic(err) 59 | } 60 | 61 | return value, nil 62 | } 63 | -------------------------------------------------------------------------------- /v2/undefined/exif_8828_oecf.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v2/common" 12 | ) 13 | 14 | type Tag8828Oecf struct { 15 | Columns uint16 16 | Rows uint16 17 | ColumnNames []string 18 | Values []exifcommon.SignedRational 19 | } 20 | 21 | func (oecf Tag8828Oecf) String() string { 22 | return fmt.Sprintf("Tag8828Oecf", oecf.Columns, oecf.Rows) 23 | } 24 | 25 | func (oecf Tag8828Oecf) EncoderName() string { 26 | return "Codec8828Oecf" 27 | } 28 | 29 | type Codec8828Oecf struct { 30 | } 31 | 32 | func (Codec8828Oecf) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 33 | defer func() { 34 | if state := recover(); state != nil { 35 | err = log.Wrap(state.(error)) 36 | } 37 | }() 38 | 39 | // TODO(dustin): Add test 40 | 41 | oecf, ok := value.(Tag8828Oecf) 42 | if ok == false { 43 | log.Panicf("can only encode a Tag8828Oecf") 44 | } 45 | 46 | b := new(bytes.Buffer) 47 | 48 | err = binary.Write(b, byteOrder, oecf.Columns) 49 | log.PanicIf(err) 50 | 51 | err = binary.Write(b, byteOrder, oecf.Rows) 52 | log.PanicIf(err) 53 | 54 | for _, name := range oecf.ColumnNames { 55 | _, err := b.Write([]byte(name)) 56 | log.PanicIf(err) 57 | 58 | _, err = b.Write([]byte{0}) 59 | log.PanicIf(err) 60 | } 61 | 62 | ve := exifcommon.NewValueEncoder(byteOrder) 63 | 64 | ed, err := ve.Encode(oecf.Values) 65 | log.PanicIf(err) 66 | 67 | _, err = b.Write(ed.Encoded) 68 | log.PanicIf(err) 69 | 70 | return b.Bytes(), uint32(b.Len()), nil 71 | } 72 | 73 | func (Codec8828Oecf) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 74 | defer func() { 75 | if state := recover(); state != nil { 76 | err = log.Wrap(state.(error)) 77 | } 78 | }() 79 | 80 | // TODO(dustin): Add test using known good data. 81 | 82 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 83 | 84 | valueBytes, err := valueContext.ReadBytes() 85 | log.PanicIf(err) 86 | 87 | oecf := Tag8828Oecf{} 88 | 89 | oecf.Columns = valueContext.ByteOrder().Uint16(valueBytes[0:2]) 90 | oecf.Rows = valueContext.ByteOrder().Uint16(valueBytes[2:4]) 91 | 92 | columnNames := make([]string, oecf.Columns) 93 | 94 | // startAt is where the current column name starts. 95 | startAt := 4 96 | 97 | // offset is our current position. 98 | offset := startAt 99 | 100 | currentColumnNumber := uint16(0) 101 | 102 | for currentColumnNumber < oecf.Columns { 103 | if valueBytes[offset] == 0 { 104 | columnName := string(valueBytes[startAt:offset]) 105 | if len(columnName) == 0 { 106 | log.Panicf("SFR column (%d) has zero length", currentColumnNumber) 107 | } 108 | 109 | columnNames[currentColumnNumber] = columnName 110 | currentColumnNumber++ 111 | 112 | offset++ 113 | startAt = offset 114 | continue 115 | } 116 | 117 | offset++ 118 | } 119 | 120 | oecf.ColumnNames = columnNames 121 | 122 | rawRationalBytes := valueBytes[offset:] 123 | 124 | rationalSize := exifcommon.TypeSignedRational.Size() 125 | if len(rawRationalBytes)%rationalSize > 0 { 126 | log.Panicf("OECF signed-rationals not aligned: (%d) %% (%d) > 0", len(rawRationalBytes), rationalSize) 127 | } 128 | 129 | rationalCount := len(rawRationalBytes) / rationalSize 130 | 131 | parser := new(exifcommon.Parser) 132 | 133 | byteOrder := valueContext.ByteOrder() 134 | 135 | items, err := parser.ParseSignedRationals(rawRationalBytes, uint32(rationalCount), byteOrder) 136 | log.PanicIf(err) 137 | 138 | oecf.Values = items 139 | 140 | return oecf, nil 141 | } 142 | 143 | func init() { 144 | registerDecoder( 145 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 146 | 0x8828, 147 | Codec8828Oecf{}) 148 | } 149 | -------------------------------------------------------------------------------- /v2/undefined/exif_8828_oecf_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag8828Oecf_String(t *testing.T) { 14 | ut := Tag8828Oecf{ 15 | Columns: 11, 16 | Rows: 22, 17 | } 18 | 19 | s := ut.String() 20 | 21 | if s != "Tag8828Oecf" { 22 | t.Fatalf("String not correct: [%s]", s) 23 | } 24 | } 25 | 26 | func TestCodec8828Oecf_Encode(t *testing.T) { 27 | ut := Tag8828Oecf{ 28 | Columns: 2, 29 | Rows: 22, 30 | ColumnNames: []string{"aa", "bb"}, 31 | Values: []exifcommon.SignedRational{{11, 22}}, 32 | } 33 | 34 | codec := Codec8828Oecf{} 35 | 36 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 37 | log.PanicIf(err) 38 | 39 | expectedBytes := []byte{ 40 | 0x00, 0x02, 41 | 0x00, 0x16, 42 | 0x61, 0x61, 0x00, 0x62, 0x62, 0x00, 43 | 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x16} 44 | 45 | if bytes.Equal(encoded, expectedBytes) != true { 46 | exifcommon.DumpBytesClause(encoded) 47 | 48 | t.Fatalf("Encoded bytes not correct.") 49 | } else if unitCount != 18 { 50 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 51 | } 52 | } 53 | 54 | func TestCodec8828Oecf_Decode(t *testing.T) { 55 | encoded := []byte{ 56 | 0x00, 0x02, 57 | 0x00, 0x16, 58 | 0x61, 0x61, 0x00, 0x62, 0x62, 0x00, 59 | 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x16} 60 | 61 | addressableData := encoded 62 | 63 | valueContext := exifcommon.NewValueContext( 64 | "", 65 | 0, 66 | uint32(len(encoded)), 67 | 0, 68 | nil, 69 | addressableData, 70 | exifcommon.TypeUndefined, 71 | exifcommon.TestDefaultByteOrder) 72 | 73 | codec := Codec8828Oecf{} 74 | 75 | value, err := codec.Decode(valueContext) 76 | log.PanicIf(err) 77 | 78 | expectedValue := Tag8828Oecf{ 79 | Columns: 2, 80 | Rows: 22, 81 | ColumnNames: []string{"aa", "bb"}, 82 | Values: []exifcommon.SignedRational{{11, 22}}, 83 | } 84 | 85 | if reflect.DeepEqual(value, expectedValue) != true { 86 | t.Fatalf("Decoded value not correct: %s", value) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /v2/undefined/exif_9000_exif_version.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | type Tag9000ExifVersion struct { 12 | ExifVersion string 13 | } 14 | 15 | func (Tag9000ExifVersion) EncoderName() string { 16 | return "Codec9000ExifVersion" 17 | } 18 | 19 | func (ev Tag9000ExifVersion) String() string { 20 | return ev.ExifVersion 21 | } 22 | 23 | type Codec9000ExifVersion struct { 24 | } 25 | 26 | func (Codec9000ExifVersion) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag9000ExifVersion) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag9000ExifVersion") 36 | } 37 | 38 | return []byte(s.ExifVersion), uint32(len(s.ExifVersion)), nil 39 | } 40 | 41 | func (Codec9000ExifVersion) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | ev := Tag9000ExifVersion{ 54 | ExifVersion: valueString, 55 | } 56 | 57 | return ev, nil 58 | } 59 | 60 | func init() { 61 | registerEncoder( 62 | Tag9000ExifVersion{}, 63 | Codec9000ExifVersion{}) 64 | 65 | registerDecoder( 66 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 67 | 0x9000, 68 | Codec9000ExifVersion{}) 69 | } 70 | -------------------------------------------------------------------------------- /v2/undefined/exif_9000_exif_version_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag9000ExifVersion_String(t *testing.T) { 14 | ut := Tag9000ExifVersion{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec9000ExifVersion_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag9000ExifVersion{s} 25 | 26 | codec := Codec9000ExifVersion{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec9000ExifVersion_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag9000ExifVersion{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec9000ExifVersion{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v2/undefined/exif_9101_components_configuration.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v2/common" 12 | ) 13 | 14 | const ( 15 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Y = 0x1 16 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cb = 0x2 17 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cr = 0x3 18 | TagUndefinedType_9101_ComponentsConfiguration_Channel_R = 0x4 19 | TagUndefinedType_9101_ComponentsConfiguration_Channel_G = 0x5 20 | TagUndefinedType_9101_ComponentsConfiguration_Channel_B = 0x6 21 | ) 22 | 23 | const ( 24 | TagUndefinedType_9101_ComponentsConfiguration_OTHER = iota 25 | TagUndefinedType_9101_ComponentsConfiguration_RGB = iota 26 | TagUndefinedType_9101_ComponentsConfiguration_YCBCR = iota 27 | ) 28 | 29 | var ( 30 | TagUndefinedType_9101_ComponentsConfiguration_Names = map[int]string{ 31 | TagUndefinedType_9101_ComponentsConfiguration_OTHER: "OTHER", 32 | TagUndefinedType_9101_ComponentsConfiguration_RGB: "RGB", 33 | TagUndefinedType_9101_ComponentsConfiguration_YCBCR: "YCBCR", 34 | } 35 | 36 | TagUndefinedType_9101_ComponentsConfiguration_Configurations = map[int][]byte{ 37 | TagUndefinedType_9101_ComponentsConfiguration_RGB: { 38 | TagUndefinedType_9101_ComponentsConfiguration_Channel_R, 39 | TagUndefinedType_9101_ComponentsConfiguration_Channel_G, 40 | TagUndefinedType_9101_ComponentsConfiguration_Channel_B, 41 | 0, 42 | }, 43 | 44 | TagUndefinedType_9101_ComponentsConfiguration_YCBCR: { 45 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Y, 46 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cb, 47 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cr, 48 | 0, 49 | }, 50 | } 51 | ) 52 | 53 | type TagExif9101ComponentsConfiguration struct { 54 | ConfigurationId int 55 | ConfigurationBytes []byte 56 | } 57 | 58 | func (TagExif9101ComponentsConfiguration) EncoderName() string { 59 | return "CodecExif9101ComponentsConfiguration" 60 | } 61 | 62 | func (cc TagExif9101ComponentsConfiguration) String() string { 63 | return fmt.Sprintf("Exif9101ComponentsConfiguration", TagUndefinedType_9101_ComponentsConfiguration_Names[cc.ConfigurationId], cc.ConfigurationBytes) 64 | } 65 | 66 | type CodecExif9101ComponentsConfiguration struct { 67 | } 68 | 69 | func (CodecExif9101ComponentsConfiguration) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 70 | defer func() { 71 | if state := recover(); state != nil { 72 | err = log.Wrap(state.(error)) 73 | } 74 | }() 75 | 76 | cc, ok := value.(TagExif9101ComponentsConfiguration) 77 | if ok == false { 78 | log.Panicf("can only encode a TagExif9101ComponentsConfiguration") 79 | } 80 | 81 | return cc.ConfigurationBytes, uint32(len(cc.ConfigurationBytes)), nil 82 | } 83 | 84 | func (CodecExif9101ComponentsConfiguration) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 85 | defer func() { 86 | if state := recover(); state != nil { 87 | err = log.Wrap(state.(error)) 88 | } 89 | }() 90 | 91 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 92 | 93 | valueBytes, err := valueContext.ReadBytes() 94 | log.PanicIf(err) 95 | 96 | for configurationId, configurationBytes := range TagUndefinedType_9101_ComponentsConfiguration_Configurations { 97 | if bytes.Equal(configurationBytes, valueBytes) == true { 98 | cc := TagExif9101ComponentsConfiguration{ 99 | ConfigurationId: configurationId, 100 | ConfigurationBytes: valueBytes, 101 | } 102 | 103 | return cc, nil 104 | } 105 | } 106 | 107 | cc := TagExif9101ComponentsConfiguration{ 108 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_OTHER, 109 | ConfigurationBytes: valueBytes, 110 | } 111 | 112 | return cc, nil 113 | } 114 | 115 | func init() { 116 | registerEncoder( 117 | TagExif9101ComponentsConfiguration{}, 118 | CodecExif9101ComponentsConfiguration{}) 119 | 120 | registerDecoder( 121 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 122 | 0x9101, 123 | CodecExif9101ComponentsConfiguration{}) 124 | } 125 | -------------------------------------------------------------------------------- /v2/undefined/exif_9101_components_configuration_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTagExif9101ComponentsConfiguration_String(t *testing.T) { 14 | ut := TagExif9101ComponentsConfiguration{ 15 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_RGB, 16 | ConfigurationBytes: []byte{0x11, 0x22, 0x33, 0x44}, 17 | } 18 | 19 | s := ut.String() 20 | 21 | if s != "Exif9101ComponentsConfiguration" { 22 | t.Fatalf("String not correct: [%s]", s) 23 | } 24 | } 25 | 26 | func TestCodecExif9101ComponentsConfiguration_Encode(t *testing.T) { 27 | configurationBytes := []byte(TagUndefinedType_9101_ComponentsConfiguration_Names[TagUndefinedType_9101_ComponentsConfiguration_RGB]) 28 | 29 | ut := TagExif9101ComponentsConfiguration{ 30 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_RGB, 31 | ConfigurationBytes: configurationBytes, 32 | } 33 | 34 | codec := CodecExif9101ComponentsConfiguration{} 35 | 36 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 37 | log.PanicIf(err) 38 | 39 | if bytes.Equal(encoded, configurationBytes) != true { 40 | exifcommon.DumpBytesClause(encoded) 41 | 42 | t.Fatalf("Encoded bytes not correct: %v", encoded) 43 | } else if unitCount != uint32(len(configurationBytes)) { 44 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 45 | } 46 | 47 | s := string(configurationBytes) 48 | 49 | if s != TagUndefinedType_9101_ComponentsConfiguration_Names[TagUndefinedType_9101_ComponentsConfiguration_RGB] { 50 | t.Fatalf("Recovered configuration name not correct: [%s]", s) 51 | } 52 | } 53 | 54 | func TestCodecExif9101ComponentsConfiguration_Decode(t *testing.T) { 55 | configurationBytes := TagUndefinedType_9101_ComponentsConfiguration_Configurations[TagUndefinedType_9101_ComponentsConfiguration_RGB] 56 | 57 | ut := TagExif9101ComponentsConfiguration{ 58 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_RGB, 59 | ConfigurationBytes: configurationBytes, 60 | } 61 | 62 | rawValueOffset := configurationBytes 63 | 64 | valueContext := exifcommon.NewValueContext( 65 | "", 66 | 0, 67 | uint32(len(configurationBytes)), 68 | 0, 69 | rawValueOffset, 70 | nil, 71 | exifcommon.TypeUndefined, 72 | exifcommon.TestDefaultByteOrder) 73 | 74 | codec := CodecExif9101ComponentsConfiguration{} 75 | 76 | value, err := codec.Decode(valueContext) 77 | log.PanicIf(err) 78 | 79 | if reflect.DeepEqual(value, ut) != true { 80 | t.Fatalf("Decoded value not correct: %s != %s\n", value, ut) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /v2/undefined/exif_927C_maker_note.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "crypto/sha1" 8 | "encoding/binary" 9 | 10 | "github.com/dsoprea/go-logging" 11 | 12 | "github.com/dsoprea/go-exif/v2/common" 13 | ) 14 | 15 | type Tag927CMakerNote struct { 16 | MakerNoteType []byte 17 | MakerNoteBytes []byte 18 | } 19 | 20 | func (Tag927CMakerNote) EncoderName() string { 21 | return "Codec927CMakerNote" 22 | } 23 | 24 | func (mn Tag927CMakerNote) String() string { 25 | parts := make([]string, len(mn.MakerNoteType)) 26 | 27 | for i, c := range mn.MakerNoteType { 28 | parts[i] = fmt.Sprintf("%02x", c) 29 | } 30 | 31 | h := sha1.New() 32 | 33 | _, err := h.Write(mn.MakerNoteBytes) 34 | log.PanicIf(err) 35 | 36 | digest := h.Sum(nil) 37 | 38 | return fmt.Sprintf("MakerNote", strings.Join(parts, " "), len(mn.MakerNoteBytes), digest) 39 | } 40 | 41 | type Codec927CMakerNote struct { 42 | } 43 | 44 | func (Codec927CMakerNote) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 45 | defer func() { 46 | if state := recover(); state != nil { 47 | err = log.Wrap(state.(error)) 48 | } 49 | }() 50 | 51 | mn, ok := value.(Tag927CMakerNote) 52 | if ok == false { 53 | log.Panicf("can only encode a Tag927CMakerNote") 54 | } 55 | 56 | // TODO(dustin): Confirm this size against the specification. 57 | 58 | return mn.MakerNoteBytes, uint32(len(mn.MakerNoteBytes)), nil 59 | } 60 | 61 | func (Codec927CMakerNote) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 62 | defer func() { 63 | if state := recover(); state != nil { 64 | err = log.Wrap(state.(error)) 65 | } 66 | }() 67 | 68 | // MakerNote 69 | // TODO(dustin): !! This is the Wild Wild West. This very well might be a child IFD, but any and all OEM's define their own formats. If we're going to be writing changes and this is complete EXIF (which may not have the first eight bytes), it might be fine. However, if these are just IFDs they'll be relative to the main EXIF, this will invalidate the MakerNote data for IFDs and any other implementations that use offsets unless we can interpret them all. It be best to return to this later and just exclude this from being written for now, though means a loss of a wealth of image metadata. 70 | // -> We can also just blindly try to interpret as an IFD and just validate that it's looks good (maybe it will even have a 'next ifd' pointer that we can validate is 0x0). 71 | 72 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 73 | 74 | valueBytes, err := valueContext.ReadBytes() 75 | log.PanicIf(err) 76 | 77 | // TODO(dustin): Doesn't work, but here as an example. 78 | // ie := NewIfdEnumerate(valueBytes, byteOrder) 79 | 80 | // // TODO(dustin): !! Validate types (might have proprietary types, but it might be worth splitting the list between valid and not valid; maybe fail if a certain proportion are invalid, or maybe aren't less then a certain small integer)? 81 | // ii, err := ie.Collect(0x0) 82 | 83 | // for _, entry := range ii.RootIfd.Entries { 84 | // fmt.Printf("ENTRY: 0x%02x %d\n", entry.TagId, entry.TagType) 85 | // } 86 | 87 | var makerNoteType []byte 88 | if len(valueBytes) >= 20 { 89 | makerNoteType = valueBytes[:20] 90 | } else { 91 | makerNoteType = valueBytes 92 | } 93 | 94 | mn := Tag927CMakerNote{ 95 | MakerNoteType: makerNoteType, 96 | 97 | // MakerNoteBytes has the whole length of bytes. There's always 98 | // the chance that the first 20 bytes includes actual data. 99 | MakerNoteBytes: valueBytes, 100 | } 101 | 102 | return mn, nil 103 | } 104 | 105 | func init() { 106 | registerEncoder( 107 | Tag927CMakerNote{}, 108 | Codec927CMakerNote{}) 109 | 110 | registerDecoder( 111 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 112 | 0x927c, 113 | Codec927CMakerNote{}) 114 | } 115 | -------------------------------------------------------------------------------- /v2/undefined/exif_927C_maker_note_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag927CMakerNote_String(t *testing.T) { 14 | ut := Tag927CMakerNote{ 15 | MakerNoteType: []byte{0, 1, 2, 3, 4}, 16 | MakerNoteBytes: []byte{5, 6, 7, 8, 9}, 17 | } 18 | 19 | s := ut.String() 20 | if s != "MakerNote" { 21 | t.Fatalf("String not correct: [%s]", s) 22 | } 23 | } 24 | 25 | func TestCodec927CMakerNote_Encode(t *testing.T) { 26 | codec := Codec927CMakerNote{} 27 | 28 | prefix := []byte{0, 1, 2, 3, 4} 29 | b := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 30 | 31 | ut := Tag927CMakerNote{ 32 | MakerNoteType: prefix, 33 | MakerNoteBytes: b, 34 | } 35 | 36 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 37 | log.PanicIf(err) 38 | 39 | if bytes.Equal(encoded, b) != true { 40 | t.Fatalf("Encoding not correct: %v", encoded) 41 | } else if unitCount != uint32(len(b)) { 42 | t.Fatalf("Unit-count not correct: (%d)", len(b)) 43 | } 44 | } 45 | 46 | func TestCodec927CMakerNote_Decode(t *testing.T) { 47 | b := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 48 | 49 | ut := Tag927CMakerNote{ 50 | MakerNoteType: b, 51 | MakerNoteBytes: b, 52 | } 53 | 54 | valueContext := exifcommon.NewValueContext( 55 | "", 56 | 0, 57 | uint32(len(b)), 58 | 0, 59 | nil, 60 | b, 61 | exifcommon.TypeUndefined, 62 | exifcommon.TestDefaultByteOrder) 63 | 64 | codec := Codec927CMakerNote{} 65 | 66 | value, err := codec.Decode(valueContext) 67 | log.PanicIf(err) 68 | 69 | if reflect.DeepEqual(value, ut) != true { 70 | t.Fatalf("Decoded value not correct: %s != %s", value, ut) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /v2/undefined/exif_9286_user_comment.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v2/common" 12 | ) 13 | 14 | var ( 15 | exif9286Logger = log.NewLogger("exifundefined.exif_9286_user_comment") 16 | ) 17 | 18 | const ( 19 | TagUndefinedType_9286_UserComment_Encoding_ASCII = iota 20 | TagUndefinedType_9286_UserComment_Encoding_JIS = iota 21 | TagUndefinedType_9286_UserComment_Encoding_UNICODE = iota 22 | TagUndefinedType_9286_UserComment_Encoding_UNDEFINED = iota 23 | ) 24 | 25 | var ( 26 | TagUndefinedType_9286_UserComment_Encoding_Names = map[int]string{ 27 | TagUndefinedType_9286_UserComment_Encoding_ASCII: "ASCII", 28 | TagUndefinedType_9286_UserComment_Encoding_JIS: "JIS", 29 | TagUndefinedType_9286_UserComment_Encoding_UNICODE: "UNICODE", 30 | TagUndefinedType_9286_UserComment_Encoding_UNDEFINED: "UNDEFINED", 31 | } 32 | 33 | TagUndefinedType_9286_UserComment_Encodings = map[int][]byte{ 34 | TagUndefinedType_9286_UserComment_Encoding_ASCII: {'A', 'S', 'C', 'I', 'I', 0, 0, 0}, 35 | TagUndefinedType_9286_UserComment_Encoding_JIS: {'J', 'I', 'S', 0, 0, 0, 0, 0}, 36 | TagUndefinedType_9286_UserComment_Encoding_UNICODE: {'U', 'n', 'i', 'c', 'o', 'd', 'e', 0}, 37 | TagUndefinedType_9286_UserComment_Encoding_UNDEFINED: {0, 0, 0, 0, 0, 0, 0, 0}, 38 | } 39 | ) 40 | 41 | type Tag9286UserComment struct { 42 | EncodingType int 43 | EncodingBytes []byte 44 | } 45 | 46 | func (Tag9286UserComment) EncoderName() string { 47 | return "Codec9286UserComment" 48 | } 49 | 50 | func (uc Tag9286UserComment) String() string { 51 | var valuePhrase string 52 | 53 | if uc.EncodingType == TagUndefinedType_9286_UserComment_Encoding_ASCII { 54 | return fmt.Sprintf("[ASCII] %s", string(uc.EncodingBytes)) 55 | } else { 56 | if len(uc.EncodingBytes) <= 8 { 57 | valuePhrase = fmt.Sprintf("%v", uc.EncodingBytes) 58 | } else { 59 | valuePhrase = fmt.Sprintf("%v...", uc.EncodingBytes[:8]) 60 | } 61 | } 62 | 63 | return fmt.Sprintf("UserComment", len(uc.EncodingBytes), TagUndefinedType_9286_UserComment_Encoding_Names[uc.EncodingType], valuePhrase, len(uc.EncodingBytes)) 64 | } 65 | 66 | type Codec9286UserComment struct { 67 | } 68 | 69 | func (Codec9286UserComment) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 70 | defer func() { 71 | if state := recover(); state != nil { 72 | err = log.Wrap(state.(error)) 73 | } 74 | }() 75 | 76 | uc, ok := value.(Tag9286UserComment) 77 | if ok == false { 78 | log.Panicf("can only encode a Tag9286UserComment") 79 | } 80 | 81 | encodingTypeBytes, found := TagUndefinedType_9286_UserComment_Encodings[uc.EncodingType] 82 | if found == false { 83 | log.Panicf("encoding-type not valid for unknown-type tag 9286 (UserComment): (%d)", uc.EncodingType) 84 | } 85 | 86 | encoded = make([]byte, len(uc.EncodingBytes)+8) 87 | 88 | copy(encoded[:8], encodingTypeBytes) 89 | copy(encoded[8:], uc.EncodingBytes) 90 | 91 | // TODO(dustin): Confirm this size against the specification. 92 | 93 | return encoded, uint32(len(encoded)), nil 94 | } 95 | 96 | func (Codec9286UserComment) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 97 | defer func() { 98 | if state := recover(); state != nil { 99 | err = log.Wrap(state.(error)) 100 | } 101 | }() 102 | 103 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 104 | 105 | valueBytes, err := valueContext.ReadBytes() 106 | log.PanicIf(err) 107 | 108 | if len(valueBytes) < 8 { 109 | return nil, ErrUnparseableValue 110 | } 111 | 112 | unknownUc := Tag9286UserComment{ 113 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_UNDEFINED, 114 | EncodingBytes: []byte{}, 115 | } 116 | 117 | encoding := valueBytes[:8] 118 | for encodingIndex, encodingBytes := range TagUndefinedType_9286_UserComment_Encodings { 119 | if bytes.Compare(encoding, encodingBytes) == 0 { 120 | uc := Tag9286UserComment{ 121 | EncodingType: encodingIndex, 122 | EncodingBytes: valueBytes[8:], 123 | } 124 | 125 | return uc, nil 126 | } 127 | } 128 | 129 | exif9286Logger.Warningf(nil, "User-comment encoding not valid. Returning 'unknown' type (the default).") 130 | return unknownUc, nil 131 | } 132 | 133 | func init() { 134 | registerEncoder( 135 | Tag9286UserComment{}, 136 | Codec9286UserComment{}) 137 | 138 | registerDecoder( 139 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 140 | 0x9286, 141 | Codec9286UserComment{}) 142 | } 143 | -------------------------------------------------------------------------------- /v2/undefined/exif_9286_user_comment_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag9286UserComment_String(t *testing.T) { 14 | comment := "some comment" 15 | 16 | ut := Tag9286UserComment{ 17 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, 18 | EncodingBytes: []byte(comment), 19 | } 20 | 21 | s := ut.String() 22 | if s != "[ASCII] some comment" { 23 | t.Fatalf("String not correct: [%s]", s) 24 | } 25 | } 26 | 27 | func TestCodec9286UserComment_Encode(t *testing.T) { 28 | comment := "some comment" 29 | 30 | ut := Tag9286UserComment{ 31 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, 32 | EncodingBytes: []byte(comment), 33 | } 34 | 35 | codec := Codec9286UserComment{} 36 | 37 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 38 | log.PanicIf(err) 39 | 40 | typeBytes := TagUndefinedType_9286_UserComment_Encodings[TagUndefinedType_9286_UserComment_Encoding_ASCII] 41 | if bytes.Equal(encoded[:8], typeBytes) != true { 42 | exifcommon.DumpBytesClause(encoded[:8]) 43 | 44 | t.Fatalf("Encoding type not correct.") 45 | } 46 | 47 | if bytes.Equal(encoded[8:], []byte(comment)) != true { 48 | exifcommon.DumpBytesClause(encoded[8:]) 49 | 50 | t.Fatalf("Encoded comment not correct.") 51 | } 52 | 53 | if unitCount != uint32(len(encoded)) { 54 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 55 | } 56 | 57 | exifcommon.DumpBytesClause(encoded) 58 | 59 | } 60 | 61 | func TestCodec9286UserComment_Decode(t *testing.T) { 62 | encoded := []byte{ 63 | 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00, 64 | 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 65 | } 66 | 67 | addressableBytes := encoded 68 | 69 | valueContext := exifcommon.NewValueContext( 70 | "", 71 | 0, 72 | uint32(len(encoded)), 73 | 0, 74 | nil, 75 | addressableBytes, 76 | exifcommon.TypeUndefined, 77 | exifcommon.TestDefaultByteOrder) 78 | 79 | codec := Codec9286UserComment{} 80 | 81 | decoded, err := codec.Decode(valueContext) 82 | log.PanicIf(err) 83 | 84 | comment := "some comment" 85 | 86 | expectedUt := Tag9286UserComment{ 87 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, 88 | EncodingBytes: []byte(comment), 89 | } 90 | 91 | if reflect.DeepEqual(decoded, expectedUt) != true { 92 | t.Fatalf("Decoded struct not correct.") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /v2/undefined/exif_A000_flashpix_version.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | type TagA000FlashpixVersion struct { 12 | FlashpixVersion string 13 | } 14 | 15 | func (TagA000FlashpixVersion) EncoderName() string { 16 | return "CodecA000FlashpixVersion" 17 | } 18 | 19 | func (fv TagA000FlashpixVersion) String() string { 20 | return fv.FlashpixVersion 21 | } 22 | 23 | type CodecA000FlashpixVersion struct { 24 | } 25 | 26 | func (CodecA000FlashpixVersion) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(TagA000FlashpixVersion) 34 | if ok == false { 35 | log.Panicf("can only encode a TagA000FlashpixVersion") 36 | } 37 | 38 | return []byte(s.FlashpixVersion), uint32(len(s.FlashpixVersion)), nil 39 | } 40 | 41 | func (CodecA000FlashpixVersion) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | fv := TagA000FlashpixVersion{ 54 | FlashpixVersion: valueString, 55 | } 56 | 57 | return fv, nil 58 | } 59 | 60 | func init() { 61 | registerEncoder( 62 | TagA000FlashpixVersion{}, 63 | CodecA000FlashpixVersion{}) 64 | 65 | registerDecoder( 66 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 67 | 0xa000, 68 | CodecA000FlashpixVersion{}) 69 | } 70 | -------------------------------------------------------------------------------- /v2/undefined/exif_A000_flashpix_version_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTagA000FlashpixVersion_String(t *testing.T) { 14 | versionPhrase := "some version" 15 | 16 | ut := TagA000FlashpixVersion{versionPhrase} 17 | 18 | s := ut.String() 19 | if s != versionPhrase { 20 | t.Fatalf("String not correct: [%s]", s) 21 | } 22 | } 23 | 24 | func TestCodecA000FlashpixVersion_Encode(t *testing.T) { 25 | versionPhrase := "some version" 26 | 27 | ut := TagA000FlashpixVersion{versionPhrase} 28 | 29 | codec := CodecA000FlashpixVersion{} 30 | 31 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 32 | log.PanicIf(err) 33 | 34 | if bytes.Equal(encoded, []byte(versionPhrase)) != true { 35 | exifcommon.DumpBytesClause(encoded) 36 | 37 | t.Fatalf("Encoding not correct.") 38 | } else if unitCount != uint32(len(encoded)) { 39 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 40 | } 41 | } 42 | 43 | func TestCodecA000FlashpixVersion_Decode(t *testing.T) { 44 | versionPhrase := "some version" 45 | 46 | expectedUt := TagA000FlashpixVersion{versionPhrase} 47 | 48 | encoded := []byte(versionPhrase) 49 | 50 | addressableBytes := encoded 51 | 52 | valueContext := exifcommon.NewValueContext( 53 | "", 54 | 0, 55 | uint32(len(encoded)), 56 | 0, 57 | nil, 58 | addressableBytes, 59 | exifcommon.TypeUndefined, 60 | exifcommon.TestDefaultByteOrder) 61 | 62 | codec := CodecA000FlashpixVersion{} 63 | 64 | decoded, err := codec.Decode(valueContext) 65 | log.PanicIf(err) 66 | 67 | if reflect.DeepEqual(decoded, expectedUt) != true { 68 | t.Fatalf("Decoded struct not correct.") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /v2/undefined/exif_A20C_spatial_frequency_response.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v2/common" 12 | ) 13 | 14 | type TagA20CSpatialFrequencyResponse struct { 15 | Columns uint16 16 | Rows uint16 17 | ColumnNames []string 18 | Values []exifcommon.Rational 19 | } 20 | 21 | func (TagA20CSpatialFrequencyResponse) EncoderName() string { 22 | return "CodecA20CSpatialFrequencyResponse" 23 | } 24 | 25 | func (sfr TagA20CSpatialFrequencyResponse) String() string { 26 | return fmt.Sprintf("CodecA20CSpatialFrequencyResponse", sfr.Columns, sfr.Rows) 27 | } 28 | 29 | type CodecA20CSpatialFrequencyResponse struct { 30 | } 31 | 32 | func (CodecA20CSpatialFrequencyResponse) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 33 | defer func() { 34 | if state := recover(); state != nil { 35 | err = log.Wrap(state.(error)) 36 | } 37 | }() 38 | 39 | // TODO(dustin): Add test. 40 | 41 | sfr, ok := value.(TagA20CSpatialFrequencyResponse) 42 | if ok == false { 43 | log.Panicf("can only encode a TagA20CSpatialFrequencyResponse") 44 | } 45 | 46 | b := new(bytes.Buffer) 47 | 48 | err = binary.Write(b, byteOrder, sfr.Columns) 49 | log.PanicIf(err) 50 | 51 | err = binary.Write(b, byteOrder, sfr.Rows) 52 | log.PanicIf(err) 53 | 54 | // Write columns. 55 | 56 | for _, name := range sfr.ColumnNames { 57 | _, err := b.WriteString(name) 58 | log.PanicIf(err) 59 | 60 | err = b.WriteByte(0) 61 | log.PanicIf(err) 62 | } 63 | 64 | // Write values. 65 | 66 | ve := exifcommon.NewValueEncoder(byteOrder) 67 | 68 | ed, err := ve.Encode(sfr.Values) 69 | log.PanicIf(err) 70 | 71 | _, err = b.Write(ed.Encoded) 72 | log.PanicIf(err) 73 | 74 | encoded = b.Bytes() 75 | 76 | // TODO(dustin): Confirm this size against the specification. 77 | 78 | return encoded, uint32(len(encoded)), nil 79 | } 80 | 81 | func (CodecA20CSpatialFrequencyResponse) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 82 | defer func() { 83 | if state := recover(); state != nil { 84 | err = log.Wrap(state.(error)) 85 | } 86 | }() 87 | 88 | // TODO(dustin): Add test using known good data. 89 | 90 | byteOrder := valueContext.ByteOrder() 91 | 92 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 93 | 94 | valueBytes, err := valueContext.ReadBytes() 95 | log.PanicIf(err) 96 | 97 | sfr := TagA20CSpatialFrequencyResponse{} 98 | 99 | sfr.Columns = byteOrder.Uint16(valueBytes[0:2]) 100 | sfr.Rows = byteOrder.Uint16(valueBytes[2:4]) 101 | 102 | columnNames := make([]string, sfr.Columns) 103 | 104 | // startAt is where the current column name starts. 105 | startAt := 4 106 | 107 | // offset is our current position. 108 | offset := 4 109 | 110 | currentColumnNumber := uint16(0) 111 | 112 | for currentColumnNumber < sfr.Columns { 113 | if valueBytes[offset] == 0 { 114 | columnName := string(valueBytes[startAt:offset]) 115 | if len(columnName) == 0 { 116 | log.Panicf("SFR column (%d) has zero length", currentColumnNumber) 117 | } 118 | 119 | columnNames[currentColumnNumber] = columnName 120 | currentColumnNumber++ 121 | 122 | offset++ 123 | startAt = offset 124 | continue 125 | } 126 | 127 | offset++ 128 | } 129 | 130 | sfr.ColumnNames = columnNames 131 | 132 | rawRationalBytes := valueBytes[offset:] 133 | 134 | rationalSize := exifcommon.TypeRational.Size() 135 | if len(rawRationalBytes)%rationalSize > 0 { 136 | log.Panicf("SFR rationals not aligned: (%d) %% (%d) > 0", len(rawRationalBytes), rationalSize) 137 | } 138 | 139 | rationalCount := len(rawRationalBytes) / rationalSize 140 | 141 | parser := new(exifcommon.Parser) 142 | 143 | items, err := parser.ParseRationals(rawRationalBytes, uint32(rationalCount), byteOrder) 144 | log.PanicIf(err) 145 | 146 | sfr.Values = items 147 | 148 | return sfr, nil 149 | } 150 | 151 | func init() { 152 | registerEncoder( 153 | TagA20CSpatialFrequencyResponse{}, 154 | CodecA20CSpatialFrequencyResponse{}) 155 | 156 | registerDecoder( 157 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 158 | 0xa20c, 159 | CodecA20CSpatialFrequencyResponse{}) 160 | } 161 | -------------------------------------------------------------------------------- /v2/undefined/exif_A20C_spatial_frequency_response_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTagA20CSpatialFrequencyResponse_String(t *testing.T) { 14 | ut := TagA20CSpatialFrequencyResponse{ 15 | Columns: 2, 16 | Rows: 9, 17 | ColumnNames: []string{"column1", "column2"}, 18 | Values: []exifcommon.Rational{ 19 | {1, 2}, 20 | {3, 4}, 21 | }, 22 | } 23 | 24 | s := ut.String() 25 | if s != "CodecA20CSpatialFrequencyResponse" { 26 | t.Fatalf("String not correct: [%s]", s) 27 | } 28 | } 29 | 30 | func TestCodecA20CSpatialFrequencyResponse_Encode(t *testing.T) { 31 | ut := TagA20CSpatialFrequencyResponse{ 32 | Columns: 2, 33 | Rows: 9, 34 | ColumnNames: []string{"column1", "column2"}, 35 | Values: []exifcommon.Rational{ 36 | {1, 2}, 37 | {3, 4}, 38 | }, 39 | } 40 | 41 | codec := CodecA20CSpatialFrequencyResponse{} 42 | 43 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 44 | log.PanicIf(err) 45 | 46 | expectedEncoded := []byte{ 47 | 0x00, 0x02, 48 | 0x00, 0x09, 49 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x31, 0x00, 50 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x32, 0x00, 51 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 52 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 53 | } 54 | 55 | if bytes.Equal(encoded, expectedEncoded) != true { 56 | exifcommon.DumpBytesClause(encoded) 57 | 58 | t.Fatalf("Encoding not correct.") 59 | } else if unitCount != uint32(len(encoded)) { 60 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 61 | } 62 | } 63 | 64 | func TestCodecA20CSpatialFrequencyResponse_Decode(t *testing.T) { 65 | expectedUt := TagA20CSpatialFrequencyResponse{ 66 | Columns: 2, 67 | Rows: 9, 68 | ColumnNames: []string{"column1", "column2"}, 69 | Values: []exifcommon.Rational{ 70 | {1, 2}, 71 | {3, 4}, 72 | }, 73 | } 74 | 75 | encoded := []byte{ 76 | 0x00, 0x02, 77 | 0x00, 0x09, 78 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x31, 0x00, 79 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x32, 0x00, 80 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 81 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 82 | } 83 | 84 | addressableBytes := encoded 85 | 86 | valueContext := exifcommon.NewValueContext( 87 | "", 88 | 0, 89 | uint32(len(encoded)), 90 | 0, 91 | nil, 92 | addressableBytes, 93 | exifcommon.TypeUndefined, 94 | exifcommon.TestDefaultByteOrder) 95 | 96 | codec := CodecA20CSpatialFrequencyResponse{} 97 | 98 | decoded, err := codec.Decode(valueContext) 99 | log.PanicIf(err) 100 | 101 | if reflect.DeepEqual(decoded, expectedUt) != true { 102 | t.Fatalf("Decoded struct not correct.") 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /v2/undefined/exif_A300_file_source.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "fmt" 5 | 6 | "encoding/binary" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | type TagExifA300FileSource uint32 14 | 15 | func (TagExifA300FileSource) EncoderName() string { 16 | return "CodecExifA300FileSource" 17 | } 18 | 19 | func (af TagExifA300FileSource) String() string { 20 | return fmt.Sprintf("0x%08x", uint32(af)) 21 | } 22 | 23 | const ( 24 | TagUndefinedType_A300_SceneType_Others TagExifA300FileSource = 0 25 | TagUndefinedType_A300_SceneType_ScannerOfTransparentType TagExifA300FileSource = 1 26 | TagUndefinedType_A300_SceneType_ScannerOfReflexType TagExifA300FileSource = 2 27 | TagUndefinedType_A300_SceneType_Dsc TagExifA300FileSource = 3 28 | ) 29 | 30 | type CodecExifA300FileSource struct { 31 | } 32 | 33 | func (CodecExifA300FileSource) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 34 | defer func() { 35 | if state := recover(); state != nil { 36 | err = log.Wrap(state.(error)) 37 | } 38 | }() 39 | 40 | st, ok := value.(TagExifA300FileSource) 41 | if ok == false { 42 | log.Panicf("can only encode a TagExifA300FileSource") 43 | } 44 | 45 | ve := exifcommon.NewValueEncoder(byteOrder) 46 | 47 | ed, err := ve.Encode([]uint32{uint32(st)}) 48 | log.PanicIf(err) 49 | 50 | // TODO(dustin): Confirm this size against the specification. It's non-specific about what type it is, but it looks to be no more than a single integer scalar. So, we're assuming it's a LONG. 51 | 52 | return ed.Encoded, 1, nil 53 | } 54 | 55 | func (CodecExifA300FileSource) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 56 | defer func() { 57 | if state := recover(); state != nil { 58 | err = log.Wrap(state.(error)) 59 | } 60 | }() 61 | 62 | valueContext.SetUndefinedValueType(exifcommon.TypeLong) 63 | 64 | valueLongs, err := valueContext.ReadLongs() 65 | log.PanicIf(err) 66 | 67 | return TagExifA300FileSource(valueLongs[0]), nil 68 | } 69 | 70 | func init() { 71 | registerEncoder( 72 | TagExifA300FileSource(0), 73 | CodecExifA300FileSource{}) 74 | 75 | registerDecoder( 76 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 77 | 0xa300, 78 | CodecExifA300FileSource{}) 79 | } 80 | -------------------------------------------------------------------------------- /v2/undefined/exif_A300_file_source_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTagExifA300FileSource_String(t *testing.T) { 14 | ut := TagExifA300FileSource(0x1234) 15 | 16 | s := ut.String() 17 | if s != "0x00001234" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodecExifA300FileSource_Encode(t *testing.T) { 23 | ut := TagExifA300FileSource(0x1234) 24 | 25 | codec := CodecExifA300FileSource{} 26 | 27 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 28 | log.PanicIf(err) 29 | 30 | expectedEncoded := []byte{0, 0, 0x12, 0x34} 31 | 32 | if bytes.Equal(encoded, expectedEncoded) != true { 33 | exifcommon.DumpBytesClause(encoded) 34 | 35 | t.Fatalf("Encoding not correct.") 36 | } else if unitCount != 1 { 37 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 38 | } 39 | } 40 | 41 | func TestCodecExifA300FileSource_Decode(t *testing.T) { 42 | expectedUt := TagExifA300FileSource(0x1234) 43 | 44 | encoded := []byte{0, 0, 0x12, 0x34} 45 | 46 | rawValueOffset := encoded 47 | 48 | valueContext := exifcommon.NewValueContext( 49 | "", 50 | 0, 51 | 1, 52 | 0, 53 | rawValueOffset, 54 | nil, 55 | exifcommon.TypeUndefined, 56 | exifcommon.TestDefaultByteOrder) 57 | 58 | codec := CodecExifA300FileSource{} 59 | 60 | decoded, err := codec.Decode(valueContext) 61 | log.PanicIf(err) 62 | 63 | if reflect.DeepEqual(decoded, expectedUt) != true { 64 | t.Fatalf("Decoded struct not correct.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /v2/undefined/exif_A301_scene_type.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "fmt" 5 | 6 | "encoding/binary" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | type TagExifA301SceneType uint32 14 | 15 | func (TagExifA301SceneType) EncoderName() string { 16 | return "CodecExifA301SceneType" 17 | } 18 | 19 | func (st TagExifA301SceneType) String() string { 20 | return fmt.Sprintf("0x%08x", uint32(st)) 21 | } 22 | 23 | const ( 24 | TagUndefinedType_A301_SceneType_DirectlyPhotographedImage TagExifA301SceneType = 1 25 | ) 26 | 27 | type CodecExifA301SceneType struct { 28 | } 29 | 30 | func (CodecExifA301SceneType) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 31 | defer func() { 32 | if state := recover(); state != nil { 33 | err = log.Wrap(state.(error)) 34 | } 35 | }() 36 | 37 | st, ok := value.(TagExifA301SceneType) 38 | if ok == false { 39 | log.Panicf("can only encode a TagExif9101ComponentsConfiguration") 40 | } 41 | 42 | ve := exifcommon.NewValueEncoder(byteOrder) 43 | 44 | ed, err := ve.Encode([]uint32{uint32(st)}) 45 | log.PanicIf(err) 46 | 47 | // TODO(dustin): Confirm this size against the specification. It's non-specific about what type it is, but it looks to be no more than a single integer scalar. So, we're assuming it's a LONG. 48 | 49 | return ed.Encoded, uint32(int(ed.UnitCount)), nil 50 | } 51 | 52 | func (CodecExifA301SceneType) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 53 | defer func() { 54 | if state := recover(); state != nil { 55 | err = log.Wrap(state.(error)) 56 | } 57 | }() 58 | 59 | valueContext.SetUndefinedValueType(exifcommon.TypeLong) 60 | 61 | valueLongs, err := valueContext.ReadLongs() 62 | log.PanicIf(err) 63 | 64 | return TagExifA301SceneType(valueLongs[0]), nil 65 | } 66 | 67 | func init() { 68 | registerEncoder( 69 | TagExifA301SceneType(0), 70 | CodecExifA301SceneType{}) 71 | 72 | registerDecoder( 73 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 74 | 0xa301, 75 | CodecExifA301SceneType{}) 76 | } 77 | -------------------------------------------------------------------------------- /v2/undefined/exif_A301_scene_type_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTagExifA301SceneType_String(t *testing.T) { 14 | ut := TagExifA301SceneType(0x1234) 15 | 16 | s := ut.String() 17 | if s != "0x00001234" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodecExifA301SceneType_Encode(t *testing.T) { 23 | ut := TagExifA301SceneType(0x1234) 24 | 25 | codec := CodecExifA301SceneType{} 26 | 27 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 28 | log.PanicIf(err) 29 | 30 | expectedEncoded := []byte{0, 0, 0x12, 0x34} 31 | 32 | if bytes.Equal(encoded, expectedEncoded) != true { 33 | exifcommon.DumpBytesClause(encoded) 34 | 35 | t.Fatalf("Encoding not correct.") 36 | } else if unitCount != 1 { 37 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 38 | } 39 | } 40 | 41 | func TestCodecExifA301SceneType_Decode(t *testing.T) { 42 | expectedUt := TagExifA301SceneType(0x1234) 43 | 44 | encoded := []byte{0, 0, 0x12, 0x34} 45 | 46 | rawValueOffset := encoded 47 | 48 | valueContext := exifcommon.NewValueContext( 49 | "", 50 | 0, 51 | 1, 52 | 0, 53 | rawValueOffset, 54 | nil, 55 | exifcommon.TypeUndefined, 56 | exifcommon.TestDefaultByteOrder) 57 | 58 | codec := CodecExifA301SceneType{} 59 | 60 | decoded, err := codec.Decode(valueContext) 61 | log.PanicIf(err) 62 | 63 | if reflect.DeepEqual(decoded, expectedUt) != true { 64 | t.Fatalf("Decoded struct not correct.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /v2/undefined/exif_A302_cfa_pattern.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v2/common" 12 | ) 13 | 14 | type TagA302CfaPattern struct { 15 | HorizontalRepeat uint16 16 | VerticalRepeat uint16 17 | CfaValue []byte 18 | } 19 | 20 | func (TagA302CfaPattern) EncoderName() string { 21 | return "CodecA302CfaPattern" 22 | } 23 | 24 | func (cp TagA302CfaPattern) String() string { 25 | return fmt.Sprintf("TagA302CfaPattern", cp.HorizontalRepeat, cp.VerticalRepeat, len(cp.CfaValue)) 26 | } 27 | 28 | type CodecA302CfaPattern struct { 29 | } 30 | 31 | func (CodecA302CfaPattern) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 32 | defer func() { 33 | if state := recover(); state != nil { 34 | err = log.Wrap(state.(error)) 35 | } 36 | }() 37 | 38 | // TODO(dustin): Add test. 39 | 40 | cp, ok := value.(TagA302CfaPattern) 41 | if ok == false { 42 | log.Panicf("can only encode a TagA302CfaPattern") 43 | } 44 | 45 | b := new(bytes.Buffer) 46 | 47 | err = binary.Write(b, byteOrder, cp.HorizontalRepeat) 48 | log.PanicIf(err) 49 | 50 | err = binary.Write(b, byteOrder, cp.VerticalRepeat) 51 | log.PanicIf(err) 52 | 53 | _, err = b.Write(cp.CfaValue) 54 | log.PanicIf(err) 55 | 56 | encoded = b.Bytes() 57 | 58 | // TODO(dustin): Confirm this size against the specification. 59 | 60 | return encoded, uint32(len(encoded)), nil 61 | } 62 | 63 | func (CodecA302CfaPattern) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 64 | defer func() { 65 | if state := recover(); state != nil { 66 | err = log.Wrap(state.(error)) 67 | } 68 | }() 69 | 70 | // TODO(dustin): Add test using known good data. 71 | 72 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 73 | 74 | valueBytes, err := valueContext.ReadBytes() 75 | log.PanicIf(err) 76 | 77 | cp := TagA302CfaPattern{} 78 | 79 | cp.HorizontalRepeat = valueContext.ByteOrder().Uint16(valueBytes[0:2]) 80 | cp.VerticalRepeat = valueContext.ByteOrder().Uint16(valueBytes[2:4]) 81 | 82 | expectedLength := int(cp.HorizontalRepeat * cp.VerticalRepeat) 83 | cp.CfaValue = valueBytes[4 : 4+expectedLength] 84 | 85 | return cp, nil 86 | } 87 | 88 | func init() { 89 | registerEncoder( 90 | TagA302CfaPattern{}, 91 | CodecA302CfaPattern{}) 92 | 93 | registerDecoder( 94 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 95 | 0xa302, 96 | CodecA302CfaPattern{}) 97 | } 98 | -------------------------------------------------------------------------------- /v2/undefined/exif_A302_cfa_pattern_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | "github.com/dsoprea/go-logging" 10 | ) 11 | 12 | func TestTagA302CfaPattern_String(t *testing.T) { 13 | ut := TagA302CfaPattern{ 14 | HorizontalRepeat: 2, 15 | VerticalRepeat: 3, 16 | CfaValue: []byte{ 17 | 0, 1, 2, 3, 4, 5, 18 | }, 19 | } 20 | 21 | s := ut.String() 22 | 23 | if s != "TagA302CfaPattern" { 24 | t.Fatalf("String not correct: [%s]", s) 25 | } 26 | } 27 | 28 | func TestCodecA302CfaPattern_Encode(t *testing.T) { 29 | ut := TagA302CfaPattern{ 30 | HorizontalRepeat: 2, 31 | VerticalRepeat: 3, 32 | CfaValue: []byte{ 33 | 0, 1, 2, 3, 4, 34 | 5, 6, 7, 8, 9, 35 | 10, 11, 12, 13, 14, 36 | }, 37 | } 38 | 39 | codec := CodecA302CfaPattern{} 40 | 41 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 42 | log.PanicIf(err) 43 | 44 | expectedBytes := []byte{ 45 | 0x00, 0x02, 46 | 0x00, 0x03, 47 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 48 | } 49 | 50 | if bytes.Equal(encoded, expectedBytes) != true { 51 | exifcommon.DumpBytesClause(encoded) 52 | 53 | t.Fatalf("Encoded bytes not correct.") 54 | } else if unitCount != 19 { 55 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 56 | } 57 | } 58 | 59 | func TestCodecA302CfaPattern_Decode(t *testing.T) { 60 | encoded := []byte{ 61 | 0x00, 0x02, 62 | 0x00, 0x03, 63 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 64 | } 65 | 66 | addressableData := encoded 67 | 68 | valueContext := exifcommon.NewValueContext( 69 | "", 70 | 0, 71 | uint32(len(encoded)), 72 | 0, 73 | nil, 74 | addressableData, 75 | exifcommon.TypeUndefined, 76 | exifcommon.TestDefaultByteOrder) 77 | 78 | codec := CodecA302CfaPattern{} 79 | 80 | value, err := codec.Decode(valueContext) 81 | log.PanicIf(err) 82 | 83 | expectedValue := TagA302CfaPattern{ 84 | HorizontalRepeat: 2, 85 | VerticalRepeat: 3, 86 | CfaValue: []byte{ 87 | 0, 1, 2, 3, 4, 5, 88 | }, 89 | } 90 | 91 | if reflect.DeepEqual(value, expectedValue) != true { 92 | t.Fatalf("Decoded value not correct: %s", value) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /v2/undefined/exif_iop_0002_interop_version.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | type Tag0002InteropVersion struct { 12 | InteropVersion string 13 | } 14 | 15 | func (Tag0002InteropVersion) EncoderName() string { 16 | return "Codec0002InteropVersion" 17 | } 18 | 19 | func (iv Tag0002InteropVersion) String() string { 20 | return iv.InteropVersion 21 | } 22 | 23 | type Codec0002InteropVersion struct { 24 | } 25 | 26 | func (Codec0002InteropVersion) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag0002InteropVersion) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag0002InteropVersion") 36 | } 37 | 38 | return []byte(s.InteropVersion), uint32(len(s.InteropVersion)), nil 39 | } 40 | 41 | func (Codec0002InteropVersion) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | iv := Tag0002InteropVersion{ 54 | InteropVersion: valueString, 55 | } 56 | 57 | return iv, nil 58 | } 59 | 60 | func init() { 61 | registerEncoder( 62 | Tag0002InteropVersion{}, 63 | Codec0002InteropVersion{}) 64 | 65 | registerDecoder( 66 | exifcommon.IfdExifIopStandardIfdIdentity.UnindexedString(), 67 | 0x0002, 68 | Codec0002InteropVersion{}) 69 | } 70 | -------------------------------------------------------------------------------- /v2/undefined/exif_iop_0002_interop_version_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag0002InteropVersion_String(t *testing.T) { 14 | ut := Tag0002InteropVersion{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec0002InteropVersion_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag0002InteropVersion{s} 25 | 26 | codec := Codec0002InteropVersion{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec0002InteropVersion_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag0002InteropVersion{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec0002InteropVersion{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v2/undefined/gps_001B_gps_processing_method.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | type Tag001BGPSProcessingMethod struct { 12 | string 13 | } 14 | 15 | func (Tag001BGPSProcessingMethod) EncoderName() string { 16 | return "Codec001BGPSProcessingMethod" 17 | } 18 | 19 | func (gpm Tag001BGPSProcessingMethod) String() string { 20 | return gpm.string 21 | } 22 | 23 | type Codec001BGPSProcessingMethod struct { 24 | } 25 | 26 | func (Codec001BGPSProcessingMethod) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag001BGPSProcessingMethod) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag001BGPSProcessingMethod") 36 | } 37 | 38 | return []byte(s.string), uint32(len(s.string)), nil 39 | } 40 | 41 | func (Codec001BGPSProcessingMethod) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | return Tag001BGPSProcessingMethod{valueString}, nil 54 | } 55 | 56 | func init() { 57 | registerEncoder( 58 | Tag001BGPSProcessingMethod{}, 59 | Codec001BGPSProcessingMethod{}) 60 | 61 | registerDecoder( 62 | exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString(), 63 | 0x001b, 64 | Codec001BGPSProcessingMethod{}) 65 | } 66 | -------------------------------------------------------------------------------- /v2/undefined/gps_001B_gps_processing_method_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag001BGPSProcessingMethod_String(t *testing.T) { 14 | ut := Tag001BGPSProcessingMethod{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec001BGPSProcessingMethod_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag001BGPSProcessingMethod{s} 25 | 26 | codec := Codec001BGPSProcessingMethod{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec001BGPSProcessingMethod_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag001BGPSProcessingMethod{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec001BGPSProcessingMethod{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v2/undefined/gps_001C_gps_area_information.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | type Tag001CGPSAreaInformation struct { 12 | string 13 | } 14 | 15 | func (Tag001CGPSAreaInformation) EncoderName() string { 16 | return "Codec001CGPSAreaInformation" 17 | } 18 | 19 | func (gai Tag001CGPSAreaInformation) String() string { 20 | return gai.string 21 | } 22 | 23 | type Codec001CGPSAreaInformation struct { 24 | } 25 | 26 | func (Codec001CGPSAreaInformation) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag001CGPSAreaInformation) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag001CGPSAreaInformation") 36 | } 37 | 38 | return []byte(s.string), uint32(len(s.string)), nil 39 | } 40 | 41 | func (Codec001CGPSAreaInformation) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | return Tag001CGPSAreaInformation{valueString}, nil 54 | } 55 | 56 | func init() { 57 | registerEncoder( 58 | Tag001CGPSAreaInformation{}, 59 | Codec001CGPSAreaInformation{}) 60 | 61 | registerDecoder( 62 | exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString(), 63 | 0x001c, 64 | Codec001CGPSAreaInformation{}) 65 | } 66 | -------------------------------------------------------------------------------- /v2/undefined/gps_001C_gps_area_information_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v2/common" 11 | ) 12 | 13 | func TestTag001CGPSAreaInformation_String(t *testing.T) { 14 | ut := Tag001CGPSAreaInformation{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec001CGPSAreaInformation_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag001CGPSAreaInformation{s} 25 | 26 | codec := Codec001CGPSAreaInformation{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec001CGPSAreaInformation_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag001CGPSAreaInformation{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec001CGPSAreaInformation{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v2/undefined/registration.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "github.com/dsoprea/go-logging" 5 | ) 6 | 7 | // UndefinedTagHandle defines one undefined-type tag with a corresponding 8 | // decoder. 9 | type UndefinedTagHandle struct { 10 | IfdPath string 11 | TagId uint16 12 | } 13 | 14 | func registerEncoder(entity EncodeableValue, encoder UndefinedValueEncoder) { 15 | typeName := entity.EncoderName() 16 | 17 | _, found := encoders[typeName] 18 | if found == true { 19 | log.Panicf("encoder already registered: %v", typeName) 20 | } 21 | 22 | encoders[typeName] = encoder 23 | } 24 | 25 | func registerDecoder(ifdPath string, tagId uint16, decoder UndefinedValueDecoder) { 26 | uth := UndefinedTagHandle{ 27 | IfdPath: ifdPath, 28 | TagId: tagId, 29 | } 30 | 31 | _, found := decoders[uth] 32 | if found == true { 33 | log.Panicf("decoder already registered: %v", uth) 34 | } 35 | 36 | decoders[uth] = decoder 37 | } 38 | 39 | var ( 40 | encoders = make(map[string]UndefinedValueEncoder) 41 | decoders = make(map[UndefinedTagHandle]UndefinedValueDecoder) 42 | ) 43 | -------------------------------------------------------------------------------- /v2/undefined/type.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "errors" 5 | 6 | "encoding/binary" 7 | 8 | "github.com/dsoprea/go-exif/v2/common" 9 | ) 10 | 11 | const ( 12 | // UnparseableUnknownTagValuePlaceholder is the string to use for an unknown 13 | // undefined tag. 14 | UnparseableUnknownTagValuePlaceholder = "!UNKNOWN" 15 | 16 | // UnparseableHandledTagValuePlaceholder is the string to use for a known 17 | // value that is not parseable. 18 | UnparseableHandledTagValuePlaceholder = "!MALFORMED" 19 | ) 20 | 21 | var ( 22 | // ErrUnparseableValue is the error for a value that we should have been 23 | // able to parse but were not able to. 24 | ErrUnparseableValue = errors.New("unparseable undefined tag") 25 | ) 26 | 27 | // UndefinedValueEncoder knows how to encode an undefined-type tag's value to 28 | // bytes. 29 | type UndefinedValueEncoder interface { 30 | Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) 31 | } 32 | 33 | // EncodeableValue wraps a value with the information that will be needed to re- 34 | // encode it later. 35 | type EncodeableValue interface { 36 | EncoderName() string 37 | String() string 38 | } 39 | 40 | // UndefinedValueDecoder knows how to decode an undefined-type tag's value from 41 | // bytes. 42 | type UndefinedValueDecoder interface { 43 | Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) 44 | } 45 | -------------------------------------------------------------------------------- /v2/utility_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/dsoprea/go-logging" 9 | ) 10 | 11 | func TestParseExifFullTimestamp(t *testing.T) { 12 | timestamp, err := ParseExifFullTimestamp("2018:11:30 13:01:49") 13 | log.PanicIf(err) 14 | 15 | actual := timestamp.Format(time.RFC3339) 16 | expected := "2018-11-30T13:01:49Z" 17 | 18 | if actual != expected { 19 | t.Fatalf("time not formatted correctly: [%s] != [%s]", actual, expected) 20 | } 21 | } 22 | 23 | func TestExifFullTimestampString(t *testing.T) { 24 | originalPhrase := "2018:11:30 13:01:49" 25 | 26 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 27 | log.PanicIf(err) 28 | 29 | restoredPhrase := ExifFullTimestampString(timestamp) 30 | if restoredPhrase != originalPhrase { 31 | t.Fatalf("Final phrase [%s] does not equal original phrase [%s]", restoredPhrase, originalPhrase) 32 | } 33 | } 34 | 35 | func ExampleParseExifFullTimestamp() { 36 | originalPhrase := "2018:11:30 13:01:49" 37 | 38 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 39 | log.PanicIf(err) 40 | 41 | fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339)) 42 | 43 | // Output: 44 | // To Go timestamp: [2018-11-30T13:01:49Z] 45 | } 46 | 47 | func ExampleExifFullTimestampString() { 48 | originalPhrase := "2018:11:30 13:01:49" 49 | 50 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 51 | log.PanicIf(err) 52 | 53 | restoredPhrase := ExifFullTimestampString(timestamp) 54 | fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase) 55 | 56 | // Output: 57 | // To EXIF timestamp: [2018:11:30 13:01:49] 58 | } 59 | 60 | func TestGpsDegreesEquals_Equals(t *testing.T) { 61 | gi := GpsDegrees{ 62 | Orientation: 'A', 63 | Degrees: 11.0, 64 | Minutes: 22.0, 65 | Seconds: 33.0, 66 | } 67 | 68 | r := GpsDegreesEquals(gi, gi) 69 | if r != true { 70 | t.Fatalf("GpsDegrees structs were not equal as expected.") 71 | } 72 | } 73 | 74 | func TestGpsDegreesEquals_NotEqual_Orientation(t *testing.T) { 75 | gi1 := GpsDegrees{ 76 | Orientation: 'A', 77 | Degrees: 11.0, 78 | Minutes: 22.0, 79 | Seconds: 33.0, 80 | } 81 | 82 | gi2 := gi1 83 | gi2.Orientation = 'B' 84 | 85 | r := GpsDegreesEquals(gi1, gi2) 86 | if r != false { 87 | t.Fatalf("GpsDegrees structs were equal but not supposed to be.") 88 | } 89 | } 90 | 91 | func TestGpsDegreesEquals_NotEqual_Position(t *testing.T) { 92 | gi1 := GpsDegrees{ 93 | Orientation: 'A', 94 | Degrees: 11.0, 95 | Minutes: 22.0, 96 | Seconds: 33.0, 97 | } 98 | 99 | gi2 := gi1 100 | gi2.Minutes = 22.5 101 | 102 | r := GpsDegreesEquals(gi1, gi2) 103 | if r != false { 104 | t.Fatalf("GpsDegrees structs were equal but not supposed to be.") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /v3/.MODULE_ROOT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/.MODULE_ROOT -------------------------------------------------------------------------------- /v3/LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright 2019 Dustin Oprea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /v3/assets/DC-008-2012_E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/DC-008-2012_E.pdf -------------------------------------------------------------------------------- /v3/assets/DC-008-Translation-2016-E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/DC-008-Translation-2016-E.pdf -------------------------------------------------------------------------------- /v3/assets/GeoTIFF_Profile_for_Georeferenced_Imagery.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/GeoTIFF_Profile_for_Georeferenced_Imagery.doc -------------------------------------------------------------------------------- /v3/assets/NDM_8901.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/NDM_8901.jpg -------------------------------------------------------------------------------- /v3/assets/NDM_8901.jpg.exif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/NDM_8901.jpg.exif -------------------------------------------------------------------------------- /v3/assets/NDM_8901.jpg.thumbnail: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/NDM_8901.jpg.thumbnail -------------------------------------------------------------------------------- /v3/assets/PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/PNG-GROUP-DRAFT PNG proposed eXIf chunk, draft 2017-06-15.pdf -------------------------------------------------------------------------------- /v3/assets/geotiff_example.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/geotiff_example.tif -------------------------------------------------------------------------------- /v3/assets/gps-2000-scaled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/gps-2000-scaled.jpg -------------------------------------------------------------------------------- /v3/assets/gps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/assets/gps.jpg -------------------------------------------------------------------------------- /v3/assets/raw_tags/requirements.txt: -------------------------------------------------------------------------------- 1 | ruamel.yaml 2 | -------------------------------------------------------------------------------- /v3/assets/raw_tags/translate_tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | """ 4 | Parses the table-data from view-source:http://www.exiv2.org/tags.html 5 | """ 6 | 7 | import sys 8 | import collections 9 | 10 | import xml.etree.ElementTree as ET 11 | 12 | import ruamel.yaml 13 | 14 | 15 | # Prepare YAML to write hex expressions (otherwise the hex will be a string and 16 | # quotes or a decimal and a base-10 number). 17 | 18 | class HexInt(int): 19 | pass 20 | 21 | def representer(dumper, data): 22 | return \ 23 | ruamel.yaml.ScalarNode( 24 | 'tag:yaml.org,2002:int', 25 | '0x{:04x}'.format(data)) 26 | 27 | ruamel.yaml.add_representer(HexInt, representer) 28 | 29 | def _write(tags): 30 | writeable = {} 31 | 32 | for tag in tags: 33 | pivot = tag['fq_key'].rindex('.') 34 | 35 | item = { 36 | 'id': HexInt(tag['id_dec']), 37 | 'name': tag['fq_key'][pivot + 1:], 38 | 'type_name': tag['type'].upper(), 39 | } 40 | 41 | ifdName = tag['ifd'] 42 | if ifdName == 'Image': 43 | ifdName = 'IFD' 44 | if ifdName == 'Photo': 45 | ifdName = 'Exif' 46 | 47 | # UserComment. Has invalid type "COMMENT". 48 | if item['id'] == 0x9286 and ifdName == 'Exif': 49 | item['type_name'] = 'UNDEFINED' 50 | 51 | try: 52 | writeable[ifdName].append(item) 53 | except KeyError: 54 | writeable[ifdName] = [item] 55 | 56 | with open('tags.yaml', 'w') as f: 57 | # Otherwise, the next dictionaries will look like Python dictionaries, 58 | # whatever sense that makes. 59 | ruamel.yaml.dump(writeable, f, default_flow_style=False) 60 | 61 | def _main(): 62 | tree = ET.parse('tags.html') 63 | root = tree.getroot() 64 | 65 | labels = [ 66 | 'id_hex', 67 | 'id_dec', 68 | 'ifd', 69 | 'fq_key', 70 | 'type', 71 | 'description', 72 | ] 73 | 74 | tags = [] 75 | for node in root.iter('tr'): 76 | values = [child.text.strip() for child in node.iter('td')] 77 | 78 | # Skips the header row. 79 | if not values: 80 | continue 81 | 82 | assert \ 83 | len(values) == len(labels), \ 84 | "Row fields count not the same as labels: {}".format(values) 85 | 86 | tags.append(dict(zip(labels, values))) 87 | 88 | _write(tags) 89 | 90 | if __name__ == '__main__': 91 | _main() 92 | -------------------------------------------------------------------------------- /v3/common/assets/NDM_8901.jpg.exif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsoprea/go-exif/6579e82b732d117f690de201d5932104f655e166/v3/common/assets/NDM_8901.jpg.exif -------------------------------------------------------------------------------- /v3/common/testing_common.go: -------------------------------------------------------------------------------- 1 | package exifcommon 2 | 3 | import ( 4 | "os" 5 | "path" 6 | 7 | "encoding/binary" 8 | "io/ioutil" 9 | 10 | "github.com/dsoprea/go-logging" 11 | ) 12 | 13 | var ( 14 | moduleRootPath = "" 15 | 16 | testExifData []byte = nil 17 | 18 | // EncodeDefaultByteOrder is the default byte-order for encoding operations. 19 | EncodeDefaultByteOrder = binary.BigEndian 20 | 21 | // Default byte order for tests. 22 | TestDefaultByteOrder = binary.BigEndian 23 | ) 24 | 25 | func GetModuleRootPath() string { 26 | if moduleRootPath == "" { 27 | moduleRootPath = os.Getenv("EXIF_MODULE_ROOT_PATH") 28 | if moduleRootPath != "" { 29 | return moduleRootPath 30 | } 31 | 32 | currentWd, err := os.Getwd() 33 | log.PanicIf(err) 34 | 35 | currentPath := currentWd 36 | 37 | visited := make([]string, 0) 38 | 39 | for { 40 | tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT") 41 | 42 | _, err := os.Stat(tryStampFilepath) 43 | if err != nil && os.IsNotExist(err) != true { 44 | log.Panic(err) 45 | } else if err == nil { 46 | break 47 | } 48 | 49 | visited = append(visited, tryStampFilepath) 50 | 51 | currentPath = path.Dir(currentPath) 52 | if currentPath == "/" { 53 | log.Panicf("could not find module-root: %v", visited) 54 | } 55 | } 56 | 57 | moduleRootPath = currentPath 58 | } 59 | 60 | return moduleRootPath 61 | } 62 | 63 | func GetTestAssetsPath() string { 64 | moduleRootPath := GetModuleRootPath() 65 | assetsPath := path.Join(moduleRootPath, "assets") 66 | 67 | return assetsPath 68 | } 69 | 70 | func getTestImageFilepath() string { 71 | assetsPath := GetTestAssetsPath() 72 | testImageFilepath := path.Join(assetsPath, "NDM_8901.jpg") 73 | return testImageFilepath 74 | } 75 | 76 | func getTestExifData() []byte { 77 | if testExifData == nil { 78 | assetsPath := GetTestAssetsPath() 79 | filepath := path.Join(assetsPath, "NDM_8901.jpg.exif") 80 | 81 | var err error 82 | 83 | testExifData, err = ioutil.ReadFile(filepath) 84 | log.PanicIf(err) 85 | } 86 | 87 | return testExifData 88 | } 89 | -------------------------------------------------------------------------------- /v3/common/utility.go: -------------------------------------------------------------------------------- 1 | package exifcommon 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/dsoprea/go-logging" 12 | ) 13 | 14 | var ( 15 | timeType = reflect.TypeOf(time.Time{}) 16 | ) 17 | 18 | // DumpBytes prints a list of hex-encoded bytes. 19 | func DumpBytes(data []byte) { 20 | fmt.Printf("DUMP: ") 21 | for _, x := range data { 22 | fmt.Printf("%02x ", x) 23 | } 24 | 25 | fmt.Printf("\n") 26 | } 27 | 28 | // DumpBytesClause prints a list like DumpBytes(), but encapsulated in 29 | // "[]byte { ... }". 30 | func DumpBytesClause(data []byte) { 31 | fmt.Printf("DUMP: ") 32 | 33 | fmt.Printf("[]byte { ") 34 | 35 | for i, x := range data { 36 | fmt.Printf("0x%02x", x) 37 | 38 | if i < len(data)-1 { 39 | fmt.Printf(", ") 40 | } 41 | } 42 | 43 | fmt.Printf(" }\n") 44 | } 45 | 46 | // DumpBytesToString returns a stringified list of hex-encoded bytes. 47 | func DumpBytesToString(data []byte) string { 48 | b := new(bytes.Buffer) 49 | 50 | for i, x := range data { 51 | _, err := b.WriteString(fmt.Sprintf("%02x", x)) 52 | log.PanicIf(err) 53 | 54 | if i < len(data)-1 { 55 | _, err := b.WriteRune(' ') 56 | log.PanicIf(err) 57 | } 58 | } 59 | 60 | return b.String() 61 | } 62 | 63 | // DumpBytesClauseToString returns a comma-separated list of hex-encoded bytes. 64 | func DumpBytesClauseToString(data []byte) string { 65 | b := new(bytes.Buffer) 66 | 67 | for i, x := range data { 68 | _, err := b.WriteString(fmt.Sprintf("0x%02x", x)) 69 | log.PanicIf(err) 70 | 71 | if i < len(data)-1 { 72 | _, err := b.WriteString(", ") 73 | log.PanicIf(err) 74 | } 75 | } 76 | 77 | return b.String() 78 | } 79 | 80 | // ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a 81 | // `time.Time` struct. It will attempt to convert to UTC first. 82 | func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) { 83 | t = t.UTC() 84 | 85 | return fmt.Sprintf("%04d:%02d:%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) 86 | } 87 | 88 | // ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC 89 | // `time.Time` struct. 90 | func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) { 91 | defer func() { 92 | if state := recover(); state != nil { 93 | err = log.Wrap(state.(error)) 94 | } 95 | }() 96 | 97 | parts := strings.Split(fullTimestampPhrase, " ") 98 | datestampValue, timestampValue := parts[0], parts[1] 99 | 100 | // Normalize the separators. 101 | datestampValue = strings.ReplaceAll(datestampValue, "-", ":") 102 | timestampValue = strings.ReplaceAll(timestampValue, "-", ":") 103 | 104 | dateParts := strings.Split(datestampValue, ":") 105 | 106 | year, err := strconv.ParseUint(dateParts[0], 10, 16) 107 | if err != nil { 108 | log.Panicf("could not parse year") 109 | } 110 | 111 | month, err := strconv.ParseUint(dateParts[1], 10, 8) 112 | if err != nil { 113 | log.Panicf("could not parse month") 114 | } 115 | 116 | day, err := strconv.ParseUint(dateParts[2], 10, 8) 117 | if err != nil { 118 | log.Panicf("could not parse day") 119 | } 120 | 121 | timeParts := strings.Split(timestampValue, ":") 122 | 123 | hour, err := strconv.ParseUint(timeParts[0], 10, 8) 124 | if err != nil { 125 | log.Panicf("could not parse hour") 126 | } 127 | 128 | minute, err := strconv.ParseUint(timeParts[1], 10, 8) 129 | if err != nil { 130 | log.Panicf("could not parse minute") 131 | } 132 | 133 | second, err := strconv.ParseUint(timeParts[2], 10, 8) 134 | if err != nil { 135 | log.Panicf("could not parse second") 136 | } 137 | 138 | timestamp = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.UTC) 139 | return timestamp, nil 140 | } 141 | 142 | // IsTime returns true if the value is a `time.Time`. 143 | func IsTime(v interface{}) bool { 144 | 145 | // TODO(dustin): Add test 146 | 147 | return reflect.TypeOf(v) == timeType 148 | } 149 | -------------------------------------------------------------------------------- /v3/common/utility_test.go: -------------------------------------------------------------------------------- 1 | package exifcommon 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/dsoprea/go-logging" 9 | ) 10 | 11 | func TestDumpBytes(t *testing.T) { 12 | DumpBytes([]byte{1, 2, 3, 4}) 13 | } 14 | 15 | func TestDumpBytesClause(t *testing.T) { 16 | DumpBytesClause([]byte{1, 2, 3, 4}) 17 | } 18 | 19 | func TestDumpBytesToString(t *testing.T) { 20 | s := DumpBytesToString([]byte{1, 2, 3, 4}) 21 | if s != "01 02 03 04" { 22 | t.Fatalf("String not correct: [%s]", s) 23 | } 24 | } 25 | 26 | func TestDumpBytesClauseToString(t *testing.T) { 27 | s := DumpBytesClauseToString([]byte{1, 2, 3, 4}) 28 | if s != "0x01, 0x02, 0x03, 0x04" { 29 | t.Fatalf("Stringified clause is not correct: [%s]", s) 30 | } 31 | } 32 | 33 | func TestExifFullTimestampString(t *testing.T) { 34 | originalPhrase := "2018:11:30 13:01:49" 35 | 36 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 37 | log.PanicIf(err) 38 | 39 | restoredPhrase := ExifFullTimestampString(timestamp) 40 | if restoredPhrase != originalPhrase { 41 | t.Fatalf("Final phrase [%s] does not equal original phrase [%s]", restoredPhrase, originalPhrase) 42 | } 43 | } 44 | 45 | func ExampleExifFullTimestampString() { 46 | originalPhrase := "2018:11:30 13:01:49" 47 | 48 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 49 | log.PanicIf(err) 50 | 51 | restoredPhrase := ExifFullTimestampString(timestamp) 52 | fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase) 53 | 54 | // Output: 55 | // To EXIF timestamp: [2018:11:30 13:01:49] 56 | } 57 | 58 | func TestParseExifFullTimestamp(t *testing.T) { 59 | timestamp, err := ParseExifFullTimestamp("2018:11:30 13:01:49") 60 | log.PanicIf(err) 61 | 62 | actual := timestamp.Format(time.RFC3339) 63 | expected := "2018-11-30T13:01:49Z" 64 | 65 | if actual != expected { 66 | t.Fatalf("time not formatted correctly: [%s] != [%s]", actual, expected) 67 | } 68 | } 69 | 70 | func ExampleParseExifFullTimestamp() { 71 | originalPhrase := "2018:11:30 13:01:49" 72 | 73 | timestamp, err := ParseExifFullTimestamp(originalPhrase) 74 | log.PanicIf(err) 75 | 76 | fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339)) 77 | 78 | // Output: 79 | // To Go timestamp: [2018-11-30T13:01:49Z] 80 | } 81 | -------------------------------------------------------------------------------- /v3/data_layer.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/dsoprea/go-logging" 7 | "github.com/dsoprea/go-utility/v2/filesystem" 8 | ) 9 | 10 | type ExifBlobSeeker interface { 11 | GetReadSeeker(initialOffset int64) (rs io.ReadSeeker, err error) 12 | } 13 | 14 | // ExifReadSeeker knows how to retrieve data from the EXIF blob relative to the 15 | // beginning of the blob (so, absolute position (0) is the first byte of the 16 | // EXIF data). 17 | type ExifReadSeeker struct { 18 | rs io.ReadSeeker 19 | } 20 | 21 | func NewExifReadSeeker(rs io.ReadSeeker) *ExifReadSeeker { 22 | return &ExifReadSeeker{ 23 | rs: rs, 24 | } 25 | } 26 | 27 | func NewExifReadSeekerWithBytes(exifData []byte) *ExifReadSeeker { 28 | sb := rifs.NewSeekableBufferWithBytes(exifData) 29 | edbs := NewExifReadSeeker(sb) 30 | 31 | return edbs 32 | } 33 | 34 | // Fork creates a new ReadSeeker instead that wraps a BouncebackReader to 35 | // maintain its own position in the stream. 36 | func (edbs *ExifReadSeeker) GetReadSeeker(initialOffset int64) (rs io.ReadSeeker, err error) { 37 | defer func() { 38 | if state := recover(); state != nil { 39 | err = log.Wrap(state.(error)) 40 | } 41 | }() 42 | 43 | br, err := rifs.NewBouncebackReader(edbs.rs) 44 | log.PanicIf(err) 45 | 46 | _, err = br.Seek(initialOffset, io.SeekStart) 47 | log.PanicIf(err) 48 | 49 | return br, nil 50 | } 51 | -------------------------------------------------------------------------------- /v3/error.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrTagNotFound indicates that the tag was not found. 9 | ErrTagNotFound = errors.New("tag not found") 10 | 11 | // ErrTagNotKnown indicates that the tag is not registered with us as a 12 | // known tag. 13 | ErrTagNotKnown = errors.New("tag is not known") 14 | ) 15 | -------------------------------------------------------------------------------- /v3/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dsoprea/go-exif/v3 2 | 3 | go 1.12 4 | 5 | // Development only 6 | // replace github.com/dsoprea/go-logging => ../../go-logging 7 | // replace github.com/dsoprea/go-utility/v2 => ../../go-utility/v2 8 | 9 | require ( 10 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd 11 | github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 12 | github.com/go-errors/errors v1.4.2 // indirect 13 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 14 | github.com/jessevdk/go-flags v1.5.0 15 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect 16 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect 17 | gopkg.in/yaml.v2 v2.4.0 18 | ) 19 | -------------------------------------------------------------------------------- /v3/go.sum: -------------------------------------------------------------------------------- 1 | github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= 2 | github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= 3 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= 4 | github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= 5 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= 6 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= 7 | github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU= 8 | github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= 9 | github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= 10 | github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= 11 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 12 | github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= 13 | github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= 14 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 15 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 16 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 17 | github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 18 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= 19 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 20 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 21 | github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= 22 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 25 | golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 26 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 27 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 28 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= 29 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 33 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= 36 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 38 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 39 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 40 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 45 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 46 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 47 | -------------------------------------------------------------------------------- /v3/gps.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/golang/geo/s2" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | var ( 15 | // ErrGpsCoordinatesNotValid means that some part of the geographic data was 16 | // unparseable. 17 | ErrGpsCoordinatesNotValid = errors.New("GPS coordinates not valid") 18 | ) 19 | 20 | // GpsDegrees is a high-level struct representing geographic data. 21 | type GpsDegrees struct { 22 | // Orientation describes the N/E/S/W direction that this position is 23 | // relative to. 24 | Orientation byte 25 | 26 | // Degrees is a simple float representing the underlying rational degrees 27 | // amount. 28 | Degrees float64 29 | 30 | // Minutes is a simple float representing the underlying rational minutes 31 | // amount. 32 | Minutes float64 33 | 34 | // Seconds is a simple float representing the underlying ration seconds 35 | // amount. 36 | Seconds float64 37 | } 38 | 39 | // NewGpsDegreesFromRationals returns a GpsDegrees struct given the EXIF-encoded 40 | // information. The refValue is the N/E/S/W direction that this position is 41 | // relative to. 42 | func NewGpsDegreesFromRationals(refValue string, rawCoordinate []exifcommon.Rational) (gd GpsDegrees, err error) { 43 | defer func() { 44 | if state := recover(); state != nil { 45 | err = log.Wrap(state.(error)) 46 | } 47 | }() 48 | 49 | if len(rawCoordinate) != 3 { 50 | log.Panicf("new GpsDegrees struct requires a raw-coordinate with exactly three rationals") 51 | } 52 | 53 | gd = GpsDegrees{ 54 | Orientation: refValue[0], 55 | Degrees: float64(rawCoordinate[0].Numerator) / float64(rawCoordinate[0].Denominator), 56 | Minutes: float64(rawCoordinate[1].Numerator) / float64(rawCoordinate[1].Denominator), 57 | Seconds: float64(rawCoordinate[2].Numerator) / float64(rawCoordinate[2].Denominator), 58 | } 59 | 60 | return gd, nil 61 | } 62 | 63 | // String provides returns a descriptive string. 64 | func (d GpsDegrees) String() string { 65 | return fmt.Sprintf("Degrees", string([]byte{d.Orientation}), d.Degrees, d.Minutes, d.Seconds) 66 | } 67 | 68 | // Decimal calculates and returns the simplified float representation of the 69 | // component degrees. 70 | func (d GpsDegrees) Decimal() float64 { 71 | decimal := float64(d.Degrees) + float64(d.Minutes)/60.0 + float64(d.Seconds)/3600.0 72 | 73 | if d.Orientation == 'S' || d.Orientation == 'W' { 74 | return -decimal 75 | } 76 | 77 | return decimal 78 | } 79 | 80 | // Raw returns a Rational struct that can be used to *write* coordinates. In 81 | // practice, the denominator are typically (1) in the original EXIF data, and, 82 | // that being the case, this will best preserve precision. 83 | func (d GpsDegrees) Raw() []exifcommon.Rational { 84 | return []exifcommon.Rational{ 85 | {Numerator: uint32(d.Degrees), Denominator: 1}, 86 | {Numerator: uint32(d.Minutes), Denominator: 1}, 87 | {Numerator: uint32(d.Seconds), Denominator: 1}, 88 | } 89 | } 90 | 91 | // GpsInfo encapsulates all of the geographic information in one place. 92 | type GpsInfo struct { 93 | Latitude, Longitude GpsDegrees 94 | Altitude int 95 | Timestamp time.Time 96 | } 97 | 98 | // String returns a descriptive string. 99 | func (gi *GpsInfo) String() string { 100 | return fmt.Sprintf("GpsInfo", 101 | gi.Latitude.Decimal(), gi.Longitude.Decimal(), gi.Altitude, gi.Timestamp) 102 | } 103 | 104 | // S2CellId returns the cell-ID of the geographic location on the earth. 105 | func (gi *GpsInfo) S2CellId() s2.CellID { 106 | latitude := gi.Latitude.Decimal() 107 | longitude := gi.Longitude.Decimal() 108 | 109 | ll := s2.LatLngFromDegrees(latitude, longitude) 110 | cellId := s2.CellIDFromLatLng(ll) 111 | 112 | if cellId.IsValid() == false { 113 | panic(ErrGpsCoordinatesNotValid) 114 | } 115 | 116 | return cellId 117 | } 118 | -------------------------------------------------------------------------------- /v3/gps_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestNewGpsDegreesFromRationals(t *testing.T) { 14 | latitudeRaw := []exifcommon.Rational{ 15 | {Numerator: 22, Denominator: 2}, 16 | {Numerator: 66, Denominator: 3}, 17 | {Numerator: 132, Denominator: 4}, 18 | } 19 | 20 | gd, err := NewGpsDegreesFromRationals("W", latitudeRaw) 21 | log.PanicIf(err) 22 | 23 | if gd.Orientation != 'W' { 24 | t.Fatalf("Orientation was not set correctly: [%s]", string([]byte{gd.Orientation})) 25 | } 26 | 27 | degreesRightBound := math.Nextafter(11.0, 12.0) 28 | minutesRightBound := math.Nextafter(22.0, 23.0) 29 | secondsRightBound := math.Nextafter(33.0, 34.0) 30 | 31 | if gd.Degrees < 11.0 || gd.Degrees >= degreesRightBound { 32 | t.Fatalf("Degrees is not correct: (%.2f)", gd.Degrees) 33 | } else if gd.Minutes < 22.0 || gd.Minutes >= minutesRightBound { 34 | t.Fatalf("Minutes is not correct: (%.2f)", gd.Minutes) 35 | } else if gd.Seconds < 33.0 || gd.Seconds >= secondsRightBound { 36 | t.Fatalf("Seconds is not correct: (%.2f)", gd.Seconds) 37 | } 38 | } 39 | 40 | func TestGpsDegrees_Raw(t *testing.T) { 41 | latitudeRaw := []exifcommon.Rational{ 42 | {Numerator: 22, Denominator: 2}, 43 | {Numerator: 66, Denominator: 3}, 44 | {Numerator: 132, Denominator: 4}, 45 | } 46 | 47 | gd, err := NewGpsDegreesFromRationals("W", latitudeRaw) 48 | log.PanicIf(err) 49 | 50 | actual := gd.Raw() 51 | 52 | expected := []exifcommon.Rational{ 53 | {Numerator: 11, Denominator: 1}, 54 | {Numerator: 22, Denominator: 1}, 55 | {Numerator: 33, Denominator: 1}, 56 | } 57 | 58 | if reflect.DeepEqual(actual, expected) != true { 59 | t.Fatalf("GpsInfo not correctly encoded down to raw: %v\n", actual) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /v3/ifd_tag_entry_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/dsoprea/go-logging" 8 | "github.com/dsoprea/go-utility/v2/filesystem" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestIfdTagEntry_RawBytes_Allocated(t *testing.T) { 14 | data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} 15 | 16 | addressableBytes := data 17 | sb := rifs.NewSeekableBufferWithBytes(addressableBytes) 18 | 19 | ite := newIfdTagEntry( 20 | exifcommon.IfdStandardIfdIdentity, 21 | 0x1, 22 | 0, 23 | exifcommon.TypeByte, 24 | 6, 25 | 0, 26 | nil, 27 | sb, 28 | exifcommon.TestDefaultByteOrder) 29 | 30 | value, err := ite.GetRawBytes() 31 | log.PanicIf(err) 32 | 33 | if bytes.Compare(value, data) != 0 { 34 | t.Fatalf("Value not expected: [%s] != [%s]", value, data) 35 | } 36 | } 37 | 38 | func TestIfdTagEntry_RawBytes_Embedded(t *testing.T) { 39 | defer func() { 40 | if state := recover(); state != nil { 41 | err := log.Wrap(state.(error)) 42 | log.PrintError(err) 43 | 44 | t.Fatalf("Test failure.") 45 | } 46 | }() 47 | 48 | data := []byte{0x11, 0x22, 0x33, 0x44} 49 | 50 | ite := newIfdTagEntry( 51 | exifcommon.IfdStandardIfdIdentity, 52 | 0x1, 53 | 0, 54 | exifcommon.TypeByte, 55 | 4, 56 | 0, 57 | data, 58 | nil, 59 | exifcommon.TestDefaultByteOrder) 60 | 61 | value, err := ite.GetRawBytes() 62 | log.PanicIf(err) 63 | 64 | if bytes.Compare(value, data) != 0 { 65 | t.Fatalf("Value not expected: %v != %v", value, data) 66 | } 67 | } 68 | 69 | func TestIfdTagEntry_String(t *testing.T) { 70 | ite := newIfdTagEntry( 71 | exifcommon.IfdStandardIfdIdentity, 72 | 0x1, 73 | 0, 74 | exifcommon.TypeByte, 75 | 6, 76 | 0, 77 | nil, 78 | nil, 79 | exifcommon.TestDefaultByteOrder) 80 | 81 | expected := "IfdTagEntry" 82 | if ite.String() != expected { 83 | t.Fatalf("string representation not expected: [%s] != [%s]", ite.String(), expected) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /v3/package.go: -------------------------------------------------------------------------------- 1 | // Package exif parses raw EXIF information given a block of raw EXIF data. It 2 | // can also construct new EXIF information, and provides tools for doing so. 3 | // This package is not involved with the parsing of particular file-formats. 4 | // 5 | // The EXIF data must first be extracted and then provided to us. Conversely, 6 | // when constructing new EXIF data, the caller is responsible for packaging 7 | // this in whichever format they require. 8 | package exif 9 | -------------------------------------------------------------------------------- /v3/tags_data_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dsoprea/go-logging" 7 | ) 8 | 9 | func TestGeotiffTags(t *testing.T) { 10 | testImageFilepath := getTestGeotiffFilepath() 11 | 12 | // Returns a slice starting with the EXIF data and going to the end of the 13 | // image. 14 | rawExif, err := SearchFileAndExtractExif(testImageFilepath) 15 | log.PanicIf(err) 16 | 17 | exifTags, _, err := GetFlatExifData(rawExif, nil) 18 | log.PanicIf(err) 19 | 20 | exifTagsIDMap := make(map[uint16]int) 21 | 22 | for _, e := range exifTags { 23 | exifTagsIDMap[e.TagId] = 1 24 | } 25 | 26 | if exifTagsIDMap[0x830e] == 0 { 27 | t.Fatal("Missing ModelPixelScaleTag.") 28 | } 29 | 30 | if exifTagsIDMap[0x8482] == 0 { 31 | t.Fatal("Missing ModelTiepointTag.") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /v3/undefined/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0xa40b 3 | 4 | The specification is not specific/clear enough to be handled. Without a working example ,we're deferring until some point in the future when either we or someone else has a better understanding. 5 | -------------------------------------------------------------------------------- /v3/undefined/accessor.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | // Encode encodes the given encodeable undefined value to bytes. 12 | func Encode(value EncodeableValue, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 13 | defer func() { 14 | if state := recover(); state != nil { 15 | err = log.Wrap(state.(error)) 16 | } 17 | }() 18 | 19 | encoderName := value.EncoderName() 20 | 21 | encoder, found := encoders[encoderName] 22 | if found == false { 23 | log.Panicf("no encoder registered for type [%s]", encoderName) 24 | } 25 | 26 | encoded, unitCount, err = encoder.Encode(value, byteOrder) 27 | log.PanicIf(err) 28 | 29 | return encoded, unitCount, nil 30 | } 31 | 32 | // Decode constructs a value from raw encoded bytes 33 | func Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 34 | defer func() { 35 | if state := recover(); state != nil { 36 | err = log.Wrap(state.(error)) 37 | } 38 | }() 39 | 40 | uth := UndefinedTagHandle{ 41 | IfdPath: valueContext.IfdPath(), 42 | TagId: valueContext.TagId(), 43 | } 44 | 45 | decoder, found := decoders[uth] 46 | if found == false { 47 | // We have no choice but to return the error. We have no way of knowing how 48 | // much data there is without already knowing what data-type this tag is. 49 | return nil, exifcommon.ErrUnhandledUndefinedTypedTag 50 | } 51 | 52 | value, err = decoder.Decode(valueContext) 53 | if err != nil { 54 | if err == ErrUnparseableValue { 55 | return nil, err 56 | } 57 | 58 | log.Panic(err) 59 | } 60 | 61 | return value, nil 62 | } 63 | -------------------------------------------------------------------------------- /v3/undefined/exif_8828_oecf.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | type Tag8828Oecf struct { 15 | Columns uint16 16 | Rows uint16 17 | ColumnNames []string 18 | Values []exifcommon.SignedRational 19 | } 20 | 21 | func (oecf Tag8828Oecf) String() string { 22 | return fmt.Sprintf("Tag8828Oecf", oecf.Columns, oecf.Rows) 23 | } 24 | 25 | func (oecf Tag8828Oecf) EncoderName() string { 26 | return "Codec8828Oecf" 27 | } 28 | 29 | type Codec8828Oecf struct { 30 | } 31 | 32 | func (Codec8828Oecf) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 33 | defer func() { 34 | if state := recover(); state != nil { 35 | err = log.Wrap(state.(error)) 36 | } 37 | }() 38 | 39 | // TODO(dustin): Add test 40 | 41 | oecf, ok := value.(Tag8828Oecf) 42 | if ok == false { 43 | log.Panicf("can only encode a Tag8828Oecf") 44 | } 45 | 46 | b := new(bytes.Buffer) 47 | 48 | err = binary.Write(b, byteOrder, oecf.Columns) 49 | log.PanicIf(err) 50 | 51 | err = binary.Write(b, byteOrder, oecf.Rows) 52 | log.PanicIf(err) 53 | 54 | for _, name := range oecf.ColumnNames { 55 | _, err := b.Write([]byte(name)) 56 | log.PanicIf(err) 57 | 58 | _, err = b.Write([]byte{0}) 59 | log.PanicIf(err) 60 | } 61 | 62 | ve := exifcommon.NewValueEncoder(byteOrder) 63 | 64 | ed, err := ve.Encode(oecf.Values) 65 | log.PanicIf(err) 66 | 67 | _, err = b.Write(ed.Encoded) 68 | log.PanicIf(err) 69 | 70 | return b.Bytes(), uint32(b.Len()), nil 71 | } 72 | 73 | func (Codec8828Oecf) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 74 | defer func() { 75 | if state := recover(); state != nil { 76 | err = log.Wrap(state.(error)) 77 | } 78 | }() 79 | 80 | // TODO(dustin): Add test using known good data. 81 | 82 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 83 | 84 | valueBytes, err := valueContext.ReadBytes() 85 | log.PanicIf(err) 86 | 87 | oecf := Tag8828Oecf{} 88 | 89 | oecf.Columns = valueContext.ByteOrder().Uint16(valueBytes[0:2]) 90 | oecf.Rows = valueContext.ByteOrder().Uint16(valueBytes[2:4]) 91 | 92 | columnNames := make([]string, oecf.Columns) 93 | 94 | // startAt is where the current column name starts. 95 | startAt := 4 96 | 97 | // offset is our current position. 98 | offset := startAt 99 | 100 | currentColumnNumber := uint16(0) 101 | 102 | for currentColumnNumber < oecf.Columns { 103 | if valueBytes[offset] == 0 { 104 | columnName := string(valueBytes[startAt:offset]) 105 | if len(columnName) == 0 { 106 | log.Panicf("SFR column (%d) has zero length", currentColumnNumber) 107 | } 108 | 109 | columnNames[currentColumnNumber] = columnName 110 | currentColumnNumber++ 111 | 112 | offset++ 113 | startAt = offset 114 | continue 115 | } 116 | 117 | offset++ 118 | } 119 | 120 | oecf.ColumnNames = columnNames 121 | 122 | rawRationalBytes := valueBytes[offset:] 123 | 124 | rationalSize := exifcommon.TypeSignedRational.Size() 125 | if len(rawRationalBytes)%rationalSize > 0 { 126 | log.Panicf("OECF signed-rationals not aligned: (%d) %% (%d) > 0", len(rawRationalBytes), rationalSize) 127 | } 128 | 129 | rationalCount := len(rawRationalBytes) / rationalSize 130 | 131 | parser := new(exifcommon.Parser) 132 | 133 | byteOrder := valueContext.ByteOrder() 134 | 135 | items, err := parser.ParseSignedRationals(rawRationalBytes, uint32(rationalCount), byteOrder) 136 | log.PanicIf(err) 137 | 138 | oecf.Values = items 139 | 140 | return oecf, nil 141 | } 142 | 143 | func init() { 144 | registerDecoder( 145 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 146 | 0x8828, 147 | Codec8828Oecf{}) 148 | } 149 | -------------------------------------------------------------------------------- /v3/undefined/exif_8828_oecf_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/dsoprea/go-utility/v2/filesystem" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | func TestTag8828Oecf_String(t *testing.T) { 15 | ut := Tag8828Oecf{ 16 | Columns: 11, 17 | Rows: 22, 18 | } 19 | 20 | s := ut.String() 21 | 22 | if s != "Tag8828Oecf" { 23 | t.Fatalf("String not correct: [%s]", s) 24 | } 25 | } 26 | 27 | func TestCodec8828Oecf_Encode(t *testing.T) { 28 | ut := Tag8828Oecf{ 29 | Columns: 2, 30 | Rows: 22, 31 | ColumnNames: []string{"aa", "bb"}, 32 | Values: []exifcommon.SignedRational{{11, 22}}, 33 | } 34 | 35 | codec := Codec8828Oecf{} 36 | 37 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 38 | log.PanicIf(err) 39 | 40 | expectedBytes := []byte{ 41 | 0x00, 0x02, 42 | 0x00, 0x16, 43 | 0x61, 0x61, 0x00, 0x62, 0x62, 0x00, 44 | 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x16} 45 | 46 | if bytes.Equal(encoded, expectedBytes) != true { 47 | exifcommon.DumpBytesClause(encoded) 48 | 49 | t.Fatalf("Encoded bytes not correct.") 50 | } else if unitCount != 18 { 51 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 52 | } 53 | } 54 | 55 | func TestCodec8828Oecf_Decode(t *testing.T) { 56 | encoded := []byte{ 57 | 0x00, 0x02, 58 | 0x00, 0x16, 59 | 0x61, 0x61, 0x00, 0x62, 0x62, 0x00, 60 | 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x16} 61 | 62 | addressableData := encoded 63 | sb := rifs.NewSeekableBufferWithBytes(addressableData) 64 | 65 | valueContext := exifcommon.NewValueContext( 66 | "", 67 | 0, 68 | uint32(len(encoded)), 69 | 0, 70 | nil, 71 | sb, 72 | exifcommon.TypeUndefined, 73 | exifcommon.TestDefaultByteOrder) 74 | 75 | codec := Codec8828Oecf{} 76 | 77 | value, err := codec.Decode(valueContext) 78 | log.PanicIf(err) 79 | 80 | expectedValue := Tag8828Oecf{ 81 | Columns: 2, 82 | Rows: 22, 83 | ColumnNames: []string{"aa", "bb"}, 84 | Values: []exifcommon.SignedRational{{11, 22}}, 85 | } 86 | 87 | if reflect.DeepEqual(value, expectedValue) != true { 88 | t.Fatalf("Decoded value not correct: %s", value) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /v3/undefined/exif_9000_exif_version.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | type Tag9000ExifVersion struct { 12 | ExifVersion string 13 | } 14 | 15 | func (Tag9000ExifVersion) EncoderName() string { 16 | return "Codec9000ExifVersion" 17 | } 18 | 19 | func (ev Tag9000ExifVersion) String() string { 20 | return ev.ExifVersion 21 | } 22 | 23 | type Codec9000ExifVersion struct { 24 | } 25 | 26 | func (Codec9000ExifVersion) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag9000ExifVersion) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag9000ExifVersion") 36 | } 37 | 38 | return []byte(s.ExifVersion), uint32(len(s.ExifVersion)), nil 39 | } 40 | 41 | func (Codec9000ExifVersion) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | ev := Tag9000ExifVersion{ 54 | ExifVersion: valueString, 55 | } 56 | 57 | return ev, nil 58 | } 59 | 60 | func init() { 61 | registerEncoder( 62 | Tag9000ExifVersion{}, 63 | Codec9000ExifVersion{}) 64 | 65 | registerDecoder( 66 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 67 | 0x9000, 68 | Codec9000ExifVersion{}) 69 | } 70 | -------------------------------------------------------------------------------- /v3/undefined/exif_9000_exif_version_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTag9000ExifVersion_String(t *testing.T) { 14 | ut := Tag9000ExifVersion{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec9000ExifVersion_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag9000ExifVersion{s} 25 | 26 | codec := Codec9000ExifVersion{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec9000ExifVersion_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag9000ExifVersion{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec9000ExifVersion{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v3/undefined/exif_9101_components_configuration.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | const ( 15 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Y = 0x1 16 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cb = 0x2 17 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cr = 0x3 18 | TagUndefinedType_9101_ComponentsConfiguration_Channel_R = 0x4 19 | TagUndefinedType_9101_ComponentsConfiguration_Channel_G = 0x5 20 | TagUndefinedType_9101_ComponentsConfiguration_Channel_B = 0x6 21 | ) 22 | 23 | const ( 24 | TagUndefinedType_9101_ComponentsConfiguration_OTHER = iota 25 | TagUndefinedType_9101_ComponentsConfiguration_RGB = iota 26 | TagUndefinedType_9101_ComponentsConfiguration_YCBCR = iota 27 | ) 28 | 29 | var ( 30 | TagUndefinedType_9101_ComponentsConfiguration_Names = map[int]string{ 31 | TagUndefinedType_9101_ComponentsConfiguration_OTHER: "OTHER", 32 | TagUndefinedType_9101_ComponentsConfiguration_RGB: "RGB", 33 | TagUndefinedType_9101_ComponentsConfiguration_YCBCR: "YCBCR", 34 | } 35 | 36 | TagUndefinedType_9101_ComponentsConfiguration_Configurations = map[int][]byte{ 37 | TagUndefinedType_9101_ComponentsConfiguration_RGB: { 38 | TagUndefinedType_9101_ComponentsConfiguration_Channel_R, 39 | TagUndefinedType_9101_ComponentsConfiguration_Channel_G, 40 | TagUndefinedType_9101_ComponentsConfiguration_Channel_B, 41 | 0, 42 | }, 43 | 44 | TagUndefinedType_9101_ComponentsConfiguration_YCBCR: { 45 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Y, 46 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cb, 47 | TagUndefinedType_9101_ComponentsConfiguration_Channel_Cr, 48 | 0, 49 | }, 50 | } 51 | ) 52 | 53 | type TagExif9101ComponentsConfiguration struct { 54 | ConfigurationId int 55 | ConfigurationBytes []byte 56 | } 57 | 58 | func (TagExif9101ComponentsConfiguration) EncoderName() string { 59 | return "CodecExif9101ComponentsConfiguration" 60 | } 61 | 62 | func (cc TagExif9101ComponentsConfiguration) String() string { 63 | return fmt.Sprintf("Exif9101ComponentsConfiguration", TagUndefinedType_9101_ComponentsConfiguration_Names[cc.ConfigurationId], cc.ConfigurationBytes) 64 | } 65 | 66 | type CodecExif9101ComponentsConfiguration struct { 67 | } 68 | 69 | func (CodecExif9101ComponentsConfiguration) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 70 | defer func() { 71 | if state := recover(); state != nil { 72 | err = log.Wrap(state.(error)) 73 | } 74 | }() 75 | 76 | cc, ok := value.(TagExif9101ComponentsConfiguration) 77 | if ok == false { 78 | log.Panicf("can only encode a TagExif9101ComponentsConfiguration") 79 | } 80 | 81 | return cc.ConfigurationBytes, uint32(len(cc.ConfigurationBytes)), nil 82 | } 83 | 84 | func (CodecExif9101ComponentsConfiguration) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 85 | defer func() { 86 | if state := recover(); state != nil { 87 | err = log.Wrap(state.(error)) 88 | } 89 | }() 90 | 91 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 92 | 93 | valueBytes, err := valueContext.ReadBytes() 94 | log.PanicIf(err) 95 | 96 | for configurationId, configurationBytes := range TagUndefinedType_9101_ComponentsConfiguration_Configurations { 97 | if bytes.Equal(configurationBytes, valueBytes) == true { 98 | cc := TagExif9101ComponentsConfiguration{ 99 | ConfigurationId: configurationId, 100 | ConfigurationBytes: valueBytes, 101 | } 102 | 103 | return cc, nil 104 | } 105 | } 106 | 107 | cc := TagExif9101ComponentsConfiguration{ 108 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_OTHER, 109 | ConfigurationBytes: valueBytes, 110 | } 111 | 112 | return cc, nil 113 | } 114 | 115 | func init() { 116 | registerEncoder( 117 | TagExif9101ComponentsConfiguration{}, 118 | CodecExif9101ComponentsConfiguration{}) 119 | 120 | registerDecoder( 121 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 122 | 0x9101, 123 | CodecExif9101ComponentsConfiguration{}) 124 | } 125 | -------------------------------------------------------------------------------- /v3/undefined/exif_9101_components_configuration_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTagExif9101ComponentsConfiguration_String(t *testing.T) { 14 | ut := TagExif9101ComponentsConfiguration{ 15 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_RGB, 16 | ConfigurationBytes: []byte{0x11, 0x22, 0x33, 0x44}, 17 | } 18 | 19 | s := ut.String() 20 | 21 | if s != "Exif9101ComponentsConfiguration" { 22 | t.Fatalf("String not correct: [%s]", s) 23 | } 24 | } 25 | 26 | func TestCodecExif9101ComponentsConfiguration_Encode(t *testing.T) { 27 | configurationBytes := []byte(TagUndefinedType_9101_ComponentsConfiguration_Names[TagUndefinedType_9101_ComponentsConfiguration_RGB]) 28 | 29 | ut := TagExif9101ComponentsConfiguration{ 30 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_RGB, 31 | ConfigurationBytes: configurationBytes, 32 | } 33 | 34 | codec := CodecExif9101ComponentsConfiguration{} 35 | 36 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 37 | log.PanicIf(err) 38 | 39 | if bytes.Equal(encoded, configurationBytes) != true { 40 | exifcommon.DumpBytesClause(encoded) 41 | 42 | t.Fatalf("Encoded bytes not correct: %v", encoded) 43 | } else if unitCount != uint32(len(configurationBytes)) { 44 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 45 | } 46 | 47 | s := string(configurationBytes) 48 | 49 | if s != TagUndefinedType_9101_ComponentsConfiguration_Names[TagUndefinedType_9101_ComponentsConfiguration_RGB] { 50 | t.Fatalf("Recovered configuration name not correct: [%s]", s) 51 | } 52 | } 53 | 54 | func TestCodecExif9101ComponentsConfiguration_Decode(t *testing.T) { 55 | configurationBytes := TagUndefinedType_9101_ComponentsConfiguration_Configurations[TagUndefinedType_9101_ComponentsConfiguration_RGB] 56 | 57 | ut := TagExif9101ComponentsConfiguration{ 58 | ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_RGB, 59 | ConfigurationBytes: configurationBytes, 60 | } 61 | 62 | rawValueOffset := configurationBytes 63 | 64 | valueContext := exifcommon.NewValueContext( 65 | "", 66 | 0, 67 | uint32(len(configurationBytes)), 68 | 0, 69 | rawValueOffset, 70 | nil, 71 | exifcommon.TypeUndefined, 72 | exifcommon.TestDefaultByteOrder) 73 | 74 | codec := CodecExif9101ComponentsConfiguration{} 75 | 76 | value, err := codec.Decode(valueContext) 77 | log.PanicIf(err) 78 | 79 | if reflect.DeepEqual(value, ut) != true { 80 | t.Fatalf("Decoded value not correct: %s != %s\n", value, ut) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /v3/undefined/exif_927C_maker_note.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "crypto/sha1" 8 | "encoding/binary" 9 | 10 | "github.com/dsoprea/go-logging" 11 | 12 | "github.com/dsoprea/go-exif/v3/common" 13 | ) 14 | 15 | type Tag927CMakerNote struct { 16 | MakerNoteType []byte 17 | MakerNoteBytes []byte 18 | } 19 | 20 | func (Tag927CMakerNote) EncoderName() string { 21 | return "Codec927CMakerNote" 22 | } 23 | 24 | func (mn Tag927CMakerNote) String() string { 25 | parts := make([]string, len(mn.MakerNoteType)) 26 | 27 | for i, c := range mn.MakerNoteType { 28 | parts[i] = fmt.Sprintf("%02x", c) 29 | } 30 | 31 | h := sha1.New() 32 | 33 | _, err := h.Write(mn.MakerNoteBytes) 34 | log.PanicIf(err) 35 | 36 | digest := h.Sum(nil) 37 | 38 | return fmt.Sprintf("MakerNote", strings.Join(parts, " "), len(mn.MakerNoteBytes), digest) 39 | } 40 | 41 | type Codec927CMakerNote struct { 42 | } 43 | 44 | func (Codec927CMakerNote) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 45 | defer func() { 46 | if state := recover(); state != nil { 47 | err = log.Wrap(state.(error)) 48 | } 49 | }() 50 | 51 | mn, ok := value.(Tag927CMakerNote) 52 | if ok == false { 53 | log.Panicf("can only encode a Tag927CMakerNote") 54 | } 55 | 56 | // TODO(dustin): Confirm this size against the specification. 57 | 58 | return mn.MakerNoteBytes, uint32(len(mn.MakerNoteBytes)), nil 59 | } 60 | 61 | func (Codec927CMakerNote) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 62 | defer func() { 63 | if state := recover(); state != nil { 64 | err = log.Wrap(state.(error)) 65 | } 66 | }() 67 | 68 | // MakerNote 69 | // TODO(dustin): !! This is the Wild Wild West. This very well might be a child IFD, but any and all OEM's define their own formats. If we're going to be writing changes and this is complete EXIF (which may not have the first eight bytes), it might be fine. However, if these are just IFDs they'll be relative to the main EXIF, this will invalidate the MakerNote data for IFDs and any other implementations that use offsets unless we can interpret them all. It be best to return to this later and just exclude this from being written for now, though means a loss of a wealth of image metadata. 70 | // -> We can also just blindly try to interpret as an IFD and just validate that it's looks good (maybe it will even have a 'next ifd' pointer that we can validate is 0x0). 71 | 72 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 73 | 74 | valueBytes, err := valueContext.ReadBytes() 75 | log.PanicIf(err) 76 | 77 | // TODO(dustin): Doesn't work, but here as an example. 78 | // ie := NewIfdEnumerate(valueBytes, byteOrder) 79 | 80 | // // TODO(dustin): !! Validate types (might have proprietary types, but it might be worth splitting the list between valid and not valid; maybe fail if a certain proportion are invalid, or maybe aren't less then a certain small integer)? 81 | // ii, err := ie.Collect(0x0) 82 | 83 | // for _, entry := range ii.RootIfd.Entries { 84 | // fmt.Printf("ENTRY: 0x%02x %d\n", entry.TagId, entry.TagType) 85 | // } 86 | 87 | var makerNoteType []byte 88 | if len(valueBytes) >= 20 { 89 | makerNoteType = valueBytes[:20] 90 | } else { 91 | makerNoteType = valueBytes 92 | } 93 | 94 | mn := Tag927CMakerNote{ 95 | MakerNoteType: makerNoteType, 96 | 97 | // MakerNoteBytes has the whole length of bytes. There's always 98 | // the chance that the first 20 bytes includes actual data. 99 | MakerNoteBytes: valueBytes, 100 | } 101 | 102 | return mn, nil 103 | } 104 | 105 | func init() { 106 | registerEncoder( 107 | Tag927CMakerNote{}, 108 | Codec927CMakerNote{}) 109 | 110 | registerDecoder( 111 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 112 | 0x927c, 113 | Codec927CMakerNote{}) 114 | } 115 | -------------------------------------------------------------------------------- /v3/undefined/exif_927C_maker_note_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/dsoprea/go-utility/v2/filesystem" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | func TestTag927CMakerNote_String(t *testing.T) { 15 | ut := Tag927CMakerNote{ 16 | MakerNoteType: []byte{0, 1, 2, 3, 4}, 17 | MakerNoteBytes: []byte{5, 6, 7, 8, 9}, 18 | } 19 | 20 | s := ut.String() 21 | if s != "MakerNote" { 22 | t.Fatalf("String not correct: [%s]", s) 23 | } 24 | } 25 | 26 | func TestCodec927CMakerNote_Encode(t *testing.T) { 27 | codec := Codec927CMakerNote{} 28 | 29 | prefix := []byte{0, 1, 2, 3, 4} 30 | b := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 31 | 32 | ut := Tag927CMakerNote{ 33 | MakerNoteType: prefix, 34 | MakerNoteBytes: b, 35 | } 36 | 37 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 38 | log.PanicIf(err) 39 | 40 | if bytes.Equal(encoded, b) != true { 41 | t.Fatalf("Encoding not correct: %v", encoded) 42 | } else if unitCount != uint32(len(b)) { 43 | t.Fatalf("Unit-count not correct: (%d)", len(b)) 44 | } 45 | } 46 | 47 | func TestCodec927CMakerNote_Decode(t *testing.T) { 48 | b := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 49 | 50 | ut := Tag927CMakerNote{ 51 | MakerNoteType: b, 52 | MakerNoteBytes: b, 53 | } 54 | 55 | sb := rifs.NewSeekableBufferWithBytes(b) 56 | 57 | valueContext := exifcommon.NewValueContext( 58 | "", 59 | 0, 60 | uint32(len(b)), 61 | 0, 62 | nil, 63 | sb, 64 | exifcommon.TypeUndefined, 65 | exifcommon.TestDefaultByteOrder) 66 | 67 | codec := Codec927CMakerNote{} 68 | 69 | value, err := codec.Decode(valueContext) 70 | log.PanicIf(err) 71 | 72 | if reflect.DeepEqual(value, ut) != true { 73 | t.Fatalf("Decoded value not correct: %s != %s", value, ut) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /v3/undefined/exif_9286_user_comment.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | var ( 15 | exif9286Logger = log.NewLogger("exifundefined.exif_9286_user_comment") 16 | ) 17 | 18 | const ( 19 | TagUndefinedType_9286_UserComment_Encoding_ASCII = iota 20 | TagUndefinedType_9286_UserComment_Encoding_JIS = iota 21 | TagUndefinedType_9286_UserComment_Encoding_UNICODE = iota 22 | TagUndefinedType_9286_UserComment_Encoding_UNDEFINED = iota 23 | ) 24 | 25 | var ( 26 | TagUndefinedType_9286_UserComment_Encoding_Names = map[int]string{ 27 | TagUndefinedType_9286_UserComment_Encoding_ASCII: "ASCII", 28 | TagUndefinedType_9286_UserComment_Encoding_JIS: "JIS", 29 | TagUndefinedType_9286_UserComment_Encoding_UNICODE: "UNICODE", 30 | TagUndefinedType_9286_UserComment_Encoding_UNDEFINED: "UNDEFINED", 31 | } 32 | 33 | TagUndefinedType_9286_UserComment_Encodings = map[int][]byte{ 34 | TagUndefinedType_9286_UserComment_Encoding_ASCII: {'A', 'S', 'C', 'I', 'I', 0, 0, 0}, 35 | TagUndefinedType_9286_UserComment_Encoding_JIS: {'J', 'I', 'S', 0, 0, 0, 0, 0}, 36 | TagUndefinedType_9286_UserComment_Encoding_UNICODE: {'U', 'n', 'i', 'c', 'o', 'd', 'e', 0}, 37 | TagUndefinedType_9286_UserComment_Encoding_UNDEFINED: {0, 0, 0, 0, 0, 0, 0, 0}, 38 | } 39 | ) 40 | 41 | type Tag9286UserComment struct { 42 | EncodingType int 43 | EncodingBytes []byte 44 | } 45 | 46 | func (Tag9286UserComment) EncoderName() string { 47 | return "Codec9286UserComment" 48 | } 49 | 50 | func (uc Tag9286UserComment) String() string { 51 | var valuePhrase string 52 | 53 | if uc.EncodingType == TagUndefinedType_9286_UserComment_Encoding_ASCII { 54 | return fmt.Sprintf("[ASCII] %s", string(uc.EncodingBytes)) 55 | } else { 56 | if len(uc.EncodingBytes) <= 8 { 57 | valuePhrase = fmt.Sprintf("%v", uc.EncodingBytes) 58 | } else { 59 | valuePhrase = fmt.Sprintf("%v...", uc.EncodingBytes[:8]) 60 | } 61 | } 62 | 63 | return fmt.Sprintf("UserComment", len(uc.EncodingBytes), TagUndefinedType_9286_UserComment_Encoding_Names[uc.EncodingType], valuePhrase, len(uc.EncodingBytes)) 64 | } 65 | 66 | type Codec9286UserComment struct { 67 | } 68 | 69 | func (Codec9286UserComment) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 70 | defer func() { 71 | if state := recover(); state != nil { 72 | err = log.Wrap(state.(error)) 73 | } 74 | }() 75 | 76 | uc, ok := value.(Tag9286UserComment) 77 | if ok == false { 78 | log.Panicf("can only encode a Tag9286UserComment") 79 | } 80 | 81 | encodingTypeBytes, found := TagUndefinedType_9286_UserComment_Encodings[uc.EncodingType] 82 | if found == false { 83 | log.Panicf("encoding-type not valid for unknown-type tag 9286 (UserComment): (%d)", uc.EncodingType) 84 | } 85 | 86 | encoded = make([]byte, len(uc.EncodingBytes)+8) 87 | 88 | copy(encoded[:8], encodingTypeBytes) 89 | copy(encoded[8:], uc.EncodingBytes) 90 | 91 | // TODO(dustin): Confirm this size against the specification. 92 | 93 | return encoded, uint32(len(encoded)), nil 94 | } 95 | 96 | func (Codec9286UserComment) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 97 | defer func() { 98 | if state := recover(); state != nil { 99 | err = log.Wrap(state.(error)) 100 | } 101 | }() 102 | 103 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 104 | 105 | valueBytes, err := valueContext.ReadBytes() 106 | log.PanicIf(err) 107 | 108 | if len(valueBytes) < 8 { 109 | return nil, ErrUnparseableValue 110 | } 111 | 112 | unknownUc := Tag9286UserComment{ 113 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_UNDEFINED, 114 | EncodingBytes: []byte{}, 115 | } 116 | 117 | encoding := valueBytes[:8] 118 | for encodingIndex, encodingBytes := range TagUndefinedType_9286_UserComment_Encodings { 119 | if bytes.Compare(encoding, encodingBytes) == 0 { 120 | uc := Tag9286UserComment{ 121 | EncodingType: encodingIndex, 122 | EncodingBytes: valueBytes[8:], 123 | } 124 | 125 | return uc, nil 126 | } 127 | } 128 | 129 | exif9286Logger.Warningf(nil, "User-comment encoding not valid. Returning 'unknown' type (the default).") 130 | return unknownUc, nil 131 | } 132 | 133 | func init() { 134 | registerEncoder( 135 | Tag9286UserComment{}, 136 | Codec9286UserComment{}) 137 | 138 | registerDecoder( 139 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 140 | 0x9286, 141 | Codec9286UserComment{}) 142 | } 143 | -------------------------------------------------------------------------------- /v3/undefined/exif_9286_user_comment_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/dsoprea/go-utility/v2/filesystem" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | func TestTag9286UserComment_String(t *testing.T) { 15 | comment := "some comment" 16 | 17 | ut := Tag9286UserComment{ 18 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, 19 | EncodingBytes: []byte(comment), 20 | } 21 | 22 | s := ut.String() 23 | if s != "[ASCII] some comment" { 24 | t.Fatalf("String not correct: [%s]", s) 25 | } 26 | } 27 | 28 | func TestCodec9286UserComment_Encode(t *testing.T) { 29 | comment := "some comment" 30 | 31 | ut := Tag9286UserComment{ 32 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, 33 | EncodingBytes: []byte(comment), 34 | } 35 | 36 | codec := Codec9286UserComment{} 37 | 38 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 39 | log.PanicIf(err) 40 | 41 | typeBytes := TagUndefinedType_9286_UserComment_Encodings[TagUndefinedType_9286_UserComment_Encoding_ASCII] 42 | if bytes.Equal(encoded[:8], typeBytes) != true { 43 | exifcommon.DumpBytesClause(encoded[:8]) 44 | 45 | t.Fatalf("Encoding type not correct.") 46 | } 47 | 48 | if bytes.Equal(encoded[8:], []byte(comment)) != true { 49 | exifcommon.DumpBytesClause(encoded[8:]) 50 | 51 | t.Fatalf("Encoded comment not correct.") 52 | } 53 | 54 | if unitCount != uint32(len(encoded)) { 55 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 56 | } 57 | 58 | exifcommon.DumpBytesClause(encoded) 59 | 60 | } 61 | 62 | func TestCodec9286UserComment_Decode(t *testing.T) { 63 | encoded := []byte{ 64 | 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00, 65 | 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 66 | } 67 | 68 | addressableBytes := encoded 69 | sb := rifs.NewSeekableBufferWithBytes(addressableBytes) 70 | 71 | valueContext := exifcommon.NewValueContext( 72 | "", 73 | 0, 74 | uint32(len(encoded)), 75 | 0, 76 | nil, 77 | sb, 78 | exifcommon.TypeUndefined, 79 | exifcommon.TestDefaultByteOrder) 80 | 81 | codec := Codec9286UserComment{} 82 | 83 | decoded, err := codec.Decode(valueContext) 84 | log.PanicIf(err) 85 | 86 | comment := "some comment" 87 | 88 | expectedUt := Tag9286UserComment{ 89 | EncodingType: TagUndefinedType_9286_UserComment_Encoding_ASCII, 90 | EncodingBytes: []byte(comment), 91 | } 92 | 93 | if reflect.DeepEqual(decoded, expectedUt) != true { 94 | t.Fatalf("Decoded struct not correct.") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /v3/undefined/exif_A000_flashpix_version.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | type TagA000FlashpixVersion struct { 12 | FlashpixVersion string 13 | } 14 | 15 | func (TagA000FlashpixVersion) EncoderName() string { 16 | return "CodecA000FlashpixVersion" 17 | } 18 | 19 | func (fv TagA000FlashpixVersion) String() string { 20 | return fv.FlashpixVersion 21 | } 22 | 23 | type CodecA000FlashpixVersion struct { 24 | } 25 | 26 | func (CodecA000FlashpixVersion) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(TagA000FlashpixVersion) 34 | if ok == false { 35 | log.Panicf("can only encode a TagA000FlashpixVersion") 36 | } 37 | 38 | return []byte(s.FlashpixVersion), uint32(len(s.FlashpixVersion)), nil 39 | } 40 | 41 | func (CodecA000FlashpixVersion) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | fv := TagA000FlashpixVersion{ 54 | FlashpixVersion: valueString, 55 | } 56 | 57 | return fv, nil 58 | } 59 | 60 | func init() { 61 | registerEncoder( 62 | TagA000FlashpixVersion{}, 63 | CodecA000FlashpixVersion{}) 64 | 65 | registerDecoder( 66 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 67 | 0xa000, 68 | CodecA000FlashpixVersion{}) 69 | } 70 | -------------------------------------------------------------------------------- /v3/undefined/exif_A000_flashpix_version_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/dsoprea/go-utility/v2/filesystem" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | func TestTagA000FlashpixVersion_String(t *testing.T) { 15 | versionPhrase := "some version" 16 | 17 | ut := TagA000FlashpixVersion{versionPhrase} 18 | 19 | s := ut.String() 20 | if s != versionPhrase { 21 | t.Fatalf("String not correct: [%s]", s) 22 | } 23 | } 24 | 25 | func TestCodecA000FlashpixVersion_Encode(t *testing.T) { 26 | versionPhrase := "some version" 27 | 28 | ut := TagA000FlashpixVersion{versionPhrase} 29 | 30 | codec := CodecA000FlashpixVersion{} 31 | 32 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 33 | log.PanicIf(err) 34 | 35 | if bytes.Equal(encoded, []byte(versionPhrase)) != true { 36 | exifcommon.DumpBytesClause(encoded) 37 | 38 | t.Fatalf("Encoding not correct.") 39 | } else if unitCount != uint32(len(encoded)) { 40 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 41 | } 42 | } 43 | 44 | func TestCodecA000FlashpixVersion_Decode(t *testing.T) { 45 | versionPhrase := "some version" 46 | 47 | expectedUt := TagA000FlashpixVersion{versionPhrase} 48 | 49 | encoded := []byte(versionPhrase) 50 | 51 | addressableBytes := encoded 52 | sb := rifs.NewSeekableBufferWithBytes(addressableBytes) 53 | 54 | valueContext := exifcommon.NewValueContext( 55 | "", 56 | 0, 57 | uint32(len(encoded)), 58 | 0, 59 | nil, 60 | sb, 61 | exifcommon.TypeUndefined, 62 | exifcommon.TestDefaultByteOrder) 63 | 64 | codec := CodecA000FlashpixVersion{} 65 | 66 | decoded, err := codec.Decode(valueContext) 67 | log.PanicIf(err) 68 | 69 | if reflect.DeepEqual(decoded, expectedUt) != true { 70 | t.Fatalf("Decoded struct not correct.") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /v3/undefined/exif_A20C_spatial_frequency_response.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | type TagA20CSpatialFrequencyResponse struct { 15 | Columns uint16 16 | Rows uint16 17 | ColumnNames []string 18 | Values []exifcommon.Rational 19 | } 20 | 21 | func (TagA20CSpatialFrequencyResponse) EncoderName() string { 22 | return "CodecA20CSpatialFrequencyResponse" 23 | } 24 | 25 | func (sfr TagA20CSpatialFrequencyResponse) String() string { 26 | return fmt.Sprintf("CodecA20CSpatialFrequencyResponse", sfr.Columns, sfr.Rows) 27 | } 28 | 29 | type CodecA20CSpatialFrequencyResponse struct { 30 | } 31 | 32 | func (CodecA20CSpatialFrequencyResponse) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 33 | defer func() { 34 | if state := recover(); state != nil { 35 | err = log.Wrap(state.(error)) 36 | } 37 | }() 38 | 39 | // TODO(dustin): Add test. 40 | 41 | sfr, ok := value.(TagA20CSpatialFrequencyResponse) 42 | if ok == false { 43 | log.Panicf("can only encode a TagA20CSpatialFrequencyResponse") 44 | } 45 | 46 | b := new(bytes.Buffer) 47 | 48 | err = binary.Write(b, byteOrder, sfr.Columns) 49 | log.PanicIf(err) 50 | 51 | err = binary.Write(b, byteOrder, sfr.Rows) 52 | log.PanicIf(err) 53 | 54 | // Write columns. 55 | 56 | for _, name := range sfr.ColumnNames { 57 | _, err := b.WriteString(name) 58 | log.PanicIf(err) 59 | 60 | err = b.WriteByte(0) 61 | log.PanicIf(err) 62 | } 63 | 64 | // Write values. 65 | 66 | ve := exifcommon.NewValueEncoder(byteOrder) 67 | 68 | ed, err := ve.Encode(sfr.Values) 69 | log.PanicIf(err) 70 | 71 | _, err = b.Write(ed.Encoded) 72 | log.PanicIf(err) 73 | 74 | encoded = b.Bytes() 75 | 76 | // TODO(dustin): Confirm this size against the specification. 77 | 78 | return encoded, uint32(len(encoded)), nil 79 | } 80 | 81 | func (CodecA20CSpatialFrequencyResponse) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 82 | defer func() { 83 | if state := recover(); state != nil { 84 | err = log.Wrap(state.(error)) 85 | } 86 | }() 87 | 88 | // TODO(dustin): Add test using known good data. 89 | 90 | byteOrder := valueContext.ByteOrder() 91 | 92 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 93 | 94 | valueBytes, err := valueContext.ReadBytes() 95 | log.PanicIf(err) 96 | 97 | sfr := TagA20CSpatialFrequencyResponse{} 98 | 99 | sfr.Columns = byteOrder.Uint16(valueBytes[0:2]) 100 | sfr.Rows = byteOrder.Uint16(valueBytes[2:4]) 101 | 102 | columnNames := make([]string, sfr.Columns) 103 | 104 | // startAt is where the current column name starts. 105 | startAt := 4 106 | 107 | // offset is our current position. 108 | offset := 4 109 | 110 | currentColumnNumber := uint16(0) 111 | 112 | for currentColumnNumber < sfr.Columns { 113 | if valueBytes[offset] == 0 { 114 | columnName := string(valueBytes[startAt:offset]) 115 | if len(columnName) == 0 { 116 | log.Panicf("SFR column (%d) has zero length", currentColumnNumber) 117 | } 118 | 119 | columnNames[currentColumnNumber] = columnName 120 | currentColumnNumber++ 121 | 122 | offset++ 123 | startAt = offset 124 | continue 125 | } 126 | 127 | offset++ 128 | } 129 | 130 | sfr.ColumnNames = columnNames 131 | 132 | rawRationalBytes := valueBytes[offset:] 133 | 134 | rationalSize := exifcommon.TypeRational.Size() 135 | if len(rawRationalBytes)%rationalSize > 0 { 136 | log.Panicf("SFR rationals not aligned: (%d) %% (%d) > 0", len(rawRationalBytes), rationalSize) 137 | } 138 | 139 | rationalCount := len(rawRationalBytes) / rationalSize 140 | 141 | parser := new(exifcommon.Parser) 142 | 143 | items, err := parser.ParseRationals(rawRationalBytes, uint32(rationalCount), byteOrder) 144 | log.PanicIf(err) 145 | 146 | sfr.Values = items 147 | 148 | return sfr, nil 149 | } 150 | 151 | func init() { 152 | registerEncoder( 153 | TagA20CSpatialFrequencyResponse{}, 154 | CodecA20CSpatialFrequencyResponse{}) 155 | 156 | registerDecoder( 157 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 158 | 0xa20c, 159 | CodecA20CSpatialFrequencyResponse{}) 160 | } 161 | -------------------------------------------------------------------------------- /v3/undefined/exif_A20C_spatial_frequency_response_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/dsoprea/go-utility/v2/filesystem" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | func TestTagA20CSpatialFrequencyResponse_String(t *testing.T) { 15 | ut := TagA20CSpatialFrequencyResponse{ 16 | Columns: 2, 17 | Rows: 9, 18 | ColumnNames: []string{"column1", "column2"}, 19 | Values: []exifcommon.Rational{ 20 | {1, 2}, 21 | {3, 4}, 22 | }, 23 | } 24 | 25 | s := ut.String() 26 | if s != "CodecA20CSpatialFrequencyResponse" { 27 | t.Fatalf("String not correct: [%s]", s) 28 | } 29 | } 30 | 31 | func TestCodecA20CSpatialFrequencyResponse_Encode(t *testing.T) { 32 | ut := TagA20CSpatialFrequencyResponse{ 33 | Columns: 2, 34 | Rows: 9, 35 | ColumnNames: []string{"column1", "column2"}, 36 | Values: []exifcommon.Rational{ 37 | {1, 2}, 38 | {3, 4}, 39 | }, 40 | } 41 | 42 | codec := CodecA20CSpatialFrequencyResponse{} 43 | 44 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 45 | log.PanicIf(err) 46 | 47 | expectedEncoded := []byte{ 48 | 0x00, 0x02, 49 | 0x00, 0x09, 50 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x31, 0x00, 51 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x32, 0x00, 52 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 53 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 54 | } 55 | 56 | if bytes.Equal(encoded, expectedEncoded) != true { 57 | exifcommon.DumpBytesClause(encoded) 58 | 59 | t.Fatalf("Encoding not correct.") 60 | } else if unitCount != uint32(len(encoded)) { 61 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 62 | } 63 | } 64 | 65 | func TestCodecA20CSpatialFrequencyResponse_Decode(t *testing.T) { 66 | expectedUt := TagA20CSpatialFrequencyResponse{ 67 | Columns: 2, 68 | Rows: 9, 69 | ColumnNames: []string{"column1", "column2"}, 70 | Values: []exifcommon.Rational{ 71 | {1, 2}, 72 | {3, 4}, 73 | }, 74 | } 75 | 76 | encoded := []byte{ 77 | 0x00, 0x02, 78 | 0x00, 0x09, 79 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x31, 0x00, 80 | 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x32, 0x00, 81 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 82 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 83 | } 84 | 85 | addressableBytes := encoded 86 | sb := rifs.NewSeekableBufferWithBytes(addressableBytes) 87 | 88 | valueContext := exifcommon.NewValueContext( 89 | "", 90 | 0, 91 | uint32(len(encoded)), 92 | 0, 93 | nil, 94 | sb, 95 | exifcommon.TypeUndefined, 96 | exifcommon.TestDefaultByteOrder) 97 | 98 | codec := CodecA20CSpatialFrequencyResponse{} 99 | 100 | decoded, err := codec.Decode(valueContext) 101 | log.PanicIf(err) 102 | 103 | if reflect.DeepEqual(decoded, expectedUt) != true { 104 | t.Fatalf("Decoded struct not correct.") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /v3/undefined/exif_A300_file_source.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "fmt" 5 | 6 | "encoding/binary" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | type TagExifA300FileSource uint32 14 | 15 | func (TagExifA300FileSource) EncoderName() string { 16 | return "CodecExifA300FileSource" 17 | } 18 | 19 | func (af TagExifA300FileSource) String() string { 20 | return fmt.Sprintf("0x%08x", uint32(af)) 21 | } 22 | 23 | const ( 24 | TagUndefinedType_A300_SceneType_Others TagExifA300FileSource = 0 25 | TagUndefinedType_A300_SceneType_ScannerOfTransparentType TagExifA300FileSource = 1 26 | TagUndefinedType_A300_SceneType_ScannerOfReflexType TagExifA300FileSource = 2 27 | TagUndefinedType_A300_SceneType_Dsc TagExifA300FileSource = 3 28 | ) 29 | 30 | type CodecExifA300FileSource struct { 31 | } 32 | 33 | func (CodecExifA300FileSource) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 34 | defer func() { 35 | if state := recover(); state != nil { 36 | err = log.Wrap(state.(error)) 37 | } 38 | }() 39 | 40 | st, ok := value.(TagExifA300FileSource) 41 | if ok == false { 42 | log.Panicf("can only encode a TagExifA300FileSource") 43 | } 44 | 45 | ve := exifcommon.NewValueEncoder(byteOrder) 46 | 47 | ed, err := ve.Encode([]uint32{uint32(st)}) 48 | log.PanicIf(err) 49 | 50 | // TODO(dustin): Confirm this size against the specification. It's non-specific about what type it is, but it looks to be no more than a single integer scalar. So, we're assuming it's a LONG. 51 | 52 | return ed.Encoded, 1, nil 53 | } 54 | 55 | func (CodecExifA300FileSource) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 56 | defer func() { 57 | if state := recover(); state != nil { 58 | err = log.Wrap(state.(error)) 59 | } 60 | }() 61 | 62 | valueContext.SetUndefinedValueType(exifcommon.TypeLong) 63 | 64 | valueLongs, err := valueContext.ReadLongs() 65 | log.PanicIf(err) 66 | 67 | return TagExifA300FileSource(valueLongs[0]), nil 68 | } 69 | 70 | func init() { 71 | registerEncoder( 72 | TagExifA300FileSource(0), 73 | CodecExifA300FileSource{}) 74 | 75 | registerDecoder( 76 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 77 | 0xa300, 78 | CodecExifA300FileSource{}) 79 | } 80 | -------------------------------------------------------------------------------- /v3/undefined/exif_A300_file_source_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTagExifA300FileSource_String(t *testing.T) { 14 | ut := TagExifA300FileSource(0x1234) 15 | 16 | s := ut.String() 17 | if s != "0x00001234" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodecExifA300FileSource_Encode(t *testing.T) { 23 | ut := TagExifA300FileSource(0x1234) 24 | 25 | codec := CodecExifA300FileSource{} 26 | 27 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 28 | log.PanicIf(err) 29 | 30 | expectedEncoded := []byte{0, 0, 0x12, 0x34} 31 | 32 | if bytes.Equal(encoded, expectedEncoded) != true { 33 | exifcommon.DumpBytesClause(encoded) 34 | 35 | t.Fatalf("Encoding not correct.") 36 | } else if unitCount != 1 { 37 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 38 | } 39 | } 40 | 41 | func TestCodecExifA300FileSource_Decode(t *testing.T) { 42 | expectedUt := TagExifA300FileSource(0x1234) 43 | 44 | encoded := []byte{0, 0, 0x12, 0x34} 45 | 46 | rawValueOffset := encoded 47 | 48 | valueContext := exifcommon.NewValueContext( 49 | "", 50 | 0, 51 | 1, 52 | 0, 53 | rawValueOffset, 54 | nil, 55 | exifcommon.TypeUndefined, 56 | exifcommon.TestDefaultByteOrder) 57 | 58 | codec := CodecExifA300FileSource{} 59 | 60 | decoded, err := codec.Decode(valueContext) 61 | log.PanicIf(err) 62 | 63 | if reflect.DeepEqual(decoded, expectedUt) != true { 64 | t.Fatalf("Decoded struct not correct.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /v3/undefined/exif_A301_scene_type.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "fmt" 5 | 6 | "encoding/binary" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | type TagExifA301SceneType uint32 14 | 15 | func (TagExifA301SceneType) EncoderName() string { 16 | return "CodecExifA301SceneType" 17 | } 18 | 19 | func (st TagExifA301SceneType) String() string { 20 | return fmt.Sprintf("0x%08x", uint32(st)) 21 | } 22 | 23 | const ( 24 | TagUndefinedType_A301_SceneType_DirectlyPhotographedImage TagExifA301SceneType = 1 25 | ) 26 | 27 | type CodecExifA301SceneType struct { 28 | } 29 | 30 | func (CodecExifA301SceneType) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 31 | defer func() { 32 | if state := recover(); state != nil { 33 | err = log.Wrap(state.(error)) 34 | } 35 | }() 36 | 37 | st, ok := value.(TagExifA301SceneType) 38 | if ok == false { 39 | log.Panicf("can only encode a TagExif9101ComponentsConfiguration") 40 | } 41 | 42 | ve := exifcommon.NewValueEncoder(byteOrder) 43 | 44 | ed, err := ve.Encode([]uint32{uint32(st)}) 45 | log.PanicIf(err) 46 | 47 | // TODO(dustin): Confirm this size against the specification. It's non-specific about what type it is, but it looks to be no more than a single integer scalar. So, we're assuming it's a LONG. 48 | 49 | return ed.Encoded, uint32(int(ed.UnitCount)), nil 50 | } 51 | 52 | func (CodecExifA301SceneType) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 53 | defer func() { 54 | if state := recover(); state != nil { 55 | err = log.Wrap(state.(error)) 56 | } 57 | }() 58 | 59 | valueContext.SetUndefinedValueType(exifcommon.TypeLong) 60 | 61 | valueLongs, err := valueContext.ReadLongs() 62 | log.PanicIf(err) 63 | 64 | return TagExifA301SceneType(valueLongs[0]), nil 65 | } 66 | 67 | func init() { 68 | registerEncoder( 69 | TagExifA301SceneType(0), 70 | CodecExifA301SceneType{}) 71 | 72 | registerDecoder( 73 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 74 | 0xa301, 75 | CodecExifA301SceneType{}) 76 | } 77 | -------------------------------------------------------------------------------- /v3/undefined/exif_A301_scene_type_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTagExifA301SceneType_String(t *testing.T) { 14 | ut := TagExifA301SceneType(0x1234) 15 | 16 | s := ut.String() 17 | if s != "0x00001234" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodecExifA301SceneType_Encode(t *testing.T) { 23 | ut := TagExifA301SceneType(0x1234) 24 | 25 | codec := CodecExifA301SceneType{} 26 | 27 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 28 | log.PanicIf(err) 29 | 30 | expectedEncoded := []byte{0, 0, 0x12, 0x34} 31 | 32 | if bytes.Equal(encoded, expectedEncoded) != true { 33 | exifcommon.DumpBytesClause(encoded) 34 | 35 | t.Fatalf("Encoding not correct.") 36 | } else if unitCount != 1 { 37 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 38 | } 39 | } 40 | 41 | func TestCodecExifA301SceneType_Decode(t *testing.T) { 42 | expectedUt := TagExifA301SceneType(0x1234) 43 | 44 | encoded := []byte{0, 0, 0x12, 0x34} 45 | 46 | rawValueOffset := encoded 47 | 48 | valueContext := exifcommon.NewValueContext( 49 | "", 50 | 0, 51 | 1, 52 | 0, 53 | rawValueOffset, 54 | nil, 55 | exifcommon.TypeUndefined, 56 | exifcommon.TestDefaultByteOrder) 57 | 58 | codec := CodecExifA301SceneType{} 59 | 60 | decoded, err := codec.Decode(valueContext) 61 | log.PanicIf(err) 62 | 63 | if reflect.DeepEqual(decoded, expectedUt) != true { 64 | t.Fatalf("Decoded struct not correct.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /v3/undefined/exif_A302_cfa_pattern.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "encoding/binary" 8 | 9 | "github.com/dsoprea/go-logging" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | type TagA302CfaPattern struct { 15 | HorizontalRepeat uint16 16 | VerticalRepeat uint16 17 | CfaValue []byte 18 | } 19 | 20 | func (TagA302CfaPattern) EncoderName() string { 21 | return "CodecA302CfaPattern" 22 | } 23 | 24 | func (cp TagA302CfaPattern) String() string { 25 | return fmt.Sprintf("TagA302CfaPattern", cp.HorizontalRepeat, cp.VerticalRepeat, len(cp.CfaValue)) 26 | } 27 | 28 | type CodecA302CfaPattern struct { 29 | } 30 | 31 | func (CodecA302CfaPattern) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 32 | defer func() { 33 | if state := recover(); state != nil { 34 | err = log.Wrap(state.(error)) 35 | } 36 | }() 37 | 38 | // TODO(dustin): Add test. 39 | 40 | cp, ok := value.(TagA302CfaPattern) 41 | if ok == false { 42 | log.Panicf("can only encode a TagA302CfaPattern") 43 | } 44 | 45 | b := new(bytes.Buffer) 46 | 47 | err = binary.Write(b, byteOrder, cp.HorizontalRepeat) 48 | log.PanicIf(err) 49 | 50 | err = binary.Write(b, byteOrder, cp.VerticalRepeat) 51 | log.PanicIf(err) 52 | 53 | _, err = b.Write(cp.CfaValue) 54 | log.PanicIf(err) 55 | 56 | encoded = b.Bytes() 57 | 58 | // TODO(dustin): Confirm this size against the specification. 59 | 60 | return encoded, uint32(len(encoded)), nil 61 | } 62 | 63 | func (CodecA302CfaPattern) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 64 | defer func() { 65 | if state := recover(); state != nil { 66 | err = log.Wrap(state.(error)) 67 | } 68 | }() 69 | 70 | // TODO(dustin): Add test using known good data. 71 | 72 | valueContext.SetUndefinedValueType(exifcommon.TypeByte) 73 | 74 | valueBytes, err := valueContext.ReadBytes() 75 | log.PanicIf(err) 76 | 77 | cp := TagA302CfaPattern{} 78 | 79 | cp.HorizontalRepeat = valueContext.ByteOrder().Uint16(valueBytes[0:2]) 80 | cp.VerticalRepeat = valueContext.ByteOrder().Uint16(valueBytes[2:4]) 81 | 82 | expectedLength := int(cp.HorizontalRepeat * cp.VerticalRepeat) 83 | cp.CfaValue = valueBytes[4 : 4+expectedLength] 84 | 85 | return cp, nil 86 | } 87 | 88 | func init() { 89 | registerEncoder( 90 | TagA302CfaPattern{}, 91 | CodecA302CfaPattern{}) 92 | 93 | registerDecoder( 94 | exifcommon.IfdExifStandardIfdIdentity.UnindexedString(), 95 | 0xa302, 96 | CodecA302CfaPattern{}) 97 | } 98 | -------------------------------------------------------------------------------- /v3/undefined/exif_A302_cfa_pattern_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | "github.com/dsoprea/go-utility/v2/filesystem" 10 | 11 | "github.com/dsoprea/go-exif/v3/common" 12 | ) 13 | 14 | func TestTagA302CfaPattern_String(t *testing.T) { 15 | ut := TagA302CfaPattern{ 16 | HorizontalRepeat: 2, 17 | VerticalRepeat: 3, 18 | CfaValue: []byte{ 19 | 0, 1, 2, 3, 4, 5, 20 | }, 21 | } 22 | 23 | s := ut.String() 24 | 25 | if s != "TagA302CfaPattern" { 26 | t.Fatalf("String not correct: [%s]", s) 27 | } 28 | } 29 | 30 | func TestCodecA302CfaPattern_Encode(t *testing.T) { 31 | ut := TagA302CfaPattern{ 32 | HorizontalRepeat: 2, 33 | VerticalRepeat: 3, 34 | CfaValue: []byte{ 35 | 0, 1, 2, 3, 4, 36 | 5, 6, 7, 8, 9, 37 | 10, 11, 12, 13, 14, 38 | }, 39 | } 40 | 41 | codec := CodecA302CfaPattern{} 42 | 43 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 44 | log.PanicIf(err) 45 | 46 | expectedBytes := []byte{ 47 | 0x00, 0x02, 48 | 0x00, 0x03, 49 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 50 | } 51 | 52 | if bytes.Equal(encoded, expectedBytes) != true { 53 | exifcommon.DumpBytesClause(encoded) 54 | 55 | t.Fatalf("Encoded bytes not correct.") 56 | } else if unitCount != 19 { 57 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 58 | } 59 | } 60 | 61 | func TestCodecA302CfaPattern_Decode(t *testing.T) { 62 | encoded := []byte{ 63 | 0x00, 0x02, 64 | 0x00, 0x03, 65 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 66 | } 67 | 68 | addressableBytes := encoded 69 | sb := rifs.NewSeekableBufferWithBytes(addressableBytes) 70 | 71 | valueContext := exifcommon.NewValueContext( 72 | "", 73 | 0, 74 | uint32(len(encoded)), 75 | 0, 76 | nil, 77 | sb, 78 | exifcommon.TypeUndefined, 79 | exifcommon.TestDefaultByteOrder) 80 | 81 | codec := CodecA302CfaPattern{} 82 | 83 | value, err := codec.Decode(valueContext) 84 | log.PanicIf(err) 85 | 86 | expectedValue := TagA302CfaPattern{ 87 | HorizontalRepeat: 2, 88 | VerticalRepeat: 3, 89 | CfaValue: []byte{ 90 | 0, 1, 2, 3, 4, 5, 91 | }, 92 | } 93 | 94 | if reflect.DeepEqual(value, expectedValue) != true { 95 | t.Fatalf("Decoded value not correct: %s", value) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /v3/undefined/exif_iop_0002_interop_version.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | type Tag0002InteropVersion struct { 12 | InteropVersion string 13 | } 14 | 15 | func (Tag0002InteropVersion) EncoderName() string { 16 | return "Codec0002InteropVersion" 17 | } 18 | 19 | func (iv Tag0002InteropVersion) String() string { 20 | return iv.InteropVersion 21 | } 22 | 23 | type Codec0002InteropVersion struct { 24 | } 25 | 26 | func (Codec0002InteropVersion) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag0002InteropVersion) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag0002InteropVersion") 36 | } 37 | 38 | return []byte(s.InteropVersion), uint32(len(s.InteropVersion)), nil 39 | } 40 | 41 | func (Codec0002InteropVersion) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | iv := Tag0002InteropVersion{ 54 | InteropVersion: valueString, 55 | } 56 | 57 | return iv, nil 58 | } 59 | 60 | func init() { 61 | registerEncoder( 62 | Tag0002InteropVersion{}, 63 | Codec0002InteropVersion{}) 64 | 65 | registerDecoder( 66 | exifcommon.IfdExifIopStandardIfdIdentity.UnindexedString(), 67 | 0x0002, 68 | Codec0002InteropVersion{}) 69 | } 70 | -------------------------------------------------------------------------------- /v3/undefined/exif_iop_0002_interop_version_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTag0002InteropVersion_String(t *testing.T) { 14 | ut := Tag0002InteropVersion{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec0002InteropVersion_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag0002InteropVersion{s} 25 | 26 | codec := Codec0002InteropVersion{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec0002InteropVersion_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag0002InteropVersion{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec0002InteropVersion{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v3/undefined/gps_001B_gps_processing_method.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | type Tag001BGPSProcessingMethod struct { 12 | string 13 | } 14 | 15 | func (Tag001BGPSProcessingMethod) EncoderName() string { 16 | return "Codec001BGPSProcessingMethod" 17 | } 18 | 19 | func (gpm Tag001BGPSProcessingMethod) String() string { 20 | return gpm.string 21 | } 22 | 23 | type Codec001BGPSProcessingMethod struct { 24 | } 25 | 26 | func (Codec001BGPSProcessingMethod) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag001BGPSProcessingMethod) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag001BGPSProcessingMethod") 36 | } 37 | 38 | return []byte(s.string), uint32(len(s.string)), nil 39 | } 40 | 41 | func (Codec001BGPSProcessingMethod) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | return Tag001BGPSProcessingMethod{valueString}, nil 54 | } 55 | 56 | func init() { 57 | registerEncoder( 58 | Tag001BGPSProcessingMethod{}, 59 | Codec001BGPSProcessingMethod{}) 60 | 61 | registerDecoder( 62 | exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString(), 63 | 0x001b, 64 | Codec001BGPSProcessingMethod{}) 65 | } 66 | -------------------------------------------------------------------------------- /v3/undefined/gps_001B_gps_processing_method_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTag001BGPSProcessingMethod_String(t *testing.T) { 14 | ut := Tag001BGPSProcessingMethod{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec001BGPSProcessingMethod_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag001BGPSProcessingMethod{s} 25 | 26 | codec := Codec001BGPSProcessingMethod{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec001BGPSProcessingMethod_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag001BGPSProcessingMethod{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec001BGPSProcessingMethod{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v3/undefined/gps_001C_gps_area_information.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/dsoprea/go-logging" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | type Tag001CGPSAreaInformation struct { 12 | string 13 | } 14 | 15 | func (Tag001CGPSAreaInformation) EncoderName() string { 16 | return "Codec001CGPSAreaInformation" 17 | } 18 | 19 | func (gai Tag001CGPSAreaInformation) String() string { 20 | return gai.string 21 | } 22 | 23 | type Codec001CGPSAreaInformation struct { 24 | } 25 | 26 | func (Codec001CGPSAreaInformation) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { 27 | defer func() { 28 | if state := recover(); state != nil { 29 | err = log.Wrap(state.(error)) 30 | } 31 | }() 32 | 33 | s, ok := value.(Tag001CGPSAreaInformation) 34 | if ok == false { 35 | log.Panicf("can only encode a Tag001CGPSAreaInformation") 36 | } 37 | 38 | return []byte(s.string), uint32(len(s.string)), nil 39 | } 40 | 41 | func (Codec001CGPSAreaInformation) Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) { 42 | defer func() { 43 | if state := recover(); state != nil { 44 | err = log.Wrap(state.(error)) 45 | } 46 | }() 47 | 48 | valueContext.SetUndefinedValueType(exifcommon.TypeAsciiNoNul) 49 | 50 | valueString, err := valueContext.ReadAsciiNoNul() 51 | log.PanicIf(err) 52 | 53 | return Tag001CGPSAreaInformation{valueString}, nil 54 | } 55 | 56 | func init() { 57 | registerEncoder( 58 | Tag001CGPSAreaInformation{}, 59 | Codec001CGPSAreaInformation{}) 60 | 61 | registerDecoder( 62 | exifcommon.IfdGpsInfoStandardIfdIdentity.UnindexedString(), 63 | 0x001c, 64 | Codec001CGPSAreaInformation{}) 65 | } 66 | -------------------------------------------------------------------------------- /v3/undefined/gps_001C_gps_area_information_test.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/dsoprea/go-logging" 9 | 10 | "github.com/dsoprea/go-exif/v3/common" 11 | ) 12 | 13 | func TestTag001CGPSAreaInformation_String(t *testing.T) { 14 | ut := Tag001CGPSAreaInformation{"abc"} 15 | s := ut.String() 16 | 17 | if s != "abc" { 18 | t.Fatalf("String not correct: [%s]", s) 19 | } 20 | } 21 | 22 | func TestCodec001CGPSAreaInformation_Encode(t *testing.T) { 23 | s := "abc" 24 | ut := Tag001CGPSAreaInformation{s} 25 | 26 | codec := Codec001CGPSAreaInformation{} 27 | 28 | encoded, unitCount, err := codec.Encode(ut, exifcommon.TestDefaultByteOrder) 29 | log.PanicIf(err) 30 | 31 | if bytes.Equal(encoded, []byte(s)) != true { 32 | t.Fatalf("Encoded bytes not correct: %v", encoded) 33 | } else if unitCount != uint32(len(s)) { 34 | t.Fatalf("Unit-count not correct: (%d)", unitCount) 35 | } 36 | } 37 | 38 | func TestCodec001CGPSAreaInformation_Decode(t *testing.T) { 39 | s := "abc" 40 | ut := Tag001CGPSAreaInformation{s} 41 | 42 | encoded := []byte(s) 43 | 44 | rawValueOffset := encoded 45 | 46 | valueContext := exifcommon.NewValueContext( 47 | "", 48 | 0, 49 | uint32(len(encoded)), 50 | 0, 51 | rawValueOffset, 52 | nil, 53 | exifcommon.TypeUndefined, 54 | exifcommon.TestDefaultByteOrder) 55 | 56 | codec := Codec001CGPSAreaInformation{} 57 | 58 | value, err := codec.Decode(valueContext) 59 | log.PanicIf(err) 60 | 61 | if reflect.DeepEqual(value, ut) != true { 62 | t.Fatalf("Decoded value not correct: %s\n", value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v3/undefined/registration.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "github.com/dsoprea/go-logging" 5 | ) 6 | 7 | // UndefinedTagHandle defines one undefined-type tag with a corresponding 8 | // decoder. 9 | type UndefinedTagHandle struct { 10 | IfdPath string 11 | TagId uint16 12 | } 13 | 14 | func registerEncoder(entity EncodeableValue, encoder UndefinedValueEncoder) { 15 | typeName := entity.EncoderName() 16 | 17 | _, found := encoders[typeName] 18 | if found == true { 19 | log.Panicf("encoder already registered: %v", typeName) 20 | } 21 | 22 | encoders[typeName] = encoder 23 | } 24 | 25 | func registerDecoder(ifdPath string, tagId uint16, decoder UndefinedValueDecoder) { 26 | uth := UndefinedTagHandle{ 27 | IfdPath: ifdPath, 28 | TagId: tagId, 29 | } 30 | 31 | _, found := decoders[uth] 32 | if found == true { 33 | log.Panicf("decoder already registered: %v", uth) 34 | } 35 | 36 | decoders[uth] = decoder 37 | } 38 | 39 | var ( 40 | encoders = make(map[string]UndefinedValueEncoder) 41 | decoders = make(map[UndefinedTagHandle]UndefinedValueDecoder) 42 | ) 43 | -------------------------------------------------------------------------------- /v3/undefined/type.go: -------------------------------------------------------------------------------- 1 | package exifundefined 2 | 3 | import ( 4 | "errors" 5 | 6 | "encoding/binary" 7 | 8 | "github.com/dsoprea/go-exif/v3/common" 9 | ) 10 | 11 | const ( 12 | // UnparseableUnknownTagValuePlaceholder is the string to use for an unknown 13 | // undefined tag. 14 | UnparseableUnknownTagValuePlaceholder = "!UNKNOWN" 15 | 16 | // UnparseableHandledTagValuePlaceholder is the string to use for a known 17 | // value that is not parseable. 18 | UnparseableHandledTagValuePlaceholder = "!MALFORMED" 19 | ) 20 | 21 | var ( 22 | // ErrUnparseableValue is the error for a value that we should have been 23 | // able to parse but were not able to. 24 | ErrUnparseableValue = errors.New("unparseable undefined tag") 25 | ) 26 | 27 | // UndefinedValueEncoder knows how to encode an undefined-type tag's value to 28 | // bytes. 29 | type UndefinedValueEncoder interface { 30 | Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) 31 | } 32 | 33 | // EncodeableValue wraps a value with the information that will be needed to re- 34 | // encode it later. 35 | type EncodeableValue interface { 36 | EncoderName() string 37 | String() string 38 | } 39 | 40 | // UndefinedValueDecoder knows how to decode an undefined-type tag's value from 41 | // bytes. 42 | type UndefinedValueDecoder interface { 43 | Decode(valueContext *exifcommon.ValueContext) (value EncodeableValue, err error) 44 | } 45 | -------------------------------------------------------------------------------- /v3/utility_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dsoprea/go-logging/v2" 8 | "github.com/dsoprea/go-utility/v2/filesystem" 9 | ) 10 | 11 | func TestGpsDegreesEquals_Equals(t *testing.T) { 12 | gi := GpsDegrees{ 13 | Orientation: 'A', 14 | Degrees: 11.0, 15 | Minutes: 22.0, 16 | Seconds: 33.0, 17 | } 18 | 19 | r := GpsDegreesEquals(gi, gi) 20 | if r != true { 21 | t.Fatalf("GpsDegrees structs were not equal as expected.") 22 | } 23 | } 24 | 25 | func TestGpsDegreesEquals_NotEqual_Orientation(t *testing.T) { 26 | gi1 := GpsDegrees{ 27 | Orientation: 'A', 28 | Degrees: 11.0, 29 | Minutes: 22.0, 30 | Seconds: 33.0, 31 | } 32 | 33 | gi2 := gi1 34 | gi2.Orientation = 'B' 35 | 36 | r := GpsDegreesEquals(gi1, gi2) 37 | if r != false { 38 | t.Fatalf("GpsDegrees structs were equal but not supposed to be.") 39 | } 40 | } 41 | 42 | func TestGpsDegreesEquals_NotEqual_Position(t *testing.T) { 43 | gi1 := GpsDegrees{ 44 | Orientation: 'A', 45 | Degrees: 11.0, 46 | Minutes: 22.0, 47 | Seconds: 33.0, 48 | } 49 | 50 | gi2 := gi1 51 | gi2.Minutes = 22.5 52 | 53 | r := GpsDegreesEquals(gi1, gi2) 54 | if r != false { 55 | t.Fatalf("GpsDegrees structs were equal but not supposed to be.") 56 | } 57 | } 58 | 59 | func TestGetFlatExifData(t *testing.T) { 60 | testExifData := getTestExifData() 61 | 62 | exifTags, _, err := GetFlatExifData(testExifData, nil) 63 | log.PanicIf(err) 64 | 65 | if len(exifTags) != 59 { 66 | t.Fatalf("Tag count not correct: (%d)", len(exifTags)) 67 | } 68 | } 69 | 70 | func TestGetFlatExifDataUniversalSearch(t *testing.T) { 71 | testExifData := getTestExifData() 72 | 73 | exifTags, _, err := GetFlatExifDataUniversalSearch(testExifData, nil, false) 74 | log.PanicIf(err) 75 | 76 | if len(exifTags) != 59 { 77 | t.Fatalf("Tag count not correct: (%d)", len(exifTags)) 78 | } 79 | } 80 | 81 | func TestGetFlatExifDataUniversalSearchWithReadSeeker(t *testing.T) { 82 | testImageFilepath := getTestImageFilepath() 83 | 84 | f, err := os.Open(testImageFilepath) 85 | log.PanicIf(err) 86 | 87 | defer f.Close() 88 | 89 | rawExif, err := SearchAndExtractExifWithReader(f) 90 | log.PanicIf(err) 91 | 92 | sb := rifs.NewSeekableBufferWithBytes(rawExif) 93 | 94 | exifTags, _, err := GetFlatExifDataUniversalSearchWithReadSeeker(sb, nil, false) 95 | log.PanicIf(err) 96 | 97 | if len(exifTags) != 59 { 98 | t.Fatalf("Tag count not correct: (%d)", len(exifTags)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /value_context_test.go: -------------------------------------------------------------------------------- 1 | package exif 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/dsoprea/go-logging" 8 | ) 9 | 10 | func TestValueContext_ReadAscii(t *testing.T) { 11 | defer func() { 12 | if state := recover(); state != nil { 13 | err := log.Wrap(state.(error)) 14 | log.PrintErrorf(err, "Test failure.") 15 | } 16 | }() 17 | 18 | rawExif, err := SearchFileAndExtractExif(testImageFilepath) 19 | log.PanicIf(err) 20 | 21 | im := NewIfdMapping() 22 | 23 | err = LoadStandardIfds(im) 24 | log.PanicIf(err) 25 | 26 | ti := NewTagIndex() 27 | 28 | _, index, err := Collect(im, ti, rawExif) 29 | log.PanicIf(err) 30 | 31 | ifd := index.RootIfd 32 | 33 | var ite *IfdTagEntry 34 | for _, thisIte := range ifd.Entries { 35 | if thisIte.TagId == 0x0110 { 36 | ite = thisIte 37 | break 38 | } 39 | } 40 | 41 | if ite == nil { 42 | t.Fatalf("Tag not found.") 43 | } 44 | 45 | valueContext := ifd.GetValueContext(ite) 46 | 47 | decodedString, err := valueContext.ReadAscii() 48 | log.PanicIf(err) 49 | 50 | decodedBytes := []byte(decodedString) 51 | 52 | expected := []byte("Canon EOS 5D Mark III") 53 | 54 | if bytes.Compare(decodedBytes, expected) != 0 { 55 | t.Fatalf("Decoded bytes not correct.") 56 | } 57 | } 58 | 59 | func TestValueContext_Undefined(t *testing.T) { 60 | defer func() { 61 | if state := recover(); state != nil { 62 | err := log.Wrap(state.(error)) 63 | log.PrintErrorf(err, "Test failure.") 64 | } 65 | }() 66 | 67 | rawExif, err := SearchFileAndExtractExif(testImageFilepath) 68 | log.PanicIf(err) 69 | 70 | im := NewIfdMapping() 71 | 72 | err = LoadStandardIfds(im) 73 | log.PanicIf(err) 74 | 75 | ti := NewTagIndex() 76 | 77 | _, index, err := Collect(im, ti, rawExif) 78 | log.PanicIf(err) 79 | 80 | ifdExif := index.Lookup[IfdPathStandardExif][0] 81 | 82 | var ite *IfdTagEntry 83 | for _, thisIte := range ifdExif.Entries { 84 | if thisIte.TagId == 0x9000 { 85 | ite = thisIte 86 | break 87 | } 88 | } 89 | 90 | if ite == nil { 91 | t.Fatalf("Tag not found.") 92 | } 93 | 94 | valueContext := ifdExif.GetValueContext(ite) 95 | 96 | value, err := valueContext.Undefined() 97 | log.PanicIf(err) 98 | 99 | gs, ok := value.(TagUnknownType_GeneralString) 100 | if ok != true { 101 | t.Fatalf("Undefined value not processed correctly.") 102 | } 103 | 104 | decodedBytes, err := gs.ValueBytes() 105 | log.PanicIf(err) 106 | 107 | expected := []byte("0230") 108 | 109 | if bytes.Compare(decodedBytes, expected) != 0 { 110 | t.Fatalf("Decoded bytes not correct.") 111 | } 112 | } 113 | --------------------------------------------------------------------------------