4

I would like to read the Popularimeter frame from a file with id3-go.

This is how the frame looks like when printing with mutagen-inspect:

$ mutagen-inspect samples/with_popm.mp3 | grep POPM
POPM=traktor@native-instruments.de=0 255/255

I would like to read the value (255/255) from the file. As I could not find any documentation, my naive approach is:

popFrame := mp3File.Frame("POPM")
log.Println(popFrame.String())

But when I run this (on a file with and also without a popularimeter tag), I get segmentation faults:

$ ./id3-go-popm-example samples/with_popm.mp3 
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x4ac302]

goroutine 1 [running]:
main.main()
    /home/ifischer/src/rivamp/rivamp-dist/id3-go-popm-example/main.go:21 +0xd2
$ ./id3-go-popm-example samples/without_popm.mp3 
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x4ac302]

goroutine 1 [running]:
main.main()
    /home/ifischer/src/rivamp/rivamp-dist/id3-go-popm-example/main.go:21 +0xd2

I setup a sample repository containing two sample files (one with, one without popularimeter frame) here: https://github.com/ifischer/id3-go-popm-example

How to I get the Popularimeter value (255/255) with id3-go library?

Wolkenarchitekt
  • 20,170
  • 29
  • 111
  • 174
  • The actual segfault/backtrace could also be helpful. – Volker Stolz Oct 29 '18 at 17:00
  • @ShiDoiSi updated question with segfault – Wolkenarchitekt Oct 29 '18 at 21:21
  • 1
    I just finished inspecting the id3-go library, it's definitely issue on the 3rd party it self. the possible solution are: 1. fix the issue on id3-go; 2. or just use another library, like @ssemilla's answer – novalagung Oct 30 '18 at 05:53
  • 1
    I've added the fix if you really need to use `id3-go`, it's a one-liner fix since it is just missing the `TDRC` field. – ssemilla Oct 30 '18 at 12:54
  • I've also added the code snippet to parse the popularimeter as it seems not to be included in `id3-go`. I hope this already answers the question. – ssemilla Nov 01 '18 at 03:41

1 Answers1

4

You're getting a segmentation fault because mp3File.Frame("POPM") is actually returning a nil value. I've narrowed it down to the V23FrameTypeMap in id3-go which lacks the TRDC frame. I'm really not familiar with the internals of ID3 but maybe you need to check if TRDC is really in V2.3 and if it is, add the missing ids in V23FrameTypeMap in id3-go.

Since it would be too much of a hassle to learn ID3, I've managed to get the POPM tag using another library https://github.com/dhowden/tag

f, _ := os.Open("with_popm.mp3")
m, _ := tag.ReadFrom(f)
fmt.Printf("%s\n", m.Raw()["POPM"])

which outputs:

traktor@native-instruments.de

Edit: I mistook TDRC as TRDC, my mistake. Anyway, here is what you need to do to get the POPM field using id3-go

Add the following in V23FrameTypeMap in github.com/mikkyang/id3-go/v2/idv23.go

V23FrameTypeMap = map[string]FrameType{
    //...
    "TDRC": FrameType{id: "TDRC", description: "Recording Date", constructor: ParseTextFrame},
}

Then you can easily get the POPM field e.g.

package main

import (
    "fmt"
    id3 "github.com/mikkyang/id3-go"
)

func main() {

    f, _ := id3.Open("with_popm.mp3")
    p := f.Frame("POPM")
    fmt.Printf("%s\n", p.Bytes())

}

Edit 2: I've added a way so that you can parse the POPM field. You can then access the rating 255/255 from Popularimeter.Rating:

import (
    "bytes"
    "encoding/binary"
    "errors"
    "fmt"

    id3 "github.com/mikkyang/id3-go"
)

func main() {

    f, _ := id3.Open("with_popm.mp3")
    popm := f.Frame("POPM")
    popularimeter, _ := FromData(popm.Bytes())
    fmt.Println(popularimeter)
}

type Rating uint8

func (r Rating) String() string {
    if r == 0 {
        return "unknown"
    }
    return fmt.Sprintf("%d/255", r)
}

type Popularimeter struct {
    Email   string
    Rating  Rating
    Counter uint32
}

func FromData(data []byte) (*Popularimeter, error) {

    tokens := bytes.SplitN(data, []byte{0x0}, 2)
    // Pupolarimeter: <string>, null, 1byte rating, 4bytes counter
    if len(tokens) != 2 || len(tokens[1]) != 5 {
        return nil, errors.New("invalid Popularimeter")
    }

    return &Popularimeter{
        Email:   string(tokens[0]),
        Rating:  Rating(tokens[1][0]),
        Counter: binary.BigEndian.Uint32(tokens[1][1:]),
    }, nil
}
ssemilla
  • 3,900
  • 12
  • 28
  • 1
    Answer deserves bounty especially for explanation! However using id3-go would be preferred as it can also modify ID3 tags – Wolkenarchitekt Oct 29 '18 at 21:26
  • I was very sleepy last night and I mistook TDRC as TRDC. Anyway, the answer is edited so you can parse POPM using `id3-go` – ssemilla Oct 30 '18 at 03:42