flac: Clean start in preparation for the new API. All functionality will be back ported later on.

This commit is contained in:
mewmew 2014-08-05 21:51:52 +02:00
parent e130d9cbbd
commit 51d342ff4b
15 changed files with 0 additions and 2938 deletions

195
flac.go
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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