tag/ogg.go

175 lines
3.9 KiB
Go
Raw Permalink Normal View History

// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tag
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
)
var (
vorbisCommentPrefix = []byte("\x03vorbis")
opusTagsPrefix = []byte("OpusTags")
)
var oggCRC32Poly04c11db7 = oggCRCTable(0x04c11db7)
type crc32Table [256]uint32
func oggCRCTable(poly uint32) *crc32Table {
var t crc32Table
for i := 0; i < 256; i++ {
crc := uint32(i) << 24
for j := 0; j < 8; j++ {
if crc&0x80000000 != 0 {
crc = (crc << 1) ^ poly
} else {
crc <<= 1
}
}
t[i] = crc
}
return &t
}
func oggCRCUpdate(crc uint32, tab *crc32Table, p []byte) uint32 {
for _, v := range p {
crc = (crc << 8) ^ tab[byte(crc>>24)^v]
}
return crc
}
type oggPageHeader struct {
Magic [4]byte // "OggS"
Version uint8
Flags uint8
GranulePosition uint64
SerialNumber uint32
SequenceNumber uint32
CRC uint32
Segments uint8
}
type oggDemuxer struct {
packetBufs map[uint32]*bytes.Buffer
}
// Read ogg packets, can return empty slice of packets and nil err
// if more data is needed
func (o *oggDemuxer) Read(r io.Reader) ([][]byte, error) {
headerBuf := &bytes.Buffer{}
var oh oggPageHeader
if err := binary.Read(io.TeeReader(r, headerBuf), binary.LittleEndian, &oh); err != nil {
return nil, err
}
if bytes.Compare(oh.Magic[:], []byte("OggS")) != 0 {
// TODO: seek for syncword?
return nil, errors.New("expected 'OggS'")
}
segmentTable := make([]byte, oh.Segments)
if _, err := io.ReadFull(r, segmentTable); err != nil {
return nil, err
}
var segmentsSize int64
for _, s := range segmentTable {
segmentsSize += int64(s)
}
segmentsData := make([]byte, segmentsSize)
if _, err := io.ReadFull(r, segmentsData); err != nil {
return nil, err
}
headerBytes := headerBuf.Bytes()
// reset CRC to zero in header before checksum
headerBytes[22] = 0
headerBytes[23] = 0
headerBytes[24] = 0
headerBytes[25] = 0
crc := oggCRCUpdate(0, oggCRC32Poly04c11db7, headerBytes)
crc = oggCRCUpdate(crc, oggCRC32Poly04c11db7, segmentTable)
crc = oggCRCUpdate(crc, oggCRC32Poly04c11db7, segmentsData)
if crc != oh.CRC {
return nil, fmt.Errorf("expected crc %x != %x", oh.CRC, crc)
}
if o.packetBufs == nil {
o.packetBufs = map[uint32]*bytes.Buffer{}
}
var packetBuf *bytes.Buffer
continued := oh.Flags&0x1 != 0
if continued {
if b, ok := o.packetBufs[oh.SerialNumber]; ok {
packetBuf = b
} else {
return nil, fmt.Errorf("could not find continued packet %d", oh.SerialNumber)
}
} else {
packetBuf = &bytes.Buffer{}
}
var packets [][]byte
var p int
for _, s := range segmentTable {
packetBuf.Write(segmentsData[p : p+int(s)])
if s < 255 {
packets = append(packets, packetBuf.Bytes())
packetBuf = &bytes.Buffer{}
}
p += int(s)
}
o.packetBufs[oh.SerialNumber] = packetBuf
return packets, nil
}
// ReadOGGTags reads OGG metadata from the io.ReadSeeker, returning the resulting
// metadata in a Metadata implementation, or non-nil error if there was a problem.
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
// and http://www.xiph.org/ogg/doc/framing.html for details.
// For Opus see https://tools.ietf.org/html/rfc7845
func ReadOGGTags(r io.Reader) (Metadata, error) {
od := &oggDemuxer{}
for {
bs, err := od.Read(r)
if err != nil {
return nil, err
}
for _, b := range bs {
switch {
case bytes.HasPrefix(b, vorbisCommentPrefix):
m := &metadataOGG{
newMetadataVorbis(),
}
err = m.readVorbisComment(bytes.NewReader(b[len(vorbisCommentPrefix):]))
return m, err
case bytes.HasPrefix(b, opusTagsPrefix):
m := &metadataOGG{
newMetadataVorbis(),
}
err = m.readVorbisComment(bytes.NewReader(b[len(opusTagsPrefix):]))
return m, err
}
}
}
}
type metadataOGG struct {
*metadataVorbis
}
func (m *metadataOGG) FileType() FileType {
return OGG
}