package flac import ( "encoding/binary" "fmt" "io" "git.gammaspectra.live/S.O.N.G/flacgo/internal/ioutilx" "git.gammaspectra.live/S.O.N.G/flacgo/meta" "github.com/icza/bitio" "github.com/mewkiz/pkg/errutil" ) // --- [ Metadata block ] ------------------------------------------------------ // encodeBlock encodes the metadata block, writing to bw. func encodeBlock(bw *bitio.Writer, body interface{}, last bool) error { switch body := body.(type) { case *meta.StreamInfo: return encodeStreamInfo(bw, body, last) case *meta.Application: return encodeApplication(bw, body, last) case *meta.SeekTable: return encodeSeekTable(bw, body, last) case *meta.VorbisComment: return encodeVorbisComment(bw, body, last) case *meta.CueSheet: return encodeCueSheet(bw, body, last) case *meta.Picture: return encodePicture(bw, body, last) default: panic(fmt.Errorf("support for metadata block body type %T not yet implemented", body)) } } // --- [ Metadata block header ] ----------------------------------------------- // encodeBlockHeader encodes the metadata block header, writing to bw. func encodeBlockHeader(bw *bitio.Writer, hdr *meta.Header) error { // 1 bit: IsLast. if err := bw.WriteBool(hdr.IsLast); err != nil { return errutil.Err(err) } // 7 bits: Type. if err := bw.WriteBits(uint64(hdr.Type), 7); err != nil { return errutil.Err(err) } // 24 bits: Length. if err := bw.WriteBits(uint64(hdr.Length), 24); err != nil { return errutil.Err(err) } return nil } // --- [ StreamInfo ] ---------------------------------------------------------- // encodeStreamInfo encodes the StreamInfo metadata block, writing to bw. func encodeStreamInfo(bw *bitio.Writer, info *meta.StreamInfo, last bool) error { // Store metadata block header. const nbits = 16 + 16 + 24 + 24 + 20 + 3 + 5 + 36 + 8*16 hdr := &meta.Header{ IsLast: last, Type: meta.TypeStreamInfo, Length: nbits / 8, } if err := encodeBlockHeader(bw, hdr); err != nil { return errutil.Err(err) } // Store metadata block body. // 16 bits: BlockSizeMin. if err := bw.WriteBits(uint64(info.BlockSizeMin), 16); err != nil { return errutil.Err(err) } // 16 bits: BlockSizeMax. if err := bw.WriteBits(uint64(info.BlockSizeMax), 16); err != nil { return errutil.Err(err) } // 24 bits: FrameSizeMin. if err := bw.WriteBits(uint64(info.FrameSizeMin), 24); err != nil { return errutil.Err(err) } // 24 bits: FrameSizeMax. if err := bw.WriteBits(uint64(info.FrameSizeMax), 24); err != nil { return errutil.Err(err) } // 20 bits: SampleRate. if err := bw.WriteBits(uint64(info.SampleRate), 20); err != nil { return errutil.Err(err) } // 3 bits: NChannels; stored as (number of channels) - 1. if err := bw.WriteBits(uint64(info.NChannels-1), 3); err != nil { return errutil.Err(err) } // 5 bits: BitsPerSample; stored as (bits-per-sample) - 1. if err := bw.WriteBits(uint64(info.BitsPerSample-1), 5); err != nil { return errutil.Err(err) } // 36 bits: NSamples. if err := bw.WriteBits(info.NSamples, 36); err != nil { return errutil.Err(err) } // 16 bytes: MD5sum. if _, err := bw.Write(info.MD5sum[:]); err != nil { return errutil.Err(err) } return nil } // --- [ Application ] --------------------------------------------------------- // encodeApplication encodes the Application metadata block, writing to bw. func encodeApplication(bw *bitio.Writer, app *meta.Application, last bool) error { // Store metadata block header. nbits := int64(32 + 8*len(app.Data)) hdr := &meta.Header{ IsLast: last, Type: meta.TypeApplication, Length: nbits / 8, } if err := encodeBlockHeader(bw, hdr); err != nil { return errutil.Err(err) } // Store metadata block body. // 32 bits: ID. if err := bw.WriteBits(uint64(app.ID), 32); err != nil { return errutil.Err(err) } // TODO: check if the Application block may contain only an ID. if _, err := bw.Write(app.Data); err != nil { return errutil.Err(err) } return nil } // --- [ SeekTable ] ----------------------------------------------------------- // encodeSeekTable encodes the SeekTable metadata block, writing to bw. func encodeSeekTable(bw *bitio.Writer, table *meta.SeekTable, last bool) error { // Store metadata block header. nbits := int64((64 + 64 + 16) * len(table.Points)) hdr := &meta.Header{ IsLast: last, Type: meta.TypeSeekTable, Length: nbits / 8, } if err := encodeBlockHeader(bw, hdr); err != nil { return errutil.Err(err) } // Store metadata block body. for _, point := range table.Points { if err := binary.Write(bw, binary.BigEndian, point); err != nil { return errutil.Err(err) } } return nil } // --- [ VorbisComment ] ------------------------------------------------------- // encodeVorbisComment encodes the VorbisComment metadata block, writing to bw. func encodeVorbisComment(bw *bitio.Writer, comment *meta.VorbisComment, last bool) error { // Store metadata block header. nbits := int64(32 + 8*len(comment.Vendor) + 32) for _, tag := range comment.Tags { nbits += int64(32 + 8*(len(tag[0])+1+len(tag[1]))) } hdr := &meta.Header{ IsLast: last, Type: meta.TypeVorbisComment, Length: nbits / 8, } if err := encodeBlockHeader(bw, hdr); err != nil { return errutil.Err(err) } // Store metadata block body. // 32 bits: vendor length. // TODO: verify that little-endian encoding is used; otherwise, switch to // using bw.WriteBits. if err := binary.Write(bw, binary.LittleEndian, uint32(len(comment.Vendor))); err != nil { return errutil.Err(err) } // (vendor length) bits: Vendor. if _, err := bw.Write([]byte(comment.Vendor)); err != nil { return errutil.Err(err) } // Store tags. // 32 bits: number of tags. if err := binary.Write(bw, binary.LittleEndian, uint32(len(comment.Tags))); err != nil { return errutil.Err(err) } for _, tag := range comment.Tags { // Store tag, which has the following format: // NAME=VALUE buf := []byte(fmt.Sprintf("%s=%s", tag[0], tag[1])) // 32 bits: vector length if err := binary.Write(bw, binary.LittleEndian, uint32(len(buf))); err != nil { return errutil.Err(err) } // (vector length): vector. if _, err := bw.Write(buf); err != nil { return errutil.Err(err) } } return nil } // --- [ CueSheet ] ------------------------------------------------------------ // encodeCueSheet encodes the CueSheet metadata block, writing to bw. func encodeCueSheet(bw *bitio.Writer, cs *meta.CueSheet, last bool) error { // Store metadata block header. nbits := int64(8*128 + 64 + 1 + 7 + 8*258 + 8) for _, track := range cs.Tracks { nbits += 64 + 8 + 8*12 + 1 + 1 + 6 + 8*13 + 8 for range track.Indicies { nbits += 64 + 8 + 8*3 } } hdr := &meta.Header{ IsLast: last, Type: meta.TypeCueSheet, Length: nbits / 8, } if err := encodeBlockHeader(bw, hdr); err != nil { return errutil.Err(err) } // Store metadata block body. // Store cue sheet. // 128 bytes: MCN. var mcn [128]byte copy(mcn[:], cs.MCN) if _, err := bw.Write(mcn[:]); err != nil { return errutil.Err(err) } // 64 bits: NLeadInSamples. if err := bw.WriteBits(cs.NLeadInSamples, 64); err != nil { return errutil.Err(err) } // 1 bit: IsCompactDisc. if err := bw.WriteBool(cs.IsCompactDisc); err != nil { return errutil.Err(err) } // 7 bits and 258 bytes: reserved. if err := bw.WriteBits(0, 7); err != nil { return errutil.Err(err) } if _, err := io.CopyN(bw, ioutilx.Zero, 258); err != nil { return errutil.Err(err) } // Store cue sheet tracks. // 8 bits: (number of tracks) if err := bw.WriteBits(uint64(len(cs.Tracks)), 8); err != nil { return errutil.Err(err) } for _, track := range cs.Tracks { // 64 bits: Offset. if err := bw.WriteBits(track.Offset, 64); err != nil { return errutil.Err(err) } // 8 bits: Num. if err := bw.WriteBits(uint64(track.Num), 8); err != nil { return errutil.Err(err) } // 12 bytes: ISRC. var isrc [12]byte copy(isrc[:], track.ISRC) if _, err := bw.Write(isrc[:]); err != nil { return errutil.Err(err) } // 1 bit: IsAudio. if err := bw.WriteBool(!track.IsAudio); err != nil { return errutil.Err(err) } // 1 bit: HasPreEmphasis. // mask = 01000000 if err := bw.WriteBool(track.HasPreEmphasis); err != nil { return errutil.Err(err) } // 6 bits and 13 bytes: reserved. // mask = 00111111 if err := bw.WriteBits(0, 6); err != nil { return errutil.Err(err) } if _, err := io.CopyN(bw, ioutilx.Zero, 13); err != nil { return errutil.Err(err) } // Store indicies. // 8 bits: (number of indicies) if err := bw.WriteBits(uint64(len(track.Indicies)), 8); err != nil { return errutil.Err(err) } for _, index := range track.Indicies { // 64 bits: Offset. if err := bw.WriteBits(index.Offset, 64); err != nil { return errutil.Err(err) } // 8 bits: Num. if err := bw.WriteBits(uint64(index.Num), 8); err != nil { return errutil.Err(err) } // 3 bytes: reserved. if _, err := io.CopyN(bw, ioutilx.Zero, 3); err != nil { return errutil.Err(err) } } } return nil } // --- [ Picture ] ------------------------------------------------------------- // encodePicture encodes the Picture metadata block, writing to bw. func encodePicture(bw *bitio.Writer, pic *meta.Picture, last bool) error { // Store metadata block header. nbits := int64(32 + 32 + 8*len(pic.MIME) + 32 + 8*len(pic.Desc) + 32 + 32 + 32 + 32 + 32 + 8*len(pic.Data)) hdr := &meta.Header{ IsLast: last, Type: meta.TypeCueSheet, Length: nbits / 8, } if err := encodeBlockHeader(bw, hdr); err != nil { return errutil.Err(err) } // Store metadata block body. // 32 bits: Type. if err := bw.WriteBits(uint64(pic.Type), 32); err != nil { return errutil.Err(err) } // 32 bits: (MIME type length). if err := bw.WriteBits(uint64(len(pic.MIME)), 32); err != nil { return errutil.Err(err) } // (MIME type length) bytes: MIME. if _, err := bw.Write([]byte(pic.MIME)); err != nil { return errutil.Err(err) } // 32 bits: (description length). if err := bw.WriteBits(uint64(len(pic.Desc)), 32); err != nil { return errutil.Err(err) } // (description length) bytes: Desc. if _, err := bw.Write([]byte(pic.Desc)); err != nil { return errutil.Err(err) } // 32 bits: Width. if err := bw.WriteBits(uint64(pic.Width), 32); err != nil { return errutil.Err(err) } // 32 bits: Height. if err := bw.WriteBits(uint64(pic.Height), 32); err != nil { return errutil.Err(err) } // 32 bits: Depth. if err := bw.WriteBits(uint64(pic.Depth), 32); err != nil { return errutil.Err(err) } // 32 bits: NPalColors. if err := bw.WriteBits(uint64(pic.NPalColors), 32); err != nil { return errutil.Err(err) } // 32 bits: (data length). if err := bw.WriteBits(uint64(len(pic.Data)), 32); err != nil { return errutil.Err(err) } // (data length) bytes: Data. if _, err := bw.Write(pic.Data); err != nil { return errutil.Err(err) } return nil }