package ffmpeg import ( "bytes" "context" "log" "os" "os/exec" "strconv" "sync" "time" "dndmusicbot/ytdl" ) type FFmpeg struct { Out *bytes.Buffer Cmd *exec.Cmd Started bool Cancel context.CancelFunc Len time.Duration Title string Channel string PMutex *sync.RWMutex ProcessState *os.ProcessState err chan error fb chan bool } func NewFFmpeg(uri string, sampleRate int, channels int) (ff *FFmpeg, err error) { var yt *ytdl.YTdl for { yt, err = ytdl.NewYTdl(uri) if err != nil { log.Println("Something went wrong, trying again.", err) continue } if yt.Url != "" { break } log.Println("Something went wrong, trying again.") time.Sleep(500 * time.Millisecond) } RETRY: ff = new(FFmpeg) ff.PMutex = &sync.RWMutex{} ff.Len = yt.Len ff.Title = yt.Title ff.Channel = yt.Channel ctx, cancel := context.WithCancel(context.Background()) ff.Cancel = cancel ff.Cmd = exec.CommandContext( ctx, "ffmpeg", "-xerror", "-i", yt.Url, "-f", "s16le", "-v", "error", // "-stats", //"-progress", "pipe:2", "-ar", strconv.Itoa(sampleRate), "-ac", strconv.Itoa(channels), "-af", "loudnorm=I=-16:LRA=11:TP=-1.5", "pipe:1", ) ff.Cmd.Stderr = os.Stdin // FFmpeg requires a certain buffer size to start writing. This seems to be enough? // This will grow big enough to fit the whole song. ff.Out = bytes.NewBuffer(make([]byte, 43920)) ff.Cmd.Stdout = ff.Out exitctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() go func() { ff.Cmd.Start() ff.err <- ff.Cmd.Wait() }() select { case <-exitctx.Done(): ff.Started = true case <-ff.err: log.Println("ffmpeg exited early, retry...") goto RETRY } return } func (ff *FFmpeg) Close() error { ff.Cancel() return nil }