diff --git a/enc.go b/enc.go new file mode 100644 index 0000000..322f6a2 --- /dev/null +++ b/enc.go @@ -0,0 +1,425 @@ +package flac + +import ( + "bytes" + "encoding/binary" + "io" + + "github.com/icza/bitio" + "github.com/mewkiz/flac/meta" + "github.com/mewkiz/pkg/errutil" +) + +// Encode writes the FLAC audio stream to w. +func Encode(w io.Writer, stream *Stream) error { + // Create a bit writer to the output stream. + buf := new(bytes.Buffer) + bw := bitio.NewWriter(buf) + enc := &encoder{bw: bw} + + // Store FLAC signature. + if _, err := bw.Write(signature); err != nil { + return errutil.Err(err) + } + + // Store StreamInfo metadata block header. + infoHdr := meta.Header{ + IsLast: len(stream.Blocks) == 0, + Type: meta.TypeStreamInfo, + // The StreamInfo metadata block body is 34 bytes in length. + // + // 34 = (16+16+24+24+20+3+5+36+8*16)/8 + Length: 34, + } + if err := enc.writeBlockHeader(infoHdr); err != nil { + return errutil.Err(err) + } + + // Store the StreamInfo metadata block. + if err := enc.writeStreamInfo(stream.Info); err != nil { + return errutil.Err(err) + } + + // Store metadata blocks. + for _, block := range stream.Blocks { + if block.Type > meta.TypePicture { + return errutil.Newf("flac.Encode: support for encoding %T of block type %d not yet implemented", block, block.Type) + } + + if err := enc.writeBlockHeader(block.Header); err != nil { + return errutil.Err(err) + } + var err error + switch body := block.Body.(type) { + case *meta.Application: + err = enc.writeApplication(body) + case *meta.SeekTable: + err = enc.writeSeekTable(body) + case *meta.VorbisComment: + err = enc.writeVorbisComment(body) + case *meta.CueSheet: + err = enc.writeCueSheet(body) + case *meta.Picture: + err = enc.writePicture(body) + default: + err = enc.writePadding(int(block.Length)) + } + if err != nil { + return errutil.Err(err) + } + } + + // Flush pending bit writes. + if err := bw.Close(); err != nil { + return errutil.Err(err) + } + + // Copy buffer to output stream. + if _, err := io.Copy(w, buf); err != nil { + return errutil.Err(err) + } + + // TODO: Implement proper encoding support for audio samples. For now, copy + // the audio sample stream verbatim from the source file. + if _, err := io.Copy(w, stream.r); err != nil { + return errutil.Err(err) + } + + return nil +} + +// An encoder represents a FLAC encoder. +type encoder struct { + // Bit writer to the output stream. + bw bitio.Writer +} + +// TODO: Consider moving metadata related encoding to the meta package. + +// writeBlockHeader writes the header of a metadata block. +func (enc *encoder) writeBlockHeader(hdr meta.Header) error { + // 1 bit: IsLast. + x := uint64(0) + if hdr.IsLast { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 7 bits: Type. + if err := enc.bw.WriteBits(uint64(hdr.Type), 7); err != nil { + return errutil.Err(err) + } + + // 24 bits: Length. + if err := enc.bw.WriteBits(uint64(hdr.Length), 24); err != nil { + return errutil.Err(err) + } + + return nil +} + +// writeStreamInfo stores the body of a StreamInfo metadata block. +func (enc *encoder) writeStreamInfo(si *meta.StreamInfo) error { + // 16 bits: BlockSizeMin. + if err := enc.bw.WriteBits(uint64(si.BlockSizeMin), 16); err != nil { + return errutil.Err(err) + } + + // 16 bits: BlockSizeMax. + if err := enc.bw.WriteBits(uint64(si.BlockSizeMax), 16); err != nil { + return errutil.Err(err) + } + + // 24 bits: FrameSizeMin. + if err := enc.bw.WriteBits(uint64(si.FrameSizeMin), 24); err != nil { + return errutil.Err(err) + } + + // 24 bits: FrameSizeMax. + if err := enc.bw.WriteBits(uint64(si.FrameSizeMax), 24); err != nil { + return errutil.Err(err) + } + + // 20 bits: SampleRate. + if err := enc.bw.WriteBits(uint64(si.SampleRate), 20); err != nil { + return errutil.Err(err) + } + + // 3 bits: NChannels; stored as (number of channels) - 1. + if err := enc.bw.WriteBits(uint64(si.NChannels-1), 3); err != nil { + return errutil.Err(err) + } + + // 5 bits: BitsPerSample; stored as (bits-per-sample) - 1. + if err := enc.bw.WriteBits(uint64(si.BitsPerSample-1), 5); err != nil { + return errutil.Err(err) + } + + // 36 bits: NSamples. + if err := enc.bw.WriteBits(uint64(si.NSamples), 36); err != nil { + return errutil.Err(err) + } + + // 16 bytes: MD5sum. + if _, err := enc.bw.Write(si.MD5sum[:]); err != nil { + return errutil.Err(err) + } + + return nil +} + +// writePadding writes the body of a Padding metadata block. +func (enc *encoder) writePadding(n int) error { + for i := 0; i < n; i++ { + if err := enc.bw.WriteByte(0); err != nil { + return errutil.Err(err) + } + } + return nil +} + +// writeApplication writes the body of an Application metadata block. +func (enc *encoder) writeApplication(app *meta.Application) error { + // 32 bits: ID. + if err := enc.bw.WriteBits(uint64(app.ID), 32); err != nil { + return errutil.Err(err) + } + + // Check if the Application block only contains an ID. + if _, err := enc.bw.Write(app.Data); err != nil { + return errutil.Err(err) + } + + return nil +} + +// writeSeekTable writes the body of a SeekTable metadata block. +func (enc *encoder) writeSeekTable(table *meta.SeekTable) error { + for _, point := range table.Points { + if err := binary.Write(enc.bw, binary.BigEndian, point); err != nil { + return errutil.Err(err) + } + } + return nil +} + +// writeVorbisComment writes the body of a VorbisComment metadata block. +func (enc *encoder) writeVorbisComment(comment *meta.VorbisComment) error { + // 32 bits: vendor length. + x := uint32(len(comment.Vendor)) + if err := binary.Write(enc.bw, binary.LittleEndian, x); err != nil { + return errutil.Err(err) + } + + // (vendor length) bits: Vendor. + if _, err := enc.bw.Write([]byte(comment.Vendor)); err != nil { + return errutil.Err(err) + } + + // Store tags. + // 32 bits: number of tags. + x = uint32(len(comment.Tags)) + if err := binary.Write(enc.bw, binary.LittleEndian, x); err != nil { + return errutil.Err(err) + } + for _, tag := range comment.Tags { + // Store tag, which has the following format: + // NAME=VALUE + buf := []byte(tag[0] + "=" + tag[1]) + + // 32 bits: vector length + x = uint32(len(buf)) + if err := binary.Write(enc.bw, binary.LittleEndian, x); err != nil { + return errutil.Err(err) + } + + // (vector length): vector. + if _, err := enc.bw.Write(buf); err != nil { + return errutil.Err(err) + } + } + + return nil +} + +// writeCueSheet writes the body of a CueSheet metadata block. +func (enc *encoder) writeCueSheet(cs *meta.CueSheet) error { + // Parse cue sheet. + // 128 bytes: MCN. + mcn := make([]byte, 128) + copy(mcn, cs.MCN) + if _, err := enc.bw.Write(mcn); err != nil { + return errutil.Err(err) + } + + // 64 bits: NLeadInSamples. + if err := enc.bw.WriteBits(cs.NLeadInSamples, 64); err != nil { + return errutil.Err(err) + } + + // 1 bit: IsCompactDisc. + x := uint64(0) + if cs.IsCompactDisc { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 7 bits and 258 bytes: reserved. + if err := enc.bw.WriteBits(0, 7); err != nil { + return errutil.Err(err) + } + // TODO: Remove unnecessary allocation. + padding := make([]byte, 258) + if _, err := enc.bw.Write(padding); err != nil { + return errutil.Err(err) + } + + // Parse cue sheet tracks. + // 8 bits: (number of tracks) + x = uint64(len(cs.Tracks)) + if err := enc.bw.WriteBits(x, 8); err != nil { + return errutil.Err(err) + } + for _, track := range cs.Tracks { + // 64 bits: Offset. + if err := enc.bw.WriteBits(track.Offset, 64); err != nil { + return errutil.Err(err) + } + + // 8 bits: Num. + if err := enc.bw.WriteBits(uint64(track.Num), 8); err != nil { + return errutil.Err(err) + } + + // 12 bytes: ISRC. + isrc := make([]byte, 12) + copy(isrc, track.ISRC) + if _, err := enc.bw.Write(isrc); err != nil { + return errutil.Err(err) + } + + // 1 bit: IsAudio. + x := uint64(0) + if !track.IsAudio { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 1 bit: HasPreEmphasis. + // mask = 01000000 + x = 0 + if track.HasPreEmphasis { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 6 bits and 13 bytes: reserved. + // mask = 00111111 + if err := enc.bw.WriteBits(0, 6); err != nil { + return errutil.Err(err) + } + // TODO: Remove unnecessary allocation. + padding := make([]byte, 13) + if _, err := enc.bw.Write(padding); err != nil { + return errutil.Err(err) + } + + // Parse indicies. + // 8 bits: (number of indicies) + x = uint64(len(track.Indicies)) + if err := enc.bw.WriteBits(x, 8); err != nil { + return errutil.Err(err) + } + for _, index := range track.Indicies { + // 64 bits: Offset. + if err := enc.bw.WriteBits(index.Offset, 64); err != nil { + return errutil.Err(err) + } + + // 8 bits: Num. + if err := enc.bw.WriteBits(uint64(index.Num), 8); err != nil { + return errutil.Err(err) + } + + // 3 bytes: reserved. + // TODO: Remove unnecessary allocation. + padding := make([]byte, 3) + if _, err := enc.bw.Write(padding); err != nil { + return errutil.Err(err) + } + } + } + + return nil +} + +// writePicture writes the body of a Picture metadata block. +func (enc *encoder) writePicture(pic *meta.Picture) error { + // 32 bits: Type. + if err := enc.bw.WriteBits(uint64(pic.Type), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: (MIME type length). + x := uint64(len(pic.MIME)) + if err := enc.bw.WriteBits(x, 32); err != nil { + return errutil.Err(err) + } + + // (MIME type length) bytes: MIME. + if _, err := enc.bw.Write([]byte(pic.MIME)); err != nil { + return errutil.Err(err) + } + + // 32 bits: (description length). + x = uint64(len(pic.Desc)) + if err := enc.bw.WriteBits(x, 32); err != nil { + return errutil.Err(err) + } + + // (description length) bytes: Desc. + if _, err := enc.bw.Write([]byte(pic.Desc)); err != nil { + return errutil.Err(err) + } + + // 32 bits: Width. + if err := enc.bw.WriteBits(uint64(pic.Width), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: Height. + if err := enc.bw.WriteBits(uint64(pic.Height), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: Depth. + if err := enc.bw.WriteBits(uint64(pic.Depth), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: NPalColors. + if err := enc.bw.WriteBits(uint64(pic.NPalColors), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: (data length). + x = uint64(len(pic.Data)) + if err := enc.bw.WriteBits(x, 32); err != nil { + return errutil.Err(err) + } + + // (data length) bytes: Data. + if _, err := enc.bw.Write([]byte(pic.Data)); err != nil { + return errutil.Err(err) + } + + return nil +} diff --git a/internal/bits/reader.go b/internal/bits/reader.go index 272f3c1..85027bc 100644 --- a/internal/bits/reader.go +++ b/internal/bits/reader.go @@ -1,4 +1,4 @@ -// Package bits provides bit reading operations and binary decoding algorithms. +// Package bits provides bit access operations and binary decoding algorithms. package bits import ( @@ -20,7 +20,7 @@ type Reader struct { } // NewReader returns a new Reader that reads bits from r. -func NewReader(r io.Reader) (br *Reader) { +func NewReader(r io.Reader) *Reader { return &Reader{r: r} } diff --git a/meta/cuesheet.go b/meta/cuesheet.go index df6c89d..14a0f1e 100644 --- a/meta/cuesheet.go +++ b/meta/cuesheet.go @@ -26,7 +26,7 @@ type CueSheet struct { Tracks []CueSheetTrack } -// parseCueSheet reads and parses the body of an CueSheet metadata block. +// parseCueSheet reads and parses the body of a CueSheet metadata block. func (block *Block) parseCueSheet() error { // Parse cue sheet. // 128 bytes: MCN. diff --git a/meta/meta.go b/meta/meta.go index 593120f..7a922d2 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -172,13 +172,13 @@ type Type uint8 // Metadata block body types. const ( - TypeStreamInfo Type = iota - TypePadding - TypeApplication - TypeSeekTable - TypeVorbisComment - TypeCueSheet - TypePicture + TypeStreamInfo Type = 0 + TypePadding Type = 1 + TypeApplication Type = 2 + TypeSeekTable Type = 3 + TypeVorbisComment Type = 4 + TypeCueSheet Type = 5 + TypePicture Type = 6 ) func (t Type) String() string { diff --git a/meta/picture.go b/meta/picture.go index efd5c3b..022e169 100644 --- a/meta/picture.go +++ b/meta/picture.go @@ -50,7 +50,7 @@ type Picture struct { Data []byte } -// parsePicture reads and parses the body of an Picture metadata block. +// parsePicture reads and parses the body of a Picture metadata block. func (block *Block) parsePicture() error { // 32 bits: Type. pic := new(Picture) diff --git a/meta/seektable.go b/meta/seektable.go index 06da3e7..c0b96ce 100644 --- a/meta/seektable.go +++ b/meta/seektable.go @@ -14,7 +14,7 @@ type SeekTable struct { Points []SeekPoint } -// parseSeekTable reads and parses the body of an SeekTable metadata block. +// parseSeekTable reads and parses the body of a SeekTable metadata block. func (block *Block) parseSeekTable() error { // The number of seek points is derived from the header length, divided by // the size of a SeekPoint; which is 18 bytes. diff --git a/meta/streaminfo.go b/meta/streaminfo.go index 007bbe6..f72fbfc 100644 --- a/meta/streaminfo.go +++ b/meta/streaminfo.go @@ -39,7 +39,7 @@ type StreamInfo struct { MD5sum [md5.Size]uint8 } -// parseStreamInfo reads and parses the body of an StreamInfo metadata block. +// parseStreamInfo reads and parses the body of a StreamInfo metadata block. func (block *Block) parseStreamInfo() error { // 16 bits: BlockSizeMin. br := bits.NewReader(block.lr) diff --git a/meta/vorbiscomment.go b/meta/vorbiscomment.go index 673d626..0642868 100644 --- a/meta/vorbiscomment.go +++ b/meta/vorbiscomment.go @@ -16,7 +16,7 @@ type VorbisComment struct { Tags [][2]string } -// parseVorbisComment reads and parses the body of an VorbisComment metadata +// parseVorbisComment reads and parses the body of a VorbisComment metadata // block. func (block *Block) parseVorbisComment() error { // 32 bits: vendor length.