Skip to content

Commit

Permalink
Migrate to go-flac v2 (go-flac/go-flac#5)
Browse files Browse the repository at this point in the history
Signed-off-by: eternal-flame-AD <[email protected]>
  • Loading branch information
eternal-flame-AD committed Aug 2, 2024
1 parent c2bd6c3 commit 90a5473
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 0 deletions.
10 changes: 10 additions & 0 deletions v2/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package flacpicture

import "errors"

var (
// ErrorNotPictureMetadataBlock is returned if the metadata provided is not a picture block.
ErrorNotPictureMetadataBlock = errors.New("Not a picture metadata block")
// ErrorUnsupportedMIME is returned if the provided image MIME type is unsupported.
ErrorUnsupportedMIME = errors.New("Unsupported MIME")
)
5 changes: 5 additions & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/go-flac/flacpicture/v2

go 1.20

require github.com/go-flac/go-flac v1.0.0
2 changes: 2 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/go-flac/go-flac v1.0.0 h1:6qI9XOVLcO50xpzm3nXvO31BgDgHhnr/p/rER/K/doY=
github.com/go-flac/go-flac v1.0.0/go.mod h1:WnZhcpmq4u1UdZMNn9LYSoASpWOCMOoxXxcWEHSzkW8=
36 changes: 36 additions & 0 deletions v2/parsepic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package flacpicture

import (
"bytes"
"image/jpeg"
"image/png"
)

// ParsePicture decodes the image and inflates the Width, Height, ColorDepth, IndexedColorCount fields. This is called automatically by NewFromImageData
func (c *MetadataBlockPicture) ParsePicture() error {
switch c.MIME {
case "image/jpeg":
img, err := jpeg.Decode(bytes.NewReader(c.ImageData))
if err != nil {
return err
}
c.IndexedColorCount = uint32(0)
size := img.Bounds()
c.Width = uint32(size.Max.X)
c.Height = uint32(size.Max.Y)
c.ColorDepth = uint32(24)
case "image/png":
img, err := png.Decode(bytes.NewReader(c.ImageData))
if err != nil {
return err
}
c.IndexedColorCount = uint32(0)
size := img.Bounds()
c.Width = uint32(size.Max.X)
c.Height = uint32(size.Max.Y)
c.ColorDepth = uint32(32)
default:
return ErrorUnsupportedMIME
}
return nil
}
136 changes: 136 additions & 0 deletions v2/picture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package flacpicture

import (
"bytes"

flac "github.com/go-flac/go-flac"
)

// PictureType defines the type of this image
type PictureType uint32

const (
PictureTypeOther PictureType = iota
PictureTypeFileIcon
PictureTypeOtherIcon
PictureTypeFrontCover
PictureTypeBackCover
PictureTypeLeaflet
PictureTypeMedia
PictureTypeLeadArtist
PictureTypeArtist
PictureTypeConductor
PictureTypeBand
PictureTypeComposer
PictureTypeLyricist
PictureTypeRecordingLocation
PictureTypeDuringRecording
PictureTypeDuringPerformance
PictureTypeScreenCapture
PictureTypeBrightColouredFish
PictureTypeIllustration
PictureTypeBandArtistLogotype
PictureTypePublisherStudioLogotype
)

// MetadataBlockPicture represents a picture metadata block
type MetadataBlockPicture struct {
PictureType PictureType
MIME string
Description string
Width uint32
Height uint32
ColorDepth uint32
IndexedColorCount uint32
ImageData []byte
}

// Marshal encodes the PictureBlock to a standard FLAC MetaDataBlock to be accepted by go-flac
func (c *MetadataBlockPicture) Marshal() flac.MetaDataBlock {
res := bytes.NewBuffer([]byte{})
res.Write(encodeUint32(uint32(c.PictureType)))
res.Write(encodeUint32(uint32(len(c.MIME))))
res.Write([]byte(c.MIME))
res.Write(encodeUint32(uint32(len(c.Description))))
res.Write([]byte(c.Description))
res.Write(encodeUint32(c.Width))
res.Write(encodeUint32(c.Height))
res.Write(encodeUint32(c.ColorDepth))
res.Write(encodeUint32(c.IndexedColorCount))
res.Write(encodeUint32(uint32(len(c.ImageData))))
res.Write(c.ImageData)
return flac.MetaDataBlock{
Type: flac.Picture,
Data: res.Bytes(),
}
}

// NewFromImageData generates a MetadataBlockPicture from image data, returns an error if the picture data connot be parsed
func NewFromImageData(pictype PictureType, description string, imgdata []byte, mime string) (*MetadataBlockPicture, error) {
res := new(MetadataBlockPicture)
res.PictureType = pictype
res.Description = description
res.MIME = mime
res.ImageData = imgdata
err := res.ParsePicture()
return res, err
}

// ParseFromMetaDataBlock parses an existing picture MetaDataBlock
func ParseFromMetaDataBlock(meta flac.MetaDataBlock) (*MetadataBlockPicture, error) {
if meta.Type != flac.Picture {
return nil, ErrorNotPictureMetadataBlock
}
res := new(MetadataBlockPicture)
data := bytes.NewBuffer(meta.Data)

if pictype, err := readUint32(data); err != nil {
return nil, err
} else {
res.PictureType = PictureType(pictype)
}

if mimebytes, err := readBytesWith32bitSize(data); err != nil {
return nil, err
} else {
res.MIME = string(mimebytes)
}

if descbytes, err := readBytesWith32bitSize(data); err != nil {
return nil, err
} else {
res.Description = string(descbytes)
}

if width, err := readUint32(data); err != nil {
return nil, err
} else {
res.Width = width
}

if height, err := readUint32(data); err != nil {
return nil, err
} else {
res.Height = height
}

if depth, err := readUint32(data); err != nil {
return nil, err
} else {
res.ColorDepth = depth
}

if count, err := readUint32(data); err != nil {
return nil, err
} else {
res.IndexedColorCount = count
}

if pic, err := readBytesWith32bitSize(data); err != nil {
return nil, err
} else {
res.ImageData = pic
}

return res, nil
}
142 changes: 142 additions & 0 deletions v2/picture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package flacpicture

import (
"archive/zip"
"bytes"
"fmt"
"io/ioutil"
"net/http"
"testing"

flac "github.com/go-flac/go-flac"
)

func httpGetBytes(url string) ([]byte, error) {
res, err := http.Get(url)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP status %d", res.StatusCode)
}
return ioutil.ReadAll(res.Body)
}

func TestPNGPictureDecode(t *testing.T) {
imgdata, err := httpGetBytes("https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png")
if err != nil {
t.Errorf("Error while downloading test file: %s", err.Error())
t.FailNow()
}
pic, err := NewFromImageData(PictureTypeArtist, "test description", imgdata, "image/png")
if err != nil {
t.Errorf("Error while constructing image data: %s", err.Error())
t.Fail()
}

if pic.MIME != "image/png" {
t.Errorf("MIME decode error: got %s", pic.MIME)
t.Fail()
}

if pic.Height != 600 || pic.Width != 800 {
t.Errorf("JPEG size error: got %dx%d", pic.Width, pic.Height)
t.Fail()
}
}
func TestJPEGPictureDecode(t *testing.T) {
imgdata, err := httpGetBytes("https://jpeg.org/images/jpeg-home.jpg")
if err != nil {
t.Errorf("Error while downloading test file: %s", err.Error())
t.FailNow()
}
pic, err := NewFromImageData(PictureTypeArtist, "test description", imgdata, "image/jpeg")
if err != nil {
t.Errorf("Error while constructing image data: %s", err.Error())
t.Fail()
}

if pic.MIME != "image/jpeg" {
t.Errorf("MIME decode error: got %s", pic.MIME)
t.Fail()
}

if pic.Height != 400 || pic.Width != 800 {
t.Errorf("JPEG size error: got %dx%d", pic.Width, pic.Height)
t.Fail()
}
}

func TestPictureModify(t *testing.T) {
imgdata, err := httpGetBytes("https://jpeg.org/images/jpeg-home.jpg")
if err != nil {
t.Errorf("Error while downloading test file: %s", err.Error())
t.FailNow()
}
pic, err := NewFromImageData(PictureTypeArtist, "test description", imgdata, "image/jpeg")
if err != nil {
t.Errorf("Error while constructing image data: %s", err.Error())
t.Fail()
}

pic.Description = "another description"

pic, err = ParseFromMetaDataBlock(pic.Marshal())
if err != nil {
t.Errorf("Error while parsing modified image data: %s", err.Error())
t.Fail()
}

if pic.Description != "another description" {
t.Errorf("description unepected: %s", pic.Description)
t.Fail()
}
}

func TestJPEGFromExistingFLAC(t *testing.T) {
zipdata, err := httpGetBytes("http://helpguide.sony.net/high-res/sample1/v1/data/Sample_BeeMoved_96kHz24bit.flac.zip")
if err != nil {
t.Errorf("Error while downloading test file: %s", err.Error())
t.FailNow()
}
zipfile, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata)))
if err != nil {
t.Errorf("Error while decompressing test file: %s", err.Error())
t.FailNow()
}
if zipfile.File[0].Name != "Sample_BeeMoved_96kHz24bit.flac" {
t.Errorf("Unexpected test file content: %s", zipfile.File[0].Name)
t.FailNow()
}

flachandle, err := zipfile.File[0].Open()
if err != nil {
t.Errorf("Failed to decompress test file: %s", err)
t.FailNow()
}

f, err := flac.ParseBytes(flachandle)
if err != nil {
t.Errorf("Failed to parse flac file: %s", err)
t.FailNow()
}

var pic *MetadataBlockPicture
for _, meta := range f.Meta {
if meta.Type == flac.Picture {
pic, err = ParseFromMetaDataBlock(*meta)
if err != nil {
t.Errorf("Error while parsing metadata image: %s", err.Error())
t.Fail()
}
}
}
if pic.PictureType != PictureTypeFrontCover {
t.Error("Picture type does not match")
t.Fail()
}
if pic.MIME != "image/jpeg" {
t.Errorf("Picture MIME does not match: %s", pic.MIME)
t.Fail()
}
}
41 changes: 41 additions & 0 deletions v2/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package flacpicture

import (
"bytes"
"encoding/binary"
"io"
)

func encodeUint32(n uint32) []byte {
buf := bytes.NewBuffer([]byte{})
if err := binary.Write(buf, binary.BigEndian, n); err != nil {
panic(err)
}
return buf.Bytes()
}

func readUint32(r io.Reader) (res uint32, err error) {
err = binary.Read(r, binary.BigEndian, &res)
return
}

func readBytesWith32bitSize(r io.Reader) (res []byte, err error) {
var size uint32
size, err = readUint32(r)
if err != nil {
return
}
bufall := bytes.NewBuffer([]byte{})
for size > 0 {
var nn int
buf := make([]byte, size)
nn, err = r.Read(buf)
if err != nil {
return
}
bufall.Write(buf)
size -= uint32(nn)
}
res = bufall.Bytes()
return
}
6 changes: 6 additions & 0 deletions v2/var.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package flacpicture

const (
// MIMEURL is the MIME string indicating that imgData is a URL pointing to the image
MIMEURL = "-->"
)

0 comments on commit 90a5473

Please sign in to comment.