1 package exif
2
3 import (
4 "bytes"
5 "fmt"
6 "strings"
7
8 "encoding/binary"
9
10 "github.com/dsoprea/go-logging"
11
12 "github.com/dsoprea/go-exif/v3/common"
13 )
14
15 const (
16
17 IfdTagEntrySize = uint32(2 + 2 + 4 + 4)
18 )
19
20 type ByteWriter struct {
21 b *bytes.Buffer
22 byteOrder binary.ByteOrder
23 }
24
25 func NewByteWriter(b *bytes.Buffer, byteOrder binary.ByteOrder) (bw *ByteWriter) {
26 return &ByteWriter{
27 b: b,
28 byteOrder: byteOrder,
29 }
30 }
31
32 func (bw ByteWriter) writeAsBytes(value interface{}) (err error) {
33 defer func() {
34 if state := recover(); state != nil {
35 err = log.Wrap(state.(error))
36 }
37 }()
38
39 err = binary.Write(bw.b, bw.byteOrder, value)
40 log.PanicIf(err)
41
42 return nil
43 }
44
45 func (bw ByteWriter) WriteUint32(value uint32) (err error) {
46 defer func() {
47 if state := recover(); state != nil {
48 err = log.Wrap(state.(error))
49 }
50 }()
51
52 err = bw.writeAsBytes(value)
53 log.PanicIf(err)
54
55 return nil
56 }
57
58 func (bw ByteWriter) WriteUint16(value uint16) (err error) {
59 defer func() {
60 if state := recover(); state != nil {
61 err = log.Wrap(state.(error))
62 }
63 }()
64
65 err = bw.writeAsBytes(value)
66 log.PanicIf(err)
67
68 return nil
69 }
70
71 func (bw ByteWriter) WriteFourBytes(value []byte) (err error) {
72 defer func() {
73 if state := recover(); state != nil {
74 err = log.Wrap(state.(error))
75 }
76 }()
77
78 len_ := len(value)
79 if len_ != 4 {
80 log.Panicf("value is not four-bytes: (%d)", len_)
81 }
82
83 _, err = bw.b.Write(value)
84 log.PanicIf(err)
85
86 return nil
87 }
88
89
90
91
92 type ifdDataAllocator struct {
93 offset uint32
94 b bytes.Buffer
95 }
96
97 func newIfdDataAllocator(ifdDataAddressableOffset uint32) *ifdDataAllocator {
98 return &ifdDataAllocator{
99 offset: ifdDataAddressableOffset,
100 }
101 }
102
103 func (ida *ifdDataAllocator) Allocate(value []byte) (offset uint32, err error) {
104 _, err = ida.b.Write(value)
105 log.PanicIf(err)
106
107 offset = ida.offset
108 ida.offset += uint32(len(value))
109
110 return offset, nil
111 }
112
113 func (ida *ifdDataAllocator) NextOffset() uint32 {
114 return ida.offset
115 }
116
117 func (ida *ifdDataAllocator) Bytes() []byte {
118 return ida.b.Bytes()
119 }
120
121
122
123
124 type IfdByteEncoder struct {
125
126 journal [][3]string
127 }
128
129 func NewIfdByteEncoder() (ibe *IfdByteEncoder) {
130 return &IfdByteEncoder{
131 journal: make([][3]string, 0),
132 }
133 }
134
135 func (ibe *IfdByteEncoder) Journal() [][3]string {
136 return ibe.journal
137 }
138
139 func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
140
141 return uint32(2) + (IfdTagEntrySize * uint32(entryCount)) + uint32(4)
142 }
143
144 func (ibe *IfdByteEncoder) pushToJournal(where, direction, format string, args ...interface{}) {
145 event := [3]string{
146 direction,
147 where,
148 fmt.Sprintf(format, args...),
149 }
150
151 ibe.journal = append(ibe.journal, event)
152 }
153
154
155
156 func (ibe *IfdByteEncoder) PrintJournal() {
157 maxWhereLength := 0
158 for _, event := range ibe.journal {
159 where := event[1]
160
161 len_ := len(where)
162 if len_ > maxWhereLength {
163 maxWhereLength = len_
164 }
165 }
166
167 level := 0
168 for i, event := range ibe.journal {
169 direction := event[0]
170 where := event[1]
171 message := event[2]
172
173 if direction != ">" && direction != "<" && direction != "-" {
174 log.Panicf("journal operation not valid: [%s]", direction)
175 }
176
177 if direction == "<" {
178 if level <= 0 {
179 log.Panicf("journal operations unbalanced (too many closes)")
180 }
181
182 level--
183 }
184
185 indent := strings.Repeat(" ", level)
186
187 fmt.Printf("%3d %s%s %s: %s\n", i, indent, direction, where, message)
188
189 if direction == ">" {
190 level++
191 }
192 }
193
194 if level != 0 {
195 log.Panicf("journal operations unbalanced (too many opens)")
196 }
197 }
198
199
200
201
202
203
204 func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *BuilderTag, bw *ByteWriter, ida *ifdDataAllocator, nextIfdOffsetToWrite uint32) (childIfdBlock []byte, err error) {
205 defer func() {
206 if state := recover(); state != nil {
207 err = log.Wrap(state.(error))
208 }
209 }()
210
211
212 err = bw.WriteUint16(bt.tagId)
213 log.PanicIf(err)
214
215
216
217 err = bw.WriteUint16(uint16(bt.typeId))
218 log.PanicIf(err)
219
220
221
222 if bt.value.IsBytes() == true {
223 effectiveType := bt.typeId
224 if bt.typeId == exifcommon.TypeUndefined {
225 effectiveType = exifcommon.TypeByte
226 }
227
228
229
230
231 typeSize := uint32(effectiveType.Size())
232
233 valueBytes := bt.value.Bytes()
234
235 len_ := len(valueBytes)
236 unitCount := uint32(len_) / typeSize
237
238 if _, found := tagsWithoutAlignment[bt.tagId]; found == false {
239 remainder := uint32(len_) % typeSize
240
241 if remainder > 0 {
242 log.Panicf("tag (0x%04x) value of (%d) bytes not evenly divisible by type-size (%d)", bt.tagId, len_, typeSize)
243 }
244 }
245
246 err = bw.WriteUint32(unitCount)
247 log.PanicIf(err)
248
249
250
251 if len_ > 4 {
252 offset, err := ida.Allocate(valueBytes)
253 log.PanicIf(err)
254
255 err = bw.WriteUint32(offset)
256 log.PanicIf(err)
257 } else {
258 fourBytes := make([]byte, 4)
259 copy(fourBytes, valueBytes)
260
261 err = bw.WriteFourBytes(fourBytes)
262 log.PanicIf(err)
263 }
264 } else {
265 if bt.value.IsIb() == false {
266 log.Panicf("tag value is not a byte-slice but also not a child IB: %v", bt)
267 }
268
269
270 err = bw.WriteUint32(1)
271 log.PanicIf(err)
272
273 if nextIfdOffsetToWrite > 0 {
274 var err error
275
276 ibe.pushToJournal("encodeTagToBytes", ">", "[%s]->[%s]", ib.IfdIdentity().UnindexedString(), bt.value.Ib().IfdIdentity().UnindexedString())
277
278
279 childIfdBlock, err = ibe.encodeAndAttachIfd(bt.value.Ib(), nextIfdOffsetToWrite)
280 log.PanicIf(err)
281
282 ibe.pushToJournal("encodeTagToBytes", "<", "[%s]->[%s]", bt.value.Ib().IfdIdentity().UnindexedString(), ib.IfdIdentity().UnindexedString())
283
284
285
286 err = bw.WriteUint32(nextIfdOffsetToWrite)
287 log.PanicIf(err)
288
289 } else {
290
291
292
293 ibe.pushToJournal("encodeTagToBytes", "-", "*Not* descending to child: [%s]", bt.value.Ib().IfdIdentity().UnindexedString())
294
295 err = bw.WriteUint32(0)
296 log.PanicIf(err)
297 }
298 }
299
300 return childIfdBlock, nil
301 }
302
303
304
305
306
307
308
309
310
311
312 func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset uint32, nextIfdOffsetToWrite uint32, setNextIb bool) (data []byte, tableSize uint32, dataSize uint32, childIfdSizes []uint32, err error) {
313 defer func() {
314 if state := recover(); state != nil {
315 err = log.Wrap(state.(error))
316 }
317 }()
318
319 ibe.pushToJournal("encodeIfdToBytes", ">", "%s", ib)
320
321 tableSize = ibe.TableSize(len(ib.tags))
322
323 b := new(bytes.Buffer)
324 bw := NewByteWriter(b, ib.byteOrder)
325
326
327 err = bw.WriteUint16(uint16(len(ib.tags)))
328 log.PanicIf(err)
329
330 ida := newIfdDataAllocator(ifdAddressableOffset)
331
332 childIfdBlocks := make([][]byte, 0)
333
334
335
336
337
338 for _, bt := range ib.tags {
339 childIfdBlock, err := ibe.encodeTagToBytes(ib, bt, bw, ida, nextIfdOffsetToWrite)
340 log.PanicIf(err)
341
342 if childIfdBlock != nil {
343
344
345 if nextIfdOffsetToWrite == 0 {
346 log.Panicf("no IFD offset provided for child-IFDs; no new child-IFDs permitted")
347 }
348
349 nextIfdOffsetToWrite += uint32(len(childIfdBlock))
350 childIfdBlocks = append(childIfdBlocks, childIfdBlock)
351 }
352 }
353
354 dataBytes := ida.Bytes()
355 dataSize = uint32(len(dataBytes))
356
357 childIfdSizes = make([]uint32, len(childIfdBlocks))
358 childIfdsTotalSize := uint32(0)
359 for i, childIfdBlock := range childIfdBlocks {
360 len_ := uint32(len(childIfdBlock))
361 childIfdSizes[i] = len_
362 childIfdsTotalSize += len_
363 }
364
365
366
367 if setNextIb == true {
368
369
370
371
372
373
374
375
376
377
378
379
380 ibe.pushToJournal("encodeIfdToBytes", "-", "Setting 'next' IFD to (0x%08x).", nextIfdOffsetToWrite)
381
382 err := bw.WriteUint32(nextIfdOffsetToWrite)
383 log.PanicIf(err)
384 } else {
385 err := bw.WriteUint32(0)
386 log.PanicIf(err)
387 }
388
389 _, err = b.Write(dataBytes)
390 log.PanicIf(err)
391
392
393
394
395
396
397
398
399
400
401 for _, childIfdBlock := range childIfdBlocks {
402 _, err = b.Write(childIfdBlock)
403 log.PanicIf(err)
404 }
405
406 ibe.pushToJournal("encodeIfdToBytes", "<", "%s", ib)
407
408 return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
409 }
410
411
412 func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffset uint32) (data []byte, err error) {
413 defer func() {
414 if state := recover(); state != nil {
415 err = log.Wrap(state.(error))
416 }
417 }()
418
419 ibe.pushToJournal("encodeAndAttachIfd", ">", "%s", ib)
420
421 b := new(bytes.Buffer)
422
423 i := 0
424
425 for thisIb := ib; thisIb != nil; thisIb = thisIb.nextIb {
426
427
428
429 ibe.pushToJournal("encodeAndAttachIfd", ">", "Beginning encoding process: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
430
431 ibe.pushToJournal("encodeAndAttachIfd", ">", "Calculating size: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
432
433 _, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, 0, false)
434 log.PanicIf(err)
435
436 ibe.pushToJournal("encodeAndAttachIfd", "<", "Finished calculating size: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
437
438 ifdAddressableOffset += tableSize
439 nextIfdOffsetToWrite := ifdAddressableOffset + allocatedDataSize
440
441 ibe.pushToJournal("encodeAndAttachIfd", ">", "Next IFD will be written at offset (0x%08x)", nextIfdOffsetToWrite)
442
443
444
445
446 setNextIb := thisIb.nextIb != nil
447
448 ibe.pushToJournal("encodeAndAttachIfd", ">", "Encoding starting: (%d) [%s] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, thisIb.IfdIdentity().UnindexedString(), nextIfdOffsetToWrite)
449
450 tableAndAllocated, effectiveTableSize, effectiveAllocatedDataSize, childIfdSizes, err :=
451 ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
452
453 log.PanicIf(err)
454
455 if effectiveTableSize != tableSize {
456 log.Panicf("written table size does not match the pre-calculated table size: (%d) != (%d) %s", effectiveTableSize, tableSize, ib)
457 } else if effectiveAllocatedDataSize != allocatedDataSize {
458 log.Panicf("written allocated-data size does not match the pre-calculated allocated-data size: (%d) != (%d) %s", effectiveAllocatedDataSize, allocatedDataSize, ib)
459 }
460
461 ibe.pushToJournal("encodeAndAttachIfd", "<", "Encoding done: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
462
463 totalChildIfdSize := uint32(0)
464 for _, childIfdSize := range childIfdSizes {
465 totalChildIfdSize += childIfdSize
466 }
467
468 if len(tableAndAllocated) != int(tableSize+allocatedDataSize+totalChildIfdSize) {
469 log.Panicf("IFD table and data is not a consistent size: (%d) != (%d)", len(tableAndAllocated), tableSize+allocatedDataSize+totalChildIfdSize)
470 }
471
472
473
474 _, err = b.Write(tableAndAllocated)
475 log.PanicIf(err)
476
477
478
479 ifdAddressableOffset += allocatedDataSize + totalChildIfdSize
480
481 ibe.pushToJournal("encodeAndAttachIfd", "<", "Finishing encoding process: (%d) [%s] [FINAL:] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, ib.IfdIdentity().UnindexedString(), nextIfdOffsetToWrite)
482
483 i++
484 }
485
486 ibe.pushToJournal("encodeAndAttachIfd", "<", "%s", ib)
487
488 return b.Bytes(), nil
489 }
490
491
492
493 func (ibe *IfdByteEncoder) EncodeToExifPayload(ib *IfdBuilder) (data []byte, err error) {
494 defer func() {
495 if state := recover(); state != nil {
496 err = log.Wrap(state.(error))
497 }
498 }()
499
500 data, err = ibe.encodeAndAttachIfd(ib, ExifDefaultFirstIfdOffset)
501 log.PanicIf(err)
502
503 return data, nil
504 }
505
506
507
508 func (ibe *IfdByteEncoder) EncodeToExif(ib *IfdBuilder) (data []byte, err error) {
509 defer func() {
510 if state := recover(); state != nil {
511 err = log.Wrap(state.(error))
512 }
513 }()
514
515 encodedIfds, err := ibe.EncodeToExifPayload(ib)
516 log.PanicIf(err)
517
518
519
520 b := new(bytes.Buffer)
521
522 headerBytes, err := BuildExifHeader(ib.byteOrder, ExifDefaultFirstIfdOffset)
523 log.PanicIf(err)
524
525 _, err = b.Write(headerBytes)
526 log.PanicIf(err)
527
528 _, err = b.Write(encodedIfds)
529 log.PanicIf(err)
530
531 return b.Bytes(), nil
532 }
533
View as plain text