Separate Photoshop and IPTC metadata parsing/dumping

This commit is contained in:
DarthSim
2023-05-29 19:48:39 +03:00
parent a246eda065
commit 2e6a3c6dd1
3 changed files with 107 additions and 78 deletions

View File

@@ -10,13 +10,9 @@ import (
)
var (
ps3Header = []byte("Photoshop 3.0\x00")
ps3BlockHeader = []byte("8BIM")
ps3IptcRecourceID = []byte("\x04\x04")
iptcTagHeader = byte(0x1c)
iptcTagHeader = byte(0x1c)
errInvalidPS3Header = errors.New("invalid Photoshop 3.0 header")
errInvalidDataSize = errors.New("invalid IPTC data size")
errInvalidDataSize = errors.New("invalid IPTC data size")
)
type IptcMap map[TagKey][]TagValue
@@ -69,7 +65,7 @@ func (m IptcMap) MarshalJSON() ([]byte, error) {
return json.Marshal(mm)
}
func ParseTags(data []byte, m IptcMap) error {
func Parse(data []byte, m IptcMap) error {
buf := bytes.NewBuffer(data)
// Min tag size is 5 (2 tagHeader)
@@ -114,52 +110,7 @@ func ParseTags(data []byte, m IptcMap) error {
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 {
func (m IptcMap) Dump() []byte {
buf := new(bytes.Buffer)
for key, values := range m {
@@ -187,19 +138,3 @@ func (m IptcMap) DumpTags() []byte {
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()
}

View File

@@ -0,0 +1,81 @@
package photoshop
import (
"bytes"
"encoding/binary"
"errors"
)
var (
ps3Header = []byte("Photoshop 3.0\x00")
ps3BlockHeader = []byte("8BIM")
errInvalidPS3Header = errors.New("invalid Photoshop 3.0 header")
)
const (
IptcKey = "\x04\x04"
ResolutionKey = "\x03\xed"
)
type PhotoshopMap map[string][]byte
func Parse(data []byte, m PhotoshopMap) 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)
m[string(resoureceID)] = blockData
}
return nil
}
func (m PhotoshopMap) Dump() []byte {
buf := new(bytes.Buffer)
buf.Grow(26)
buf.Write(ps3Header)
buf.Write(ps3BlockHeader)
for id, data := range m {
buf.WriteString(id)
// Write empty name
buf.Write([]byte{0, 0})
binary.Write(buf, binary.BigEndian, uint32(len(data)))
buf.Write(data)
}
return buf.Bytes()
}

View File

@@ -7,18 +7,27 @@ import (
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/imagemeta/iptc"
"github.com/imgproxy/imgproxy/v3/imagemeta/photoshop"
"github.com/imgproxy/imgproxy/v3/options"
"github.com/imgproxy/imgproxy/v3/vips"
)
func stripIPTC(img *vips.Image) []byte {
iptcData, err := img.GetBlob("iptc-data")
if err != nil || len(iptcData) == 0 {
func stripPS3(img *vips.Image) []byte {
ps3Data, err := img.GetBlob("iptc-data")
if err != nil || len(ps3Data) == 0 {
return nil
}
ps3Map := make(photoshop.PhotoshopMap)
photoshop.Parse(ps3Data, ps3Map)
iptcData, found := ps3Map[photoshop.IptcKey]
if !found {
return nil
}
iptcMap := make(iptc.IptcMap)
err = iptc.ParsePS3(iptcData, iptcMap)
err = iptc.Parse(iptcData, iptcMap)
if err != nil {
return nil
}
@@ -33,7 +42,11 @@ func stripIPTC(img *vips.Image) []byte {
return nil
}
return iptcMap.Dump()
ps3Map = photoshop.PhotoshopMap{
photoshop.IptcKey: iptcMap.Dump(),
}
return ps3Map.Dump()
}
func stripXMP(img *vips.Image) []byte {
@@ -97,10 +110,10 @@ func stripMetadata(pctx *pipelineContext, img *vips.Image, po *options.Processin
return nil
}
var iptcData, xmpData []byte
var ps3Data, xmpData []byte
if po.KeepCopyright {
iptcData = stripIPTC(img)
ps3Data = stripPS3(img)
xmpData = stripXMP(img)
}
@@ -109,8 +122,8 @@ func stripMetadata(pctx *pipelineContext, img *vips.Image, po *options.Processin
}
if po.KeepCopyright {
if len(iptcData) > 0 {
img.SetBlob("iptc-data", iptcData)
if len(ps3Data) > 0 {
img.SetBlob("iptc-data", ps3Data)
}
if len(xmpData) > 0 {