5

I currently have this function, that pipes youtubedl command thru ffmpeg, and then pipes the output of ffmpeg to an HTTP client.

func pipeThruFfmpegToMp3(vi *VideoInfo, rw web.ResponseWriter) error {
    var ffmpeg *exec.Cmd
        ffmpeg = exec.Command("ffmpeg", "-i", "-","-acodec", "libmp3lame","-f", "mp3","-")
    }

    youtube := exec.Command("youtube-dl", "-f", "137", "video_url", "-o", "-")

    var ytbuf, ffbuf bytes.Buffer
    youtube.Stderr = &ytbuf
    ffmpeg.Stderr = &ffbuf

    audio, err := youtube.StdoutPipe()
    if err != nil {
        log.Printf("pipeThruFfmpegToMp3: %v\n", err)
        return err
    }

    ffmpeg.Stdin = audio
    ffmpeg.Stdout = rw

    // Headers sent, no turning back now
    rw.Header().Set("Content-Type", "audio/mpeg")
    rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=\"%s.mp3\"", vi.GetSlug()))
    rw.Flush()

    youtube.Start()
    ffmpeg.Start()
    ffmpeg.Wait()
    youtube.Wait()

    // check ytvbuf, ytbuf, ffbuf for stderr errors

    if ffbuf.Len() != 0 {
        log.Printf("pipeThruFfmpegToMp3: %v\n", ffbuf.String())
    }

    if ytbuf.Len() != 0 {
        log.Printf("pipeThruYouTubeDLToMp3: %v\n", ytbuf.String())
    }

    return nil
}

The problem is, when youtubedl has a error, it sends a 0kb file, instead of returning the error and not sending the file.

So is there anyway I can fix this to have if youtubedl errores out to return instead of continuing sending?

nadermx
  • 2,596
  • 7
  • 31
  • 66
  • Not piped directly to the client, no. You'd have to buffer the output in RAM to determine if it's an error, and based on that either send an error response or send your payload response. – Adrian Feb 19 '20 at 20:10
  • @Adrian it wouldn't be able to return error if ffmpeg errores either to avoid sending the 0kb? And if I have to buffer, what is the minimum and how would I do that? – nadermx Feb 19 '20 at 20:16
  • @Adrian I guess specifically, since youtube-dl errores out instantly or works and same with ffmpeg, I just feel there has to be a way to sort of else if it – nadermx Feb 19 '20 at 20:29
  • You can, you just have to buffer it instead of piping directly to the client. – Adrian Feb 19 '20 at 20:48
  • https://stackoverflow.com/questions/18935446/program-received-signal-sigpipe-broken-pipe – hek2mgl Feb 24 '20 at 00:09

2 Answers2

3

I got this work by replacing the Start() and Wait() to a CombinedOutput()

var ffbuf bytes.Buffer
ffmpeg.Stderr = &ffbuf

audio, err := youtube.Output()
if err != nil {
    log.Printf("pipeThruFfmpegToMp3: %v\n", err)
    return err
}

ffmpeg.Stdin = bytes.NewReader(audio)
nadermx
  • 2,596
  • 7
  • 31
  • 66
  • You now have the entire youtube video in the `audio` byte slice, presenting a high likelihood to run out of memory. – Shang Jian Ding Feb 26 '20 at 01:44
  • @ShangjianDing I changed it from `CombinedOutput()` to `Output()` – nadermx Feb 26 '20 at 01:54
  • That doesn't solve the issue. See function signature of `Output()`, which is `func (c *Cmd) Output() ([]byte, error)`. The `[]byte` there means that it's going to be doing the same thing as `CombinedOutput()` – Shang Jian Ding Feb 26 '20 at 01:57
3

How about simply not discarding the error code returned by youtube-dl?

Change

youtube.Wait()

to

err = youtube.Wait()
if err != nil {
    log.Printf("youtube-dl failed: %v\n", err)
    return err
}

See the documentation on Go's wait() API for more detail.

aij
  • 5,903
  • 3
  • 37
  • 41