admin b5c083e06f
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
first commit
2026-06-04 18:10:52 +07:00

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 }