mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-10-02 17:09:48 +02:00
Separate Photoshop and IPTC metadata parsing/dumping
This commit is contained in:
@@ -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
imagemeta/photoshop/photoshop.go
Normal file
81
imagemeta/photoshop/photoshop.go
Normal 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()
|
||||
}
|
@@ -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 {
|
||||
|
Reference in New Issue
Block a user