113 lines
2.7 KiB
Go
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
|
|
}
|