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() }