mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-01-08 10:45:04 +02:00
206 lines
4.2 KiB
Go
206 lines
4.2 KiB
Go
package iptc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
var (
|
|
ps3Header = []byte("Photoshop 3.0\x00")
|
|
ps3BlockHeader = []byte("8BIM")
|
|
ps3IptcRecourceID = []byte("\x04\x04")
|
|
iptcTagHeader = byte(0x1c)
|
|
|
|
errInvalidPS3Header = errors.New("invalid Photoshop 3.0 header")
|
|
errInvalidDataSize = errors.New("invalid IPTC data size")
|
|
)
|
|
|
|
type IptcMap map[TagKey][]TagValue
|
|
|
|
func (m IptcMap) AddTag(key TagKey, data []byte) error {
|
|
info, infoFound := tagInfoMap[key]
|
|
if !infoFound {
|
|
return fmt.Errorf("unknown tag %d:%d", key.RecordID, key.TagID)
|
|
}
|
|
|
|
dataSize := len(data)
|
|
if dataSize < info.MinSize || dataSize > info.MaxSize {
|
|
return fmt.Errorf("invalid tag data size. Min: %d, Max: %d, Has: %d", info.MinSize, info.MaxSize, dataSize)
|
|
}
|
|
|
|
value := TagValue{info.Format, data}
|
|
|
|
if info.Repeatable {
|
|
m[key] = append(m[key], value)
|
|
} else {
|
|
m[key] = []TagValue{value}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m IptcMap) MarshalJSON() ([]byte, error) {
|
|
mm := make(map[string]interface{}, len(m))
|
|
for key, values := range m {
|
|
info, infoFound := tagInfoMap[key]
|
|
if !infoFound {
|
|
continue
|
|
}
|
|
|
|
if info.Repeatable {
|
|
mm[info.Title] = values
|
|
} else {
|
|
mm[info.Title] = values[0]
|
|
}
|
|
|
|
// Add some additional fields for backward compatibility
|
|
if key.RecordID == 2 {
|
|
if key.TagID == 5 {
|
|
mm["Name"] = values[0]
|
|
} else if key.TagID == 120 {
|
|
mm["Caption"] = values[0]
|
|
}
|
|
}
|
|
}
|
|
return json.Marshal(mm)
|
|
}
|
|
|
|
func ParseTags(data []byte, m IptcMap) error {
|
|
buf := bytes.NewBuffer(data)
|
|
|
|
// Min tag size is 5 (2 tagHeader)
|
|
for buf.Len() >= 5 {
|
|
if buf.Next(1)[0] != iptcTagHeader {
|
|
continue
|
|
}
|
|
|
|
recordID, _ := buf.ReadByte()
|
|
tagID, _ := buf.ReadByte()
|
|
dataSize16 := binary.BigEndian.Uint16(buf.Next(2))
|
|
|
|
var dataSize int
|
|
|
|
if dataSize16 < 32768 {
|
|
dataSize = int(dataSize16)
|
|
} else {
|
|
dataSizeSize := dataSize16 & 32767
|
|
|
|
switch dataSizeSize {
|
|
case 4:
|
|
dataSize32 := uint32(0)
|
|
if err := binary.Read(buf, binary.BigEndian, &dataSize32); err != nil {
|
|
return fmt.Errorf("%s: %s", errInvalidDataSize, err)
|
|
}
|
|
dataSize = int(dataSize32)
|
|
case 8:
|
|
dataSize64 := uint64(0)
|
|
if err := binary.Read(buf, binary.BigEndian, &dataSize64); err != nil {
|
|
return fmt.Errorf("%s: %s", errInvalidDataSize, err)
|
|
}
|
|
dataSize = int(dataSize64)
|
|
default:
|
|
return errInvalidDataSize
|
|
}
|
|
}
|
|
|
|
// Ignore errors here. If tag is invalid, just don't add it
|
|
m.AddTag(TagKey{recordID, tagID}, buf.Next(dataSize))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ParsePS3(data []byte, m IptcMap) error {
|
|
buf := bytes.NewBuffer(data)
|
|
|
|
if !bytes.Equal(buf.Next(14), ps3Header) {
|
|
return errInvalidPS3Header
|
|
}
|
|
|
|
// Read blocks
|
|
// Minimal block size is 12 (4 blockHeader + 2 resoureceID + 2 name + 4 blockSize)
|
|
for buf.Len() >= 12 {
|
|
if !bytes.Equal(buf.Bytes()[:4], ps3BlockHeader) {
|
|
buf.Next(1)
|
|
continue
|
|
}
|
|
|
|
// Skip block header
|
|
buf.Next(4)
|
|
|
|
resoureceID := buf.Next(2)
|
|
|
|
// Skip name
|
|
// Name is zero terminated string padded to even
|
|
for buf.Len() > 0 && buf.Next(2)[1] != 0 {
|
|
}
|
|
|
|
if buf.Len() < 4 {
|
|
break
|
|
}
|
|
|
|
blockSize := int(binary.BigEndian.Uint32(buf.Next(4)))
|
|
|
|
if buf.Len() < blockSize {
|
|
break
|
|
}
|
|
blockData := buf.Next(blockSize)
|
|
|
|
// 1028 is IPTC tags block
|
|
if bytes.Equal(resoureceID, ps3IptcRecourceID) {
|
|
return ParseTags(blockData, m)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m IptcMap) DumpTags() []byte {
|
|
buf := new(bytes.Buffer)
|
|
|
|
for key, values := range m {
|
|
for _, value := range values {
|
|
dataSize := len(value.Raw)
|
|
// Skip tags with too big data size
|
|
if dataSize > math.MaxUint32 {
|
|
continue
|
|
}
|
|
|
|
buf.WriteByte(iptcTagHeader)
|
|
buf.WriteByte(key.RecordID)
|
|
buf.WriteByte(key.TagID)
|
|
|
|
if dataSize < (1 << 15) {
|
|
binary.Write(buf, binary.BigEndian, uint16(dataSize))
|
|
} else {
|
|
binary.Write(buf, binary.BigEndian, uint16(4+(1<<15)))
|
|
binary.Write(buf, binary.BigEndian, uint32(dataSize))
|
|
}
|
|
|
|
buf.Write(value.Raw)
|
|
}
|
|
}
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func (m IptcMap) Dump() []byte {
|
|
tagsDump := m.DumpTags()
|
|
|
|
buf := new(bytes.Buffer)
|
|
buf.Grow(26)
|
|
|
|
buf.Write(ps3Header)
|
|
buf.Write(ps3BlockHeader)
|
|
buf.Write(ps3IptcRecourceID)
|
|
buf.Write([]byte{0, 0})
|
|
binary.Write(buf, binary.BigEndian, uint32(len(tagsDump)))
|
|
buf.Write(tagsDump)
|
|
|
|
return buf.Bytes()
|
|
}
|