Skip to content

Commit

Permalink
add popularimeter frame
Browse files Browse the repository at this point in the history
  • Loading branch information
dhulihan authored and n10v committed Jun 7, 2020
1 parent db1352e commit 8403eb1
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 3 deletions.
3 changes: 3 additions & 0 deletions common_ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
"Original lyricist/text writer": "TOLY",
"Original artist/performer": "TOPE",
"Original release year": "TORY",
"Popularimeter": "POPM",
"File owner/licensee": "TOWN",
"Lead artist/Lead performer/Soloist/Performing group": "TPE1",
"Band/Orchestra/Accompaniment": "TPE2",
Expand Down Expand Up @@ -90,6 +91,7 @@ var (
"Original filename": "TOFN",
"Original lyricist/text writer": "TOLY",
"Original artist/performer": "TOPE",
"Popularimeter": "POPM",
"File owner/licensee": "TOWN",
"Lead artist/Lead performer/Soloist/Performing group": "TPE1",
"Band/Orchestra/Accompaniment": "TPE2",
Expand Down Expand Up @@ -136,6 +138,7 @@ var (
var parsers = map[string]func(*bufReader) (Framer, error){
"APIC": parsePictureFrame,
"COMM": parseCommentFrame,
"POPM": parsePopularimeterFrame,
"TXXX": parseUserDefinedTextFrame,
"UFID": parseUFIDFrame,
"USLT": parseUnsynchronisedLyricsFrame,
Expand Down
28 changes: 28 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"log"
"math/big"
"os"
"sync"

Expand Down Expand Up @@ -268,3 +269,30 @@ func ExampleUnsynchronisedLyricsFrame_add() {
}
tag.AddUnsynchronisedLyricsFrame(uslt)
}

func ExamplePopularimeterFrame_add() {
tag := id3v2.NewEmptyTag()

popmFrame := id3v2.PopularimeterFrame{
Email: "[email protected]",
Rating: 128,
Counter: big.NewInt(10000000000000000),
}
tag.AddFrame(tag.CommonID("Popularimeter"), popmFrame)
}

func ExamplePopularimeterFrame_get() {
tag, err := id3v2.Open("file.mp3", id3v2.Options{Parse: true})
if tag == nil || err != nil {
log.Fatal("Error while opening mp3 file: ", err)
}

f := tag.GetLastFrame(tag.CommonID("Popularimeter"))
popm, ok := f.(id3v2.PopularimeterFrame)
if !ok {
log.Fatal("Couldn't assert POPM frame")
}

// do something with POPM Frame
fmt.Printf("Email: %s, Rating: %d, Counter: %d", popm.Email, popm.Rating, popm.Counter)
}
21 changes: 21 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestParse(t *testing.T) {
defer tag.Close()

testTextFrames(t, tag)
testPopularimeterFrame(t, tag)
testPictureFrames(t, tag)
testUSLTFrames(t, tag)
testTXXXFrames(t, tag)
Expand Down Expand Up @@ -186,6 +187,26 @@ func compareTXXXFrames(actual, expected UserDefinedTextFrame) error {
return nil
}

func testPopularimeterFrame(t *testing.T, tag *Tag) {
actual := tag.GetLastFrame(tag.CommonID("Popularimeter")).(PopularimeterFrame)

if actual.Size() != popmFrame.Size() {
t.Errorf("Expected size: %d, got: %d", popmFrame.Size(), actual.Size())
}

if actual.Email != popmFrame.Email {
t.Errorf("Expected email: %v, got: %v", popmFrame.Email, actual.Email)
}

if actual.Rating != popmFrame.Rating {
t.Errorf("Expected rating: %v, got: %v", popmFrame.Rating, actual.Rating)
}

if actual.Counter.Text(16) != popmFrame.Counter.Text(16) {
t.Errorf("Expected counter: %s, got: %s", popmFrame.Counter.Text(16), actual.Counter.Text(16))
}
}

func testUFIDFrames(t *testing.T, tag *Tag) {
ufidFrames := tag.GetFrames("UFID")
if len(ufidFrames) != 1 {
Expand Down
68 changes: 68 additions & 0 deletions popularimeter_frame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package id3v2

import (
"io"
"math/big"
)

// PopularimeterFrame structure is used for Popularimeter (POPM).
// https://id3.org/id3v2.3.0#Popularimeter
type PopularimeterFrame struct {
// Email is the identifier for a POPM frame.
Email string

// The rating is 1-255 where 1 is worst and 255 is best. 0 is unknown.
Rating uint8

// Counter is the number of times this file has been played by this email.
Counter *big.Int
}

func (pf PopularimeterFrame) UniqueIdentifier() string {
return pf.Email
}

func (pf PopularimeterFrame) Size() int {
ratingSize := 1
return len(pf.Email) + 1 + ratingSize + len(pf.counterBytes())
}

// counterBytes returns a byte slice that represents the counter.
func (pf PopularimeterFrame) counterBytes() []byte {
bytes := pf.Counter.Bytes()

// Specification requires at least 4 bytes for counter, pad if necessary.
bytesNeeded := 4 - len(bytes)
if bytesNeeded > 0 {
padding := make([]byte, bytesNeeded)
bytes = append(padding, bytes...)
}

return bytes
}

func (pf PopularimeterFrame) WriteTo(w io.Writer) (n int64, err error) {
return useBufWriter(w, func(bw *bufWriter) {
bw.WriteString(pf.Email)
bw.WriteByte(0)
bw.WriteByte(pf.Rating)
bw.Write(pf.counterBytes())
})
}

func parsePopularimeterFrame(br *bufReader) (Framer, error) {
email := br.ReadText(EncodingISO)
rating := br.ReadByte()

counter := big.NewInt(0)
remainingBytes := br.ReadAll()
counter = counter.SetBytes(remainingBytes)

pf := PopularimeterFrame{
Email: string(email),
Rating: rating,
Counter: counter,
}

return pf, nil
}
34 changes: 34 additions & 0 deletions popularimeter_frame_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package id3v2

import (
"bytes"
"math/big"
"testing"
)

// Make sure that counter of popularimeter is at least 4 bytes even if it's small number.
func TestPopularimeterFrameSmallCounter(t *testing.T) {
popmFrame := PopularimeterFrame{
Email: "[email protected]",
Rating: 1,
Counter: big.NewInt(1),
}

expectedBodyLength := len(popmFrame.Email) + 1 + 1 + 4

buf := new(bytes.Buffer)
written, err := popmFrame.WriteTo(buf)
if err != nil {
t.Fatalf("Error by writing: %v", err)
}

if written != int64(expectedBodyLength) {
t.Fatalf("Expected popularimeter frame body length: %v, got: %v", expectedBodyLength, written)
}

expectedCounter := []byte{0, 0, 0, 1}
gotCounter := buf.Bytes()[expectedBodyLength-4:]
if !bytes.Equal(expectedCounter, gotCounter) {
t.Fatalf("Expected popularimeter counter: %v, got: %v", expectedCounter, gotCounter)
}
}
15 changes: 12 additions & 3 deletions tag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"bytes"
"fmt"
"io/ioutil"
"math/big"
"os"
"strings"
"sync"
Expand All @@ -20,10 +21,10 @@ const (
frontCoverPath = "testdata/front_cover.jpg"
backCoverPath = "testdata/back_cover.jpg"

framesSize = 211948
framesSize = 211978
tagSize = tagHeaderSize + framesSize
musicSize = 3840834
countOfFrames = 14
countOfFrames = 15
)

var (
Expand Down Expand Up @@ -79,6 +80,12 @@ var (
Text: "Der eigentliche Text",
}

popmFrame = PopularimeterFrame{
Email: "[email protected]",
Rating: 128,
Counter: big.NewInt(10000000000000000),
}

unknownFrameID = "WPUB"
unknownFrame = UnknownFrame{
Body: []byte("https://soundcloud.com/suicidepart2"),
Expand Down Expand Up @@ -117,6 +124,8 @@ func resetMP3Tag() error {
tag.AddUserDefinedTextFrame(musicBrainzUDTF)
tag.AddUFIDFrame(musicBrainzUF)

tag.AddFrame(tag.CommonID("Popularimeter"), popmFrame)

tag.AddCommentFrame(engComm)
tag.AddCommentFrame(gerComm)

Expand Down Expand Up @@ -146,7 +155,7 @@ func TestCountLenSize(t *testing.T) {
}

// Check len of tag.AllFrames().
if len(tag.AllFrames()) != 11 {
if len(tag.AllFrames()) != 12 {
t.Errorf("Expected: %v, got: %v", 11, len(tag.AllFrames()))
}

Expand Down

0 comments on commit 8403eb1

Please sign in to comment.