flac: Clean start in preparation for the new API. All functionality will be back ported later on.
This commit is contained in:
parent
e130d9cbbd
commit
51d342ff4b
195
flac.go
195
flac.go
|
@ -1,195 +0,0 @@
|
|||
// Package flac provides access to FLAC (Free Lossless Audio Codec) files. [1]
|
||||
//
|
||||
// The basic structure of a FLAC bitstream is:
|
||||
// - The four byte string signature "fLaC".
|
||||
// - The StreamInfo metadata block.
|
||||
// - Zero or more other metadata blocks.
|
||||
// - One or more audio frames.
|
||||
//
|
||||
// [1]: http://flac.sourceforge.net/format.html
|
||||
package flac
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mewkiz/flac/frame"
|
||||
"github.com/mewkiz/flac/meta"
|
||||
)
|
||||
|
||||
// A Stream is a FLAC bitstream.
|
||||
type Stream struct {
|
||||
// Metadata blocks.
|
||||
MetaBlocks []*meta.Block
|
||||
// Audio frames.
|
||||
Frames []*frame.Frame
|
||||
// The underlying reader of the stream.
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// Parse reads the provided file and returns a parsed FLAC bitstream. It parses
|
||||
// all metadata blocks and all audio frames. Use Open instead for more
|
||||
// granularity.
|
||||
func Parse(filePath string) (s *Stream, err error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return ParseStream(f)
|
||||
}
|
||||
|
||||
// Open validates the FLAC signature of the provided file and returns a handle
|
||||
// to the FLAC bitstream. Callers should close the stream when done reading from
|
||||
// it. Call either Stream.Parse or Stream.ParseBlocks and Stream.ParseFrames to
|
||||
// parse the metadata blocks and audio frames.
|
||||
func Open(filePath string) (s *Stream, err error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewStream(f)
|
||||
}
|
||||
|
||||
// Close closes the underlying reader of the stream.
|
||||
func (s *Stream) Close() error {
|
||||
r, ok := s.r.(io.Closer)
|
||||
if ok {
|
||||
return r.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseStream reads from the provided io.Reader and returns a parsed FLAC
|
||||
// bitstream. It parses all metadata blocks and all audio frames. Use NewStream
|
||||
// instead for more granularity.
|
||||
func ParseStream(r io.Reader) (s *Stream, err error) {
|
||||
s, err = NewStream(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewStream validates the FLAC signature of the provided io.Reader and returns
|
||||
// a handle to the FLAC bitstream. Call either Stream.Parse or
|
||||
// Stream.ParseBlocks and Stream.ParseFrames to parse the metadata blocks and
|
||||
// audio frames.
|
||||
func NewStream(r io.Reader) (s *Stream, err error) {
|
||||
// signature is present at the beginning of each FLAC file.
|
||||
const signature = "fLaC"
|
||||
|
||||
// Verify "fLaC" signature (size: 4 bytes).
|
||||
buf := make([]byte, 4)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig := string(buf)
|
||||
if sig != signature {
|
||||
return nil, fmt.Errorf("flac.NewStream: invalid signature; expected %q, got %q", signature, sig)
|
||||
}
|
||||
|
||||
s = &Stream{r: r}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Parse reads and parses all metadata blocks and audio frames of the stream.
|
||||
// Use Stream.ParseBlocks and Stream.ParseFrames instead for more granularity.
|
||||
func (s *Stream) Parse() (err error) {
|
||||
err = s.ParseBlocks(meta.TypeAll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.ParseFrames()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseBlocks reads and parses the specified metadata blocks of the stream,
|
||||
// based on the provided types bitfield. The StreamInfo block type is always
|
||||
// included.
|
||||
func (s *Stream) ParseBlocks(types meta.BlockType) (err error) {
|
||||
// The StreamInfo block type is always included.
|
||||
types |= meta.TypeStreamInfo
|
||||
|
||||
// Read metadata blocks.
|
||||
isFirst := true
|
||||
var isLast bool
|
||||
for !isLast {
|
||||
// Read metadata block header.
|
||||
block, err := meta.NewBlock(s.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if block.Header.IsLast {
|
||||
isLast = true
|
||||
}
|
||||
|
||||
// The first block type must be StreamInfo.
|
||||
if isFirst {
|
||||
if block.Header.BlockType != meta.TypeStreamInfo {
|
||||
return fmt.Errorf("flac.Stream.ParseBlocks: first block type is invalid; expected %d (StreamInfo), got %d", meta.TypeStreamInfo, block.Header.BlockType)
|
||||
}
|
||||
isFirst = false
|
||||
}
|
||||
|
||||
// Check if the metadata block type is present in the provided types
|
||||
// bitfield.
|
||||
if block.Header.BlockType&types != 0 {
|
||||
// Read metadata block body.
|
||||
err = block.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Ignore metadata block body.
|
||||
err = block.Skip()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Store the decoded metadata block.
|
||||
s.MetaBlocks = append(s.MetaBlocks, block)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseFrames reads and parses the audio frames of the stream.
|
||||
func (s *Stream) ParseFrames() (err error) {
|
||||
// The first block is always a StreamInfo block.
|
||||
si := s.MetaBlocks[0].Body.(*meta.StreamInfo)
|
||||
|
||||
// Read audio frames.
|
||||
// uint64 won't overflow since the max value of SampleCount is
|
||||
// 0x0000000FFFFFFFFF.
|
||||
md5sum := md5.New()
|
||||
var i uint64
|
||||
for i < si.SampleCount {
|
||||
f, err := frame.NewFrame(s.r, md5sum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Frames = append(s.Frames, f)
|
||||
i += uint64(len(f.SubFrames[0].Samples))
|
||||
}
|
||||
got := md5sum.Sum(nil)
|
||||
want := si.MD5sum[:]
|
||||
if !bytes.Equal(got, want) {
|
||||
return fmt.Errorf("flac.Stream.ParseFrames: md5 mismatch; got %32x, want %32x", got, want)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
160
frame/frame.go
160
frame/frame.go
|
@ -1,160 +0,0 @@
|
|||
// Package frame contains functions for parsing FLAC encoded audio data.
|
||||
package frame
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/mewkiz/pkg/bit"
|
||||
"github.com/mewkiz/pkg/hashutil/crc16"
|
||||
)
|
||||
|
||||
// A Frame is an audio frame, consisting of a frame header and one subframe per
|
||||
// channel.
|
||||
type Frame struct {
|
||||
// Audio frame header.
|
||||
Header *Header
|
||||
// Audio subframes, one per channel.
|
||||
SubFrames []*SubFrame
|
||||
}
|
||||
|
||||
// NewFrame parses and returns a new frame, which consists of a frame header and
|
||||
// one subframe per channel.
|
||||
//
|
||||
// Frame format (pseudo code):
|
||||
//
|
||||
// type FRAME struct {
|
||||
// header FRAME_HEADER
|
||||
// subframes []SUBFRAME
|
||||
// _ uint0 to uint7 // zero-padding to byte alignment.
|
||||
// footer uint16 // CRC-16 of the entire frame, excluding the footer.
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#frame
|
||||
func NewFrame(r io.Reader, md5sum hash.Hash) (frame *Frame, err error) {
|
||||
// Create a new hash reader which adds the data from all read operations to a
|
||||
// running hash.
|
||||
crc := crc16.NewIBM()
|
||||
hr := io.TeeReader(r, crc)
|
||||
|
||||
// Frame header.
|
||||
frame = new(Frame)
|
||||
frame.Header, err = NewHeader(hr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subframes.
|
||||
br := bit.NewReader(hr)
|
||||
hdr := frame.Header
|
||||
for subFrameNum := 0; subFrameNum < hdr.ChannelOrder.ChannelCount(); subFrameNum++ {
|
||||
// NOTE: This piece of code is based on https://github.com/eaburns/flac/blob/master/decode.go#L437
|
||||
// It is governed by a MIT license: https://github.com/eaburns/flac/blob/master/LICENSE
|
||||
bps := uint(hdr.BitsPerSample)
|
||||
switch hdr.ChannelOrder {
|
||||
case ChannelLeftSide, ChannelMidSide:
|
||||
if subFrameNum == 1 {
|
||||
bps++
|
||||
}
|
||||
case ChannelRightSide:
|
||||
if subFrameNum == 0 {
|
||||
bps++
|
||||
}
|
||||
}
|
||||
|
||||
subframe, err := hdr.NewSubFrame(br, bps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frame.SubFrames = append(frame.SubFrames, subframe)
|
||||
}
|
||||
|
||||
// Padding.
|
||||
// TODO(u): Verify paddings.
|
||||
// ignore bits up to byte boundery.
|
||||
br = bit.NewReader(hr)
|
||||
|
||||
// Frame footer.
|
||||
// Verify the CRC-16.
|
||||
got := crc.Sum16()
|
||||
|
||||
var want uint16
|
||||
err = binary.Read(r, binary.BigEndian, &want)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if got != want {
|
||||
return nil, fmt.Errorf("frame.NewFrame: checksum mismatch; expected 0x%04X, got 0x%04X", want, got)
|
||||
}
|
||||
|
||||
// Decorrelate the left and right channels from each other.
|
||||
decorrelate(frame)
|
||||
|
||||
// Write decoded samples to a running md5 hash.
|
||||
var buf [3]byte
|
||||
for i := 0; i < len(frame.SubFrames[0].Samples); i++ {
|
||||
for _, subframe := range frame.SubFrames {
|
||||
sample := subframe.Samples[i]
|
||||
switch hdr.BitsPerSample {
|
||||
case 8:
|
||||
buf[0] = uint8(sample)
|
||||
_, err = md5sum.Write(buf[:1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 16:
|
||||
buf[0] = uint8(sample & 0xFF) // TODO(u): check; uint8 always truncates, so skip 0xFF mask?
|
||||
buf[1] = uint8(sample >> 8 & 0xFF) // TODO(u): skip 0xFF mask?
|
||||
_, err = md5sum.Write(buf[:2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 24:
|
||||
buf[0] = uint8(sample & 0xFF) // TODO(u): check; uint8 always truncates, so skip 0xFF mask?
|
||||
buf[1] = uint8(sample >> 8 & 0xFF)
|
||||
buf[2] = uint8(sample >> 16 & 0xFF) // TODO(u): skip 0xFF mask?
|
||||
_, err = md5sum.Write(buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
// decorrelate decorrelates the left and right channels from each other.
|
||||
//
|
||||
// ref: https://www.xiph.org/flac/format.html#interchannel
|
||||
func decorrelate(frame *Frame) {
|
||||
// NOTE: This piece of code is based on https://github.com/eaburns/flac/blob/master/decode.go#L341
|
||||
// It is governed by a MIT license: https://github.com/eaburns/flac/blob/master/LICENSE
|
||||
// TODO(u): Verify that the channel mapping is correct (left, right, mid, leftSample, ...)
|
||||
switch frame.Header.ChannelOrder {
|
||||
case ChannelLeftSide:
|
||||
left := frame.SubFrames[0].Samples
|
||||
side := frame.SubFrames[1].Samples
|
||||
for i, leftSample := range left {
|
||||
side[i] = leftSample - side[i]
|
||||
}
|
||||
case ChannelRightSide:
|
||||
side := frame.SubFrames[0].Samples
|
||||
right := frame.SubFrames[1].Samples
|
||||
for i, rightSample := range right {
|
||||
side[i] += rightSample
|
||||
}
|
||||
case ChannelMidSide:
|
||||
mid := frame.SubFrames[0].Samples
|
||||
side := frame.SubFrames[1].Samples
|
||||
for i, midSample := range mid {
|
||||
sideSample := side[i]
|
||||
midSample *= 2
|
||||
midSample |= (sideSample & 1) // if side is odd
|
||||
mid[i] = (midSample + sideSample) / 2
|
||||
side[i] = (midSample - sideSample) / 2
|
||||
}
|
||||
}
|
||||
}
|
426
frame/header.go
426
frame/header.go
|
@ -1,426 +0,0 @@
|
|||
package frame
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/mewkiz/pkg/bit"
|
||||
"github.com/mewkiz/pkg/dbg"
|
||||
"github.com/mewkiz/pkg/hashutil/crc8"
|
||||
)
|
||||
|
||||
// A Header is a frame header, which contains information about the frame like
|
||||
// the block size, sample rate, number of channels, etc, and an 8-bit CRC.
|
||||
type Header struct {
|
||||
// Blocking strategy:
|
||||
// false: fixed-sample count stream.
|
||||
// true: variable-sample count stream.
|
||||
HasVariableSampleCount bool
|
||||
// Sample count is the number of samples in any of a block's subblocks.
|
||||
SampleCount uint16
|
||||
// Sample rate. Get from StreamInfo metadata block if set to 0.
|
||||
SampleRate uint32
|
||||
// Channel order specifies the order in which channels are stored in the
|
||||
// frame.
|
||||
ChannelOrder ChannelOrder
|
||||
// Sample size in bits-per-sample. Get from StreamInfo metadata block if set
|
||||
// to 0.
|
||||
BitsPerSample uint8
|
||||
// Sample number is the frame's starting sample number, used by
|
||||
// variable-sample count streams.
|
||||
SampleNum uint64
|
||||
// Frame number, used by fixed-sample count streams. The frame's starting
|
||||
// sample number will be the frame number times the sample count.
|
||||
FrameNum uint32
|
||||
}
|
||||
|
||||
// Sync code for frame headers. Bit representation: 11111111111110.
|
||||
const SyncCode = 0x3FFE
|
||||
|
||||
// ChannelOrder specifies the order in which channels are stored.
|
||||
type ChannelOrder uint8
|
||||
|
||||
// Channel assignment. The following abbreviations are used:
|
||||
// L: left
|
||||
// R: right
|
||||
// C: center
|
||||
// Lfe: low-frequency effects
|
||||
// Ls: left surround
|
||||
// Rs: right surround
|
||||
//
|
||||
// The first 6 channel constants follow the SMPTE/ITU-R channel order:
|
||||
// L R C Lfe Ls Rs
|
||||
const (
|
||||
ChannelMono ChannelOrder = iota // 1 channel: mono.
|
||||
ChannelLR // 2 channels: left, right
|
||||
ChannelLRC // 3 channels: left, right, center
|
||||
ChannelLRLsRs // 4 channels: left, right, left surround, right surround
|
||||
ChannelLRCLsRs // 5 channels: left, right, center, left surround, right surround
|
||||
ChannelLRCLfeLsRs // 6 channels: left, right, center, low-frequency effects, left surround, right surround
|
||||
Channel7 // 7 channels: not defined
|
||||
Channel8 // 8 channels: not defined
|
||||
ChannelLeftSide // left/side stereo: left, side (difference)
|
||||
ChannelRightSide // side/right stereo: side (difference), right
|
||||
ChannelMidSide // mid/side stereo: mid (average), side (difference)
|
||||
)
|
||||
|
||||
// channelCount maps from a channel assignment to its number of channels.
|
||||
var channelCount = map[ChannelOrder]int{
|
||||
ChannelMono: 1,
|
||||
ChannelLR: 2,
|
||||
ChannelLRC: 3,
|
||||
ChannelLRLsRs: 4,
|
||||
ChannelLRCLsRs: 5,
|
||||
ChannelLRCLfeLsRs: 6,
|
||||
Channel7: 7,
|
||||
Channel8: 8,
|
||||
ChannelLeftSide: 2,
|
||||
ChannelRightSide: 2,
|
||||
ChannelMidSide: 2,
|
||||
}
|
||||
|
||||
// ChannelCount returns the number of channels used by the provided channel
|
||||
// order.
|
||||
func (order ChannelOrder) ChannelCount() int {
|
||||
return channelCount[order]
|
||||
}
|
||||
|
||||
// NewHeader parses and returns a new frame header.
|
||||
//
|
||||
// Frame header format (pseudo code):
|
||||
// // ref: http://flac.sourceforge.net/format.html#frame_header
|
||||
//
|
||||
// type FRAME_HEADER struct {
|
||||
// sync_code uint14
|
||||
// _ uint1
|
||||
// has_variable_sample_count bool // referred to as "variable blocksize" in the spec.
|
||||
// sample_count_spec uint4 // referred to as "blocksize" in the spec.
|
||||
// sample_rate_spec uint4
|
||||
// channel_assignment uint4
|
||||
// sample_size_spec uint3
|
||||
// _ uint1
|
||||
// if has_variable_sample_count {
|
||||
// // "UTF-8" coded int, from 1 to 7 bytes.
|
||||
// sample_num uint36
|
||||
// } else {
|
||||
// // "UTF-8" coded int, from 1 to 6 bytes.
|
||||
// frame_num uint31
|
||||
// }
|
||||
// switch sample_count_spec {
|
||||
// case 0110:
|
||||
// sample_count uint8 // sample_count-1
|
||||
// case 0111:
|
||||
// sample_count uint16 // sample_count-1
|
||||
// }
|
||||
// switch sample_rate_spec {
|
||||
// case 1100:
|
||||
// sample_rate uint8 // sample rate in kHz.
|
||||
// case 1101:
|
||||
// sample_rate uint16 // sample rate in Hz.
|
||||
// case 1110:
|
||||
// sample_rate uint16 // sample rate in daHz (tens of Hz).
|
||||
// }
|
||||
// crc8 uint8
|
||||
// }
|
||||
func NewHeader(r io.Reader) (hdr *Header, err error) {
|
||||
// Create a new hash reader which adds the data from all read operations to a
|
||||
// running hash.
|
||||
h := crc8.NewATM()
|
||||
hr := io.TeeReader(r, h)
|
||||
|
||||
br := bit.NewReader(hr)
|
||||
// field 0: sync_code (14 bits)
|
||||
// field 1: reserved (1 bit)
|
||||
// field 2: has_variable_sample_count (1 bit)
|
||||
// field 3: sample_count_spec (4 bits)
|
||||
// field 4: sample_rate_spec (4 bits)
|
||||
// field 5: channel_assignment (4 bits)
|
||||
// field 6: sample_size_spec (3 bits)
|
||||
// field 7: reserved (1 bit)
|
||||
fields, err := br.ReadFields(14, 1, 1, 4, 4, 4, 3, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sync code.
|
||||
// field 0: sync_code (14 bits)
|
||||
syncCode := fields[0]
|
||||
if syncCode != SyncCode {
|
||||
return nil, fmt.Errorf("frame.NewHeader: invalid sync code; expected '%014b', got '%014b'", SyncCode, syncCode)
|
||||
}
|
||||
|
||||
// Reserved.
|
||||
// field 1: reserved (1 bit)
|
||||
if fields[1] != 0 {
|
||||
return nil, errors.New("frame.NewHeader: all reserved bits must be 0")
|
||||
}
|
||||
|
||||
// Blocking strategy.
|
||||
hdr = new(Header)
|
||||
// field 2: has_variable_sample_count (1 bit)
|
||||
if fields[2] != 0 {
|
||||
// blocking strategy:
|
||||
// 0: fixed-sample count.
|
||||
// 1: variable-sample count.
|
||||
hdr.HasVariableSampleCount = true
|
||||
}
|
||||
|
||||
// Channel assignment.
|
||||
// 0000-0111: (number of independent channels)-1. Where defined, the
|
||||
// channel order follows SMPTE/ITU-R recommendations. The
|
||||
// assignments are as follows:
|
||||
// 1 channel: mono
|
||||
// 2 channels: left, right
|
||||
// 3 channels: left, right, center
|
||||
// 4 channels: left, right, left surround, right surround
|
||||
// 5 channels: left, right, center, left surround, right surround
|
||||
// 6 channels: left, right, center, low-frequency effects, left surround, right surround
|
||||
// 7 channels: not defined
|
||||
// 8 channels: not defined
|
||||
// 1000: left/side stereo: left, side (difference)
|
||||
// 1001: side/right stereo: side (difference), right
|
||||
// 1010: mid/side stereo: mid (average), side (difference)
|
||||
// 1011-1111: reserved
|
||||
// field 5: channel_assignment (4 bits)
|
||||
n := fields[5]
|
||||
switch {
|
||||
case n >= 0 && n <= 10:
|
||||
// 0000-0111: (number of independent channels)-1. Where defined, the
|
||||
// channel order follows SMPTE/ITU-R recommendations. The
|
||||
// assignments are as follows:
|
||||
// 1 channel: mono
|
||||
// 2 channels: left, right
|
||||
// 3 channels: left, right, center
|
||||
// 4 channels: left, right, left surround, right surround
|
||||
// 5 channels: left, right, center, left surround, right surround
|
||||
// 6 channels: left, right, center, low-frequency effects, left surround, right surround
|
||||
// 7 channels: not defined
|
||||
// 8 channels: not defined
|
||||
// 1000: left/side stereo: left, side (difference)
|
||||
// 1001: side/right stereo: side (difference), right
|
||||
// 1010: mid/side stereo: mid (average), side (difference)
|
||||
hdr.ChannelOrder = ChannelOrder(n)
|
||||
case n >= 11 && n <= 15:
|
||||
// 1011-1111: reserved
|
||||
return nil, fmt.Errorf("frame.NewHeader: invalid channel order; reserved bit pattern: %04b", n)
|
||||
default:
|
||||
// should be unreachable.
|
||||
panic(fmt.Errorf("frame.NewHeader: unhandled channel assignment bit pattern: %04b", n))
|
||||
}
|
||||
|
||||
// Sample size.
|
||||
// 000: get from STREAMINFO metadata block.
|
||||
// 001: 8 bits per sample.
|
||||
// 010: 12 bits per sample.
|
||||
// 011: reserved.
|
||||
// 100: 16 bits per sample.
|
||||
// 101: 20 bits per sample.
|
||||
// 110: 24 bits per sample.
|
||||
// 111: reserved.
|
||||
// field 6: sample_size_spec (3 bits)
|
||||
n = fields[6]
|
||||
switch n {
|
||||
case 0:
|
||||
// 000: get from STREAMINFO metadata block.
|
||||
// TODO(u): Should we try to read StreamInfo from here? We won't always
|
||||
// have access to it.
|
||||
panic("not yet implemented; bits-per-sample 0")
|
||||
case 1:
|
||||
// 001: 8 bits per sample.
|
||||
hdr.BitsPerSample = 8
|
||||
case 2:
|
||||
// 010: 12 bits per sample.
|
||||
hdr.BitsPerSample = 12
|
||||
case 3, 7:
|
||||
// 011: reserved.
|
||||
// 111: reserved.
|
||||
return nil, fmt.Errorf("frame.NewHeader: invalid sample size; reserved bit pattern: %03b", n)
|
||||
case 4:
|
||||
// 100: 16 bits per sample.
|
||||
hdr.BitsPerSample = 16
|
||||
case 5:
|
||||
// 101: 20 bits per sample.
|
||||
hdr.BitsPerSample = 20
|
||||
case 6:
|
||||
// 110: 24 bits per sample.
|
||||
hdr.BitsPerSample = 24
|
||||
default:
|
||||
// should be unreachable.
|
||||
panic(fmt.Errorf("frame.NewHeader: unhandled sample size bit pattern: %03b", n))
|
||||
}
|
||||
|
||||
// Reserved.
|
||||
// field 7: reserved (1 bit)
|
||||
if fields[7] != 0 {
|
||||
return nil, errors.New("frame.NewHeader: all reserved bits must be 0")
|
||||
}
|
||||
|
||||
// "UTF-8" coded sample number or frame number.
|
||||
if hdr.HasVariableSampleCount {
|
||||
// Sample number.
|
||||
hdr.SampleNum, err = decodeUTF8Int(hr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbg.Println("UTF-8 decoded sample number:", hdr.SampleNum)
|
||||
} else {
|
||||
// Frame number.
|
||||
frameNum, err := decodeUTF8Int(hr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.FrameNum = uint32(frameNum)
|
||||
dbg.Println("UTF-8 decoded frame number:", hdr.FrameNum)
|
||||
}
|
||||
|
||||
// Block size.
|
||||
// 0000: reserved.
|
||||
// 0001: 192 samples.
|
||||
// 0010-0101: 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608.
|
||||
// 0110: get 8 bit (sampleCount-1) from end of header.
|
||||
// 0111: get 16 bit (sampleCount-1) from end of header.
|
||||
// 1000-1111: 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/
|
||||
// 16384/32768.
|
||||
// field 3: sample_count_spec (4 bits)
|
||||
n = fields[3]
|
||||
switch {
|
||||
case n == 0:
|
||||
// 0000: reserved.
|
||||
return nil, errors.New("frame.NewHeader: invalid block size; reserved bit pattern")
|
||||
case n == 1:
|
||||
// 0001: 192 samples.
|
||||
hdr.SampleCount = 192
|
||||
case n >= 2 && n <= 5:
|
||||
// 0010-0101: 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608.
|
||||
hdr.SampleCount = uint16(576 * math.Pow(2, float64(n-2)))
|
||||
case n == 6:
|
||||
// 0110: get 8 bit (sampleCount-1) from end of header.
|
||||
var x uint8
|
||||
err = binary.Read(hr, binary.BigEndian, &x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.SampleCount = uint16(x) + 1
|
||||
case n == 7:
|
||||
// 0111: get 16 bit (sampleCount-1) from end of header.
|
||||
var x uint16
|
||||
err = binary.Read(hr, binary.BigEndian, &x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.SampleCount = x + 1
|
||||
case n >= 8 && n <= 15:
|
||||
// 1000-1111: 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/
|
||||
// 16384/32768.
|
||||
hdr.SampleCount = uint16(256 * math.Pow(2, float64(n-8)))
|
||||
default:
|
||||
// should be unreachable.
|
||||
panic(fmt.Errorf("frame.NewHeader: unhandled block size bit pattern: %04b", n))
|
||||
}
|
||||
|
||||
// Sample rate:
|
||||
// 0000: get from STREAMINFO metadata block.
|
||||
// 0001: 88.2kHz.
|
||||
// 0010: 176.4kHz.
|
||||
// 0011: 192kHz.
|
||||
// 0100: 8kHz.
|
||||
// 0101: 16kHz.
|
||||
// 0110: 22.05kHz.
|
||||
// 0111: 24kHz.
|
||||
// 1000: 32kHz.
|
||||
// 1001: 44.1kHz.
|
||||
// 1010: 48kHz.
|
||||
// 1011: 96kHz.
|
||||
// 1100: get 8 bit sample rate (in kHz) from end of header.
|
||||
// 1101: get 16 bit sample rate (in Hz) from end of header.
|
||||
// 1110: get 16 bit sample rate (in tens of Hz) from end of header.
|
||||
// 1111: invalid, to prevent sync-fooling string of 1s.
|
||||
// field 4: sample_rate_spec (4 bits)
|
||||
n = fields[4]
|
||||
switch n {
|
||||
case 0:
|
||||
// 0000: get from STREAMINFO metadata block.
|
||||
// TODO(u): Add flag to get from StreamInfo?
|
||||
panic("not yet implemented; sample rate 0")
|
||||
case 1:
|
||||
//0001: 88.2kHz.
|
||||
hdr.SampleRate = 88200
|
||||
case 2:
|
||||
//0010: 176.4kHz.
|
||||
hdr.SampleRate = 176400
|
||||
case 3:
|
||||
//0011: 192kHz.
|
||||
hdr.SampleRate = 192000
|
||||
case 4:
|
||||
//0100: 8kHz.
|
||||
hdr.SampleRate = 8000
|
||||
case 5:
|
||||
//0101: 16kHz.
|
||||
hdr.SampleRate = 16000
|
||||
case 6:
|
||||
//0110: 22.05kHz.
|
||||
hdr.SampleRate = 22050
|
||||
case 7:
|
||||
//0111: 24kHz.
|
||||
hdr.SampleRate = 24000
|
||||
case 8:
|
||||
//1000: 32kHz.
|
||||
hdr.SampleRate = 32000
|
||||
case 9:
|
||||
//1001: 44.1kHz.
|
||||
hdr.SampleRate = 44100
|
||||
case 10:
|
||||
//1010: 48kHz.
|
||||
hdr.SampleRate = 48000
|
||||
case 11:
|
||||
//1011: 96kHz.
|
||||
hdr.SampleRate = 96000
|
||||
case 12:
|
||||
//1100: get 8 bit sample rate (in kHz) from end of header.
|
||||
var sampleRate_kHz uint8
|
||||
err = binary.Read(hr, binary.BigEndian, &sampleRate_kHz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.SampleRate = uint32(sampleRate_kHz) * 1000
|
||||
case 13:
|
||||
//1101: get 16 bit sample rate (in Hz) from end of header.
|
||||
var sampleRate_Hz uint16
|
||||
err = binary.Read(hr, binary.BigEndian, &sampleRate_Hz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.SampleRate = uint32(sampleRate_Hz)
|
||||
case 14:
|
||||
//1110: get 16 bit sample rate (in tens of Hz) from end of header.
|
||||
var sampleRate_daHz uint16
|
||||
err = binary.Read(hr, binary.BigEndian, &sampleRate_daHz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.SampleRate = uint32(sampleRate_daHz) * 10
|
||||
case 15:
|
||||
//1111: invalid, to prevent sync-fooling string of 1s.
|
||||
return nil, fmt.Errorf("frame.NewHeader: invalid sample rate bit pattern: %04b", n)
|
||||
default:
|
||||
// should be unreachable.
|
||||
panic(fmt.Errorf("frame.NewHeader: unhandled sample rate bit pattern: %04b", n))
|
||||
}
|
||||
|
||||
// Verify the CRC-8.
|
||||
got := h.Sum8()
|
||||
|
||||
var want uint8
|
||||
err = binary.Read(r, binary.BigEndian, &want)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if got != want {
|
||||
return nil, fmt.Errorf("frame.NewHeader: checksum mismatch; expected 0x%02X, got 0x%02X", want, got)
|
||||
}
|
||||
|
||||
return hdr, nil
|
||||
}
|
|
@ -1,502 +0,0 @@
|
|||
// TODO(u): Get rid of all panics :)
|
||||
|
||||
package frame
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/mewkiz/pkg/bit"
|
||||
"github.com/mewkiz/pkg/bitutil"
|
||||
"github.com/mewkiz/pkg/dbg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dbg.Debug = false
|
||||
}
|
||||
|
||||
// A SubFrame contains the decoded audio data of a channel.
|
||||
type SubFrame struct {
|
||||
// Header specifies the attributes of the subframe, like prediction method
|
||||
// and order, residual coding parameters, etc.
|
||||
Header *SubHeader
|
||||
// Samples contains the decoded audio samples of the channel.
|
||||
Samples []Sample
|
||||
}
|
||||
|
||||
// TODO(u): Remove Sample type.
|
||||
|
||||
// A Sample is an audio sample. The size of each sample is between 4 and 32
|
||||
// bits.
|
||||
type Sample int32
|
||||
|
||||
// NewSubFrame parses and returns a new subframe, which consists of a subframe
|
||||
// header and encoded audio samples.
|
||||
//
|
||||
// Subframe format (pseudo code):
|
||||
//
|
||||
// type SUBFRAME struct {
|
||||
// header SUBFRAME_HEADER
|
||||
// enc_samples SUBFRAME_CONSTANT || SUBFRAME_FIXED || SUBFRAME_LPC ||
|
||||
// SUBFRAME_VERBATIM
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#subframe
|
||||
func (h *Header) NewSubFrame(br *bit.Reader, bps uint) (subframe *SubFrame, err error) {
|
||||
// Parse subframe header.
|
||||
subframe = new(SubFrame)
|
||||
subframe.Header, err = h.NewSubHeader(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode samples.
|
||||
sh := subframe.Header
|
||||
switch sh.PredMethod {
|
||||
case PredConstant:
|
||||
subframe.Samples, err = h.DecodeConstant(br, bps)
|
||||
case PredFixed:
|
||||
subframe.Samples, err = h.DecodeFixed(br, int(sh.PredOrder), bps)
|
||||
case PredLPC:
|
||||
subframe.Samples, err = h.DecodeLPC(br, int(sh.PredOrder), bps)
|
||||
case PredVerbatim:
|
||||
subframe.Samples, err = h.DecodeVerbatim(br, bps)
|
||||
default:
|
||||
return nil, fmt.Errorf("frame.Header.NewSubFrame: unknown subframe prediction method: %d", sh.PredMethod)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subframe, nil
|
||||
}
|
||||
|
||||
// A SubHeader is a subframe header, which contains information about how the
|
||||
// subframe audio samples are encoded.
|
||||
type SubHeader struct {
|
||||
// PredMethod is the subframe prediction method.
|
||||
PredMethod PredMethod
|
||||
// WastedBitCount is the number of wasted bits per sample.
|
||||
WastedBitCount int8
|
||||
// PredOrder is the subframe predictor order, which is used accordingly:
|
||||
// Fixed: Predictor order.
|
||||
// LPC: LPC order.
|
||||
PredOrder int8
|
||||
}
|
||||
|
||||
// PredMethod specifies the subframe prediction method.
|
||||
type PredMethod int8
|
||||
|
||||
// Subframe prediction methods.
|
||||
const (
|
||||
PredConstant PredMethod = iota
|
||||
PredFixed
|
||||
PredLPC
|
||||
PredVerbatim
|
||||
)
|
||||
|
||||
// NewSubHeader parses and returns a new subframe header.
|
||||
//
|
||||
// Subframe header format (pseudo code):
|
||||
// type SUBFRAME_HEADER struct {
|
||||
// _ uint1 // zero-padding, to prevent sync-fooling.
|
||||
// type uint6
|
||||
// // 0: no wasted bits-per-sample in source subblock, k = 0.
|
||||
// // 1: k wasted bits-per-sample in source subblock, k-1 follows, unary
|
||||
// // coded; e.g. k=3 => 001 follows, k=7 => 0000001 follows.
|
||||
// wasted_bit_count uint1+k
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#subframe_header
|
||||
func (h *Header) NewSubHeader(br *bit.Reader) (sh *SubHeader, err error) {
|
||||
// field 0: padding (1 bit)
|
||||
// field 1: type (6 bits)
|
||||
fields, err := br.ReadFields(1, 6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Padding.
|
||||
// field 0: padding (1 bit)
|
||||
if fields[0] != 0 {
|
||||
return nil, errors.New("frame.Header.NewSubHeader: invalid padding; must be 0")
|
||||
}
|
||||
|
||||
// Subframe prediction method.
|
||||
// 000000: SUBFRAME_CONSTANT
|
||||
// 000001: SUBFRAME_VERBATIM
|
||||
// 00001x: reserved
|
||||
// 0001xx: reserved
|
||||
// 001xxx: if(xxx <= 4) SUBFRAME_FIXED, xxx=order ; else reserved
|
||||
// 01xxxx: reserved
|
||||
// 1xxxxx: SUBFRAME_LPC, xxxxx=order-1
|
||||
sh = new(SubHeader)
|
||||
// field 1: type (6 bits)
|
||||
n := fields[1]
|
||||
switch {
|
||||
case n == 0:
|
||||
// 000000: SUBFRAME_CONSTANT
|
||||
sh.PredMethod = PredConstant
|
||||
case n == 1:
|
||||
// 000001: SUBFRAME_VERBATIM
|
||||
sh.PredMethod = PredVerbatim
|
||||
case n < 8:
|
||||
// 00001x: reserved
|
||||
// 0001xx: reserved
|
||||
return nil, fmt.Errorf("frame.Header.NewSubHeader: invalid subframe prediction method; reserved bit pattern: %06b", n)
|
||||
case n < 16:
|
||||
// 001xxx: if(xxx <= 4) SUBFRAME_FIXED, xxx=order ; else reserved
|
||||
const predOrderMask = 0x07
|
||||
sh.PredOrder = int8(n) & predOrderMask
|
||||
if sh.PredOrder > 4 {
|
||||
return nil, fmt.Errorf("frame.Header.NewSubHeader: invalid subframe prediction method; reserved bit pattern: %06b", n)
|
||||
}
|
||||
sh.PredMethod = PredFixed
|
||||
case n < 32:
|
||||
// 01xxxx: reserved
|
||||
return nil, fmt.Errorf("frame.Header.NewSubHeader: invalid subframe prediction method; reserved bit pattern: %06b", n)
|
||||
case n < 64:
|
||||
// 1xxxxx: SUBFRAME_LPC, xxxxx=order-1
|
||||
const predOrderMask = 0x1F
|
||||
sh.PredOrder = int8(n)&predOrderMask + 1
|
||||
sh.PredMethod = PredLPC
|
||||
default:
|
||||
// should be unreachable.
|
||||
panic(fmt.Errorf("frame.Header.NewSubHeader: unhandled subframe prediction method; bit pattern: %06b", n))
|
||||
}
|
||||
|
||||
// Wasted bits-per-sample, 1+k bits.
|
||||
hasWastedBits, err := br.Read(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasWastedBits != 0 {
|
||||
// k wasted bits-per-sample in source subblock, k-1 follows, unary coded;
|
||||
// e.g. k=3 => 001 follows, k=7 => 0000001 follows.
|
||||
// TODO(u): Verify that the unary decoding is correct.
|
||||
n, err := bitutil.DecodeUnary(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh.WastedBitCount = int8(n) + 1
|
||||
dbg.Println("wasted bits-per-sample:", sh.WastedBitCount)
|
||||
panic("not yet implemented; wasted bits")
|
||||
}
|
||||
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
// DecodeConstant decodes and returns a slice of samples. The first sample is
|
||||
// constant throughout the entire subframe.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#subframe_constant
|
||||
func (h *Header) DecodeConstant(br *bit.Reader, bps uint) (samples []Sample, err error) {
|
||||
// Read constant sample.
|
||||
x, err := br.Read(bps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sample := Sample(signExtend(x, bps))
|
||||
dbg.Println("Constant sample:", sample)
|
||||
|
||||
// Duplicate the constant sample, sample count number of times.
|
||||
samples = make([]Sample, h.SampleCount)
|
||||
for i := range samples {
|
||||
samples[i] = sample
|
||||
}
|
||||
|
||||
return samples, nil
|
||||
}
|
||||
|
||||
// signExtend interprets x as a signed n-bit integer value and sign extends it
|
||||
// to 32 bits.
|
||||
func signExtend(x uint64, n uint) int32 {
|
||||
// x is signed if its most significant bit is set.
|
||||
if x&(1<<(n-1)) != 0 {
|
||||
// Sign extend x.
|
||||
return int32(x | ^uint64(0)<<n)
|
||||
}
|
||||
return int32(x)
|
||||
}
|
||||
|
||||
// fixedCoeffs maps from prediction order to the LPC coefficients used in fixed
|
||||
// encoding.
|
||||
//
|
||||
// x_0[n] = 0
|
||||
// x_1[n] = x[n-1]
|
||||
// x_2[n] = 2*x[n-1] - x[n-2]
|
||||
// x_3[n] = 3*x[n-1] - 3*x[n-2] + x[n-3]
|
||||
//
|
||||
// ref: Section 2.2 of http://www.hpl.hp.com/techreports/1999/HPL-1999-144.pdf
|
||||
var fixedCoeffs = [...][]int32{
|
||||
1: {1},
|
||||
2: {2, -1},
|
||||
3: {3, -3, 1},
|
||||
// TODO(u): Verify the definition of the coefficients for prediction order 4.
|
||||
4: {4, -6, 4, -1},
|
||||
}
|
||||
|
||||
// DecodeFixed decodes and returns a slice of samples.
|
||||
//
|
||||
// TODO(u): Add more detailed documentation.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#subframe_fixed
|
||||
func (h *Header) DecodeFixed(br *bit.Reader, predOrder int, bps uint) (samples []Sample, err error) {
|
||||
// Unencoded warm-up samples:
|
||||
// n bits = frame's bits-per-sample * predictor order
|
||||
warm := make([]Sample, predOrder)
|
||||
dbg.Println("Fixed prediction order:", predOrder)
|
||||
for i := range warm {
|
||||
x, err := br.Read(bps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sample := Sample(signExtend(x, bps))
|
||||
dbg.Println("Fixed warm-up sample:", sample)
|
||||
warm[i] = sample
|
||||
}
|
||||
|
||||
residuals, err := h.DecodeResidual(br, predOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbg.Println("residuals:", residuals)
|
||||
dbg.Println("coeff:", fixedCoeffs[predOrder])
|
||||
return lpcDecode(fixedCoeffs[predOrder], warm, residuals, 0), nil
|
||||
}
|
||||
|
||||
// lpcDecode decodes a set of samples using LPC (Linear Predictive Coding) with
|
||||
// FIR (Finite Impulse Response) predictors.
|
||||
func lpcDecode(coeffs []int32, warm []Sample, residuals []int32, shift uint) (samples []Sample) {
|
||||
samples = make([]Sample, len(warm)+len(residuals))
|
||||
copy(samples, warm)
|
||||
// Note: The following code is borrowed from https://github.com/eaburns/flac/blob/master/decode.go#L751
|
||||
// It is governed by a MIT license: https://github.com/eaburns/flac/blob/master/LICENSE
|
||||
for i := len(warm); i < len(samples); i++ {
|
||||
var sum int32
|
||||
for j, coeff := range coeffs {
|
||||
sum += coeff * int32(samples[i-j-1])
|
||||
samples[i] = Sample(residuals[i-len(warm)] + sum>>shift)
|
||||
}
|
||||
}
|
||||
dbg.Println("samples:", samples)
|
||||
return samples
|
||||
}
|
||||
|
||||
// DecodeLPC decodes and returns a slice of samples.
|
||||
//
|
||||
// TODO(u): Add more detailed documentation.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#subframe_lpc
|
||||
func (h *Header) DecodeLPC(br *bit.Reader, lpcOrder int, bps uint) (samples []Sample, err error) {
|
||||
dbg.Println("lpcOrder:", lpcOrder)
|
||||
// Unencoded warm-up samples:
|
||||
// n bits = frame's bits-per-sample * lpc order
|
||||
dbg.Println("bps:", bps)
|
||||
warm := make([]Sample, lpcOrder)
|
||||
for i := range warm {
|
||||
x, err := br.Read(bps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sample := Sample(signExtend(x, bps))
|
||||
dbg.Println("LPC warm-up sample:", sample)
|
||||
warm[i] = sample
|
||||
}
|
||||
dbg.Println("warm:", warm)
|
||||
|
||||
// (Quantized linear predictor coefficients' precision in bits) - 1.
|
||||
x, err := br.Read(4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if x == 0xF {
|
||||
// 1111: invalid.
|
||||
return nil, errors.New("frame.Header.DecodeLPC: invalid quantized lpc precision; reserved bit pattern: 1111")
|
||||
}
|
||||
qlpcPrec := int(x) + 1
|
||||
dbg.Println("qlpcPrec:", qlpcPrec)
|
||||
|
||||
// Quantized linear predictor coefficient shift needed in bits.
|
||||
x, err = br.Read(5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qlpcShift := signExtend(x, 5)
|
||||
if qlpcShift < 0 {
|
||||
panic("qlpcShift is negative")
|
||||
}
|
||||
dbg.Println("qlpcShift:", qlpcShift)
|
||||
|
||||
// Unencoded predictor coefficients.
|
||||
coeffs := make([]int32, lpcOrder)
|
||||
for i := range coeffs {
|
||||
x, err := br.Read(uint(qlpcPrec))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coeff := int32(signExtend(x, uint(qlpcPrec)))
|
||||
dbg.Println("coeff:", coeff)
|
||||
coeffs[i] = coeff
|
||||
}
|
||||
dbg.Println("coeffs:", coeffs)
|
||||
|
||||
residuals, err := h.DecodeResidual(br, lpcOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = residuals
|
||||
dbg.Println("residuals:", residuals)
|
||||
|
||||
return lpcDecode(coeffs, warm, residuals, uint(qlpcShift)), nil
|
||||
}
|
||||
|
||||
// DecodeVerbatim decodes and returns a slice of samples. The samples are stored
|
||||
// unencoded.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#subframe_verbatim
|
||||
func (h *Header) DecodeVerbatim(br *bit.Reader, bps uint) (samples []Sample, err error) {
|
||||
// Read unencoded samples.
|
||||
samples = make([]Sample, h.SampleCount)
|
||||
for i := range samples {
|
||||
x, err := br.Read(bps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sample := Sample(signExtend(x, bps))
|
||||
dbg.Println("Verbatim sample:", sample)
|
||||
samples[i] = sample
|
||||
}
|
||||
|
||||
return samples, nil
|
||||
}
|
||||
|
||||
// DecodeResidual decodes and returns a slice of residuals.
|
||||
//
|
||||
// TODO(u): Add more detailed documentation.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#residual
|
||||
func (h *Header) DecodeResidual(br *bit.Reader, predOrder int) (residuals []int32, err error) {
|
||||
// Residual coding method.
|
||||
method, err := br.Read(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch method {
|
||||
case 0:
|
||||
// 00: partitioned Rice coding with 4-bit Rice parameter;
|
||||
// RESIDUAL_CODING_METHOD_PARTITIONED_RICE follows
|
||||
return h.DecodeRice(br, predOrder)
|
||||
case 1:
|
||||
// 01: partitioned Rice coding with 5-bit Rice parameter;
|
||||
// RESIDUAL_CODING_METHOD_PARTITIONED_RICE2 follows
|
||||
return h.DecodeRice2(br, predOrder)
|
||||
}
|
||||
// 1x: reserved
|
||||
return nil, fmt.Errorf("frame.Header.DecodeResidual: invalid residual coding method; reserved bit pattern: %02b", method)
|
||||
}
|
||||
|
||||
// DecodeRice decodes and returns a slice of residuals. The residual coding
|
||||
// method used is partitioned Rice coding with a 4-bit Rice parameter.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#partitioned_rice
|
||||
func (h *Header) DecodeRice(br *bit.Reader, predOrder int) (residuals []int32, err error) {
|
||||
return h.decodeRice(br, predOrder, 4)
|
||||
}
|
||||
|
||||
// DecodeRice2 decodes and returns a slice of residuals. The residual coding
|
||||
// method used is partitioned Rice coding with a 5-bit Rice parameter.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#partitioned_rice2
|
||||
func (h *Header) DecodeRice2(br *bit.Reader, predOrder int) (residuals []int32, err error) {
|
||||
return h.decodeRice(br, predOrder, 5)
|
||||
}
|
||||
|
||||
// decodeRice decodes and returns a slice of residuals. The residual coding
|
||||
// method used is partitioned Rice coding with a n-bit Rice parameter, as
|
||||
// specified by paramSize.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#partitioned_rice2
|
||||
func (h *Header) decodeRice(br *bit.Reader, predOrder int, paramSize uint) (residuals []int32, err error) {
|
||||
// Partition order.
|
||||
partOrder, err := br.Read(4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rice partitions.
|
||||
partCount := int(math.Pow(2, float64(partOrder)))
|
||||
residuals = make([]int32, 0, h.SampleCount)
|
||||
guess := int(h.SampleCount)
|
||||
var total int
|
||||
for partNum := 0; partNum < partCount; partNum++ {
|
||||
partSampleCount := int(h.SampleCount) / partCount
|
||||
|
||||
// Encoding parameter.
|
||||
riceParam, err := br.Read(paramSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if paramSize == 4 && riceParam == 0xF || paramSize == 5 && riceParam == 0x1F {
|
||||
// 1111 or 11111: Escape code, meaning the partition is in unencoded
|
||||
// binary form using n bits per sample; n follows as a 5-bit number.
|
||||
n, err := br.Read(5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = n
|
||||
panic("not yet implemented: unencoded samples.")
|
||||
}
|
||||
dbg.Println("riceParam:", riceParam)
|
||||
|
||||
// Encoded residual.
|
||||
if partOrder == 0 {
|
||||
partSampleCount = int(h.SampleCount) - predOrder
|
||||
} else if partNum != 0 {
|
||||
partSampleCount = int(h.SampleCount) / int(math.Pow(2, float64(partOrder)))
|
||||
} else {
|
||||
partSampleCount = int(h.SampleCount)/int(math.Pow(2, float64(partOrder))) - predOrder
|
||||
}
|
||||
total += partSampleCount
|
||||
dbg.Println("partSampleCount:", partSampleCount)
|
||||
|
||||
// Decode rice partition residuals.
|
||||
partResiduals, err := decodeRiceResidual(br, uint(riceParam), partSampleCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
residuals = append(residuals, partResiduals...)
|
||||
}
|
||||
if guess < total {
|
||||
panic(fmt.Sprintf("guess (%d) < total (%d)", guess, total))
|
||||
}
|
||||
|
||||
return residuals, nil
|
||||
}
|
||||
|
||||
// decodeRiceResidual decodes the residual signals of a partition encoded using
|
||||
// Rice coding.
|
||||
func decodeRiceResidual(br *bit.Reader, k uint, n int) (residuals []int32, err error) {
|
||||
residuals = make([]int32, n)
|
||||
for i := 0; i < n; i++ {
|
||||
// Read unary encoded most significant bits.
|
||||
high, err := bitutil.DecodeUnary(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read binary encoded least significant bits.
|
||||
low, err := br.Read(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
residual := int32(high<<k | low)
|
||||
|
||||
// ZigZag decode.
|
||||
residual = int32(bitutil.DecodeZigZag(int(residual)))
|
||||
|
||||
residuals[i] = residual
|
||||
}
|
||||
return residuals, nil
|
||||
}
|
157
frame/utf8.go
157
frame/utf8.go
|
@ -1,157 +0,0 @@
|
|||
package frame
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mewkiz/pkg/readerutil"
|
||||
)
|
||||
|
||||
const (
|
||||
t1 = 0x00 // 0000 0000
|
||||
tx = 0x80 // 1000 0000
|
||||
t2 = 0xC0 // 1100 0000
|
||||
t3 = 0xE0 // 1110 0000
|
||||
t4 = 0xF0 // 1111 0000
|
||||
t5 = 0xF8 // 1111 1000
|
||||
t6 = 0xFC // 1111 1100
|
||||
t7 = 0xFE // 1111 1110
|
||||
t8 = 0xFF // 1111 1111
|
||||
|
||||
maskx = 0x3F // 0011 1111
|
||||
mask2 = 0x1F // 0001 1111
|
||||
mask3 = 0x0F // 0000 1111
|
||||
mask4 = 0x07 // 0000 0111
|
||||
mask5 = 0x03 // 0000 0011
|
||||
mask6 = 0x01 // 0000 0001
|
||||
|
||||
rune1Max = 1<<7 - 1
|
||||
rune2Max = 1<<11 - 1
|
||||
rune3Max = 1<<16 - 1
|
||||
rune4Max = 1<<21 - 1
|
||||
rune5Max = 1<<26 - 1
|
||||
rune6Max = 1<<31 - 1
|
||||
)
|
||||
|
||||
// decodeUTF8Int decodes a "UTF-8" coded number and returns it.
|
||||
//
|
||||
// ref: http://permalink.gmane.org/gmane.comp.audio.compression.flac.devel/3033
|
||||
//
|
||||
// Algorithm description:
|
||||
// - read one byte B0 from the stream
|
||||
// - if B0 = 0xxxxxxx then the read value is B0 -> end
|
||||
// - if B0 = 10xxxxxx, the encoding is invalid
|
||||
// - if B0 = 11xxxxxx, set L to the number of leading binary 1s minus 1:
|
||||
// B0 = 110xxxxx -> L = 1
|
||||
// B0 = 1110xxxx -> L = 2
|
||||
// B0 = 11110xxx -> L = 3
|
||||
// B0 = 111110xx -> L = 4
|
||||
// B0 = 1111110x -> L = 5
|
||||
// B0 = 11111110 -> L = 6
|
||||
// - assign the bits following the encoding (the x bits in the examples) to
|
||||
// a variable R with a magnitude of at least 36 bits
|
||||
// - loop from 1 to L
|
||||
// - left shift R 6 bits
|
||||
// - read B from the stream
|
||||
// - if B does not match 10xxxxxx, the encoding is invalid
|
||||
// - set R = R or <the lower 6 bits from B>
|
||||
// - the read value is R
|
||||
func decodeUTF8Int(r io.Reader) (n uint64, err error) {
|
||||
c0, err := readerutil.ReadByte(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 1-byte, 7-bit sequence?
|
||||
if c0 < tx {
|
||||
// if c0 == 0xxxxxxx
|
||||
// total: 7 bits (7)
|
||||
return uint64(c0), nil
|
||||
}
|
||||
|
||||
// unexpected continuation byte?
|
||||
if c0 < t2 {
|
||||
// if c0 == 10xxxxxx
|
||||
return 0, errors.New("frame.decodeUTF8Int: unexpected continuation byte")
|
||||
}
|
||||
|
||||
// get number of continuation bytes and store bits from c0.
|
||||
var l int
|
||||
switch {
|
||||
case c0 < t3:
|
||||
// if c0 == 110xxxxx
|
||||
// total: 11 bits (5 + 6)
|
||||
l = 1
|
||||
n = uint64(c0 & mask2)
|
||||
case c0 < t4:
|
||||
// if c0 == 1110xxxx
|
||||
// total: 16 bits (4 + 6 + 6)
|
||||
l = 2
|
||||
n = uint64(c0 & mask3)
|
||||
case c0 < t5:
|
||||
// if c0 == 11110xxx
|
||||
// total: 21 bits (3 + 6 + 6 + 6)
|
||||
l = 3
|
||||
n = uint64(c0 & mask4)
|
||||
case c0 < t6:
|
||||
// if c0 == 111110xx
|
||||
// total: 26 bits (2 + 6 + 6 + 6 + 6)
|
||||
l = 4
|
||||
n = uint64(c0 & mask5)
|
||||
case c0 < t7:
|
||||
// if c0 == 1111110x
|
||||
// total: 31 bits (1 + 6 + 6 + 6 + 6 + 6)
|
||||
l = 5
|
||||
n = uint64(c0 & mask6)
|
||||
case c0 < t8:
|
||||
// if c0 == 11111110
|
||||
// total: 36 bits (0 + 6 + 6 + 6 + 6 + 6 + 6)
|
||||
l = 6
|
||||
n = 0
|
||||
}
|
||||
|
||||
// store bits from continuation bytes.
|
||||
for i := 0; i < l; i++ {
|
||||
n <<= 6
|
||||
c, err := readerutil.ReadByte(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if c < tx || t2 <= c {
|
||||
// if c != 10xxxxxx
|
||||
return 0, errors.New("frame.decodeUTF8Int: expected continuation byte")
|
||||
}
|
||||
n |= uint64(c & maskx)
|
||||
}
|
||||
|
||||
// check if number representation is larger than necessary.
|
||||
switch l {
|
||||
case 1:
|
||||
if n <= rune1Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
}
|
||||
case 2:
|
||||
if n <= rune2Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
}
|
||||
case 3:
|
||||
if n <= rune3Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
}
|
||||
case 4:
|
||||
if n <= rune4Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
}
|
||||
case 5:
|
||||
if n <= rune5Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
}
|
||||
case 6:
|
||||
if n <= rune6Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
// registeredApplications maps from a registered application ID to a
|
||||
// description.
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/id.html
|
||||
var registeredApplications = map[ID]string{
|
||||
"ATCH": "FlacFile",
|
||||
"BSOL": "beSolo",
|
||||
"BUGS": "Bugs Player",
|
||||
"Cues": "GoldWave cue points (specification)",
|
||||
"Fica": "CUE Splitter",
|
||||
"Ftol": "flac-tools",
|
||||
"MOTB": "MOTB MetaCzar",
|
||||
"MPSE": "MP3 Stream Editor",
|
||||
"MuML": "MusicML: Music Metadata Language",
|
||||
"RIFF": "Sound Devices RIFF chunk storage",
|
||||
"SFFL": "Sound Font FLAC",
|
||||
"SONY": "Sony Creative Software",
|
||||
"SQEZ": "flacsqueeze",
|
||||
"TtWv": "TwistedWave",
|
||||
"UITS": "UITS Embedding tools",
|
||||
"aiff": "FLAC AIFF chunk storage",
|
||||
"imag": "flac-image application for storing arbitrary files in APPLICATION metadata blocks",
|
||||
"peem": "Parseable Embedded Extensible Metadata (specification)",
|
||||
"qfst": "QFLAC Studio",
|
||||
"riff": "FLAC RIFF chunk storage",
|
||||
"tune": "TagTuner",
|
||||
"xbat": "XBAT",
|
||||
"xmcd": "xmcd",
|
||||
}
|
||||
|
||||
// An ID is a 4 byte identifier of a registered application.
|
||||
type ID string
|
||||
|
||||
func (id ID) String() string {
|
||||
s, ok := registeredApplications[id]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("<unregistered ID: %q>", string(id))
|
||||
}
|
||||
|
||||
// An Application metadata block is used by third-party applications. The only
|
||||
// mandatory field is a 32-bit identifier. This ID is granted upon request to an
|
||||
// application by the FLAC maintainers. The remainder of the block is defined by
|
||||
// the registered application.
|
||||
type Application struct {
|
||||
// Registered application ID.
|
||||
ID ID
|
||||
// Application data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// ParseApplication parses and returns a new Application metadata block. The
|
||||
// provided io.Reader should limit the amount of data that can be read to
|
||||
// header.Length bytes.
|
||||
//
|
||||
// Application format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_APPLICATION struct {
|
||||
// ID uint32
|
||||
// Data [header.Length-4]byte
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_application
|
||||
func ParseApplication(r io.Reader) (app *Application, err error) {
|
||||
// Application ID (size: 4 bytes).
|
||||
buf, err := readBytes(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app = &Application{ID: ID(buf)}
|
||||
if _, ok := registeredApplications[app.ID]; !ok {
|
||||
log.Printf("meta.ParseApplication: unregistered application ID %q.\n", string(app.ID))
|
||||
}
|
||||
|
||||
// Data.
|
||||
app.Data, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
338
meta/cuesheet.go
338
meta/cuesheet.go
|
@ -1,338 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/eaburns/bit"
|
||||
)
|
||||
|
||||
// A CueSheet metadata block stores various information that can be used in a
|
||||
// cue sheet. It supports track and index points, compatible with Red Book CD
|
||||
// digital audio discs, as well as other CD-DA metadata such as media catalog
|
||||
// number and track ISRCs. The CueSheet block is especially useful for backing
|
||||
// up CD-DA discs, but it can be used as a general purpose cueing mechanism for
|
||||
// playback.
|
||||
type CueSheet struct {
|
||||
// Media catalog number, in ASCII printable characters 0x20-0x7e. In general,
|
||||
// the media catalog number may be 0 to 128 bytes long; any unused characters
|
||||
// should be right-padded with NUL characters. For CD-DA, this is a thirteen
|
||||
// digit number, followed by 115 NUL bytes.
|
||||
MCN string
|
||||
// The number of lead-in samples. This field has meaning only for CD-DA
|
||||
// cuesheets; for other uses it should be 0. For CD-DA, the lead-in is the
|
||||
// TRACK 00 area where the table of contents is stored; more precisely, it is
|
||||
// the number of samples from the first sample of the media to the first
|
||||
// sample of the first index point of the first track. According to the Red
|
||||
// Book, the lead-in must be silence and CD grabbing software does not
|
||||
// usually store it; additionally, the lead-in must be at least two seconds
|
||||
// but may be longer. For these reasons the lead-in length is stored here so
|
||||
// that the absolute position of the first track can be computed. Note that
|
||||
// the lead-in stored here is the number of samples up to the first index
|
||||
// point of the first track, not necessarily to INDEX 01 of the first track;
|
||||
// even the first track may have INDEX 00 data.
|
||||
LeadInSampleCount uint64
|
||||
// Specifies whether the CueSheet corresponds to a Compact Disc or not.
|
||||
IsCompactDisc bool
|
||||
// The number of tracks. Must be at least 1 (because of the requisite
|
||||
// lead-out track). For CD-DA, this number must be no more than 100 (99
|
||||
// regular tracks and one lead-out track).
|
||||
TrackCount uint8
|
||||
// One or more tracks. A CueSheet block is required to have a lead-out track;
|
||||
// it is always the last track in the CueSheet. For CD-DA, the lead-out track
|
||||
// number must be 170 as specified by the Red Book, otherwise is must be 255.
|
||||
Tracks []CueSheetTrack
|
||||
}
|
||||
|
||||
// A CueSheetTrack contains information about a track within a CueSheet.
|
||||
type CueSheetTrack struct {
|
||||
// Track offset in samples, relative to the beginning of the FLAC audio
|
||||
// stream. It is the offset to the first index point of the track. (Note how
|
||||
// this differs from CD-DA, where the track's offset in the TOC is that of
|
||||
// the track's INDEX 01 even if there is an INDEX 00.) For CD-DA, the offset
|
||||
// must be evenly divisible by 588 samples (588 samples = 44100 samples/sec *
|
||||
// 1/75th of a sec).
|
||||
Offset uint64
|
||||
// Track number. A track number of 0 is not allowed to avoid conflicting with
|
||||
// the CD-DA spec, which reserves this for the lead-in. For CD-DA the number
|
||||
// must be 1-99, or 170 for the lead-out; for non-CD-DA, the track number
|
||||
// must for 255 for the lead-out. It is not required but encouraged to start
|
||||
// with track 1 and increase sequentially. Track numbers must be unique
|
||||
// within a CueSheet.
|
||||
TrackNum uint8
|
||||
// Track ISRC. This is a 12-digit alphanumeric code. A value of 12 ASCII NUL
|
||||
// characters may be used to denote absence of an ISRC.
|
||||
ISRC string
|
||||
// The track type: true for audio, false for non-audio.
|
||||
IsAudio bool
|
||||
// The pre-emphasis flag: false for no pre-emphasis, true for pre-emphasis.
|
||||
// This corresponds to the CD-DA Q-channel control bit 5.
|
||||
HasPreEmphasis bool
|
||||
// The number of track index points. There must be at least one index in
|
||||
// every track in a CueSheet except for the lead-out track, which must have
|
||||
// zero. For CD-DA, this number may be no more than 100.
|
||||
TrackIndexCount uint8
|
||||
// For all tracks except the lead-out track, one or more track index points.
|
||||
TrackIndexes []CueSheetTrackIndex
|
||||
}
|
||||
|
||||
// A CueSheetTrackIndex contains information about an index point in a track.
|
||||
type CueSheetTrackIndex struct {
|
||||
// Offset in samples, relative to the track offset, of the index point. For
|
||||
// CD-DA, the offset must be evenly divisible by 588 samples (588 samples =
|
||||
// 44100 samples/sec * 1/75th of a sec). Note that the offset is from the
|
||||
// beginning of the track, not the beginning of the audio data.
|
||||
Offset uint64
|
||||
// The index point number. For CD-DA, an index number of 0 corresponds to the
|
||||
// track pre-gap. The first index in a track must have a number of 0 or 1,
|
||||
// and subsequently, index numbers must increase by 1. Index numbers must be
|
||||
// unique within a track.
|
||||
IndexPointNum uint8
|
||||
}
|
||||
|
||||
// ParseCueSheet parses and returns a new CueSheet metadata block. The provided
|
||||
// io.Reader should limit the amount of data that can be read to header.Length
|
||||
// bytes.
|
||||
//
|
||||
// Cue sheet format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_CUESHEET struct {
|
||||
// mcn [128]byte
|
||||
// lead_in_sample_count uint64
|
||||
// is_compact_disc bool
|
||||
// _ uint7
|
||||
// _ [258]byte
|
||||
// track_count uint8
|
||||
// tracks [track_count]track
|
||||
// }
|
||||
//
|
||||
// type track struct {
|
||||
// offset uint64
|
||||
// track_num uint8
|
||||
// isrc [12]byte
|
||||
// is_audio bool
|
||||
// has_pre_emphasis bool
|
||||
// _ uint6
|
||||
// _ [13]byte
|
||||
// track_index_count uint8
|
||||
// track_indexes [track_index_count]track_index
|
||||
// }
|
||||
//
|
||||
// type track_index {
|
||||
// offset uint64
|
||||
// index_point_num uint8
|
||||
// _ [3]byte
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_cuesheet
|
||||
func ParseCueSheet(r io.Reader) (cs *CueSheet, err error) {
|
||||
errReservedNotZero := errors.New("meta.ParseCueSheet: all reserved bits must be 0")
|
||||
|
||||
// Media catalog number (size: 128 bytes).
|
||||
buf, err := readBytes(r, 128)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs = new(CueSheet)
|
||||
cs.MCN = getStringFromSZ(buf)
|
||||
for _, r := range cs.MCN {
|
||||
if r < 0x20 || r > 0x7E {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid character in media catalog number; expected >= 0x20 and <= 0x7E, got 0x%02X", r)
|
||||
}
|
||||
}
|
||||
|
||||
// Lead-in sample count.
|
||||
err = binary.Read(r, binary.BigEndian, &cs.LeadInSampleCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
br := bit.NewReader(r)
|
||||
// field 0: is_compact_disc (1 bit)
|
||||
// field 1: reserved (7 bits)
|
||||
fields, err := br.ReadFields(1, 7)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Is compact disc.
|
||||
if fields[0] != 0 {
|
||||
cs.IsCompactDisc = true
|
||||
}
|
||||
|
||||
// Reserved.
|
||||
if fields[1] != 0 {
|
||||
return nil, errReservedNotZero
|
||||
}
|
||||
buf, err = readBytes(r, 258) // 258 reserved bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isAllZero(buf) {
|
||||
return nil, errReservedNotZero
|
||||
}
|
||||
|
||||
// Handle error checking of LeadInSampleCount here, since IsCompactDisc is
|
||||
// required.
|
||||
if !cs.IsCompactDisc && cs.LeadInSampleCount != 0 {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid lead-in sample count for non CD-DA; expected 0, got %d", cs.LeadInSampleCount)
|
||||
}
|
||||
|
||||
// Track count.
|
||||
err = binary.Read(r, binary.BigEndian, &cs.TrackCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cs.TrackCount < 1 {
|
||||
return nil, errors.New("meta.ParseCueSheet: at least one track (the lead-out track) is required")
|
||||
}
|
||||
if cs.IsCompactDisc && cs.TrackCount > 100 {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: too many tracks for CD-DA cue sheet; expected <= 100, got %d", cs.TrackCount)
|
||||
}
|
||||
|
||||
// Tracks.
|
||||
cs.Tracks = make([]CueSheetTrack, cs.TrackCount)
|
||||
for i := 0; i < len(cs.Tracks); i++ {
|
||||
// Track offset.
|
||||
track := &cs.Tracks[i]
|
||||
err = binary.Read(r, binary.BigEndian, &track.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cs.IsCompactDisc && track.Offset%588 != 0 {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid track offset (%d) for CD-DA; must be evenly divisible by 588", track.Offset)
|
||||
}
|
||||
|
||||
// Track number.
|
||||
err = binary.Read(r, binary.BigEndian, &track.TrackNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if track.TrackNum == 0 {
|
||||
// A track number of 0 is not allowed to avoid conflicting with the
|
||||
// CD-DA spec, which reserves this for the lead-in.
|
||||
return nil, errors.New("meta.ParseCueSheet: track number 0 not allowed")
|
||||
}
|
||||
if cs.IsCompactDisc {
|
||||
if i == len(cs.Tracks)-1 {
|
||||
if track.TrackNum != 170 {
|
||||
// The lead-out track number must be 170 for CD-DA.
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid lead-out track number for CD-DA; expected 170, got %d", track.TrackNum)
|
||||
}
|
||||
} else if track.TrackNum > 99 {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid track number for CD-DA; expected <= 99, got %d", track.TrackNum)
|
||||
}
|
||||
} else {
|
||||
if i == len(cs.Tracks)-1 && track.TrackNum != 255 {
|
||||
// The lead-out track number must be 255 for non-CD-DA.
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid lead-out track number for non CD-DA; expected 255, got %d", track.TrackNum)
|
||||
}
|
||||
}
|
||||
|
||||
// Track ISRC (size: 12 bytes).
|
||||
buf, err = readBytes(r, 12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
track.ISRC = getStringFromSZ(buf)
|
||||
|
||||
// field 0: is_audio (1 bit)
|
||||
// field 1: has_pre_emphasis (1 bit)
|
||||
// field 2: reserved (6 bits)
|
||||
fields, err = br.ReadFields(1, 1, 6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Is audio.
|
||||
if fields[0] == 0 {
|
||||
// track type:
|
||||
// 0: audio.
|
||||
// 1: non-audio.
|
||||
track.IsAudio = true
|
||||
}
|
||||
|
||||
// Has pre-emphasis.
|
||||
if fields[1] != 0 {
|
||||
track.HasPreEmphasis = true
|
||||
}
|
||||
|
||||
// Reserved.
|
||||
if fields[2] != 0 {
|
||||
return nil, errReservedNotZero
|
||||
}
|
||||
buf, err = readBytes(r, 13) // 13 reserved bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isAllZero(buf) {
|
||||
return nil, errReservedNotZero
|
||||
}
|
||||
|
||||
// Track index point count.
|
||||
err = binary.Read(r, binary.BigEndian, &track.TrackIndexCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i == len(cs.Tracks)-1 {
|
||||
// Lead-out must have zero track index points.
|
||||
if track.TrackIndexCount != 0 {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid number of track points for the lead-out track; expected 0, got %d", track.TrackIndexCount)
|
||||
}
|
||||
} else {
|
||||
if track.TrackIndexCount < 1 {
|
||||
// Every track, except for the lead-out track, must have at least
|
||||
// one track index point.
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid number of track points; expected >= 1, got %d", track.TrackIndexCount)
|
||||
}
|
||||
if cs.IsCompactDisc && track.TrackIndexCount > 100 {
|
||||
return nil, fmt.Errorf("meta.ParseCueSheet: invalid number of track points for CD-DA; expected <= 100, got %d", track.TrackIndexCount)
|
||||
}
|
||||
}
|
||||
|
||||
// Track indexes.
|
||||
if track.TrackIndexCount > 0 {
|
||||
track.TrackIndexes = make([]CueSheetTrackIndex, track.TrackIndexCount)
|
||||
for j := 0; j < len(track.TrackIndexes); j++ {
|
||||
// Track index point offset.
|
||||
trackIndex := &track.TrackIndexes[j]
|
||||
err = binary.Read(r, binary.BigEndian, &trackIndex.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Track index point num
|
||||
err = binary.Read(r, binary.BigEndian, &trackIndex.IndexPointNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reserved.
|
||||
buf, err = readBytes(r, 3) // 3 reserved bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isAllZero(buf) {
|
||||
return nil, errReservedNotZero
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// getStringFromSZ converts the provided byte slice to a string after
|
||||
// terminating it at the first occurance of a NULL character.
|
||||
func getStringFromSZ(buf []byte) string {
|
||||
// Locate the first NULL character.
|
||||
posNull := bytes.IndexRune(buf, 0)
|
||||
if posNull != -1 {
|
||||
// Terminate the string at first occurance of a NULL character.
|
||||
buf = buf[:posNull]
|
||||
}
|
||||
return string(buf)
|
||||
}
|
220
meta/meta.go
220
meta/meta.go
|
@ -1,220 +0,0 @@
|
|||
// Package meta contains functions for parsing FLAC metadata.
|
||||
package meta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/eaburns/bit"
|
||||
)
|
||||
|
||||
// A Block is a metadata block, consisting of a block header and a block body.
|
||||
type Block struct {
|
||||
// The underlying reader of the block.
|
||||
r io.Reader
|
||||
// Metadata block header.
|
||||
Header *BlockHeader
|
||||
// Metadata block body: *StreamInfo, *Application, *SeekTable, etc.
|
||||
Body interface{}
|
||||
}
|
||||
|
||||
// ParseBlock reads from the provided io.Reader and returns a parsed metadata
|
||||
// block. It parses both the header and the body of the metadata block. Use
|
||||
// NewBlock instead for more granularity.
|
||||
func ParseBlock(r io.Reader) (block *Block, err error) {
|
||||
block, err = NewBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = block.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// NewBlock reads and parses a metadata block header from the provided io.Reader
|
||||
// and returns a handle to the metadata block. Call Block.Parse to parse the
|
||||
// metadata block body and Block.Skip to ignore it.
|
||||
func NewBlock(r io.Reader) (block *Block, err error) {
|
||||
// Read metadata block header.
|
||||
block = &Block{r: r}
|
||||
block.Header, err = ParseBlockHeader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// Parse reads and parses the metadata block body.
|
||||
func (block *Block) Parse() (err error) {
|
||||
// Read metadata block.
|
||||
lr := io.LimitReader(block.r, int64(block.Header.Length))
|
||||
switch block.Header.BlockType {
|
||||
case TypeStreamInfo:
|
||||
block.Body, err = ParseStreamInfo(lr)
|
||||
case TypePadding:
|
||||
err = VerifyPadding(lr)
|
||||
case TypeApplication:
|
||||
block.Body, err = ParseApplication(lr)
|
||||
case TypeSeekTable:
|
||||
block.Body, err = ParseSeekTable(lr)
|
||||
case TypeVorbisComment:
|
||||
block.Body, err = ParseVorbisComment(lr)
|
||||
case TypeCueSheet:
|
||||
block.Body, err = ParseCueSheet(lr)
|
||||
case TypePicture:
|
||||
block.Body, err = ParsePicture(lr)
|
||||
case TypeReserved:
|
||||
block.Body, err = ioutil.ReadAll(lr)
|
||||
default:
|
||||
return fmt.Errorf("meta.Block.ParseBlock: block type '%d' not yet supported", block.Header.BlockType)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip ignores the contents of the metadata block body.
|
||||
func (block *Block) Skip() (err error) {
|
||||
if r, ok := block.r.(io.Seeker); ok {
|
||||
_, err = r.Seek(int64(block.Header.Length), os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err = io.CopyN(ioutil.Discard, block.r, int64(block.Header.Length))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockType is used to identify the metadata block type.
|
||||
type BlockType uint8
|
||||
|
||||
// Metadata block types.
|
||||
const (
|
||||
TypeStreamInfo BlockType = 1 << iota
|
||||
TypePadding
|
||||
TypeApplication
|
||||
TypeSeekTable
|
||||
TypeVorbisComment
|
||||
TypeCueSheet
|
||||
TypePicture
|
||||
TypeReserved
|
||||
|
||||
// TypeAll is a bitmask of all block types, except padding.
|
||||
TypeAll = TypeStreamInfo | TypeApplication | TypeSeekTable | TypeVorbisComment | TypeCueSheet | TypePicture | TypeReserved
|
||||
// TypeAllStrict is a bitmask of all block types, including padding but excluding reserved.
|
||||
TypeAllStrict = TypeStreamInfo | TypePadding | TypeApplication | TypeSeekTable | TypeVorbisComment | TypeCueSheet | TypePicture
|
||||
)
|
||||
|
||||
// blockTypeName is a map from BlockType to name.
|
||||
var blockTypeName = map[BlockType]string{
|
||||
TypeStreamInfo: "stream info",
|
||||
TypePadding: "padding",
|
||||
TypeApplication: "application",
|
||||
TypeSeekTable: "seek table",
|
||||
TypeVorbisComment: "vorbis comment",
|
||||
TypeCueSheet: "cue sheet",
|
||||
TypePicture: "picture",
|
||||
}
|
||||
|
||||
func (t BlockType) String() string {
|
||||
if s, ok := blockTypeName[t]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("unknown block type %d", uint8(t))
|
||||
}
|
||||
|
||||
// A BlockHeader contains type and length information about a metadata block.
|
||||
type BlockHeader struct {
|
||||
// IsLast is true if this block is the last metadata block before the audio
|
||||
// frames, and false otherwise.
|
||||
IsLast bool
|
||||
// Block type.
|
||||
BlockType BlockType
|
||||
// Length in bytes of the metadata body.
|
||||
Length int
|
||||
}
|
||||
|
||||
// ParseBlockHeader parses and returns a new metadata block header.
|
||||
//
|
||||
// Block header format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_HEADER struct {
|
||||
// is_last bool
|
||||
// block_type uint7
|
||||
// length uint24
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_header
|
||||
func ParseBlockHeader(r io.Reader) (h *BlockHeader, err error) {
|
||||
br := bit.NewReader(r)
|
||||
// field 0: is_last (1 bit)
|
||||
// field 1: block_type (7 bits)
|
||||
// field 2: length (24 bits)
|
||||
fields, err := br.ReadFields(1, 7, 24)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Is last.
|
||||
h = new(BlockHeader)
|
||||
if fields[0] != 0 {
|
||||
h.IsLast = true
|
||||
}
|
||||
|
||||
// Block type.
|
||||
// 0: Streaminfo
|
||||
// 1: Padding
|
||||
// 2: Application
|
||||
// 3: Seektable
|
||||
// 4: Vorbis_comment
|
||||
// 5: Cuesheet
|
||||
// 6: Picture
|
||||
// 7-126: reserved
|
||||
// 127: invalid, to avoid confusion with a frame sync code
|
||||
blockType := fields[1]
|
||||
switch blockType {
|
||||
case 0:
|
||||
h.BlockType = TypeStreamInfo
|
||||
case 1:
|
||||
h.BlockType = TypePadding
|
||||
case 2:
|
||||
h.BlockType = TypeApplication
|
||||
case 3:
|
||||
h.BlockType = TypeSeekTable
|
||||
case 4:
|
||||
h.BlockType = TypeVorbisComment
|
||||
case 5:
|
||||
h.BlockType = TypeCueSheet
|
||||
case 6:
|
||||
h.BlockType = TypePicture
|
||||
default:
|
||||
if blockType >= 7 && blockType <= 126 {
|
||||
// block type 7-126: reserved.
|
||||
log.Printf("meta.ParseBlockHeader: reserved block type %d.\n", uint8(blockType))
|
||||
h.BlockType = TypeReserved
|
||||
} else {
|
||||
// block type 127: invalid.
|
||||
return nil, errors.New("meta.ParseBlockHeader: invalid block type")
|
||||
}
|
||||
}
|
||||
|
||||
// Length.
|
||||
// int won't overflow since the max value of Length is 0x00FFFFFF.
|
||||
h.Length = int(fields[2])
|
||||
return h, nil
|
||||
}
|
|
@ -1,297 +0,0 @@
|
|||
package meta_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/mewkiz/flac"
|
||||
"github.com/mewkiz/flac/meta"
|
||||
)
|
||||
|
||||
var golden = []struct {
|
||||
name string
|
||||
blocks []*meta.Block
|
||||
}{
|
||||
// i=0
|
||||
{
|
||||
name: "../testdata/59996.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1000, BlockSizeMax: 0x1000, FrameSizeMin: 0x44c5, FrameSizeMax: 0x4588, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x18, SampleCount: 0x2000, MD5sum: [16]uint8{0x95, 0xba, 0xe5, 0xe2, 0xc7, 0x45, 0xbb, 0x3c, 0xa9, 0x5c, 0xa3, 0xb1, 0x35, 0xc9, 0x43, 0xf4}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x10, Length: 202},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.2.1 20070917", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "Description", Value: "Waving a bamboo staff"}, meta.VorbisEntry{Name: "YEAR", Value: "2008"}, meta.VorbisEntry{Name: "ARTIST", Value: "qubodup aka Iwan Gabovitch | qubodup@gmail.com"}, meta.VorbisEntry{Name: "COMMENTS", Value: "I release this file into the public domain"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=1
|
||||
{
|
||||
name: "../testdata/172960.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1000, BlockSizeMax: 0x1000, FrameSizeMin: 0xb7c, FrameSizeMax: 0x256b, SampleRate: 0x17700, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0xaaa3, MD5sum: [16]uint8{0x76, 0x3d, 0xa8, 0xa5, 0xb7, 0x58, 0xe6, 0x2, 0x61, 0xb4, 0xd4, 0xc2, 0x88, 0x4d, 0x8e, 0xe}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x10, Length: 180},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.2.1 20070917", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "GENRE", Value: "Sound Clip"}, meta.VorbisEntry{Name: "ARTIST", Value: "Iwan 'qubodup' Gabovitch"}, meta.VorbisEntry{Name: "Artist Homepage", Value: "http://qubodup.net"}, meta.VorbisEntry{Name: "Artist Email", Value: "qubodup@gmail.com"}, meta.VorbisEntry{Name: "DATE", Value: "2012"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=2
|
||||
{
|
||||
name: "../testdata/189983.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0x94d, FrameSizeMax: 0x264a, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x50f4, MD5sum: [16]uint8{0x63, 0x28, 0xed, 0x6d, 0xd3, 0xe, 0x55, 0xfb, 0xa5, 0x73, 0x69, 0x2b, 0xb7, 0x35, 0x73, 0xb7}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x10, Length: 40},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.2.1 20070917"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=3
|
||||
{
|
||||
name: "testdata/input-SCPAP.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0xe, FrameSizeMax: 0x10, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x16f8, MD5sum: [16]uint8{0x74, 0xff, 0xd4, 0x73, 0x7e, 0xb5, 0x48, 0x8d, 0x51, 0x2b, 0xe4, 0xaf, 0x58, 0x94, 0x33, 0x62}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x8, Length: 180},
|
||||
Body: &meta.SeekTable{Points: []meta.SeekPoint{meta.SeekPoint{SampleNum: 0x0, Offset: 0x0, SampleCount: 0x1200}, meta.SeekPoint{SampleNum: 0x1200, Offset: 0xe, SampleCount: 0x4f8}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x20, Length: 540},
|
||||
Body: &meta.CueSheet{MCN: "1234567890123", LeadInSampleCount: 0x15888, IsCompactDisc: true, TrackCount: 0x3, Tracks: []meta.CueSheetTrack{meta.CueSheetTrack{Offset: 0x0, TrackNum: 0x1, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x2, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}, meta.CueSheetTrackIndex{Offset: 0x24c, IndexPointNum: 0x2}}}, meta.CueSheetTrack{Offset: 0xb7c, TrackNum: 0x2, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x1, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}}}, meta.CueSheetTrack{Offset: 0x16f8, TrackNum: 0xaa, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x0, TrackIndexes: []meta.CueSheetTrackIndex(nil)}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x2, Length: 4},
|
||||
Body: nil,
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x4, Length: 4},
|
||||
Body: &meta.Application{ID: "fake", Data: []uint8{}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x2, Length: 3201},
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=4
|
||||
{
|
||||
name: "testdata/input-SCVA.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0xe, FrameSizeMax: 0x10, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x16f8, MD5sum: [16]uint8{0x74, 0xff, 0xd4, 0x73, 0x7e, 0xb5, 0x48, 0x8d, 0x51, 0x2b, 0xe4, 0xaf, 0x58, 0x94, 0x33, 0x62}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x8, Length: 180},
|
||||
Body: &meta.SeekTable{Points: []meta.SeekPoint{meta.SeekPoint{SampleNum: 0x0, Offset: 0x0, SampleCount: 0x1200}, meta.SeekPoint{SampleNum: 0x1200, Offset: 0xe, SampleCount: 0x4f8}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x20, Length: 540},
|
||||
Body: &meta.CueSheet{MCN: "1234567890123", LeadInSampleCount: 0x15888, IsCompactDisc: true, TrackCount: 0x3, Tracks: []meta.CueSheetTrack{meta.CueSheetTrack{Offset: 0x0, TrackNum: 0x1, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x2, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}, meta.CueSheetTrackIndex{Offset: 0x24c, IndexPointNum: 0x2}}}, meta.CueSheetTrack{Offset: 0xb7c, TrackNum: 0x2, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x1, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}}}, meta.CueSheetTrack{Offset: 0x16f8, TrackNum: 0xaa, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x0, TrackIndexes: []meta.CueSheetTrackIndex(nil)}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x10, Length: 203},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.1.3 20060805", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "artist", Value: "1"}, meta.VorbisEntry{Name: "title", Value: "2"}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x4, Length: 4},
|
||||
Body: &meta.Application{ID: "fake", Data: []uint8{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=5
|
||||
{
|
||||
name: "testdata/input-SCVAUP.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0xe, FrameSizeMax: 0x10, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x16f8, MD5sum: [16]uint8{0x74, 0xff, 0xd4, 0x73, 0x7e, 0xb5, 0x48, 0x8d, 0x51, 0x2b, 0xe4, 0xaf, 0x58, 0x94, 0x33, 0x62}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x8, Length: 180},
|
||||
Body: &meta.SeekTable{Points: []meta.SeekPoint{meta.SeekPoint{SampleNum: 0x0, Offset: 0x0, SampleCount: 0x1200}, meta.SeekPoint{SampleNum: 0x1200, Offset: 0xe, SampleCount: 0x4f8}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x20, Length: 540},
|
||||
Body: &meta.CueSheet{MCN: "1234567890123", LeadInSampleCount: 0x15888, IsCompactDisc: true, TrackCount: 0x3, Tracks: []meta.CueSheetTrack{meta.CueSheetTrack{Offset: 0x0, TrackNum: 0x1, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x2, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}, meta.CueSheetTrackIndex{Offset: 0x24c, IndexPointNum: 0x2}}}, meta.CueSheetTrack{Offset: 0xb7c, TrackNum: 0x2, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x1, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}}}, meta.CueSheetTrack{Offset: 0x16f8, TrackNum: 0xaa, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x0, TrackIndexes: []meta.CueSheetTrackIndex(nil)}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x10, Length: 203},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.1.3 20060805", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "artist", Value: "1"}, meta.VorbisEntry{Name: "title", Value: "2"}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x4, Length: 4},
|
||||
Body: &meta.Application{ID: "fake", Data: []uint8{}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x80, Length: 0},
|
||||
Body: nil,
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x2, Length: 3201},
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
// i=6
|
||||
{
|
||||
name: "testdata/input-SCVPAP.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0xe, FrameSizeMax: 0x10, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x16f8, MD5sum: [16]uint8{0x74, 0xff, 0xd4, 0x73, 0x7e, 0xb5, 0x48, 0x8d, 0x51, 0x2b, 0xe4, 0xaf, 0x58, 0x94, 0x33, 0x62}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x8, Length: 180},
|
||||
Body: &meta.SeekTable{Points: []meta.SeekPoint{meta.SeekPoint{SampleNum: 0x0, Offset: 0x0, SampleCount: 0x1200}, meta.SeekPoint{SampleNum: 0x1200, Offset: 0xe, SampleCount: 0x4f8}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x20, Length: 540},
|
||||
Body: &meta.CueSheet{MCN: "1234567890123", LeadInSampleCount: 0x15888, IsCompactDisc: true, TrackCount: 0x3, Tracks: []meta.CueSheetTrack{meta.CueSheetTrack{Offset: 0x0, TrackNum: 0x1, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x2, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}, meta.CueSheetTrackIndex{Offset: 0x24c, IndexPointNum: 0x2}}}, meta.CueSheetTrack{Offset: 0xb7c, TrackNum: 0x2, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x1, TrackIndexes: []meta.CueSheetTrackIndex{meta.CueSheetTrackIndex{Offset: 0x0, IndexPointNum: 0x1}}}, meta.CueSheetTrack{Offset: 0x16f8, TrackNum: 0xaa, ISRC: "", IsAudio: true, HasPreEmphasis: false, TrackIndexCount: 0x0, TrackIndexes: []meta.CueSheetTrackIndex(nil)}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x10, Length: 203},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.1.3 20060805", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "artist", Value: "1"}, meta.VorbisEntry{Name: "title", Value: "2"}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x2, Length: 4},
|
||||
Body: nil,
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x4, Length: 4},
|
||||
Body: &meta.Application{ID: "fake", Data: []uint8{}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x2, Length: 3201},
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=7
|
||||
{
|
||||
name: "testdata/input-SVAUP.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0xe, FrameSizeMax: 0x10, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x16f8, MD5sum: [16]uint8{0x74, 0xff, 0xd4, 0x73, 0x7e, 0xb5, 0x48, 0x8d, 0x51, 0x2b, 0xe4, 0xaf, 0x58, 0x94, 0x33, 0x62}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x8, Length: 180},
|
||||
Body: &meta.SeekTable{Points: []meta.SeekPoint{meta.SeekPoint{SampleNum: 0x0, Offset: 0x0, SampleCount: 0x1200}, meta.SeekPoint{SampleNum: 0x1200, Offset: 0xe, SampleCount: 0x4f8}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}, meta.SeekPoint{SampleNum: 0xffffffffffffffff, Offset: 0x0, SampleCount: 0x0}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x10, Length: 203},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.1.3 20060805", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "artist", Value: "1"}, meta.VorbisEntry{Name: "title", Value: "2"}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x4, Length: 4},
|
||||
Body: &meta.Application{ID: "fake", Data: []uint8{}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x80, Length: 0},
|
||||
Body: nil,
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x2, Length: 3201},
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// i=8
|
||||
{
|
||||
name: "testdata/input-VA.flac",
|
||||
blocks: []*meta.Block{
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x1, Length: 34},
|
||||
Body: &meta.StreamInfo{BlockSizeMin: 0x1200, BlockSizeMax: 0x1200, FrameSizeMin: 0xe, FrameSizeMax: 0x10, SampleRate: 0xac44, ChannelCount: 0x2, BitsPerSample: 0x10, SampleCount: 0x16f8, MD5sum: [16]uint8{0x74, 0xff, 0xd4, 0x73, 0x7e, 0xb5, 0x48, 0x8d, 0x51, 0x2b, 0xe4, 0xaf, 0x58, 0x94, 0x33, 0x62}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: false, BlockType: 0x10, Length: 203},
|
||||
Body: &meta.VorbisComment{Vendor: "reference libFLAC 1.1.3 20060805", Entries: []meta.VorbisEntry{meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_TRACK_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_PEAK", Value: "0.99996948"}, meta.VorbisEntry{Name: "REPLAYGAIN_ALBUM_GAIN", Value: "-7.89 dB"}, meta.VorbisEntry{Name: "artist", Value: "1"}, meta.VorbisEntry{Name: "title", Value: "2"}}},
|
||||
},
|
||||
{
|
||||
Header: &meta.BlockHeader{IsLast: true, BlockType: 0x4, Length: 4},
|
||||
Body: &meta.Application{ID: "fake", Data: []uint8{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestParseBlocks(t *testing.T) {
|
||||
for i, g := range golden {
|
||||
s, err := flac.Open(g.name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
err = s.ParseBlocks(meta.TypeAllStrict)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(s.MetaBlocks) != len(g.blocks) {
|
||||
t.Errorf("i=%d: invalid number of metadata blocks; expected %d, got %d.", i, len(g.blocks), len(s.MetaBlocks))
|
||||
continue
|
||||
}
|
||||
|
||||
for j, got := range s.MetaBlocks {
|
||||
want := g.blocks[j]
|
||||
if !reflect.DeepEqual(got.Header, want.Header) {
|
||||
t.Errorf("i=%d, j=%d: metadata block headers differ; expected %#v, got %#v.", i, j, want.Header, got.Header)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Body, want.Body) {
|
||||
t.Errorf("i=%d, j=%d: metadata block bodies differ; expected %#v, got %#v.", i, j, want.Body, got.Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePicture(t *testing.T) {
|
||||
s, err := flac.Open("testdata/silence.flac")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
err = s.ParseBlocks(meta.TypePicture)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want, err := ioutil.ReadFile("testdata/silence.jpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, block := range s.MetaBlocks {
|
||||
if block.Header.BlockType == meta.TypePicture {
|
||||
pic := block.Body.(*meta.Picture)
|
||||
got := pic.Data
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("picture data differ; expected %v, got %v", want, got)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// VerifyPadding verifies that the padding metadata block only contains 0 bits.
|
||||
// The provided io.Reader should limit the amount of data that can be read to
|
||||
// header.Length bytes.
|
||||
func VerifyPadding(r io.Reader) (err error) {
|
||||
// Verify up to 4 kb of padding each iteration.
|
||||
var buf [4096]byte
|
||||
for {
|
||||
n, err := r.Read(buf[:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !isAllZero(buf[:n]) {
|
||||
return errors.New("meta.VerifyPadding: invalid padding; must contain only zeroes")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isAllZero returns true if the value of each byte in the provided slice is 0,
|
||||
// and false otherwise.
|
||||
func isAllZero(buf []byte) bool {
|
||||
for _, b := range buf {
|
||||
if b != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
165
meta/picture.go
165
meta/picture.go
|
@ -1,165 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// A Picture metadata block stores pictures associated with the file, most
|
||||
// commonly cover art from CDs. There may be more than one Picture block in a
|
||||
// file.
|
||||
type Picture struct {
|
||||
// The picture type according to the ID3v2 APIC frame:
|
||||
// 0 - Other
|
||||
// 1 - 32x32 pixels 'file icon' (PNG only)
|
||||
// 2 - Other file icon
|
||||
// 3 - Cover (front)
|
||||
// 4 - Cover (back)
|
||||
// 5 - Leaflet page
|
||||
// 6 - Media (e.g. label side of CD)
|
||||
// 7 - Lead artist/lead performer/soloist
|
||||
// 8 - Artist/performer
|
||||
// 9 - Conductor
|
||||
// 10 - Band/Orchestra
|
||||
// 11 - Composer
|
||||
// 12 - Lyricist/text writer
|
||||
// 13 - Recording Location
|
||||
// 14 - During recording
|
||||
// 15 - During performance
|
||||
// 16 - Movie/video screen capture
|
||||
// 17 - A bright coloured fish
|
||||
// 18 - Illustration
|
||||
// 19 - Band/artist logotype
|
||||
// 20 - Publisher/Studio logotype
|
||||
//
|
||||
// Others are reserved and should not be used. There may only be one each of
|
||||
// picture type 1 and 2 in a file.
|
||||
Type uint32
|
||||
// The MIME type string, in printable ASCII characters 0x20-0x7e. The MIME
|
||||
// type may also be `-->` to signify that the data part is a URL of the
|
||||
// picture instead of the picture data itself.
|
||||
MIME string
|
||||
// The description of the picture, in UTF-8.
|
||||
Desc string
|
||||
// The width of the picture in pixels.
|
||||
Width uint32
|
||||
// The height of the picture in pixels.
|
||||
Height uint32
|
||||
// The color depth of the picture in bits-per-pixel.
|
||||
ColorDepth uint32
|
||||
// For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for
|
||||
// non-indexed pictures.
|
||||
ColorCount uint32
|
||||
// The binary picture data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// ParsePicture parses and returns a new Picture metadata block. The provided
|
||||
// io.Reader should limit the amount of data that can be read to header.Length
|
||||
// bytes.
|
||||
//
|
||||
// Picture format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_PICTURE struct {
|
||||
// type uint32
|
||||
// mime_length uint32
|
||||
// mime_string [mime_length]byte
|
||||
// desc_length uint32
|
||||
// desc_string [desc_length]byte
|
||||
// width uint32
|
||||
// height uint32
|
||||
// color_depth uint32
|
||||
// color_count uint32
|
||||
// data_length uint32
|
||||
// data [data_length]byte
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_picture
|
||||
func ParsePicture(r io.Reader) (pic *Picture, err error) {
|
||||
// Type.
|
||||
pic = new(Picture)
|
||||
err = binary.Read(r, binary.BigEndian, &pic.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pic.Type > 20 {
|
||||
return nil, fmt.Errorf("meta.ParsePicture: reserved picture type: %d", pic.Type)
|
||||
}
|
||||
|
||||
// Mime length.
|
||||
var mimeLen uint32
|
||||
err = binary.Read(r, binary.BigEndian, &mimeLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Mime string.
|
||||
buf, err := readBytes(r, int(mimeLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pic.MIME = getStringFromSZ(buf)
|
||||
for _, r := range pic.MIME {
|
||||
if r < 0x20 || r > 0x7E {
|
||||
return nil, fmt.Errorf("meta.ParsePicture: invalid character in MIME type; expected >= 0x20 and <= 0x7E, got 0x%02X", r)
|
||||
}
|
||||
}
|
||||
|
||||
// Desc length.
|
||||
var descLen uint32
|
||||
err = binary.Read(r, binary.BigEndian, &descLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Desc string.
|
||||
buf, err = readBytes(r, int(descLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pic.Desc = getStringFromSZ(buf)
|
||||
|
||||
// Width.
|
||||
err = binary.Read(r, binary.BigEndian, &pic.Width)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Height.
|
||||
err = binary.Read(r, binary.BigEndian, &pic.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ColorDepth.
|
||||
err = binary.Read(r, binary.BigEndian, &pic.ColorDepth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ColorCount.
|
||||
err = binary.Read(r, binary.BigEndian, &pic.ColorCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Data length.
|
||||
var dataLen uint32
|
||||
err = binary.Read(r, binary.BigEndian, &dataLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Data.
|
||||
pic.Data, err = ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pic.Data) != int(dataLen) {
|
||||
return nil, fmt.Errorf("meta.ParsePicture: invalid data length; expected %d, got %d", dataLen, len(pic.Data))
|
||||
}
|
||||
|
||||
return pic, nil
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// readBuf is the local buffer used by readBytes.
|
||||
var readBuf = make([]byte, 4096)
|
||||
|
||||
// readBytes reads and returns exactly n bytes from the provided io.Reader. The
|
||||
// local buffer is reused in between calls to reduce generation of garbage. It
|
||||
// is the callers responsibility to make a copy of the returned data.
|
||||
//
|
||||
// The local buffer is initially 4096 bytes and will grow automatically if so
|
||||
// required.
|
||||
func readBytes(r io.Reader, n int) ([]byte, error) {
|
||||
if n > len(readBuf) {
|
||||
readBuf = make([]byte, n)
|
||||
}
|
||||
_, err := io.ReadFull(r, readBuf[:n])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readBuf[:n], nil
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A SeekTable metadata block is an optional block for storing seek points. It
|
||||
// is possible to seek to any given sample in a FLAC stream without a seek
|
||||
// table, but the delay can be unpredictable since the bitrate may vary widely
|
||||
// within a stream. By adding seek points to a stream, this delay can be
|
||||
// significantly reduced. Each seek point takes 18 bytes, so 1% resolution
|
||||
// within a stream adds less than 2k.
|
||||
//
|
||||
// There can be only one SeekTable in a stream, but the table can have any
|
||||
// number of seek points. There is also a special 'placeholder' seekpoint which
|
||||
// will be ignored by decoders but which can be used to reserve space for future
|
||||
// seek point insertion.
|
||||
type SeekTable struct {
|
||||
// One or more seek points.
|
||||
Points []SeekPoint
|
||||
}
|
||||
|
||||
// A SeekPoint specifies the offset of a sample.
|
||||
type SeekPoint struct {
|
||||
// Sample number of first sample in the target frame, or 0xFFFFFFFFFFFFFFFF
|
||||
// for a placeholder point.
|
||||
SampleNum uint64
|
||||
// Offset (in bytes) from the first byte of the first frame header to the
|
||||
// first byte of the target frame's header.
|
||||
Offset uint64
|
||||
// Number of samples in the target frame.
|
||||
SampleCount uint16
|
||||
}
|
||||
|
||||
// PlaceholderPoint is the sample number used for placeholder points. For
|
||||
// placeholder points, the second and third field values in the SeekPoint
|
||||
// structure are undefined.
|
||||
const PlaceholderPoint = 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
// ParseSeekTable parses and returns a new SeekTable metadata block. The
|
||||
// provided io.Reader should limit the amount of data that can be read to
|
||||
// header.Length bytes.
|
||||
//
|
||||
// Seek table format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_SEEKTABLE struct {
|
||||
// // The number of seek points is implied by the metadata header 'length'
|
||||
// // field, i.e. equal to length / 18.
|
||||
// points []point
|
||||
// }
|
||||
//
|
||||
// type point struct {
|
||||
// sample_num uint64
|
||||
// offset uint64
|
||||
// sample_count uint16
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_seektable
|
||||
func ParseSeekTable(r io.Reader) (st *SeekTable, err error) {
|
||||
st = new(SeekTable)
|
||||
var hasPrev bool
|
||||
var prevSampleNum uint64
|
||||
for {
|
||||
var point SeekPoint
|
||||
err = binary.Read(r, binary.BigEndian, &point)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if hasPrev && point.SampleNum != PlaceholderPoint {
|
||||
// - Seek points within a table must be sorted in ascending order by
|
||||
// sample number.
|
||||
// - Seek points within a table must be unique by sample number, with
|
||||
// the exception of placeholder points.
|
||||
// - The previous two notes imply that there may be any number of
|
||||
// placeholder points, but they must all occur at the end of the
|
||||
// table.
|
||||
if prevSampleNum == point.SampleNum {
|
||||
return nil, fmt.Errorf("meta.ParseSeekTable: invalid seek point; sample number (%d) is not unique", point.SampleNum)
|
||||
} else if prevSampleNum > point.SampleNum {
|
||||
return nil, fmt.Errorf("meta.ParseSeekTable: invalid seek point; sample number (%d) is not in ascending order", point.SampleNum)
|
||||
}
|
||||
}
|
||||
prevSampleNum = point.SampleNum
|
||||
hasPrev = true
|
||||
st.Points = append(st.Points, point)
|
||||
}
|
||||
return st, nil
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/eaburns/bit"
|
||||
)
|
||||
|
||||
// A StreamInfo metadata block has information about the entire stream. The
|
||||
// first metadata block in a FLAC stream must be a StreamInfo metadata block.
|
||||
type StreamInfo struct {
|
||||
// The minimum block size (in samples) used in the stream.
|
||||
BlockSizeMin uint16
|
||||
// The maximum block size (in samples) used in the stream.
|
||||
// (BlockSizeMin == BlockSizeMax) implies a fixed-blocksize stream.
|
||||
BlockSizeMax uint16
|
||||
// The minimum frame size (in bytes) used in the stream. A value of 0 implies
|
||||
// that the minimum frame size is not known.
|
||||
FrameSizeMin uint32
|
||||
// The maximum frame size (in bytes) used in the stream. A value of 0 implies
|
||||
// that the maximum frame size is not known.
|
||||
FrameSizeMax uint32
|
||||
// Sample rate in Hz. Though 20 bits are available, the maximum sample rate
|
||||
// is limited by the structure of frame headers to 655350Hz. A value of 0 is
|
||||
// invalid.
|
||||
SampleRate uint32
|
||||
// Number of channels. FLAC supports from 1 to 8 channels.
|
||||
ChannelCount uint8
|
||||
// Bits per sample. FLAC supports from 4 to 32 bits per sample. Currently the
|
||||
// reference encoder and decoders only support up to 24 bits per sample.
|
||||
BitsPerSample uint8
|
||||
// Total number of samples in stream. This refers to inter-channel samples,
|
||||
// i.e. one second of 44.1Khz audio will have 44100 samples regardless of the
|
||||
// number of channels. A value of 0 implies that the number is channels is
|
||||
// not known.
|
||||
SampleCount uint64
|
||||
// MD5 signature of the unencoded audio data. This allows the decoder to
|
||||
// determine if an error exists in the audio data even when the error does
|
||||
// not result in an invalid bitstream.
|
||||
MD5sum [16]byte
|
||||
}
|
||||
|
||||
// ParseStreamInfo parses and returns a new StreamInfo metadata block. The
|
||||
// provided io.Reader should limit the amount of data that can be read to
|
||||
// header.Length bytes.
|
||||
//
|
||||
// Stream info format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_STREAMINFO struct {
|
||||
// block_size_min uint16
|
||||
// block_size_max uint16
|
||||
// frame_size_min uint24
|
||||
// frame_size_max uint24
|
||||
// sample_rate uint20
|
||||
// channel_count uint3 // (number of channels)-1.
|
||||
// bits_per_sample uint5 // (bits per sample)-1.
|
||||
// sample_count uint36
|
||||
// md5sum [16]byte
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_streaminfo
|
||||
func ParseStreamInfo(r io.Reader) (si *StreamInfo, err error) {
|
||||
br := bit.NewReader(r)
|
||||
// field 0: block_size_min (16 bits)
|
||||
// field 1: block_size_max (16 bits)
|
||||
// field 2: frame_size_min (24 bits)
|
||||
// field 3: frame_size_max (24 bits)
|
||||
// field 4: sample_rate (20 bits)
|
||||
// field 5: channel_count (3 bits)
|
||||
// field 6: bits_per_sample (5 bits)
|
||||
// field 7: sample_count (36 bits)
|
||||
fields, err := br.ReadFields(16, 16, 24, 24, 20, 3, 5, 36)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Minimum block size.
|
||||
si = new(StreamInfo)
|
||||
si.BlockSizeMin = uint16(fields[0])
|
||||
if si.BlockSizeMin < 16 {
|
||||
return nil, fmt.Errorf("meta.ParseStreamInfo: invalid min block size; expected >= 16, got %d", si.BlockSizeMin)
|
||||
}
|
||||
|
||||
// Maximum block size.
|
||||
si.BlockSizeMax = uint16(fields[1])
|
||||
if si.BlockSizeMax < 16 || si.BlockSizeMax > 65535 {
|
||||
return nil, fmt.Errorf("meta.ParseStreamInfo: invalid min block size; expected >= 16 and <= 65535, got %d", si.BlockSizeMax)
|
||||
}
|
||||
|
||||
// Minimum frame size.
|
||||
si.FrameSizeMin = uint32(fields[2])
|
||||
|
||||
// Maximum frame size.
|
||||
si.FrameSizeMax = uint32(fields[3])
|
||||
|
||||
// Sample rate.
|
||||
si.SampleRate = uint32(fields[4])
|
||||
if si.SampleRate > 655350 || si.SampleRate == 0 {
|
||||
return nil, fmt.Errorf("meta.ParseStreamInfo: invalid sample rate; expected > 0 and <= 655350, got %d", si.SampleRate)
|
||||
}
|
||||
|
||||
// According to the specification 1 should be added to both ChannelCount and
|
||||
// BitsPerSample:
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_streaminfo
|
||||
|
||||
// Channel count.
|
||||
si.ChannelCount = uint8(fields[5]) + 1
|
||||
if si.ChannelCount < 1 || si.ChannelCount > 8 {
|
||||
return nil, fmt.Errorf("meta.ParseStreamInfo: invalid number of channels; expected >= 1 and <= 8, got %d", si.ChannelCount)
|
||||
}
|
||||
|
||||
// Bits per sample.
|
||||
si.BitsPerSample = uint8(fields[6]) + 1
|
||||
if si.BitsPerSample < 4 || si.BitsPerSample > 32 {
|
||||
return nil, fmt.Errorf("meta.ParseStreamInfo: invalid number of bits per sample; expected >= 4 and <= 32, got %d", si.BitsPerSample)
|
||||
}
|
||||
|
||||
// Sample count.
|
||||
si.SampleCount = fields[7]
|
||||
|
||||
// MD5 signature of the unencoded audio data.
|
||||
_, err = io.ReadFull(r, si.MD5sum[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return si, nil
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A VorbisComment metadata block stores a list of human-readable name/value
|
||||
// pairs. Values are encoded using UTF-8. It is an implementation of the Vorbis
|
||||
// comment specification (without the framing bit). This is the only officially
|
||||
// supported tagging mechanism in FLAC. There may be only one VorbisComment
|
||||
// block in a stream. In some external documentation, Vorbis comments are called
|
||||
// FLAC tags to lessen confusion.
|
||||
type VorbisComment struct {
|
||||
Vendor string
|
||||
Entries []VorbisEntry
|
||||
}
|
||||
|
||||
// A VorbisEntry is a name/value pair.
|
||||
type VorbisEntry struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// ParseVorbisComment parses and returns a new VorbisComment metadata block. The
|
||||
// provided io.Reader should limit the amount of data that can be read to
|
||||
// header.Length bytes.
|
||||
//
|
||||
// Vorbis comment format (pseudo code):
|
||||
//
|
||||
// type METADATA_BLOCK_VORBIS_COMMENT struct {
|
||||
// vendor_length uint32
|
||||
// vendor_string [vendor_length]byte
|
||||
// comment_count uint32
|
||||
// comments [comment_count]comment
|
||||
// }
|
||||
//
|
||||
// type comment struct {
|
||||
// vector_length uint32
|
||||
// // vector_string is a name/value pair. Example: "NAME=value".
|
||||
// vector_string [length]byte
|
||||
// }
|
||||
//
|
||||
// ref: http://flac.sourceforge.net/format.html#metadata_block_vorbis_comment
|
||||
func ParseVorbisComment(r io.Reader) (vc *VorbisComment, err error) {
|
||||
// Vendor length.
|
||||
var vendorLen uint32
|
||||
err = binary.Read(r, binary.LittleEndian, &vendorLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Vendor string.
|
||||
buf, err := readBytes(r, int(vendorLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vc = new(VorbisComment)
|
||||
vc.Vendor = string(buf)
|
||||
|
||||
// Comment count.
|
||||
var commentCount uint32
|
||||
err = binary.Read(r, binary.LittleEndian, &commentCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Comments.
|
||||
if commentCount > 0 {
|
||||
vc.Entries = make([]VorbisEntry, commentCount)
|
||||
for i := 0; i < len(vc.Entries); i++ {
|
||||
// Vector length
|
||||
var vectorLen uint32
|
||||
err = binary.Read(r, binary.LittleEndian, &vectorLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Vector string.
|
||||
buf, err = readBytes(r, int(vectorLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vector := string(buf)
|
||||
pos := strings.Index(vector, "=")
|
||||
if pos == -1 {
|
||||
return nil, fmt.Errorf("meta.ParseVorbisComment: invalid comment vector; no '=' present in: %q", vector)
|
||||
}
|
||||
|
||||
// Comment.
|
||||
entry := VorbisEntry{
|
||||
Name: vector[:pos],
|
||||
Value: vector[pos+1:],
|
||||
}
|
||||
vc.Entries[i] = entry
|
||||
}
|
||||
}
|
||||
return vc, nil
|
||||
}
|
Loading…
Reference in a new issue