forked from dsoprea/go-exif
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ifd_tag_entry.go
233 lines (186 loc) · 6.25 KB
/
ifd_tag_entry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package exif
import (
"fmt"
"reflect"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
var (
iteLogger = log.NewLogger("exif.ifd_tag_entry")
)
type IfdTagEntry struct {
TagId uint16
TagIndex int
TagType TagTypePrimitive
UnitCount uint32
ValueOffset uint32
RawValueOffset []byte
// ChildIfdName is the right most atom in the IFD-path. We need this to
// construct the fully-qualified IFD-path.
ChildIfdName string
// ChildIfdPath is the IFD-path of the child if this tag represents a child
// IFD.
ChildIfdPath string
// ChildFqIfdPath is the IFD-path of the child if this tag represents a
// child IFD. Includes indices.
ChildFqIfdPath string
// TODO(dustin): !! IB's host the child-IBs directly in the tag, but that's not the case here. Refactor to accomodate it for a consistent experience.
// IfdPath is the IFD that this tag belongs to.
IfdPath string
// TODO(dustin): !! We now parse and read the value immediately. Update the rest of the logic to use this and get rid of all of the staggered and different resolution mechanisms.
value []byte
isUnhandledUnknown bool
}
func (ite *IfdTagEntry) String() string {
return fmt.Sprintf("IfdTagEntry<TAG-IFD-PATH=[%s] TAG-ID=(0x%04x) TAG-TYPE=[%s] UNIT-COUNT=(%d)>", ite.IfdPath, ite.TagId, TypeNames[ite.TagType], ite.UnitCount)
}
// TODO(dustin): TODO(dustin): Stop exporting IfdPath and TagId.
//
// func (ite *IfdTagEntry) IfdPath() string {
// return ite.IfdPath
// }
// TODO(dustin): TODO(dustin): Stop exporting IfdPath and TagId.
//
// func (ite *IfdTagEntry) TagId() uint16 {
// return ite.TagId
// }
// ValueString renders a string from whatever the value in this tag is.
func (ite *IfdTagEntry) ValueString(addressableData []byte, byteOrder binary.ByteOrder) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
valueContext :=
newValueContextFromTag(
ite,
addressableData,
byteOrder)
if ite.TagType == TypeUndefined {
valueRaw, err := valueContext.Undefined()
log.PanicIf(err)
value = fmt.Sprintf("%v", valueRaw)
} else {
value, err = valueContext.Format()
log.PanicIf(err)
}
return value, nil
}
// ValueBytes renders a specific list of bytes from the value in this tag.
func (ite *IfdTagEntry) ValueBytes(addressableData []byte, byteOrder binary.ByteOrder) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// Return the exact bytes of the unknown-type value. Returning a string
// (`ValueString`) is easy because we can just pass everything to
// `Sprintf()`. Returning the raw, typed value (`Value`) is easy
// (obviously). However, here, in order to produce the list of bytes, we
// need to coerce whatever `Undefined()` returns.
if ite.TagType == TypeUndefined {
valueContext :=
newValueContextFromTag(
ite,
addressableData,
byteOrder)
value, err := valueContext.Undefined()
log.PanicIf(err)
switch value.(type) {
case []byte:
return value.([]byte), nil
case TagUnknownType_UnknownValue:
b := []byte(value.(TagUnknownType_UnknownValue))
return b, nil
case string:
return []byte(value.(string)), nil
case UnknownTagValue:
valueBytes, err := value.(UnknownTagValue).ValueBytes()
log.PanicIf(err)
return valueBytes, nil
default:
// TODO(dustin): !! Finish translating the rest of the types (make reusable and replace into other similar implementations?)
log.Panicf("can not produce bytes for unknown-type tag (0x%04x) (2): [%s]", ite.TagId, reflect.TypeOf(value))
}
}
originalType := NewTagType(ite.TagType, byteOrder)
byteCount := uint32(originalType.Type().Size()) * ite.UnitCount
tt := NewTagType(TypeByte, byteOrder)
if tt.valueIsEmbedded(byteCount) == true {
iteLogger.Debugf(nil, "Reading BYTE value (ITE; embedded).")
// In this case, the bytes normally used for the offset are actually
// data.
value, err = tt.ParseBytes(ite.RawValueOffset, byteCount)
log.PanicIf(err)
} else {
iteLogger.Debugf(nil, "Reading BYTE value (ITE; at offset).")
value, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount)
log.PanicIf(err)
}
return value, nil
}
// Value returns the specific, parsed, typed value from the tag.
func (ite *IfdTagEntry) Value(addressableData []byte, byteOrder binary.ByteOrder) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
valueContext :=
newValueContextFromTag(
ite,
addressableData,
byteOrder)
if ite.TagType == TypeUndefined {
value, err = valueContext.Undefined()
log.PanicIf(err)
} else {
tt := NewTagType(ite.TagType, byteOrder)
value, err = tt.Resolve(valueContext)
log.PanicIf(err)
}
return value, nil
}
// IfdTagEntryValueResolver instances know how to resolve the values for any
// tag for a particular EXIF block.
type IfdTagEntryValueResolver struct {
addressableData []byte
byteOrder binary.ByteOrder
}
func NewIfdTagEntryValueResolver(exifData []byte, byteOrder binary.ByteOrder) (itevr *IfdTagEntryValueResolver) {
return &IfdTagEntryValueResolver{
addressableData: exifData[ExifAddressableAreaStart:],
byteOrder: byteOrder,
}
}
// ValueBytes will resolve embedded or allocated data from the tag and return the raw bytes.
func (itevr *IfdTagEntryValueResolver) ValueBytes(ite *IfdTagEntry) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// OBSOLETE(dustin): This is now redundant. Use `(*ValueContext).readRawEncoded()` instead of this method.
valueContext := newValueContextFromTag(
ite,
itevr.addressableData,
itevr.byteOrder)
rawBytes, err := valueContext.readRawEncoded()
log.PanicIf(err)
return rawBytes, nil
}
func (itevr *IfdTagEntryValueResolver) Value(ite *IfdTagEntry) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// OBSOLETE(dustin): This is now redundant. Use `(*ValueContext).Values()` instead of this method.
valueContext := newValueContextFromTag(
ite,
itevr.addressableData,
itevr.byteOrder)
values, err := valueContext.Values()
log.PanicIf(err)
return values, nil
}