2015-05-19 21:56:05 +02:00
|
|
|
// 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 (
|
2019-11-22 12:23:40 +01:00
|
|
|
"bytes"
|
2022-05-30 13:04:23 +02:00
|
|
|
"encoding/binary"
|
2015-05-19 21:56:05 +02:00
|
|
|
"errors"
|
2022-05-30 13:04:23 +02:00
|
|
|
"fmt"
|
2015-05-19 21:56:05 +02:00
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
var (
|
|
|
|
vorbisCommentPrefix = []byte("\x03vorbis")
|
|
|
|
opusTagsPrefix = []byte("OpusTags")
|
2015-05-20 13:43:08 +02:00
|
|
|
)
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
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
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
return &t
|
|
|
|
}
|
|
|
|
|
|
|
|
func oggCRCUpdate(crc uint32, tab *crc32Table, p []byte) uint32 {
|
|
|
|
for _, v := range p {
|
|
|
|
crc = (crc << 8) ^ tab[byte(crc>>24)^v]
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
2022-05-30 13:04:23 +02:00
|
|
|
return crc
|
|
|
|
}
|
|
|
|
|
|
|
|
type oggPageHeader struct {
|
|
|
|
Magic [4]byte // "OggS"
|
|
|
|
Version uint8
|
|
|
|
Flags uint8
|
|
|
|
GranulePosition uint64
|
|
|
|
SerialNumber uint32
|
|
|
|
SequenceNumber uint32
|
|
|
|
CRC uint32
|
|
|
|
Segments uint8
|
|
|
|
}
|
2015-05-19 21:56:05 +02:00
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
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 {
|
2015-05-19 21:56:05 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
if bytes.Compare(oh.Magic[:], []byte("OggS")) != 0 {
|
|
|
|
// TODO: seek for syncword?
|
|
|
|
return nil, errors.New("expected 'OggS'")
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
segmentTable := make([]byte, oh.Segments)
|
|
|
|
if _, err := io.ReadFull(r, segmentTable); err != nil {
|
2015-05-19 21:56:05 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-05-30 13:04:23 +02:00
|
|
|
var segmentsSize int64
|
|
|
|
for _, s := range segmentTable {
|
|
|
|
segmentsSize += int64(s)
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
2022-05-30 13:04:23 +02:00
|
|
|
segmentsData := make([]byte, segmentsSize)
|
|
|
|
if _, err := io.ReadFull(r, segmentsData); err != nil {
|
2015-05-19 21:56:05 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
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)
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
if o.packetBufs == nil {
|
|
|
|
o.packetBufs = map[uint32]*bytes.Buffer{}
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
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{}
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
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)
|
2015-05-19 21:56:05 +02:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
o.packetBufs[oh.SerialNumber] = packetBuf
|
2015-05-24 02:44:45 +02:00
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
return packets, nil
|
|
|
|
}
|
2019-11-22 12:23:40 +01:00
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
// 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{}
|
2019-11-22 12:23:40 +01:00
|
|
|
for {
|
2022-05-30 13:04:23 +02:00
|
|
|
bs, err := od.Read(r)
|
2019-11-22 12:23:40 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-30 13:04:23 +02:00
|
|
|
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
|
2019-11-22 12:23:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-24 02:44:45 +02:00
|
|
|
type metadataOGG struct {
|
|
|
|
*metadataVorbis
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *metadataOGG) FileType() FileType {
|
|
|
|
return OGG
|
|
|
|
}
|