//go:build !disable_format_flac // +build !disable_format_flac package flac import ( "bytes" "git.gammaspectra.live/S.O.N.G/Kirika/audio" "git.gammaspectra.live/S.O.N.G/Kirika/audio/format" "git.gammaspectra.live/S.O.N.G/Kirika/cgo" libflac "git.gammaspectra.live/S.O.N.G/goflac" "io" ) type Format struct { } func NewFormat() Format { return Format{} } func (f Format) Name() string { return "flac" } func (f Format) Description() string { return "libFLAC (S.O.N.G/goflac)" } func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { currentPosition, _ := r.Seek(0, io.SeekCurrent) decoder, err := libflac.NewDecoderReader(r) if err != nil { if _, err2 := r.Seek(currentPosition, io.SeekStart); err2 != nil { return audio.Source{}, err } if decoder, err = libflac.NewDecoderReaderOgg(r); err != nil { return audio.Source{}, err } } newChannel := make(chan []float32) go func() { defer close(newChannel) defer decoder.Close() frameNumber := 0 for { currentFrame, err := decoder.ReadFrame() if err != nil { return } bitDepth := decoder.Depth if currentFrame.Depth != 0 { bitDepth = currentFrame.Depth } newChannel <- cgo.Int32ToFloat32(currentFrame.Buffer, bitDepth) frameNumber++ } }() return audio.Source{ Channels: decoder.Channels, SampleRate: decoder.Rate, Blocks: newChannel, }, nil } func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) { currentPosition, _ := r.Seek(0, io.SeekCurrent) decoder, err := libflac.NewDecoderReader(r) if err != nil { if _, err2 := r.Seek(currentPosition, io.SeekStart); err2 != nil { return audio.Source{}, nil, err } if decoder, err = libflac.NewDecoderReaderOgg(r); err != nil { return audio.Source{}, nil, err } } newChannel := make(chan []float32) analyzerChannel := make(format.AnalyzerChannel) go func() { defer close(newChannel) defer close(analyzerChannel) defer decoder.Close() frameNumber := 0 for { currentFrame, err := decoder.ReadFrame() if err != nil { return } bitDepth := decoder.Depth channels := decoder.Channels if currentFrame.Depth != 0 { bitDepth = currentFrame.Depth } if currentFrame.Channels != 0 { channels = currentFrame.Channels } newChannel <- cgo.Int32ToFloat32(currentFrame.Buffer, bitDepth) analyzerChannel <- &format.AnalyzerPacket{ Samples: currentFrame.Buffer, Channels: channels, BitDepth: bitDepth, SampleRate: decoder.Rate, } frameNumber++ } }() return audio.Source{ Channels: decoder.Channels, SampleRate: decoder.Rate, Blocks: newChannel, }, analyzerChannel, nil } func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error { var bitsPerSample = 16 var compressionLevel = 8 var blockSize = 0 var streamable = true 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 = intVal } else if int64Val, ok = val.(int64); ok { bitsPerSample = int(int64Val) } } if val, ok = options["compression_level"]; ok { if intVal, ok = val.(int); ok { compressionLevel = intVal } else if int64Val, ok = val.(int64); ok { compressionLevel = int(int64Val) } } if val, ok = options["block_size"]; ok { if intVal, ok = val.(int); ok { blockSize = intVal } else if int64Val, ok = val.(int64); ok { blockSize = int(int64Val) } } if val, ok = options["streamable"]; ok { if boolVal, ok = val.(bool); ok { streamable = boolVal } } if val, ok = options["ogg"]; ok { if boolVal, ok = val.(bool); ok { ogg = boolVal } } } var encoder *libflac.Encoder var err error if writeSeeker, ok := writer.(libflac.FlacWriter); ok { if ogg { encoder, err = libflac.NewEncoderWriteSeekerOgg(writeSeeker, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize) } else { encoder, err = libflac.NewEncoderWriteSeeker(writeSeeker, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize) } } else { if ogg { encoder, err = libflac.NewEncoderWriterOgg(writer, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize) } else { encoder, err = libflac.NewEncoderWriter(writer, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize) } } if err != nil { return err } defer encoder.Close() for block := range source.Blocks { err = encoder.WriteFrame(libflac.Frame{ Rate: source.SampleRate, Channels: source.Channels, Depth: bitsPerSample, Buffer: cgo.Float32ToInt32(block, bitsPerSample), }) if err != nil { return err } } return nil } func (f Format) Identify(peek []byte, extension string) bool { return bytes.Compare(peek[:4], []byte{'O', 'g', 'g', 'S'}) == 0 || bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || (bytes.Compare(peek[:3], []byte{'I', 'D', '3'}) == 0 && extension != "mp3") || extension == "flac" || extension == "ogg" }