ifd_builder_encode.go (15437B)
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 // Tag-ID + Tag-Type + Unit-Count + Value/Offset. 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 // ifdOffsetIterator keeps track of where the next IFD should be written by 90 // keeping track of where the offsets start, the data that has been added, and 91 // bumping the offset *when* the data is added. 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 // IfdByteEncoder converts an IB to raw bytes (for writing) while also figuring 122 // out all of the allocations and indirection that is required for extended 123 // data. 124 type IfdByteEncoder struct { 125 // journal holds a list of actions taken while encoding. 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 // Tag-Count + (Entry-Size * Entry-Count) + Next-IFD-Offset. 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 // PrintJournal prints a hierarchical representation of the steps taken during 155 // encoding. 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 // encodeTagToBytes encodes the given tag to a byte stream. If 200 // `nextIfdOffsetToWrite` is more than (0), recurse into child IFDs 201 // (`nextIfdOffsetToWrite` is required in order for them to know where the its 202 // IFD data will be written, in order for them to know the offset of where 203 // their allocated-data block will start, which follows right behind). 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 // Write tag-ID. 212 err = bw.WriteUint16(bt.tagId) 213 log.PanicIf(err) 214 215 // Works for both values and child IFDs (which have an official size of 216 // LONG). 217 err = bw.WriteUint16(uint16(bt.typeId)) 218 log.PanicIf(err) 219 220 // Write unit-count. 221 222 if bt.value.IsBytes() == true { 223 effectiveType := bt.typeId 224 if bt.typeId == exifcommon.TypeUndefined { 225 effectiveType = exifcommon.TypeByte 226 } 227 228 // It's a non-unknown value.Calculate the count of values of 229 // the type that we're writing and the raw bytes for the whole list. 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 // Write four-byte value/offset. 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 // Write unit-count (one LONG representing one offset). 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 // Create the block of IFD data and everything it requires. 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 // Use the next-IFD offset for it. The IFD will actually get 285 // attached after we return. 286 err = bw.WriteUint32(nextIfdOffsetToWrite) 287 log.PanicIf(err) 288 289 } else { 290 // No child-IFDs are to be allocated. Finish the entry with a NULL 291 // pointer. 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 // encodeIfdToBytes encodes the given IB to a byte-slice. We are given the 304 // offset at which this IFD will be written. This method is used called both to 305 // pre-determine how big the table is going to be (so that we can calculate the 306 // address to allocate data at) as well as to write the final table. 307 // 308 // It is necessary to fully realize the table in order to predetermine its size 309 // because it is not enough to know the size of the table: If there are child 310 // IFDs, we will not be able to allocate them without first knowing how much 311 // data we need to allocate for the current IFD. 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 // Write tag count. 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 // Write raw bytes for each tag entry. Allocate larger data to be referred 335 // to in the follow-up data-block as required. Any "unknown"-byte tags that 336 // we can't parse will not be present here (using AddTagsFromExisting(), at 337 // least). 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 // We aren't allowed to have non-nil child IFDs if we're just 344 // sizing things up. 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 // N the link from this IFD to the next IFD that will be written in the 366 // next cycle. 367 if setNextIb == true { 368 // Write address of next IFD in chain. This will be the original 369 // allocation offset plus the size of everything we have allocated for 370 // this IFD and its child-IFDs. 371 // 372 // It is critical that this number is stepped properly. We experienced 373 // an issue whereby it first looked like we were duplicating the IFD and 374 // then that we were duplicating the tags in the wrong IFD, and then 375 // finally we determined that the next-IFD offset for the first IFD was 376 // accidentally pointing back to the EXIF IFD, so we were visiting it 377 // twice when visiting through the tags after decoding. It was an 378 // expensive bug to find. 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 // Append any child IFD blocks after our table and data blocks. These IFDs 393 // were equipped with the appropriate offset information so it's expected 394 // that all offsets referred to by these will be correct. 395 // 396 // Note that child-IFDs are append after the current IFD and before the 397 // next IFD, as opposed to the root IFDs, which are chained together but 398 // will be interrupted by these child-IFDs (which is expected, per the 399 // standard). 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 // encodeAndAttachIfd is a reentrant function that processes the IFD chain. 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 // Do a dry-run in order to pre-determine its size requirement. 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 // Write our IFD as well as any child-IFDs (now that we know the offset 444 // where new IFDs and their data will be allocated). 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 // TODO(dustin): We might want to verify the original tableAndAllocated length, too. 473 474 _, err = b.Write(tableAndAllocated) 475 log.PanicIf(err) 476 477 // Advance past what we've allocated, thus far. 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 // EncodeToExifPayload is the base encoding step that transcribes the entire IB 492 // structure to its on-disk layout. 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 // EncodeToExif calls EncodeToExifPayload and then packages the result into a 507 // complete EXIF block. 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 // Wrap the IFD in a formal EXIF block. 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 }