iptc.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package iptc
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "math"
  9. )
  10. var (
  11. ps3Header = []byte("Photoshop 3.0\x00")
  12. ps3BlockHeader = []byte("8BIM")
  13. ps3IptcRecourceID = []byte("\x04\x04")
  14. iptcTagHeader = byte(0x1c)
  15. errInvalidPS3Header = errors.New("invalid Photoshop 3.0 header")
  16. errInvalidDataSize = errors.New("invalid IPTC data size")
  17. )
  18. type IptcMap map[TagKey][]TagValue
  19. func (m IptcMap) AddTag(key TagKey, data []byte) error {
  20. info, infoFound := tagInfoMap[key]
  21. if !infoFound {
  22. return fmt.Errorf("unknown tag %d:%d", key.RecordID, key.TagID)
  23. }
  24. dataSize := len(data)
  25. if dataSize < info.MinSize || dataSize > info.MaxSize {
  26. return fmt.Errorf("invalid tag data size. Min: %d, Max: %d, Has: %d", info.MinSize, info.MaxSize, dataSize)
  27. }
  28. value := TagValue{info.Format, data}
  29. if info.Repeatable {
  30. m[key] = append(m[key], value)
  31. } else {
  32. m[key] = []TagValue{value}
  33. }
  34. return nil
  35. }
  36. func (m IptcMap) MarshalJSON() ([]byte, error) {
  37. mm := make(map[string]interface{}, len(m))
  38. for key, values := range m {
  39. info, infoFound := tagInfoMap[key]
  40. if !infoFound {
  41. continue
  42. }
  43. if info.Repeatable {
  44. mm[info.Title] = values
  45. } else {
  46. mm[info.Title] = values[0]
  47. }
  48. // Add some additional fields for backward compatibility
  49. if key.RecordID == 2 {
  50. if key.TagID == 5 {
  51. mm["Name"] = values[0]
  52. } else if key.TagID == 120 {
  53. mm["Caption"] = values[0]
  54. }
  55. }
  56. }
  57. return json.Marshal(mm)
  58. }
  59. func ParseTags(data []byte, m IptcMap) error {
  60. buf := bytes.NewBuffer(data)
  61. // Min tag size is 5 (2 tagHeader)
  62. for buf.Len() >= 5 {
  63. if buf.Next(1)[0] != iptcTagHeader {
  64. continue
  65. }
  66. recordID, _ := buf.ReadByte()
  67. tagID, _ := buf.ReadByte()
  68. dataSize16 := binary.BigEndian.Uint16(buf.Next(2))
  69. var dataSize int
  70. if dataSize16 < 32768 {
  71. dataSize = int(dataSize16)
  72. } else {
  73. dataSizeSize := dataSize16 & 32767
  74. switch dataSizeSize {
  75. case 4:
  76. dataSize32 := uint32(0)
  77. if err := binary.Read(buf, binary.BigEndian, &dataSize32); err != nil {
  78. return fmt.Errorf("%s: %s", errInvalidDataSize, err)
  79. }
  80. dataSize = int(dataSize32)
  81. case 8:
  82. dataSize64 := uint64(0)
  83. if err := binary.Read(buf, binary.BigEndian, &dataSize64); err != nil {
  84. return fmt.Errorf("%s: %s", errInvalidDataSize, err)
  85. }
  86. dataSize = int(dataSize64)
  87. default:
  88. return errInvalidDataSize
  89. }
  90. }
  91. // Ignore errors here. If tag is invalid, just don't add it
  92. m.AddTag(TagKey{recordID, tagID}, buf.Next(dataSize))
  93. }
  94. return nil
  95. }
  96. func ParsePS3(data []byte, m IptcMap) error {
  97. buf := bytes.NewBuffer(data)
  98. if !bytes.Equal(buf.Next(14), ps3Header) {
  99. return errInvalidPS3Header
  100. }
  101. // Read blocks
  102. // Minimal block size is 12 (4 blockHeader + 2 resoureceID + 2 name + 4 blockSize)
  103. for buf.Len() >= 12 {
  104. if !bytes.Equal(buf.Bytes()[:4], ps3BlockHeader) {
  105. buf.Next(1)
  106. continue
  107. }
  108. // Skip block header
  109. buf.Next(4)
  110. resoureceID := buf.Next(2)
  111. // Skip name
  112. // Name is zero terminated string padded to even
  113. for buf.Len() > 0 && buf.Next(2)[1] != 0 {
  114. }
  115. if buf.Len() < 4 {
  116. break
  117. }
  118. blockSize := int(binary.BigEndian.Uint32(buf.Next(4)))
  119. if buf.Len() < blockSize {
  120. break
  121. }
  122. blockData := buf.Next(blockSize)
  123. // 1028 is IPTC tags block
  124. if bytes.Equal(resoureceID, ps3IptcRecourceID) {
  125. return ParseTags(blockData, m)
  126. }
  127. }
  128. return nil
  129. }
  130. func (m IptcMap) DumpTags() []byte {
  131. buf := new(bytes.Buffer)
  132. for key, values := range m {
  133. for _, value := range values {
  134. dataSize := len(value.Raw)
  135. // Skip tags with too big data size
  136. if dataSize > math.MaxUint32 {
  137. continue
  138. }
  139. buf.WriteByte(iptcTagHeader)
  140. buf.WriteByte(key.RecordID)
  141. buf.WriteByte(key.TagID)
  142. if dataSize < (1 << 15) {
  143. binary.Write(buf, binary.BigEndian, uint16(dataSize))
  144. } else {
  145. binary.Write(buf, binary.BigEndian, uint16(4+(1<<15)))
  146. binary.Write(buf, binary.BigEndian, uint32(dataSize))
  147. }
  148. buf.Write(value.Raw)
  149. }
  150. }
  151. return buf.Bytes()
  152. }
  153. func (m IptcMap) Dump() []byte {
  154. tagsDump := m.DumpTags()
  155. buf := new(bytes.Buffer)
  156. buf.Grow(26)
  157. buf.Write(ps3Header)
  158. buf.Write(ps3BlockHeader)
  159. buf.Write(ps3IptcRecourceID)
  160. buf.Write([]byte{0, 0})
  161. binary.Write(buf, binary.BigEndian, uint32(len(tagsDump)))
  162. buf.Write(tagsDump)
  163. return buf.Bytes()
  164. }