Some checks failed
CodeQL / Analyze (go) (push) Successful in 6m28s
Docker Image / build-docker (push) Failing after 13m26s
Lint and Testing / lint (push) Successful in 11m17s
Lint and Testing / test (push) Successful in 11m17s
Lint and Testing / golangci (push) Successful in 2m40s
128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
package transcode
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gopxl/beep"
|
|
"github.com/gopxl/beep/flac"
|
|
"github.com/gopxl/beep/mp3"
|
|
beepwav "github.com/gopxl/beep/wav"
|
|
)
|
|
|
|
var probeFormats = []string{
|
|
".wav", ".wave", ".mp3", ".flac", ".ogg", ".opus",
|
|
".m4a", ".m4b", ".mp4", ".mov", ".m4v", ".3gp", ".3g2", ".aac",
|
|
}
|
|
|
|
func supportedFormatsMessage() string {
|
|
return strings.Join(probeFormats, ", ")
|
|
}
|
|
|
|
func resolveFormat(path string) (string, error) {
|
|
ext := strings.ToLower(filepath.Ext(path))
|
|
if ext != "" {
|
|
return ext, nil
|
|
}
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
ext = sniffFormat(f)
|
|
if ext == "" {
|
|
return "", fmt.Errorf("could not detect audio format (supported: %s)", supportedFormatsMessage())
|
|
}
|
|
return ext, nil
|
|
}
|
|
|
|
func openDecoder(path string) (beep.Streamer, beep.Format, io.Closer, error) {
|
|
ext, err := resolveFormat(path)
|
|
if err != nil {
|
|
return nil, beep.Format{}, nil, err
|
|
}
|
|
streamer, format, closer, err := decodeByExt(path, ext)
|
|
if err == nil {
|
|
return streamer, format, closer, nil
|
|
}
|
|
for _, try := range probeFormats {
|
|
if try == ext {
|
|
continue
|
|
}
|
|
streamer, format, closer, tryErr := decodeByExt(path, try)
|
|
if tryErr == nil {
|
|
return streamer, format, closer, nil
|
|
}
|
|
}
|
|
return nil, beep.Format{}, nil, fmt.Errorf("unsupported audio format %q (supported: %s): %w", ext, supportedFormatsMessage(), err)
|
|
}
|
|
|
|
func decodeByExt(path, ext string) (beep.Streamer, beep.Format, io.Closer, error) {
|
|
switch ext {
|
|
case ".wav", ".wave":
|
|
return decodeBeepFile(path, ext)
|
|
case ".mp3":
|
|
return decodeBeepFile(path, ext)
|
|
case ".flac":
|
|
return decodeBeepFile(path, ext)
|
|
case ".ogg", ".opus":
|
|
return decodeOggFile(path)
|
|
case ".m4a", ".m4b", ".mp4", ".mov", ".m4v", ".aac":
|
|
return decodeAACAsStreamer(path, ext)
|
|
case ".webm":
|
|
return nil, beep.Format{}, nil, fmt.Errorf("webm is not supported yet")
|
|
default:
|
|
return nil, beep.Format{}, nil, fmt.Errorf("unsupported extension %q", ext)
|
|
}
|
|
}
|
|
|
|
func decodeBeepFile(path, ext string) (beep.Streamer, beep.Format, io.Closer, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, beep.Format{}, nil, err
|
|
}
|
|
var (
|
|
streamer beep.StreamSeekCloser
|
|
format beep.Format
|
|
decErr error
|
|
)
|
|
switch ext {
|
|
case ".wav", ".wave":
|
|
streamer, format, decErr = beepwav.Decode(f)
|
|
case ".mp3":
|
|
streamer, format, decErr = mp3.Decode(f)
|
|
case ".flac":
|
|
streamer, format, decErr = flac.Decode(f)
|
|
default:
|
|
f.Close()
|
|
return nil, beep.Format{}, nil, fmt.Errorf("internal: beep decode for %q", ext)
|
|
}
|
|
if decErr != nil {
|
|
f.Close()
|
|
return nil, beep.Format{}, nil, decErr
|
|
}
|
|
return streamer, format, f, nil
|
|
}
|
|
|
|
func decodeAACAsStreamer(path, ext string) (beep.Streamer, beep.Format, io.Closer, error) {
|
|
samples, sr, ch, err := decodeAACPath(path, ext)
|
|
if err != nil {
|
|
return nil, beep.Format{}, nil, err
|
|
}
|
|
if ch <= 0 {
|
|
ch = 1
|
|
}
|
|
return newSamplesStreamer(samples, sr), beep.Format{
|
|
SampleRate: beep.SampleRate(sr),
|
|
NumChannels: ch,
|
|
Precision: 2,
|
|
}, noopCloser{}, nil
|
|
}
|
|
|
|
type noopCloser struct{}
|
|
|
|
func (noopCloser) Close() error { return nil }
|