231 lines
5 KiB
Go
231 lines
5 KiB
Go
//go:build !disable_format_flac && (disable_codec_libflac || !cgo)
|
|
|
|
package flac
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
|
libflac "git.gammaspectra.live/S.O.N.G/flacgo"
|
|
"git.gammaspectra.live/S.O.N.G/flacgo/frame"
|
|
"git.gammaspectra.live/S.O.N.G/flacgo/meta"
|
|
"io"
|
|
)
|
|
|
|
type Format struct {
|
|
}
|
|
|
|
func NewFormat() Format {
|
|
return Format{}
|
|
}
|
|
|
|
func (f Format) Name() string {
|
|
return "flac"
|
|
}
|
|
|
|
func (f Format) DecoderDescription() string {
|
|
return "S.O.N.G/flacgo"
|
|
}
|
|
|
|
func (f Format) EncoderDescription() string {
|
|
return f.DecoderDescription()
|
|
}
|
|
|
|
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|
decoder, err := libflac.New(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
source := audio.NewSource[int32](int(decoder.Info.BitsPerSample), int(decoder.Info.SampleRate), int(decoder.Info.NChannels))
|
|
|
|
go func() {
|
|
defer source.Close()
|
|
defer decoder.Close()
|
|
|
|
frameNumber := 0
|
|
|
|
for {
|
|
currentFrame, err := decoder.ParseNext()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
bitDepth := int(decoder.Info.BitsPerSample)
|
|
channels := int(decoder.Info.NChannels)
|
|
|
|
if currentFrame.BitsPerSample != 0 {
|
|
bitDepth = int(currentFrame.BitsPerSample)
|
|
}
|
|
if len(currentFrame.Subframes) != 0 {
|
|
channels = len(currentFrame.Subframes)
|
|
}
|
|
|
|
buffer := make([]int32, len(currentFrame.Subframes[0].Samples)*channels)
|
|
for i := range buffer {
|
|
buffer[i] = currentFrame.Subframes[i%channels].Samples[i/channels]
|
|
}
|
|
|
|
source.IngestInt32(buffer, bitDepth)
|
|
|
|
frameNumber++
|
|
}
|
|
}()
|
|
|
|
return source, nil
|
|
}
|
|
|
|
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
|
|
return format.NewAnalyzerChannel(f.Open(r))
|
|
}
|
|
|
|
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
|
var bitsPerSample uint8 = 16
|
|
var blockSize uint16 = 0
|
|
var ogg = false
|
|
|
|
if options != nil {
|
|
var val interface{}
|
|
var ok bool
|
|
var intVal int
|
|
var int64Val int64
|
|
var boolVal bool
|
|
if val, ok = options["bitdepth"]; ok {
|
|
if intVal, ok = val.(int); ok {
|
|
bitsPerSample = uint8(intVal)
|
|
} else if int64Val, ok = val.(int64); ok {
|
|
bitsPerSample = uint8(int(int64Val))
|
|
}
|
|
}
|
|
if val, ok = options["block_size"]; ok {
|
|
if intVal, ok = val.(int); ok {
|
|
blockSize = uint16(intVal)
|
|
} else if int64Val, ok = val.(int64); ok {
|
|
blockSize = uint16(int(int64Val))
|
|
}
|
|
}
|
|
if val, ok = options["ogg"]; ok {
|
|
if boolVal, ok = val.(bool); ok {
|
|
ogg = boolVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if blockSize == 0 {
|
|
blockSize = 4096
|
|
}
|
|
|
|
sampleRate := uint32(source.GetSampleRate())
|
|
|
|
if source.GetChannels() > 8 {
|
|
return errors.New("inputs with more than 8 channels not supported")
|
|
}
|
|
|
|
channels := frame.Channels(source.GetChannels() - 1)
|
|
|
|
if ogg {
|
|
return errors.New("ogg not supported")
|
|
}
|
|
|
|
encoder, err := libflac.NewEncoder(writer, &meta.StreamInfo{
|
|
BlockSizeMin: blockSize,
|
|
BlockSizeMax: blockSize,
|
|
SampleRate: sampleRate,
|
|
NChannels: uint8(channels.Count()),
|
|
BitsPerSample: bitsPerSample,
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer encoder.Close()
|
|
|
|
ccount := channels.Count()
|
|
|
|
subframes := make([]*frame.Subframe, ccount)
|
|
|
|
for i := 0; i < ccount; i++ {
|
|
|
|
subframes[i] = &frame.Subframe{
|
|
Samples: make([]int32, blockSize),
|
|
NSamples: int(blockSize),
|
|
}
|
|
}
|
|
|
|
buf := make([]int32, 0, blockSize)
|
|
|
|
//Uses verbatim samples + constant. No savings will be had, but it's a proper FLAC
|
|
|
|
for block := range source.ToInt32(int(bitsPerSample)).GetBlocks() {
|
|
|
|
buf = append(buf, block...)
|
|
|
|
for len(buf) >= int(blockSize)*ccount {
|
|
|
|
for i := 0; i < ccount; i++ {
|
|
//De-interleave samples
|
|
for j := 0; j < int(blockSize); j++ {
|
|
subframes[i].Samples[j] = buf[j*ccount+i]
|
|
}
|
|
}
|
|
|
|
err = encoder.WriteFrame(&frame.Frame{
|
|
Header: frame.Header{
|
|
HasFixedBlockSize: true,
|
|
BlockSize: blockSize,
|
|
SampleRate: sampleRate,
|
|
Channels: channels,
|
|
BitsPerSample: bitsPerSample,
|
|
},
|
|
Subframes: subframes,
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
buf = buf[int(blockSize)*ccount:]
|
|
}
|
|
|
|
if len(buf) == 0 {
|
|
//free
|
|
buf = nil
|
|
}
|
|
}
|
|
|
|
if len(buf) > 0 {
|
|
for i := 0; i < ccount; i++ {
|
|
//De-interleave samples
|
|
for j := 0; j < len(buf)/ccount; j++ {
|
|
subframes[i].Samples[j] = buf[j*ccount+i]
|
|
}
|
|
subframes[i].Samples = subframes[i].Samples[0 : len(buf)/ccount]
|
|
subframes[i].NSamples = len(subframes[i].Samples)
|
|
}
|
|
|
|
err = encoder.WriteFrame(&frame.Frame{
|
|
Header: frame.Header{
|
|
HasFixedBlockSize: true,
|
|
BlockSize: uint16(subframes[0].NSamples),
|
|
SampleRate: sampleRate,
|
|
Channels: channels,
|
|
BitsPerSample: bitsPerSample,
|
|
},
|
|
Subframes: subframes,
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f Format) Identify(peek [format.IdentifyPeekBytes]byte, extension string) bool {
|
|
//No Ogg supported
|
|
return bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || (bytes.Compare(peek[:3], []byte{'I', 'D', '3'}) == 0 && extension != "mp3") || extension == "flac"
|
|
}
|