通过管道传输HTTP响应

3
我该如何像在NodeJS中那样将HTTP响应进行管道传输呢?以下是我在NodeJS中使用的代码片段:
request({
  url: audio_file_url,
}).pipe(ffmpeg_process.stdin);

我该如何在Go中实现相同的结果?

我正在尝试将来自HTTP的音频流传输到FFmpeg进程中,以便它可以即时转换并将转换后的文件返回给客户端。

以下是我的源代码,以便大家清楚明白:

func encodeAudio(w http.ResponseWriter, req *http.Request) {
    path, err := exec.LookPath("youtube-dl")
    if err != nil {
        log.Fatal("LookPath: ", err)
    }
    path_ff, err_ff := exec.LookPath("ffmpeg")
    if err != nil {
        log.Fatal("LookPath: ", err_ff)
    }

    streamLink := exec.Command(path,"-f", "140", "-g", "https://www.youtube.com/watch?v=VIDEOID")

    var out bytes.Buffer
    streamLink.Stdout = &out
    cmdFF := exec.Command(path_ff, "-i", "pipe:0", "-acodec", "libmp3lame", "-f", "mp3", "-")
    resp, err := http.Get(out.String())
    if err != nil {
        log.Fatal(err)
    }
    // pr, pw := io.Pipe()
    defer resp.Body.Close()
    cmdFF.Stdin = resp.Body
    cmdFF.Stdout = w
    streamLink.Run()
    //get ffmpeg running in another goroutine to receive data
    errCh := make(chan error, 1)
    go func() {
        errCh <- cmdFF.Run()
    }()

    // close the pipeline to signal the end of the stream
    // pw.Close()
    // pr.Close()

    // check for an error from ffmpeg
    if err := <-errCh; err != nil {
        // ff error
    }
}

错误: 2014/07/29 23:04:02 获取:不支持的协议方案“”


@JimB 音频来自另一个源。我只需要在服务器上处理它并将ffmpeg响应发送回客户端。你所说的还适用吗? - viperfx
好的,我已经编辑了我的答案,展示了我目前的进展@JimB - viperfx
好的,谢谢。看来我又误解了 :/ - JimB
顺便说一下,如果exec.LookPath可以找到可执行文件,那么您不需要使用它,exec.Command也能找到它。 - OneOfOne
在运行命令之前,您要先检查 out。这里也有很多多余的东西。阅读文档可能会帮助澄清一些混淆。 - JimB
显示剩余4条评论
2个回答

3

以下是使用标准的http处理程序函数可能的答案。我没有测试此内容,但它可以使用一些简单的shell命令代理。

func encodeAudio(w http.ResponseWriter, req *http.Request) {

    streamLink := exec.Command("youtube-dl", "-f", "140", "-g", "https://www.youtube.com/watch?v=VIDEOID")
    out, err := streamLink.Output()
    if err != nil {
        log.Fatal(err)
    }

    cmdFF := exec.Command("ffmpeg", "-i", "pipe:0", "-acodec", "libmp3lame", "-f", "mp3", "-")
    resp, err := http.Get(string(out))
    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()
    cmdFF.Stdin = resp.Body

    cmdFF.Stdout = w
    if err := cmdFF.Run(); err != nil {
        log.Fatal(err)
    }
}

哈哈,对于混淆的问题我很抱歉。不过我认为我们已经非常接近了。唯一缺少的就是http.Get()。这个响应需要被导入到cmdFF.Stdin中。 - viperfx
我开始编写其余的代码,但最后出现了一个错误。我已经更新了我的原始帖子。@JimB - viperfx
@viperfx:现在应该更接近你想要的了。 - JimB
无法确定原因。如果连接关闭,可能是下载失败或ffmpeg退出。请尝试检查流下载的标头或ffmpeg进程的stderr输出。 - JimB
@viperfx:这就是你已经在做的事情。http.Response.Body是一个流。 - JimB
显示剩余7条评论

1

http.Request.Body 是一个 io.ReadCloser,因此您可以将其导入 exec.Cmd.Stdin:

func Handler(rw http.ResponseWriter, req *http.Request) {
    cmd := exec.Command("ffmpeg", other, args, ...)
    cmd.Stdin = req.Body
    go func() {
        defer req.Body.Close()

        if err := cmd.Run(); err != nil {
            // do something
        }
    }()
    //redirect the user and check for progress?
}

//编辑 我误解了问题,但是答案仍然适用于 http.Get 版本:

http.Response.Body 就像 http.Request.Body 一样是一个 io.ReadCloser

func EncodeUrl(url, fn string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    cmd := exec.Command("ffmpeg", ......, fn)
    cmd.Stdin = resp.Body
    return cmd.Run()
}

//编辑2:

根据Martini文档,这个应该可以工作,但是我强烈建议学习如何使用ServeMux或至少使用Gorilla

m := martini.Classic()
m.Get("/stream/:ytid", func(params martini.Params, rw http.ResponseWriter,
                            req *http.Request) string {
    ytid := params["ytid"]
    stream_link := exec.Command("youtube-dl","-f", "140", "-g", "https://www.youtube.com/watch?v=" + ytid)
    var out bytes.Buffer
    stream_link.Stdout = &out
    errr := stream_link.Run()
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Link", out.String())

    cmd_ff := exec.Command("ffmpeg", "-i", "pipe:0", "-acodec", "libmp3lame", "-f", "mp3", "-")
    resp, err := http.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    cmd_ff.Stdin = resp.Body
    go func() {
        defer resp.Body.Close()
        if err := cmd_ff.Run(); err != nil {
            log.Fatal(err)
        }
    }()
    return "Youtube ID: " + ytid
})
m.Run()

处理程序不是用于提供HTTP服务,而是作为服务器使用的吗?我不需要使用net/http请求URL,然后将其主体管道传输到cmd.Stdin中吗? - viperfx
我误读了问题,我以为你想在服务器上使用http处理程序来完成它,然而同样的事情仍然适用,使用http.Get返回一个也有.Bodyhttp.Response,我会更新示例。 - OneOfOne
如果您有时间的话,可以简要介绍一下您推荐的做法。 - viperfx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接