From 36cc17efed51a9bae283d6a3a7a10997492945e7 Mon Sep 17 00:00:00 2001 From: Henry Eklind Date: Sat, 26 May 2018 00:27:20 +0900 Subject: [PATCH] Skip ID3v2 data prepended to flac files on parsing (#21) * Change signature to flacSignature In order to disambiguate the introduction of the ID3v2 signature. * Add check flac files containing ID3v2 data * Implement skipId3v2 Note: a new import for decoding synchronized integers from ID3v2 headers was introduced. * Add error checking on the r.Discard calls * Fix comments * Use limited scope for error handling * Capitalize ID in skipID3v2 * Fix comments * Add testcase for skipping id3 data --- enc.go | 2 +- flac.go | 54 ++++++++++++++++++++++++++++++++++++++++------ flac_test.go | 13 +++++++++++ testdata/id3.flac | Bin 0 -> 75904 bytes 4 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 flac_test.go create mode 100644 testdata/id3.flac diff --git a/enc.go b/enc.go index 5740c30..4144645 100644 --- a/enc.go +++ b/enc.go @@ -24,7 +24,7 @@ func Encode(w io.Writer, stream *Stream) error { enc := &encoder{bw: bitio.NewWriter(buf)} // Store FLAC signature. - if _, err := enc.bw.Write(signature); err != nil { + if _, err := enc.bw.Write(flacSignature); err != nil { return errutil.Err(err) } diff --git a/flac.go b/flac.go index b47a3de..c5f34f9 100644 --- a/flac.go +++ b/flac.go @@ -30,6 +30,8 @@ import ( "github.com/mewkiz/flac/frame" "github.com/mewkiz/flac/meta" + + "github.com/mikkyang/id3-go/encodedbytes" ) // A Stream contains the metadata blocks and provides access to the audio frames @@ -79,8 +81,11 @@ func New(r io.Reader) (stream *Stream, err error) { return stream, nil } -// signature marks the beginning of a FLAC stream. -var signature = []byte("fLaC") +// flacSignature marks the beginning of a FLAC stream. +var flacSignature = []byte("fLaC") + +// id3Signature marks the beginning of an ID3 stream, used to skip over ID3 data. +var id3Signature = []byte("ID3") // parseStreamInfo verifies the signature which marks the beginning of a FLAC // stream, and parses the StreamInfo metadata block. It returns a boolean value @@ -90,12 +95,24 @@ func (stream *Stream) parseStreamInfo() (isLast bool, err error) { // Verify FLAC signature. r := stream.r var buf [4]byte - _, err = io.ReadFull(r, buf[:]) - if err != nil { + if _, err = io.ReadFull(r, buf[:]); err != nil { return false, err } - if !bytes.Equal(buf[:], signature) { - return false, fmt.Errorf("flac.parseStreamInfo: invalid FLAC signature; expected %q, got %q", signature, buf) + + // Skip prepended ID3v2 data. + if bytes.Equal(buf[:3], id3Signature) { + if err := stream.skipID3v2(); err != nil { + return false, err + } + + // Second attempt at verifying signature. + if _, err = io.ReadFull(r, buf[:]); err != nil { + return false, err + } + } + + if !bytes.Equal(buf[:], flacSignature) { + return false, fmt.Errorf("flac.parseStreamInfo: invalid FLAC signature; expected %q, got %q", flacSignature, buf) } // Parse StreamInfo metadata block. @@ -111,6 +128,31 @@ func (stream *Stream) parseStreamInfo() (isLast bool, err error) { return block.IsLast, nil } +// skipId3v2 skips ID3v2 data prepended to flac files. +func (stream *Stream) skipID3v2() error { + r := bufio.NewReader(stream.r) + + // Discard unnecessary data from the ID3v2 header. + if _, err := r.Discard(2); err != nil { + return err + } + + // Read the size from the ID3v2 header. + var sizeBuf [4]byte + if _, err := r.Read(sizeBuf[:]); err != nil { + return err + } + + // The size is encoded as a synchsafe integer. + size, err := encodedbytes.SynchInt(sizeBuf[:]) + if err != nil { + return err + } + + _, err = r.Discard(int(size)) + return err +} + // Parse creates a new Stream for accessing the metadata blocks and audio // samples of r. It reads and parses the FLAC signature and all metadata blocks. // diff --git a/flac_test.go b/flac_test.go new file mode 100644 index 0000000..63d4142 --- /dev/null +++ b/flac_test.go @@ -0,0 +1,13 @@ +package flac_test + +import ( + "testing" + + "github.com/mewkiz/flac" +) + +func TestSkipID3v2(t *testing.T) { + if _, err := flac.ParseFile("testdata/id3.flac"); err != nil { + t.Fatal(err) + } +} diff --git a/testdata/id3.flac b/testdata/id3.flac new file mode 100644 index 0000000000000000000000000000000000000000..bf4e74f2d1906d37556ef16b9bb362f8f89f443d GIT binary patch literal 75904 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 vbF# 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