Files
imgproxy/imagemeta/iptc/iptc.go
2025-03-24 21:01:54 +03:00

136 lines
2.8 KiB
Go

package iptc
import (
"bytes"
"encoding/binary"
"encoding/json"
"math"
)
var iptcTagHeader = byte(0x1c)
type IptcMap map[TagKey][]TagValue
func (m IptcMap) AddTag(key TagKey, data []byte) error {
info, infoFound := tagInfoMap[key]
if !infoFound {
return newIptcError("unknown tag %d:%d", key.RecordID, key.TagID)
}
dataSize := len(data)
if dataSize < info.MinSize || dataSize > info.MaxSize {
return newIptcError("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 {
switch key.TagID {
case 5:
mm["Name"] = values[0]
case 120:
mm["Caption"] = values[0]
}
}
}
return json.Marshal(mm)
}
func Parse(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 newIptcError("invalid IPTC data size: %s", err)
}
dataSize = int(dataSize32)
case 8:
dataSize64 := uint64(0)
if err := binary.Read(buf, binary.BigEndian, &dataSize64); err != nil {
return newIptcError("invalid IPTC data size: %s", err)
}
dataSize = int(dataSize64)
default:
return newIptcError("invalid IPTC data size")
}
}
// Ignore errors here. If tag is invalid, just don't add it
m.AddTag(TagKey{recordID, tagID}, buf.Next(dataSize))
}
return nil
}
func (m IptcMap) Dump() []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()
}