meta: Implement CueSheet parsing.
This commit is contained in:
parent
c71d9382fd
commit
9c19fb744f
146
meta/cuesheet.go
146
meta/cuesheet.go
|
@ -1,5 +1,13 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// A CueSheet describes how tracks are laid out within a FLAC stream.
|
||||
//
|
||||
// ref: https://www.xiph.org/flac/format.html#metadata_block_cuesheet
|
||||
|
@ -19,7 +27,143 @@ type CueSheet struct {
|
|||
|
||||
// parseCueSheet reads and parses the body of an CueSheet metadata block.
|
||||
func (block *Block) parseCueSheet() error {
|
||||
panic("not yet implemented.")
|
||||
// Parse cue sheet.
|
||||
// 128 bytes: MCN.
|
||||
buf, err := readBytes(block.lr, 128)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cs := new(CueSheet)
|
||||
block.Body = cs
|
||||
cs.MCN = stringFromSZ(buf)
|
||||
|
||||
// 64 bits: NLeadInSamples.
|
||||
err = binary.Read(block.lr, binary.BigEndian, &cs.NLeadInSamples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1 bit: IsCompactDisc.
|
||||
var x uint8
|
||||
err = binary.Read(block.lr, binary.BigEndian, &x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if x&1 != 0 {
|
||||
cs.IsCompactDisc = true
|
||||
}
|
||||
|
||||
// 7 bits and 258 bytes: reserved.
|
||||
// mask = 11111110
|
||||
if x&0xFE != 0 {
|
||||
return ErrInvalidPadding
|
||||
}
|
||||
lr := io.LimitReader(block.lr, 258)
|
||||
zr := zeros{r: lr}
|
||||
_, err = io.Copy(ioutil.Discard, zr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse cue sheet tracks.
|
||||
// 8 bits: (number of tracks)
|
||||
err = binary.Read(block.lr, binary.BigEndian, &x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if x < 1 {
|
||||
return errors.New("meta.Block.parseCueSheet: at least one track required")
|
||||
}
|
||||
cs.Tracks = make([]CueSheetTrack, x)
|
||||
for i := range cs.Tracks {
|
||||
// 64 bits: Offset.
|
||||
track := &cs.Tracks[i]
|
||||
err = binary.Read(block.lr, binary.BigEndian, &track.Offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 8 bits: Num.
|
||||
err = binary.Read(block.lr, binary.BigEndian, &track.Num)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 12 bytes: ISRC.
|
||||
buf, err = readBytes(block.lr, 12)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
track.ISRC = stringFromSZ(buf)
|
||||
|
||||
// 1 bit: IsAudio.
|
||||
err = binary.Read(block.lr, binary.BigEndian, &x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if x&1 == 0 {
|
||||
track.IsAudio = true
|
||||
}
|
||||
|
||||
// 1 bit: HasPreEmphasis.
|
||||
if x&2 == 0 {
|
||||
track.HasPreEmphasis = true
|
||||
}
|
||||
|
||||
// 6 bits and 13 bytes: reserved.
|
||||
// mask = 11111110
|
||||
if x&0xFC != 0 {
|
||||
return ErrInvalidPadding
|
||||
}
|
||||
lr = io.LimitReader(block.lr, 13)
|
||||
zr = zeros{r: lr}
|
||||
_, err = io.Copy(ioutil.Discard, zr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse indicies.
|
||||
// 8 bits: (number of indicies)
|
||||
err = binary.Read(block.lr, binary.BigEndian, &x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
track.Indicies = make([]CueSheetTrackIndex, x)
|
||||
for i := range track.Indicies {
|
||||
index := &track.Indicies[i]
|
||||
// 64 bits: Offset.
|
||||
err = binary.Read(block.lr, binary.BigEndian, &index.Offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 8 bits: Num.
|
||||
err = binary.Read(block.lr, binary.BigEndian, &index.Num)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3 bytes: reserved.
|
||||
lr = io.LimitReader(block.lr, 3)
|
||||
zr = zeros{r: lr}
|
||||
_, err = io.Copy(ioutil.Discard, zr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stringFromSZ converts the provided byte slice to a string after terminating
|
||||
// it at the first occurance of a NULL character.
|
||||
func stringFromSZ(buf []byte) string {
|
||||
pos := bytes.IndexByte(buf, 0)
|
||||
if pos == -1 {
|
||||
return string(buf)
|
||||
}
|
||||
return string(buf[:pos])
|
||||
}
|
||||
|
||||
// CueSheetTrack contains the start offset of a track and other track specific
|
||||
|
|
|
@ -55,8 +55,8 @@ func Parse(r io.Reader) (block *Block, err error) {
|
|||
|
||||
// Errors returned by Parse.
|
||||
var (
|
||||
ErrReserved = errors.New("meta.Block.Parse: reserved block type")
|
||||
ErrInvalid = errors.New("meta.Block.Parse: invalid block type")
|
||||
ErrReservedType = errors.New("meta.Block.Parse: reserved block type")
|
||||
ErrInvalidType = errors.New("meta.Block.Parse: invalid block type")
|
||||
)
|
||||
|
||||
// Parse reads and parses the metadata block body.
|
||||
|
@ -78,9 +78,9 @@ func (block *Block) Parse() error {
|
|||
return block.parsePicture()
|
||||
}
|
||||
if block.Type >= 7 && block.Type <= 126 {
|
||||
return ErrReserved
|
||||
return ErrReservedType
|
||||
}
|
||||
return ErrInvalid
|
||||
return ErrInvalidType
|
||||
}
|
||||
|
||||
// Skip ignores the contents of the metadata block body.
|
||||
|
|
|
@ -6,11 +6,6 @@ import (
|
|||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Errors returned by verifyPadding.
|
||||
var (
|
||||
ErrInvalidPadding = errors.New("meta.Block.verifyPadding: invalid padding")
|
||||
)
|
||||
|
||||
// verifyPadding verifies the body of a Padding metadata block. It should only
|
||||
// contain zero-padding.
|
||||
//
|
||||
|
@ -21,6 +16,11 @@ func (block *Block) verifyPadding() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Errors returned by zeros.Read.
|
||||
var (
|
||||
ErrInvalidPadding = errors.New("invalid padding")
|
||||
)
|
||||
|
||||
// zeros implements an io.Reader, with a Read method which returns an error if
|
||||
// any byte read isn't zero.
|
||||
type zeros struct {
|
||||
|
|
Loading…
Reference in a new issue