Przeglądaj źródła

Separate Photoshop and IPTC metadata parsing/dumping

DarthSim 1 rok temu
rodzic
commit
2e6a3c6dd1

+ 4 - 69
imagemeta/iptc/iptc.go

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

+ 81 - 0
imagemeta/photoshop/photoshop.go

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

+ 22 - 9
processing/strip_metadata.go

@@ -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 {