Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3 #84

Closed
wants to merge 3 commits into from
Closed

V3 #84

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
testdata/test.mp3
3 changes: 3 additions & 0 deletions v3/MIGRATION_GUIDE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## AddTextFrame was deleted

TODO
120 changes: 120 additions & 0 deletions v3/bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
"bytes"
"io/ioutil"
"testing"
)

var frontCoverPicture = mustReadFile(frontCoverPath)

func BenchmarkParseAllFrames(b *testing.B) {
writeTag(b, EncodingUTF8)
musicContent := mustReadFile(mp3Path)
b.ResetTimer()

for n := 0; n < b.N; n++ {
tag, err := ParseReader(bytes.NewReader(musicContent), parseOpts)
if tag == nil || err != nil {
b.Fatal("Error while opening mp3 file:", err)
}
}
}

func BenchmarkParseAllFramesISO(b *testing.B) {
writeTag(b, EncodingISO)
musicContent := mustReadFile(mp3Path)
b.ResetTimer()

for n := 0; n < b.N; n++ {
tag, err := ParseReader(bytes.NewReader(musicContent), parseOpts)
if tag == nil || err != nil {
b.Fatal("Error while opening mp3 file:", err)
}
}
}

func BenchmarkParseArtistAndTitle(b *testing.B) {
writeTag(b, EncodingUTF8)
musicContent := mustReadFile(mp3Path)
b.ResetTimer()

for n := 0; n < b.N; n++ {
opts := Options{Parse: true, ParseFrames: []string{"Artist", "Title"}}
tag, err := ParseReader(bytes.NewReader(musicContent), opts)
if tag == nil || err != nil {
b.Fatal("Error while opening mp3 file:", err)
}
}
}

func BenchmarkWrite(b *testing.B) {
for n := 0; n < b.N; n++ {
benchWrite(b, EncodingUTF8)
}
}

func BenchmarkWriteISO(b *testing.B) {
for n := 0; n < b.N; n++ {
benchWrite(b, EncodingISO)
}
}

func benchWrite(b *testing.B, encoding Encoding) {
tag := NewEmptyTag()
setFrames(tag, encoding)
if _, err := tag.WriteTo(ioutil.Discard); err != nil {
b.Error("Error while writing a tag:", err)
}
}

func writeTag(b *testing.B, encoding Encoding) {
tag, err := Open(mp3Path, Options{Parse: false})
if tag == nil || err != nil {
b.Fatal("Error while opening mp3 file:", err)
}
defer tag.Close()

setFrames(tag, encoding)

if err = tag.Save(); err != nil {
b.Error("Error while saving a tag:", err)
}
}

func setFrames(tag *Tag, encoding Encoding) {
tag.SetTitle("Title")
tag.SetArtist("Artist")
tag.SetAlbum("Album")
tag.SetYear("2016")
tag.SetGenre("Genre")

pic := PictureFrame{
Encoding: encoding,
MimeType: "image/jpeg",
PictureType: PTFrontCover,
Description: "Front cover",
Picture: frontCoverPicture,
}
tag.AddAttachedPicture(pic)

uslt := UnsynchronisedLyricsFrame{
Encoding: encoding,
Language: "eng",
ContentDescriptor: "Content descriptor",
Lyrics: "bogem/id3v2",
}
tag.AddUnsynchronisedLyricsFrame(uslt)

comm := CommentFrame{
Encoding: encoding,
Language: "eng",
Description: "Short description",
Text: "The actual text",
}
tag.AddCommentFrame(comm)
}
187 changes: 187 additions & 0 deletions v3/buf_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package id3v2

import (
"bufio"
"bytes"
"io"
)

// bufReader is used for convenient parsing of frames.
type bufReader struct {
buf *bufio.Reader
err error
}

// newBufReader returns *bufReader with specified rd.
func newBufReader(rd io.Reader) *bufReader {
return &bufReader{buf: bufio.NewReader(rd)}
}

func (br *bufReader) Discard(n int) {
if br.err != nil {
return
}
_, br.err = br.buf.Discard(n)
}

func (br *bufReader) Err() error {
return br.err
}

// Read calls br.buf.Read(p) and returns the results of it.
// It does nothing, if br.err != nil.
//
// NOTE: if br.buf.Read(p) returns the error, it doesn't save it in
// br.err and it's not returned from br.Err().
func (br *bufReader) Read(p []byte) (n int, err error) {
if br.err != nil {
return 0, br.err
}
return br.buf.Read(p)
}

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF.
// Because ReadAll is defined to read from src until EOF,
// it does not treat an EOF from Read as an error to be reported.
func (br *bufReader) ReadAll() []byte {
if br.err != nil {
return nil
}
buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
_, err := buf.ReadFrom(br)
if err != nil && br.err == nil {
br.err = err
return nil
}
return buf.Bytes()
}

func (br *bufReader) ReadByte() byte {
if br.err != nil {
return 0
}
var b byte
b, br.err = br.buf.ReadByte()
return b
}

// Next returns a slice containing the next n bytes from the buffer,
// advancing the buffer as if the bytes had been returned by Read.
// If there are fewer than n bytes in the buffer, Next returns the entire buffer.
// The slice is only valid until the next call to a read or write method.
func (br *bufReader) Next(n int) []byte {
if br.err != nil {
return nil
}
var b []byte
b, br.err = br.next(n)
return b
}

func (br *bufReader) next(n int) ([]byte, error) {
if n == 0 {
return nil, nil
}

peeked, err := br.buf.Peek(n)
if err != nil {
return nil, err
}

if _, err := br.buf.Discard(n); err != nil {
return nil, err
}

return peeked, nil
}

// readTillDelim reads until the first occurrence of delim in the input,
// returning a slice containing the data up to and NOT including the delim.
// If ReadTillDelim encounters an error before finding a delimiter,
// it returns the data read before the error and the error itself.
// ReadTillDelim returns err != nil if and only if ReadTillDelim didn't find
// delim.
func (br *bufReader) readTillDelim(delim byte) ([]byte, error) {
read, err := br.buf.ReadBytes(delim)
if err != nil || len(read) == 0 {
return read, err
}
err = br.buf.UnreadByte()
return read[:len(read)-1], err
}

// readTillDelims reads until the first occurrence of delims in the input,
// returning a slice containing the data up to and NOT including the delimiters.
// If ReadTillDelims encounters an error before finding a delimiters,
// it returns the data read before the error and the error itself.
// ReadTillDelims returns err != nil if and only if ReadTillDelims didn't find
// delims.
func (br *bufReader) readTillDelims(delims []byte) ([]byte, error) {
if len(delims) == 0 {
return nil, nil
}
if len(delims) == 1 {
return br.readTillDelim(delims[0])
}

result := make([]byte, 0)

for {
read, err := br.readTillDelim(delims[0])
if err != nil {
return result, err
}
result = append(result, read...)

peeked, err := br.buf.Peek(len(delims))
if err != nil {
return result, err
}

if bytes.Equal(peeked, delims) {
break
}

b, err := br.buf.ReadByte()
if err != nil {
return result, err
}
result = append(result, b)
}

return result, nil
}

// ReadText reads until the first occurrence of delims in the input,
// returning a slice containing the data up to and NOT including the delimiters.
// But it discards then termination bytes according to provided encoding.
func (br *bufReader) ReadText(encoding Encoding) []byte {
if br.err != nil {
return nil
}

var text []byte
delims := encoding.TerminationBytes
text, br.err = br.readTillDelims(delims)

// See https://github.com/bogem/id3v2/issues/51.
if encoding.Equals(EncodingUTF16) &&
// See https://github.com/bogem/id3v2/issues/53#issuecomment-604038434.
!bytes.Equal(text, bom) {
text = append(text, br.ReadByte())
}

br.Discard(len(delims))

return text
}

func (br *bufReader) Reset(rd io.Reader) {
br.buf.Reset(rd)
br.err = nil
}
Loading