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

120 lines
3.0 KiB
Go

package transcode
import (
"context"
"fmt"
"os"
)
// Engine converts input audio to PCM WAV using pure Go decoders (no ffmpeg).
type Engine struct{}
// NewEngine creates a transcoder. The ffmpegPath argument is ignored (kept for config compatibility).
func NewEngine(_ string) *Engine {
return &Engine{}
}
func (e *Engine) Available() error {
return nil
}
func (e *Engine) Transcode(ctx context.Context, src, dst string, opt Options) error {
if err := opt.Validate(); err != nil {
return err
}
spec, err := ResolveFormat(opt.Format)
if err != nil {
return err
}
dst, err = OutputPath(dst, spec.ID)
if err != nil {
return err
}
streamer, format, closer, err := openDecoder(src)
if err != nil {
return err
}
defer closer.Close()
s, format := buildPipeline(streamer, format, opt)
samples, err := drainSamples(ctx, s)
if err != nil {
return err
}
ch := format.NumChannels
if opt.Channels > 0 {
ch = opt.Channels
}
sr := int(format.SampleRate)
if opt.SampleRate > 0 {
sr = opt.SampleRate
}
if err := writePCM16WAV(dst, sr, ch, samples); err != nil {
return fmt.Errorf("write wav: %w", err)
}
return nil
}
func Transcode(ctx context.Context, src, dst string, opt Options) error {
return NewEngine("").Transcode(ctx, src, dst, opt)
}
func ToWhisperWAV(ctx context.Context, src, dst string) error {
return Transcode(ctx, src, dst, WhisperOptions())
}
// SupportedInputFormats lists file extensions decoded without external tools.
func SupportedInputFormats() []string {
return append([]string(nil), probeFormats...)
}
func (e *Engine) Probe(ctx context.Context, path string) (MediaInfo, error) {
_ = ctx
ext, err := resolveFormat(path)
if err != nil {
return MediaInfo{}, err
}
streamer, format, closer, err := openDecoder(path)
if err != nil {
return MediaInfo{}, err
}
defer closer.Close()
info := MediaInfo{
Format: ext,
Streams: []StreamInfo{{
Index: 0,
Codec: extTrim(ext),
Type: "audio",
SampleRate: int(format.SampleRate),
Channels: format.NumChannels,
}},
}
if st, err := os.Stat(path); err == nil {
info.BitRate = st.Size() * 8
}
_ = streamer
return info, nil
}
func extTrim(ext string) string {
if len(ext) > 0 && ext[0] == '.' {
return ext[1:]
}
return ext
}
// MediaInfo describes decoded input (for optional diagnostics).
type MediaInfo struct {
Format string `json:"format"`
Duration float64 `json:"duration_seconds"`
BitRate int64 `json:"bit_rate"`
Streams []StreamInfo `json:"streams"`
}
type StreamInfo struct {
Index int `json:"index"`
Codec string `json:"codec"`
Type string `json:"type"`
SampleRate int `json:"sample_rate,omitempty"`
Channels int `json:"channels,omitempty"`
}