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 }