use exif-terminator

This commit is contained in:
tsmethurst 2022-01-23 14:41:31 +01:00
commit 7d024ce74d
117 changed files with 3873 additions and 8725 deletions

View file

@ -1,21 +0,0 @@
language: go
go:
- master
- stable
- "1.14"
- "1.13"
- "1.12"
env:
- GO111MODULE=on
install:
- go get -t ./...
script:
# v1
- go test -v .
# v2
- cd v2
- go test -v .
- cd ..
after_success:
- cd v2
- curl -s https://codecov.io/bash | bash

View file

@ -1,8 +0,0 @@
[![Build Status](https://travis-ci.org/dsoprea/go-png-image-structure.svg?branch=master)](https://travis-ci.org/dsoprea/go-png-image-structure)
[![codecov](https://codecov.io/gh/dsoprea/go-png-image-structure/branch/master/graph/badge.svg)](https://codecov.io/gh/dsoprea/go-png-image-structure)
[![Go Report Card](https://goreportcard.com/badge/github.com/dsoprea/go-png-image-structure/v2)](https://goreportcard.com/report/github.com/dsoprea/go-png-image-structure/v2)
[![GoDoc](https://godoc.org/github.com/dsoprea/go-png-image-structure/v2?status.svg)](https://godoc.org/github.com/dsoprea/go-png-image-structure/v2)
## Overview
Parse raw PNG data into individual chunks. Parse/modify EXIF data and write an updated image.

View file

@ -1,89 +0,0 @@
package pngstructure
import (
"fmt"
"bytes"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
type ChunkDecoder struct {
}
func NewChunkDecoder() *ChunkDecoder {
return new(ChunkDecoder)
}
func (cd *ChunkDecoder) Decode(c *Chunk) (decoded interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
switch c.Type {
case "IHDR":
ihdr, err := cd.decodeIHDR(c)
log.PanicIf(err)
return ihdr, nil
}
// We don't decode this particular type.
return nil, nil
}
type ChunkIHDR struct {
Width uint32
Height uint32
BitDepth uint8
ColorType uint8
CompressionMethod uint8
FilterMethod uint8
InterlaceMethod uint8
}
func (ihdr *ChunkIHDR) String() string {
return fmt.Sprintf("IHDR<WIDTH=(%d) HEIGHT=(%d) DEPTH=(%d) COLOR-TYPE=(%d) COMP-METHOD=(%d) FILTER-METHOD=(%d) INTRLC-METHOD=(%d)>", ihdr.Width, ihdr.Height, ihdr.BitDepth, ihdr.ColorType, ihdr.CompressionMethod, ihdr.FilterMethod, ihdr.InterlaceMethod)
}
func (cd *ChunkDecoder) decodeIHDR(c *Chunk) (ihdr *ChunkIHDR, err error) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
b := bytes.NewBuffer(c.Data)
ihdr = new(ChunkIHDR)
err = binary.Read(b, binary.BigEndian, &ihdr.Width)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.Height)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.BitDepth)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.ColorType)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.CompressionMethod)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.FilterMethod)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.InterlaceMethod)
log.PanicIf(err)
return ihdr, nil
}

View file

@ -1,106 +0,0 @@
package pngstructure
import (
"bufio"
"bytes"
"io"
"os"
"github.com/dsoprea/go-logging"
"github.com/dsoprea/go-utility/image"
)
// PngMediaParser knows how to parse a PNG stream.
type PngMediaParser struct {
}
// NewPngMediaParser returns a new `PngMediaParser` struct.
func NewPngMediaParser() *PngMediaParser {
// TODO(dustin): Add test
return new(PngMediaParser)
}
// Parse parses a PNG stream given a `io.ReadSeeker`.
func (pmp *PngMediaParser) Parse(rs io.ReadSeeker, size int) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): Add test
ps := NewPngSplitter()
err = ps.readHeader(rs)
log.PanicIf(err)
s := bufio.NewScanner(rs)
// Since each segment can be any size, our buffer must be allowed to grow
// as large as the file.
buffer := []byte{}
s.Buffer(buffer, size)
s.Split(ps.Split)
for s.Scan() != false {
}
log.PanicIf(s.Err())
return ps.Chunks(), nil
}
// ParseFile parses a PNG stream given a file-path.
func (pmp *PngMediaParser) ParseFile(filepath string) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
f, err := os.Open(filepath)
log.PanicIf(err)
defer f.Close()
stat, err := f.Stat()
log.PanicIf(err)
size := stat.Size()
chunks, err := pmp.Parse(f, int(size))
log.PanicIf(err)
return chunks, nil
}
// ParseBytes parses a PNG stream given a byte-slice.
func (pmp *PngMediaParser) ParseBytes(data []byte) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): Add test
br := bytes.NewReader(data)
chunks, err := pmp.Parse(br, len(data))
log.PanicIf(err)
return chunks, nil
}
// LooksLikeFormat returns a boolean indicating whether the stream looks like a
// PNG image.
func (pmp *PngMediaParser) LooksLikeFormat(data []byte) bool {
return bytes.Compare(data[:len(PngSignature)], PngSignature[:]) == 0
}
var (
// Enforce interface conformance.
_ riimage.MediaParser = new(PngMediaParser)
)

View file

@ -1,64 +0,0 @@
package pngstructure
import (
"os"
"path"
"github.com/dsoprea/go-logging"
)
var (
assetsPath = ""
)
func getModuleRootPath() string {
moduleRootPath := os.Getenv("PNG_MODULE_ROOT_PATH")
if moduleRootPath != "" {
return moduleRootPath
}
currentWd, err := os.Getwd()
log.PanicIf(err)
currentPath := currentWd
visited := make([]string, 0)
for {
tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT")
_, err := os.Stat(tryStampFilepath)
if err != nil && os.IsNotExist(err) != true {
log.Panic(err)
} else if err == nil {
break
}
visited = append(visited, tryStampFilepath)
currentPath = path.Dir(currentPath)
if currentPath == "/" {
log.Panicf("could not find module-root: %v", visited)
}
}
return currentPath
}
func getTestAssetsPath() string {
if assetsPath == "" {
moduleRootPath := getModuleRootPath()
assetsPath = path.Join(moduleRootPath, "assets")
}
return assetsPath
}
func getTestBasicImageFilepath() string {
assetsPath := getTestAssetsPath()
return path.Join(assetsPath, "libpng.png")
}
func getTestExifImageFilepath() string {
assetsPath := getTestAssetsPath()
return path.Join(assetsPath, "exif.png")
}

View file

@ -1,65 +0,0 @@
package pngstructure
import (
"fmt"
"bytes"
"github.com/dsoprea/go-logging"
)
func DumpBytes(data []byte) {
fmt.Printf("DUMP: ")
for _, x := range data {
fmt.Printf("%02x ", x)
}
fmt.Printf("\n")
}
func DumpBytesClause(data []byte) {
fmt.Printf("DUMP: ")
fmt.Printf("[]byte { ")
for i, x := range data {
fmt.Printf("0x%02x", x)
if i < len(data) - 1 {
fmt.Printf(", ")
}
}
fmt.Printf(" }\n")
}
func DumpBytesToString(data []byte) string {
b := new(bytes.Buffer)
for i, x := range data {
_, err := b.WriteString(fmt.Sprintf("%02x", x))
log.PanicIf(err)
if i < len(data) - 1 {
_, err := b.WriteRune(' ')
log.PanicIf(err)
}
}
return b.String()
}
func DumpBytesClauseToString(data []byte) string {
b := new(bytes.Buffer)
for i, x := range data {
_, err := b.WriteString(fmt.Sprintf("0x%02x", x))
log.PanicIf(err)
if i < len(data) - 1 {
_, err := b.WriteString(", ")
log.PanicIf(err)
}
}
return b.String()
}

View file

@ -0,0 +1,87 @@
package pngstructure
import (
"bytes"
"fmt"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
type ChunkDecoder struct {
}
func NewChunkDecoder() *ChunkDecoder {
return new(ChunkDecoder)
}
func (cd *ChunkDecoder) Decode(c *Chunk) (decoded interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
switch c.Type {
case "IHDR":
ihdr, err := cd.decodeIHDR(c)
log.PanicIf(err)
return ihdr, nil
}
// We don't decode this particular type.
return nil, nil
}
type ChunkIHDR struct {
Width uint32
Height uint32
BitDepth uint8
ColorType uint8
CompressionMethod uint8
FilterMethod uint8
InterlaceMethod uint8
}
func (ihdr *ChunkIHDR) String() string {
return fmt.Sprintf("IHDR<WIDTH=(%d) HEIGHT=(%d) DEPTH=(%d) COLOR-TYPE=(%d) COMP-METHOD=(%d) FILTER-METHOD=(%d) INTRLC-METHOD=(%d)>", ihdr.Width, ihdr.Height, ihdr.BitDepth, ihdr.ColorType, ihdr.CompressionMethod, ihdr.FilterMethod, ihdr.InterlaceMethod)
}
func (cd *ChunkDecoder) decodeIHDR(c *Chunk) (ihdr *ChunkIHDR, err error) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
b := bytes.NewBuffer(c.Data)
ihdr = new(ChunkIHDR)
err = binary.Read(b, binary.BigEndian, &ihdr.Width)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.Height)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.BitDepth)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.ColorType)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.CompressionMethod)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.FilterMethod)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &ihdr.InterlaceMethod)
log.PanicIf(err)
return ihdr, nil
}

View file

@ -0,0 +1,118 @@
package pngstructure
import (
"bufio"
"bytes"
"image"
"io"
"os"
"image/png"
"github.com/dsoprea/go-logging"
"github.com/dsoprea/go-utility/v2/image"
)
// PngMediaParser knows how to parse a PNG stream.
type PngMediaParser struct {
}
// NewPngMediaParser returns a new `PngMediaParser` struct.
func NewPngMediaParser() *PngMediaParser {
// TODO(dustin): Add test
return new(PngMediaParser)
}
// Parse parses a PNG stream given a `io.ReadSeeker`.
func (pmp *PngMediaParser) Parse(rs io.ReadSeeker, size int) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): Add test
ps := NewPngSplitter()
err = ps.readHeader(rs)
log.PanicIf(err)
s := bufio.NewScanner(rs)
// Since each segment can be any size, our buffer must be allowed to grow
// as large as the file.
buffer := []byte{}
s.Buffer(buffer, size)
s.Split(ps.Split)
for s.Scan() != false {
}
log.PanicIf(s.Err())
return ps.Chunks(), nil
}
// ParseFile parses a PNG stream given a file-path.
func (pmp *PngMediaParser) ParseFile(filepath string) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
f, err := os.Open(filepath)
log.PanicIf(err)
defer f.Close()
stat, err := f.Stat()
log.PanicIf(err)
size := stat.Size()
chunks, err := pmp.Parse(f, int(size))
log.PanicIf(err)
return chunks, nil
}
// ParseBytes parses a PNG stream given a byte-slice.
func (pmp *PngMediaParser) ParseBytes(data []byte) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): Add test
br := bytes.NewReader(data)
chunks, err := pmp.Parse(br, len(data))
log.PanicIf(err)
return chunks, nil
}
// LooksLikeFormat returns a boolean indicating whether the stream looks like a
// PNG image.
func (pmp *PngMediaParser) LooksLikeFormat(data []byte) bool {
return bytes.Compare(data[:len(PngSignature)], PngSignature[:]) == 0
}
// GetImage returns an image.Image-compatible struct.
func (pmp *PngMediaParser) GetImage(r io.Reader) (img image.Image, err error) {
img, err = png.Decode(r)
log.PanicIf(err)
return img, nil
}
var (
// Enforce interface conformance.
_ riimage.MediaParser = new(PngMediaParser)
)

View file

@ -9,9 +9,10 @@ import (
"encoding/binary"
"hash/crc32"
"github.com/dsoprea/go-exif/v2"
"github.com/dsoprea/go-exif/v3"
"github.com/dsoprea/go-exif/v3/common"
"github.com/dsoprea/go-logging"
"github.com/dsoprea/go-utility/image"
"github.com/dsoprea/go-utility/v2/image"
)
var (
@ -22,7 +23,6 @@ var (
var (
ErrNotPng = errors.New("not png data")
ErrNoExif = errors.New("file does not have EXIF")
ErrCrcFailure = errors.New("crc failure")
)
@ -111,7 +111,7 @@ func (cs *ChunkSlice) FindExif() (chunk *Chunk, err error) {
return chunks[0], nil
}
log.Panic(ErrNoExif)
log.Panic(exif.ErrNoExif)
// Never called.
return nil, nil
@ -128,7 +128,9 @@ func (cs *ChunkSlice) Exif() (rootIfd *exif.Ifd, data []byte, err error) {
chunk, err := cs.FindExif()
log.PanicIf(err)
im := exif.NewIfdMappingWithStandard()
im, err := exifcommon.NewIfdMappingWithStandard()
log.PanicIf(err)
ti := exif.NewTagIndex()
// TODO(dustin): Refactor and support `exif.GetExifData()`.
@ -180,7 +182,7 @@ func (cs *ChunkSlice) SetExif(ib *exif.IfdBuilder) (err error) {
exifChunk.Data = exifData
exifChunk.Length = uint32(len(exifData))
} else {
if log.Is(err, ErrNoExif) != true {
if log.Is(err, exif.ErrNoExif) != true {
log.Panic(err)
}

View file

@ -0,0 +1,64 @@
package pngstructure
import (
"os"
"path"
"github.com/dsoprea/go-logging"
)
var (
assetsPath = ""
)
func getModuleRootPath() string {
moduleRootPath := os.Getenv("PNG_MODULE_ROOT_PATH")
if moduleRootPath != "" {
return moduleRootPath
}
currentWd, err := os.Getwd()
log.PanicIf(err)
currentPath := currentWd
visited := make([]string, 0)
for {
tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT")
_, err := os.Stat(tryStampFilepath)
if err != nil && os.IsNotExist(err) != true {
log.Panic(err)
} else if err == nil {
break
}
visited = append(visited, tryStampFilepath)
currentPath = path.Dir(currentPath)
if currentPath == "/" {
log.Panicf("could not find module-root: %v", visited)
}
}
return currentPath
}
func getTestAssetsPath() string {
if assetsPath == "" {
moduleRootPath := getModuleRootPath()
assetsPath = path.Join(moduleRootPath, "assets")
}
return assetsPath
}
func getTestBasicImageFilepath() string {
assetsPath := getTestAssetsPath()
return path.Join(assetsPath, "libpng.png")
}
func getTestExifImageFilepath() string {
assetsPath := getTestAssetsPath()
return path.Join(assetsPath, "exif.png")
}

View file

@ -0,0 +1,65 @@
package pngstructure
import (
"bytes"
"fmt"
"github.com/dsoprea/go-logging"
)
func DumpBytes(data []byte) {
fmt.Printf("DUMP: ")
for _, x := range data {
fmt.Printf("%02x ", x)
}
fmt.Printf("\n")
}
func DumpBytesClause(data []byte) {
fmt.Printf("DUMP: ")
fmt.Printf("[]byte { ")
for i, x := range data {
fmt.Printf("0x%02x", x)
if i < len(data)-1 {
fmt.Printf(", ")
}
}
fmt.Printf(" }\n")
}
func DumpBytesToString(data []byte) string {
b := new(bytes.Buffer)
for i, x := range data {
_, err := b.WriteString(fmt.Sprintf("%02x", x))
log.PanicIf(err)
if i < len(data)-1 {
_, err := b.WriteRune(' ')
log.PanicIf(err)
}
}
return b.String()
}
func DumpBytesClauseToString(data []byte) string {
b := new(bytes.Buffer)
for i, x := range data {
_, err := b.WriteString(fmt.Sprintf("0x%02x", x))
log.PanicIf(err)
if i < len(data)-1 {
_, err := b.WriteString(", ")
log.PanicIf(err)
}
}
return b.String()
}