flacgo/cmd/flac2wav/flac2wav.go
2022-07-26 16:14:37 +02:00

113 lines
2.7 KiB
Go

// The flac2wav tool converts FLAC files to WAV files.
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"git.gammaspectra.live/S.O.N.G/flacgo"
"github.com/go-audio/audio"
"github.com/go-audio/wav"
"github.com/mewkiz/pkg/osutil"
"github.com/mewkiz/pkg/pathutil"
"github.com/pkg/errors"
)
func usage() {
const use = `
Usage: flac2wav [OPTION]... FILE.flac...`
fmt.Fprintln(os.Stderr, use[1:])
flag.PrintDefaults()
}
func main() {
// Parse command line arguments.
var (
// force overwrite WAV file if present already.
force bool
)
flag.BoolVar(&force, "f", false, "force overwrite")
flag.Usage = usage
flag.Parse()
for _, path := range flag.Args() {
err := flac2wav(path, force)
if err != nil {
log.Fatalf("%+v", err)
}
}
}
// flac2wav converts the provided FLAC file to a WAV file.
func flac2wav(path string, force bool) error {
// Open FLAC file.
stream, err := flac.Open(path)
if err != nil {
return errors.WithStack(err)
}
defer stream.Close()
// Create WAV file.
wavPath := pathutil.TrimExt(path) + ".wav"
if !force {
if osutil.Exists(wavPath) {
return errors.Errorf("WAV file %q already present; use the -f flag to force overwrite", wavPath)
}
}
fw, err := os.Create(wavPath)
if err != nil {
return errors.WithStack(err)
}
defer fw.Close()
// Create WAV encoder.
wavAudioFormat := 1 // PCM
enc := wav.NewEncoder(fw, int(stream.Info.SampleRate), int(stream.Info.BitsPerSample), int(stream.Info.NChannels), wavAudioFormat)
defer enc.Close()
var data []int
for {
// Decode FLAC audio samples.
frame, err := stream.ParseNext()
if err != nil {
if err == io.EOF {
break
}
return errors.WithStack(err)
}
// Encode WAV audio samples.
data = data[:0]
for i := 0; i < frame.Subframes[0].NSamples; i++ {
for _, subframe := range frame.Subframes {
sample := int(subframe.Samples[i])
if frame.BitsPerSample == 8 {
// WAV files with 8 bit-per-sample are stored with unsigned
// values, WAV files with more than 8 bit-per-sample are stored
// as signed values (ref page 59-60 of [1]).
//
// [1]: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf
// ref: https://git.gammaspectra.live/S.O.N.G/flacgo/issues/51#issuecomment-1046183409
const midpointValue = 0x80
sample += midpointValue
}
data = append(data, sample)
}
}
buf := &audio.IntBuffer{
Format: &audio.Format{
NumChannels: int(stream.Info.NChannels),
SampleRate: int(stream.Info.SampleRate),
},
Data: data,
SourceBitDepth: int(stream.Info.BitsPerSample),
}
if err := enc.Write(buf); err != nil {
return errors.WithStack(err)
}
}
return nil
}