// 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 }