radio-stream-recorder/vorbis/vorbis.go
2021-06-03 19:07:27 +02:00

97 lines
2.3 KiB
Go

package vorbis
import (
"bytes"
"errors"
"io"
)
var (
ErrNoHeaderSegment = errors.New("no header segment")
ErrNoMetadata = errors.New("no metadata found")
ErrCallReadRestAfterReadMetadata = errors.New("please call vorbis.Decoder.ReadRest() after having called vorbis.Decoder.ReadMetadata()")
ErrReadMetadataCalledTwice = errors.New("cannot call vorbis.Decoder.ReadMetadata() twice on the same file")
)
type Decoder struct {
r io.Reader
hasMetadata bool
}
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{
r: r,
}
}
func (d *Decoder) readPage() (page OggPage, hdr VorbisHeader, err error) {
// Decode page.
page, err = OggDecode(d.r)
if err != nil {
return page, hdr, err
}
// We need to be able to access `page.Segments[0]`.
if len(page.Segments) == 0 {
return page, hdr, ErrNoHeaderSegment
}
// Decode Vorbis header, stored in `page.Segments[0]`.
hdr, err = VorbisHeaderDecode(bytes.NewBuffer(page.Segments[0]))
if err != nil {
return page, hdr, err
}
return page, hdr, nil
}
// Reads the Ogg/Vorbis file until it finds its metadata. Leaves the reader
// right after the end of the metadata. `crc32Sum` gives the crc32 checksum
// of the page containing the metadata. It is equivalent to the page checksum
// used in the Ogg container. Since the page contains more than just metadata,
// the checksum can usually be used as a unique identifier.
func (d *Decoder) ReadMetadata() (metadata *VorbisComment, crc32Sum uint32, err error) {
if d.hasMetadata {
return nil, 0, ErrReadMetadataCalledTwice
}
for {
page, hdr, err := d.readPage()
if err != nil {
return nil, 0, err
}
if (page.Header.HeaderType & FHeaderTypeEOS) > 0 {
// End of stream
return nil, 0, ErrNoMetadata
}
if hdr.PackType == PackTypeComment {
d.hasMetadata = true
return hdr.Comment, page.Header.Checksum, nil
}
}
}
// Must to be called after `ReadMetadata()`. Reads the rest of the Ogg/Vorbis
// file, leaving the reader right after the end of the Ogg/Vorbis file.
func (d *Decoder) ReadRest() error {
if !d.hasMetadata {
return ErrCallReadRestAfterReadMetadata
}
for {
page, _, err := d.readPage()
if err != nil {
return err
}
if (page.Header.HeaderType & FHeaderTypeEOS) > 0 {
// End of stream
break
}
}
return nil
}