...

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

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

     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  }
   533  

View as plain text